diff --git a/opendrift/models/basemodel/__init__.py b/opendrift/models/basemodel/__init__.py index 06223859d..973fc0172 100644 --- a/opendrift/models/basemodel/__init__.py +++ b/opendrift/models/basemodel/__init__.py @@ -223,6 +223,8 @@ def __init__(self, super().__init__() + self.profiles_depth = None + self.show_continuous_performance = False self.origin_marker = None # Dictionary to store named seeding locations @@ -234,8 +236,7 @@ def __init__(self, # List to store GeoJSON dicts of seeding commands self.seed_geojson = [] - self.env = Environment(self.required_variables, - self.required_profiles_z_range, self._config) + self.env = Environment(self.required_variables, self._config) # Make copies of dictionaries so that they are private to each instance self.status_categories = ['active'] # Particles are active by default @@ -409,6 +410,9 @@ def __init__(self, 'description': 'Add horizontal diffusivity (random walk)', 'level': CONFIG_LEVEL_BASIC }, + 'drift:profiles_depth': {'type': 'float', 'default': 50, 'min': 0, 'max': None, + 'level': CONFIG_LEVEL_ADVANCED, 'units': 'meters', 'description': + 'Environment profiles will be retrieved from surface and down to this depth'}, 'drift:wind_uncertainty': { 'type': 'float', 'default': 0, @@ -592,8 +596,10 @@ def add_readers_from_file(self, *args, **kwargs): '''Make readers from a file containing list of URLs or paths to netCDF datasets''' self.env.add_readers_from_file(*args, **kwargs) + # To be overloaded by sublasses, but this parent method must be called def prepare_run(self): - pass # to be overloaded when needed + # Copy profile_depth from config + self.profiles_depth = self.get_config('drift:profiles_depth') def store_present_positions(self, IDs=None, lons=None, lats=None): """Store present element positions, in case they shall be moved back""" @@ -661,8 +667,7 @@ def interact_with_coastline(self, final=False): self.time, self.elements.lon, self.elements.lat, - self.elements.z, - None) + self.elements.z) self.environment.land_binary_mask = en.land_binary_mask if i == 'stranding': # Deactivate elements on land, but not in air @@ -749,10 +754,6 @@ def ElementType(self): def required_variables(self): """Any trajectory model implementation must list needed variables.""" - @abstractproperty - def required_profiles_z_range(self): - """Any trajectory model implementation must list range or return None.""" - def test_data_folder(self): import opendrift return os.path.abspath( @@ -914,8 +915,7 @@ def closest_ocean_points(self, lon, lat): lon=lon, lat=lat, z=0 * lon, - time=land_reader.start_time, - profiles=None)[0]['land_binary_mask'] + time=land_reader.start_time)[0]['land_binary_mask'] if land.max() == 0: logger.info('All points are in ocean') return lon, lat @@ -936,8 +936,7 @@ def closest_ocean_points(self, lon, lat): lon=longrid, lat=latgrid, z=0 * longrid, - time=land_reader.start_time, - profiles=None)[0]['land_binary_mask'] + time=land_reader.start_time)[0]['land_binary_mask'] if landgrid.min() == 1 or np.isnan(landgrid.min()): logger.warning('No ocean pixels nearby, cannot move elements.') return lon, lat @@ -1092,8 +1091,7 @@ def seed_elements(self, self.time = time[0] env, env_profiles, missing = \ self.env.get_environment(['sea_floor_depth_below_sea_level'], - time=time[0], lon=lon, lat=lat, - z=0*lon, profiles=None) + time=time[0], lon=lon, lat=lat, z=0*lon) elif seafloor_fallback is not None: env = { 'sea_floor_depth_below_sea_level': @@ -2046,7 +2044,8 @@ def run(self, self.elements.lon, self.elements.lat, self.elements.z, - self.required_profiles) + self.required_profiles, + self.profiles_depth) self.store_previous_variables() diff --git a/opendrift/models/basemodel/environment.py b/opendrift/models/basemodel/environment.py index 1d0a53dbd..7eb0fb04b 100644 --- a/opendrift/models/basemodel/environment.py +++ b/opendrift/models/basemodel/environment.py @@ -21,7 +21,6 @@ class Environment(Timeable, Configurable): readers: OrderedDict priority_list: OrderedDict required_variables: Dict - required_profiles_z_range: List[float] # [min_depth, max_depth] discarded_readers: Dict @@ -29,7 +28,7 @@ class Environment(Timeable, Configurable): __finalized__ = False - def __init__(self, required_variables, required_profiles_z_range, _config): + def __init__(self, required_variables, _config): super().__init__() self.readers = OrderedDict() @@ -37,7 +36,6 @@ def __init__(self, required_variables, required_profiles_z_range, _config): self.discarded_readers = {} self.required_variables = required_variables - self.required_profiles_z_range = required_profiles_z_range self._config = _config # reference to simulation config # Add constant and fallback environment variables to config @@ -521,7 +519,7 @@ def missing_variables(self): if var not in self.priority_list ] - def get_environment(self, variables, time, lon, lat, z, profiles): + def get_environment(self, variables, time, lon, lat, z, profiles=None, profiles_depth=None): '''Retrieve environmental variables at requested positions. Args: @@ -536,7 +534,9 @@ def get_environment(self, variables, time, lon, lat, z, profiles): z: depth to get value for - profiles: ? + profiles: list of variables for which profiles are needed + + profiles_depth: depth of profiles in meters, as a positive number Updates: Buffer (raw data blocks) for each reader stored for performance: @@ -562,6 +562,8 @@ def get_environment(self, variables, time, lon, lat, z, profiles): for readername, reader in self.readers.copy().items(): self.discard_reader_if_not_relevant(reader, time) + if profiles_depth is None: + profiles_depth = np.abs(z).max() if 'drift:truncate_ocean_model_below_m' in self._config: truncate_depth = self.get_config( 'drift:truncate_ocean_model_below_m') @@ -570,12 +572,7 @@ def get_environment(self, variables, time, lon, lat, z, profiles): truncate_depth) z = z.copy() z[z < -truncate_depth] = -truncate_depth - if self.required_profiles_z_range is not None: - self.required_profiles_z_range = np.array( - self.required_profiles_z_range) - self.required_profiles_z_range[ - self.required_profiles_z_range < - -truncate_depth] = -truncate_depth + profiles_depth = np.minimum(profiles_depth, truncate_depth) # Initialise more lazy readers if necessary missing_variables = ['missingvar'] @@ -631,7 +628,7 @@ def get_environment(self, variables, time, lon, lat, z, profiles): 'Missing variables: calling get_environment recursively' ) return self.get_environment( - variables, time, lon, lat, z, profiles) + variables, time, lon, lat, z, profiles, profiles_depth) continue # Fetch given variables at given positions from current reader try: @@ -648,7 +645,7 @@ def get_environment(self, variables, time, lon, lat, z, profiles): env_tmp, env_profiles_tmp = \ reader.get_variables_interpolated( variable_group, profiles_from_reader, - self.required_profiles_z_range, time, + profiles_depth, time, lon[missing_indices], lat[missing_indices], z[missing_indices], self.proj_latlon) @@ -662,7 +659,7 @@ def get_environment(self, variables, time, lon, lat, z, profiles): 'Missing variables: calling get_environment recursively' ) return self.get_environment( - variables, time, lon, lat, z, profiles) + variables, time, lon, lat, z, profiles, profiles_depth) continue except Exception as e: # Unknown error @@ -693,7 +690,7 @@ def get_environment(self, variables, time, lon, lat, z, profiles): 'Missing variables: calling get_environment recursively' ) return self.get_environment( - variables, time, lon, lat, z, profiles) + variables, time, lon, lat, z, profiles, profiles_depth) continue # Copy retrieved variables to env array, and mask nan-values @@ -806,9 +803,7 @@ def get_environment(self, variables, time, lon, lat, z, profiles): logger.debug('Creating empty dictionary for profiles not ' 'profided by any reader: ' + str(self.required_profiles)) - env_profiles = {} - env_profiles['z'] = \ - np.array(self.required_profiles_z_range)[::-1] + env_profiles = {'z': [0, -profiles_depth]} if var not in env_profiles: logger.debug( ' Using fallback value %s for %s for all profiles' diff --git a/opendrift/models/chemicaldrift.py b/opendrift/models/chemicaldrift.py index 9c2951b57..d12eadb1d 100644 --- a/opendrift/models/chemicaldrift.py +++ b/opendrift/models/chemicaldrift.py @@ -112,9 +112,6 @@ class ChemicalDrift(OceanDrift): 'pH_sediment':{'fallback': 6.9, 'profiles': False}, # supplied by the user, with pH_sediment as standard name # } - # The depth range (in m) which profiles shall cover - required_profiles_z_range = [-20, 0] - def specie_num2name(self,num): return self.name_species[num] @@ -374,6 +371,7 @@ def prepare_run(self): if (hasattr(value,'sigma') or hasattr(value,'z') ): self.DOC_vertical_levels_given = True + super(ChemicalDrift, self).prepare_run() def init_species(self): # Initialize specie types diff --git a/opendrift/models/larvalfish.py b/opendrift/models/larvalfish.py index 8be3efb01..9c4b89337 100644 --- a/opendrift/models/larvalfish.py +++ b/opendrift/models/larvalfish.py @@ -82,7 +82,6 @@ class LarvalFish(OceanDrift): 'sea_surface_wave_stokes_drift_y_velocity': {'fallback': 0}, } - required_profiles_z_range = [0, -50] # The depth range (in m) which profiles should cover def __init__(self, *args, **kwargs): diff --git a/opendrift/models/model_template.py b/opendrift/models/model_template.py index e197ee835..ac1e9a623 100644 --- a/opendrift/models/model_template.py +++ b/opendrift/models/model_template.py @@ -102,11 +102,10 @@ class ModelTemplate(OceanDrift): # If the attribute 'profiles' is True for a variable (as for # ocean_vertical_diffusivity in the example above), a vertical profile # of this variable is obtained over the vertical depth interval as - # defined below. These profiles are used for the vertical turbulence scheme: + # defined by config setting drift:profiles_depth. These profiles are used for the vertical turbulence scheme: # https://opendrift.github.io/_modules/opendrift/models/oceandrift.html#OceanDrift.vertical_mixing # Profiles are typically required for ocean_vertical_diffusivity, and sometimes for # salinity and temperature in order to calculate the stratification. - required_profiles_z_range = [-50, 0] def __init__(self, *args, **kwargs): @@ -236,6 +235,7 @@ def update(self): def prepare_run(self): """Code to be run before a simulation loop (``update()``)""" + # This method must also call the corresponding method of parent class def bottom_interaction(self, seafloor_depth): """Sub method of vertical_mixing, determines settling""" diff --git a/opendrift/models/oceandrift.py b/opendrift/models/oceandrift.py index 9850c3cad..1da5b7c92 100644 --- a/opendrift/models/oceandrift.py +++ b/opendrift/models/oceandrift.py @@ -96,8 +96,6 @@ class OceanDrift(OpenDriftSimulation): 'land_binary_mask': {'fallback': None}, } - # The depth range (in m) which profiles shall cover - required_profiles_z_range = [-20, 0] def __init__(self, *args, **kwargs): @@ -116,6 +114,9 @@ def __init__(self, *args, **kwargs): else: logger.debug('No machine learning correction available.') + if hasattr(self, 'required_profiles_z_range'): + raise ValueError('self.required_profiles_z_range is obsolete, and replaced by config setting drift:profile_depth. Please update your model.') + # Calling general constructor of parent class super(OceanDrift, self).__init__(*args, **kwargs) @@ -138,9 +139,6 @@ def __init__(self, *args, **kwargs): 'vertical_mixing:TSprofiles': {'type': 'bool', 'default': False, 'level': CONFIG_LEVEL_ADVANCED, 'description': 'Update T and S profiles within inner loop of vertical mixing. This takes more time, but may be slightly more accurate.'}, - 'drift:profiles_depth': {'type': 'float', 'default': 50, 'min': 0, 'max': None, - 'level': CONFIG_LEVEL_ADVANCED, 'units': 'meters', 'description': - 'Environment profiles will be retrieved from surface and down to this depth'}, 'drift:wind_drift_depth': {'type': 'float', 'default': 0.1, 'min': 0, 'max': 10, 'units': 'meters', 'description': 'The direct wind drift (windage) is linearly decreasing from the surface value (wind_drift_factor) until 0 at this depth.', @@ -394,6 +392,9 @@ def update_terminal_velocity(self, Tprofiles=None, Sprofiles=None, def prepare_vertical_mixing(self): pass # To be implemented by subclasses as needed + def prepare_run(self): + super(OceanDrift, self).prepare_run() + def vertical_advection(self): """Move particles vertically according to vertical ocean current @@ -520,7 +521,7 @@ def vertical_mixing(self, store_depths=False): diffusivity_fallback): Kprofiles = self.environment_profiles[ 'ocean_vertical_diffusivity'] - mixing_z = self.environment_profiles['z'] + mixing_z = self.environment_profiles['z'].copy() logger.debug('Using diffusivity from ocean model') else: logger.debug('Using diffusivity from Large1994 since model diffusivities not available') @@ -528,7 +529,7 @@ def vertical_mixing(self, store_depths=False): Kprofiles = self.get_diffusivity_profile('windspeed_Large1994', np.abs(mixing_z)) elif diffusivity_model == 'constant': logger.debug('Using constant diffusivity specified by fallback_values[''ocean_vertical_diffusivity''] = %s m2.s-1' % (diffusivity_fallback)) - mixing_z = self.environment_profiles['z'] + mixing_z = self.environment_profiles['z'].copy() Kprofiles = diffusivity_fallback*np.ones( self.environment_profiles['ocean_vertical_diffusivity'].shape) # keep constant value for ocean_vertical_diffusivity else: diff --git a/opendrift/models/openberg_old.py b/opendrift/models/openberg_old.py index 2d58afb82..1cafc06bb 100644 --- a/opendrift/models/openberg_old.py +++ b/opendrift/models/openberg_old.py @@ -105,8 +105,6 @@ class OpenBergOld(OpenDriftSimulation): 'land_binary_mask': {'fallback': None}, } - required_profiles_z_range = [-120, 0] # [min_depth, max_depth] - # Default colors for plotting status_colors = {'initial': 'green', 'active': 'blue', 'missing_data': 'gray', 'stranded': 'red'} diff --git a/opendrift/models/openhns.py b/opendrift/models/openhns.py index 0e8211887..fdb5f9c80 100644 --- a/opendrift/models/openhns.py +++ b/opendrift/models/openhns.py @@ -178,8 +178,6 @@ class OpenHNS(OceanDrift): }, } - # The depth range (in m) which profiles shall cover - required_profiles_z_range = [-20, 0] max_speed = 1.3 # m/s diff --git a/opendrift/models/openoil/openoil.py b/opendrift/models/openoil/openoil.py index 19a683031..b5af42cc1 100644 --- a/opendrift/models/openoil/openoil.py +++ b/opendrift/models/openoil/openoil.py @@ -292,8 +292,6 @@ class OpenOil(OceanDrift): }, } - # The depth range (in m) which profiles shall cover - required_profiles_z_range = [-20, 0] max_speed = 1.3 # m/s @@ -673,7 +671,6 @@ def oil_weathering(self): self.timer_end('main loop:updating elements:oil weathering') def prepare_run(self): - if self.oil_weathering_model == 'noaa': self.noaa_mass_balance = {} # Populate with seeded mass spread on oiltype.mass_fraction @@ -706,6 +703,8 @@ def prepare_run(self): logger.warning('Could not load max water content file') print(e) + super(OpenOil, self).prepare_run() + def oil_weathering_noaa(self): '''Oil weathering scheme adopted from NOAA PyGNOME model: https://github.com/NOAA-ORR-ERD/PyGnome diff --git a/opendrift/models/pelagicegg.py b/opendrift/models/pelagicegg.py index 452bfc049..e2193555b 100644 --- a/opendrift/models/pelagicegg.py +++ b/opendrift/models/pelagicegg.py @@ -77,8 +77,6 @@ class PelagicEggDrift(OceanDrift): 'upward_sea_water_velocity': {'fallback': 0}, } - # The depth range (in m) which profiles shall cover - required_profiles_z_range = [-120, 0] # Default colors for plotting status_colors = {'initial': 'green', 'active': 'blue', diff --git a/opendrift/models/radionuclides.py b/opendrift/models/radionuclides.py index 0e0e8c26f..009e860eb 100644 --- a/opendrift/models/radionuclides.py +++ b/opendrift/models/radionuclides.py @@ -92,9 +92,6 @@ class RadionuclideDrift(OceanDrift): 'conc3': {'fallback': 1.e-3}, } - # The depth range (in m) which profiles shall cover - required_profiles_z_range = [-20, 0] - def specie_num2name(self,num): return self.name_species[num] @@ -226,7 +223,7 @@ def prepare_run(self): logger.info('nspecies: %s' % self.nspecies) logger.info('Transfer rates:\n %s' % self.transfer_rates) - + super(RadionuclideDrift, self).prepare_run() def init_species(self): diff --git a/opendrift/models/sealice.py b/opendrift/models/sealice.py index cfa65ee6a..41f197d96 100644 --- a/opendrift/models/sealice.py +++ b/opendrift/models/sealice.py @@ -96,7 +96,6 @@ class SeaLice(OceanDrift): 'sea_water_salinity': {'fallback': 34} } - # required_profiles_z_range = [0, -50] # The depth range (in m) which profiles should cover def __init__(self,*args, **kwargs): @@ -184,6 +183,8 @@ def prepare_run(self): self.population() self.ref_date = datetime(1,1,1,0,0) + super(SeaLice, self).prepare_run() + def new_born(self): """ Approach by Rittenhouse et al., (2016) [Rittenhouse, M.A., C.W. Revie diff --git a/opendrift/readers/basereader/continuous.py b/opendrift/readers/basereader/continuous.py index 6009f0ccd..65fdf79e7 100644 --- a/opendrift/readers/basereader/continuous.py +++ b/opendrift/readers/basereader/continuous.py @@ -38,7 +38,7 @@ def _get_variables_interpolated_(self, variables, profiles, env_profiles = None if profiles is not None: # Copying data from environment to vertical profiles - env_profiles = {'z': profiles_depth} + env_profiles = {'z': [0, -profiles_depth]} for var in profiles: env_profiles[var] = np.ma.array([env[var], env[var]]) diff --git a/opendrift/readers/basereader/structured.py b/opendrift/readers/basereader/structured.py index b4fdfd20b..eb051cf16 100644 --- a/opendrift/readers/basereader/structured.py +++ b/opendrift/readers/basereader/structured.py @@ -233,7 +233,7 @@ def _get_variables_interpolated_(self, variables, profiles, profiles_depth, # requested block has the depth range required for profiles mx = np.append(reader_x, [reader_x[-1], reader_x[-1]]) my = np.append(reader_y, [reader_y[-1], reader_y[-1]]) - mz = np.append(z, [profiles_depth[0], profiles_depth[1]]) + mz = np.append(z, [0, -profiles_depth]) else: mx = reader_x my = reader_y @@ -322,7 +322,7 @@ def _get_variables_interpolated_(self, variables, profiles, profiles_depth, 'cover element positions within timestep. ' 'Buffer size (%s) must be increased. See `Variables.set_buffer_size`.' % (self.name, str(self.buffer))) - # TODO; could add dynamic incraes of buffer size here + # TODO: could add dynamic increase of buffer size here ############################################################ # Interpolate before/after blocks onto particles in space @@ -389,7 +389,7 @@ def _get_variables_interpolated_(self, variables, profiles, profiles_depth, env_profiles = env_profiles_before else: # Copying data from environment to vertical profiles - env_profiles = {'z': profiles_depth} + env_profiles = {'z': [0, -profiles_depth]} for var in profiles: env_profiles[var] = np.ma.array([env[var], env[var]]) self.timer_end('interpolation_time') diff --git a/opendrift/readers/basereader/unstructured.py b/opendrift/readers/basereader/unstructured.py index c6c7da3f9..44c31b486 100644 --- a/opendrift/readers/basereader/unstructured.py +++ b/opendrift/readers/basereader/unstructured.py @@ -67,7 +67,7 @@ def _get_variables_interpolated_(self, variables, profiles, env_profiles = None if profiles is not None: # Copying data from environment to vertical profiles - env_profiles = {'z': profiles_depth} + env_profiles = {'z': [0, -profiles_depth]} for var in profiles: env_profiles[var] = np.ma.array([env[var], env[var]]) diff --git a/opendrift/readers/basereader/variables.py b/opendrift/readers/basereader/variables.py index c90b6001d..8d8795536 100644 --- a/opendrift/readers/basereader/variables.py +++ b/opendrift/readers/basereader/variables.py @@ -860,7 +860,7 @@ def get_variables_interpolated(self, profiles: List of variable names that should be returned for the range in `profiles_depth`. - profiles_depth: A range [z-start, z-end] for which to return values for profile-variables. The exact z-depth are given by the reader and returned as `z` variable in `env_profiles`. + profiles_depth: Profiles variables will be retrieved from surface and down to this depth. The exact z-depth are given by the reader and returned as `z` variable in `env_profiles`. time: datetime or None, time at which data are requested. Can be None (default) if reader/variable has no time diff --git a/tests/models/test_environment.py b/tests/models/test_environment.py index f77e24528..cd11cb749 100644 --- a/tests/models/test_environment.py +++ b/tests/models/test_environment.py @@ -13,7 +13,7 @@ def test_add_readers(test_data_roms): 'fallback': 10 }, } - env = Environment(required_variables, None, c._config) + env = Environment(required_variables, c._config) env.add_reader(test_data_roms) env.finalize() diff --git a/tests/models/test_readers.py b/tests/models/test_readers.py index d6c42d667..988bde213 100644 --- a/tests/models/test_readers.py +++ b/tests/models/test_readers.py @@ -387,7 +387,7 @@ def test_vertical_profiles(self): # test also profiles depth data, profiles = norkyst3d.get_variables_interpolated( variables, profiles=['sea_water_temperature'], - profiles_depth = [-100, 0], + profiles_depth = 100, time = norkyst3d.start_time, lon=lon, lat=lat, z=0) assert profiles['z'].min() == -150 @@ -395,7 +395,7 @@ def test_vertical_profiles(self): # NB TODO: must use later time, otherwise cache (with deeper profile) is reused data, profiles = norkyst3d.get_variables_interpolated( variables, profiles=['sea_water_temperature'], - profiles_depth = [-30, 0], + profiles_depth = 30, time = norkyst3d.start_time + timedelta(hours=1), lon=lon, lat=lat, z=0) assert profiles['z'].min() == -75 @@ -403,20 +403,31 @@ def test_vertical_profiles(self): norkyst3d.verticalbuffer=0 data, profiles = norkyst3d.get_variables_interpolated( variables, profiles=['sea_water_temperature'], - profiles_depth = [-30, 0], + profiles_depth = 30, time = norkyst3d.start_time + timedelta(hours=2), lon=lon, lat=lat, z=0) assert profiles['z'].min() == -50 def test_vertical_profile_from_simulation(self): - norkyst3d = reader_netCDF_CF_generic.Reader(o.test_data_folder() + + o = OpenOil() + r = reader_netCDF_CF_generic.Reader(o.test_data_folder() + '14Jan2016_NorKyst_z_3d/NorKyst-800m_ZDEPTHS_his_00_3Dsubset.nc') - lon = np.array([4.73]) - lat = np.array([62.35]) variables = ['x_sea_water_velocity', 'x_sea_water_velocity', 'sea_water_temperature'] - # TODO TO BE COMPLETED - + N = 10 + o.add_reader(r) + o.set_config('environment:constant', {'land_binary_mask': 0, 'x_wind': 0, 'y_wind': 0}) + o.seed_elements(lon=np.linspace(4.5, 4.8, N), lat=np.linspace(63, 63.2, N), time=r.start_time) + o.run(steps=1) + assert o.environment_profiles['z'].min() == -75 + + o = OpenOil() + o.set_config('drift:profiles_depth', 20) + o.add_reader(r) + o.set_config('environment:constant', {'land_binary_mask': 0, 'x_wind': 0, 'y_wind': 0}) + o.seed_elements(lon=np.linspace(4.5, 4.8, N), lat=np.linspace(63, 63.2, N), time=r.start_time) + o.run(steps=1) + assert o.environment_profiles['z'].min() == -50 def test_vertical_interpolation(self): norkyst3d = reader_netCDF_CF_generic.Reader(o.test_data_folder() + @@ -430,7 +441,7 @@ def test_vertical_interpolation(self): # space (horizontally, vertically) and then in time data, profiles = norkyst3d.get_variables_interpolated( variables, profiles=['sea_water_temperature'], - profiles_depth = [-100, 0], + profiles_depth = 100, time = norkyst3d.start_time + timedelta(seconds=900), lon=lon, lat=lat, z=z) # Check surface value @@ -486,7 +497,7 @@ def test_get_environment(self): o.env.get_environment(list(o.required_variables), reader_nordic.start_time, testlon, testlat, testz, - o.required_profiles) + o.required_profiles, profiles_depth=120) self.assertAlmostEqual(env['sea_water_temperature'][0], 4.251, 2) self.assertAlmostEqual(env['sea_water_temperature'][1], -0.148, 3) self.assertAlmostEqual(env['sea_water_temperature'][4], 10.0)