def walrus_model_with_extra_volume(tmp_path, walrus_input_on_extra_volume): (input_dir, extra_volumes) = walrus_input_on_extra_volume model = BmiClientDocker(image=walrus_docker_image, image_port=55555, input_dir=str(input_dir), extra_volumes=extra_volumes) yield model del model
def test_container_start_failure(self, exit_container): expected = r"Failed to start Docker container with image" with pytest.raises(DeadDockerContainerException, match=expected) as excinfo: BmiClientDocker(image=exit_container) assert excinfo.value.exitcode == 25 assert b'my stderr' in excinfo.value.logs assert b'my stdout' in excinfo.value.logs
def _start_container(self): if CFG["container_engine"] == "docker": self.bmi = BmiClientDocker( image=self.docker_image, image_port=55555, work_dir=str(self.work_dir), timeout=300, ) elif CFG["container_engine"] == "singularity": self.bmi = BmiClientSingularity( image=self._singularity_image(CFG["singularity_dir"]), work_dir=str(self.work_dir), timeout=300, ) else: raise ValueError( f"Unknown container technology: {CFG['container_engine']}")
def _start_container(self): additional_input_dirs = [str(self.parameter_set.directory)] if self.forcing: additional_input_dirs.append(self.forcing.directory) if CFG["container_engine"] == "docker": self.bmi = BmiClientDocker( image=self.docker_image, image_port=55555, work_dir=str(self.work_dir), input_dirs=additional_input_dirs, timeout=300, ) elif CFG["container_engine"] == "singularity": self.bmi = BmiClientSingularity( image=self._singularity_image(CFG["singularity_dir"]), work_dir=str(self.work_dir), input_dirs=additional_input_dirs, timeout=300, ) else: raise ValueError( f"Unknown container technology in CFG: {CFG['container_engine']}" )
def setup( # type: ignore self, maximum_soil_moisture_storage: float = None, initial_soil_moisture_storage: float = None, start_time: str = None, end_time: str = None, solver: Solver = None, cfg_dir: str = None, ) -> Tuple[str, str]: """Configure model run. 1. Creates config file and config directory based on the forcing variables and time range 2. Start bmi container and store as :py:attr:`bmi` Args: maximum_soil_moisture_storage: in mm. Range is specfied in `model parameter range file <https://github.com/wknoben/MARRMoT/blob/master/MARRMoT/Models/Parameter%20range%20files/m_01_collie1_1p_1s_parameter_ranges.m>`_. initial_soil_moisture_storage: in mm. start_time: Start time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing start time is used. end_time: End time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing end time is used. solver: Solver settings cfg_dir: a run directory given by user or created for user. Returns: Path to config file and path to config directory """ if maximum_soil_moisture_storage: self._parameters = [maximum_soil_moisture_storage] if initial_soil_moisture_storage: self.store_ini = [initial_soil_moisture_storage] if solver: self.solver = solver cfg_dir_as_path = None if cfg_dir: cfg_dir_as_path = to_absolute_path(cfg_dir) cfg_dir_as_path = _generate_cfg_dir(cfg_dir_as_path) config_file = self._create_marrmot_config(cfg_dir_as_path, start_time, end_time) if CFG["container_engine"].lower() == "singularity": message = f"The singularity image {self.singularity_image} does not exist." assert self.singularity_image.exists(), message self.bmi = BmiClientSingularity( image=str(self.singularity_image), work_dir=str(cfg_dir_as_path), timeout=300, ) elif CFG["container_engine"].lower() == "docker": self.bmi = BmiClientDocker( image=self.docker_image, image_port=55555, work_dir=str(cfg_dir_as_path), timeout=300, ) else: raise ValueError( f"Unknown container technology in CFG: {CFG['container_engine']}" ) return str(config_file), str(cfg_dir_as_path)
class MarrmotM01(AbstractModel[MarrmotForcing]): """eWaterCycle implementation of Marrmot Collie River 1 (traditional bucket) model. It sets MarrmotM01 parameter with an initial value that is the mean value of the range specfied in `model parameter range file <https://github.com/wknoben/MARRMoT/blob/master/MARRMoT/Models/Parameter%20range%20files/m_01_collie1_1p_1s_parameter_ranges.m>`_. Args: version: pick a version for which an ewatercycle grpc4bmi docker image is available. forcing: a MarrmotForcing object. If forcing file contains parameter and other settings, those are used and can be changed in :py:meth:`setup`. Example: See examples/marrmotM01.ipynb in `ewatercycle repository <https://github.com/eWaterCycle/ewatercycle>`_ """ model_name = "m_01_collie1_1p_1s" """Name of model in Matlab code.""" available_versions = ("2020.11", ) """Versions for which ewatercycle grpc4bmi docker images are available.""" def __init__(self, version: str, forcing: MarrmotForcing): # noqa: D107 super().__init__(version, forcing=forcing) self._parameters = [1000.0] self.store_ini = [900.0] self.solver = Solver() self._check_forcing(forcing) self._set_singularity_image() self._set_docker_image() def _set_docker_image(self): images = {"2020.11": "ewatercycle/marrmot-grpc4bmi:2020.11"} self.docker_image = images[self.version] def _set_singularity_image(self): images = {"2020.11": "ewatercycle-marrmot-grpc4bmi_2020.11.sif"} if CFG.get("singularity_dir"): self.singularity_image = CFG["singularity_dir"] / images[ self.version] # unable to subclass with more specialized arguments so ignore type def setup( # type: ignore self, maximum_soil_moisture_storage: float = None, initial_soil_moisture_storage: float = None, start_time: str = None, end_time: str = None, solver: Solver = None, cfg_dir: str = None, ) -> Tuple[str, str]: """Configure model run. 1. Creates config file and config directory based on the forcing variables and time range 2. Start bmi container and store as :py:attr:`bmi` Args: maximum_soil_moisture_storage: in mm. Range is specfied in `model parameter range file <https://github.com/wknoben/MARRMoT/blob/master/MARRMoT/Models/Parameter%20range%20files/m_01_collie1_1p_1s_parameter_ranges.m>`_. initial_soil_moisture_storage: in mm. start_time: Start time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing start time is used. end_time: End time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing end time is used. solver: Solver settings cfg_dir: a run directory given by user or created for user. Returns: Path to config file and path to config directory """ if maximum_soil_moisture_storage: self._parameters = [maximum_soil_moisture_storage] if initial_soil_moisture_storage: self.store_ini = [initial_soil_moisture_storage] if solver: self.solver = solver cfg_dir_as_path = None if cfg_dir: cfg_dir_as_path = to_absolute_path(cfg_dir) cfg_dir_as_path = _generate_cfg_dir(cfg_dir_as_path) config_file = self._create_marrmot_config(cfg_dir_as_path, start_time, end_time) if CFG["container_engine"].lower() == "singularity": message = f"The singularity image {self.singularity_image} does not exist." assert self.singularity_image.exists(), message self.bmi = BmiClientSingularity( image=str(self.singularity_image), work_dir=str(cfg_dir_as_path), timeout=300, ) elif CFG["container_engine"].lower() == "docker": self.bmi = BmiClientDocker( image=self.docker_image, image_port=55555, work_dir=str(cfg_dir_as_path), timeout=300, ) else: raise ValueError( f"Unknown container technology in CFG: {CFG['container_engine']}" ) return str(config_file), str(cfg_dir_as_path) def _check_forcing(self, forcing): """Check forcing argument and get path, start and end time of forcing data.""" if isinstance(forcing, MarrmotForcing): forcing_dir = to_absolute_path(forcing.directory) self.forcing_file = str(forcing_dir / forcing.forcing_file) # convert date_strings to datetime objects self.forcing_start_time = get_time(forcing.start_time) self.forcing_end_time = get_time(forcing.end_time) else: raise TypeError( f"Unknown forcing type: {forcing}. Please supply a " " MarrmotForcing object.") # parse start/end time forcing_data = sio.loadmat(self.forcing_file, mat_dtype=True) if "parameters" in forcing_data: self._parameters = forcing_data["parameters"][0] if "store_ini" in forcing_data: self.store_ini = forcing_data["store_ini"][0] if "solver" in forcing_data: forcing_solver = forcing_data["solver"] self.solver.name = forcing_solver["name"][0][0][0] self.solver.resnorm_tolerance = forcing_solver[ "resnorm_tolerance"][0][0][0] self.solver.resnorm_maxiter = forcing_solver["resnorm_maxiter"][0][ 0][0] def _create_marrmot_config(self, cfg_dir: Path, start_time_iso: str = None, end_time_iso: str = None) -> Path: """Write model configuration file. Adds the model parameters to forcing file for the given period and writes this information to a model configuration file. Args: cfg_dir: a run directory given by user or created for user. start_time_iso: Start time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing start time is used. end_time_iso: End time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing end time is used. Returns: Path for Marrmot config file """ forcing_data = sio.loadmat(self.forcing_file, mat_dtype=True) # overwrite dates if given if start_time_iso is not None: start_time = get_time(start_time_iso) if self.forcing_start_time <= start_time <= self.forcing_end_time: forcing_data["time_start"][0][0:6] = [ start_time.year, start_time.month, start_time.day, start_time.hour, start_time.minute, start_time.second, ] self.forcing_start_time = start_time else: raise ValueError("start_time outside forcing time range") if end_time_iso is not None: end_time = get_time(end_time_iso) if self.forcing_start_time <= end_time <= self.forcing_end_time: forcing_data["time_end"][0][0:6] = [ end_time.year, end_time.month, end_time.day, end_time.hour, end_time.minute, end_time.second, ] self.forcing_end_time = end_time else: raise ValueError("end_time outside forcing time range") # combine forcing and model parameters forcing_data.update( model_name=self.model_name, parameters=self._parameters, solver=asdict(self.solver), store_ini=self.store_ini, ) config_file = cfg_dir / "marrmot-m01_config.mat" sio.savemat(config_file, forcing_data) return config_file def get_value_as_xarray(self, name: str) -> xr.DataArray: """Return the value as xarray object.""" marrmot_vars = {"S(t)", "flux_out_Q", "flux_out_Ea", "wb"} if name not in marrmot_vars: raise NotImplementedError("Variable '{}' is not implemented. " "Please choose one of {}.".format( name, marrmot_vars)) # Get time information time_units = self.bmi.get_time_units() grid = self.bmi.get_var_grid(name) shape = self.bmi.get_grid_shape(grid) # Extract the data and store it in an xarray DataArray return xr.DataArray( data=np.reshape(self.bmi.get_value(name), shape), coords={ "longitude": self.bmi.get_grid_x(grid), "latitude": self.bmi.get_grid_y(grid), "time": num2date(self.bmi.get_current_time(), time_units), }, dims=["latitude", "longitude"], name=name, attrs={"units": self.bmi.get_var_units(name)}, ) @property def parameters(self) -> Iterable[Tuple[str, Any]]: """List the parameters for this model.""" return [ ("maximum_soil_moisture_storage", self._parameters[0]), ("initial_soil_moisture_storage", self.store_ini[0]), ("solver", self.solver), ("start time", self.forcing_start_time.strftime("%Y-%m-%dT%H:%M:%SZ")), ("end time", self.forcing_end_time.strftime("%Y-%m-%dT%H:%M:%SZ")), ]
def setup( # type: ignore self, maximum_soil_moisture_storage: float = None, threshold_flow_generation_evap_change: float = None, leakage_saturated_zone_flow_coefficient: float = None, zero_deficit_base_flow_speed: float = None, baseflow_coefficient: float = None, gamma_distribution_chi_parameter: float = None, gamma_distribution_phi_parameter: float = None, initial_upper_zone_storage: float = None, initial_saturated_zone_storage: float = None, start_time: str = None, end_time: str = None, solver: Solver = None, cfg_dir: str = None, ) -> Tuple[str, str]: """Configure model run. 1. Creates config file and config directory based on the forcing variables and time range 2. Start bmi container and store as :py:attr:`bmi` Args: maximum_soil_moisture_storage: in mm. Range is specfied in `model parameter range file <https://github.com/wknoben/MARRMoT/blob/master/MARRMoT/Models/Parameter%20range%20files/m_01_collie1_1p_1s_parameter_ranges.m>`_. threshold_flow_generation_evap_change. leakage_saturated_zone_flow_coefficient: in mm/d. zero_deficit_base_flow_speed: in mm/d. baseflow_coefficient: in mm-1. gamma_distribution_chi_parameter. gamma_distribution_phi_parameter. initial_upper_zone_storage: in mm. initial_saturated_zone_storage: in mm. start_time: Start time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing start time is used. end_time: End time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing end time is used. solver: Solver settings cfg_dir: a run directory given by user or created for user. Returns: Path to config file and path to config directory """ arguments = vars() arguments_subset = {key: arguments[key] for key in M14_PARAMS} for index, key in enumerate(M14_PARAMS): if arguments_subset[key] is not None: self._parameters[index] = arguments_subset[key] if initial_upper_zone_storage: self.store_ini[0] = initial_upper_zone_storage if initial_saturated_zone_storage: self.store_ini[1] = initial_saturated_zone_storage if solver: self.solver = solver cfg_dir_as_path = None if cfg_dir: cfg_dir_as_path = to_absolute_path(cfg_dir) cfg_dir_as_path = _generate_cfg_dir(cfg_dir_as_path) config_file = self._create_marrmot_config(cfg_dir_as_path, start_time, end_time) if CFG["container_engine"].lower() == "singularity": message = f"The singularity image {self.singularity_image} does not exist." assert self.singularity_image.exists(), message self.bmi = BmiClientSingularity( image=str(self.singularity_image), work_dir=str(cfg_dir_as_path), timeout=300, ) elif CFG["container_engine"].lower() == "docker": self.bmi = BmiClientDocker( image=self.docker_image, image_port=55555, work_dir=str(cfg_dir_as_path), timeout=300, ) else: raise ValueError( f"Unknown container technology in CFG: {CFG['container_engine']}" ) return str(config_file), str(cfg_dir_as_path)
def test_inputdir_absent(self, tmp_path): dirthatdoesnotexist = 'dirthatdoesnotexist' input_dir = tmp_path / dirthatdoesnotexist with pytest.raises(NotADirectoryError, match=dirthatdoesnotexist): BmiClientDocker(image=walrus_docker_image, image_port=55555, input_dir=str(input_dir))
def walrus_model(tmp_path, walrus_input): model = BmiClientDocker(image=walrus_docker_image, image_port=55555, input_dir=str(tmp_path)) yield model del model
def walrus_model(tmp_path, walrus_input): model = BmiClientDocker(image="ewatercycle/walrus-grpc4bmi:v0.2.0", image_port=55555, input_dir=str(tmp_path)) yield model del model
def setup( # type: ignore self, IrrigationEfficiency: str = None, # noqa: N803 start_time: str = None, end_time: str = None, MaskMap: str = None, cfg_dir: str = None, ) -> Tuple[str, str]: """Configure model run. 1. Creates config file and config directory based on the forcing variables and time range. 2. Start bmi container and store as :py:attr:`bmi` Args: IrrigationEfficiency: Field application irrigation efficiency. max 1, ~0.90 drip irrigation, ~0.75 sprinkling start_time: Start time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing start time is used. end_time: End time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing end time is used. MaskMap: Mask map to use instead of one supplied in parameter set. Path to a NetCDF or pcraster file with same dimensions as parameter set map files and a boolean variable. cfg_dir: a run directory given by user or created for user. Returns: Path to config file and path to config directory """ # TODO forcing can be a part of parameter_set cfg_dir_as_path = None if cfg_dir: cfg_dir_as_path = to_absolute_path(cfg_dir) cfg_dir_as_path = _generate_workdir(cfg_dir_as_path) config_file = self._create_lisflood_config( cfg_dir_as_path, start_time, end_time, IrrigationEfficiency, MaskMap, ) assert self.parameter_set is not None input_dirs = [str(self.parameter_set.directory), str(self.forcing_dir)] if MaskMap is not None: mask_map = to_absolute_path(MaskMap) try: mask_map.relative_to(self.parameter_set.directory) except ValueError: # If not relative add dir input_dirs.append(str(mask_map.parent)) if CFG["container_engine"].lower() == "singularity": image = get_singularity_image(self.version, CFG["singularity_dir"]) self.bmi = BmiClientSingularity( image=str(image), input_dirs=input_dirs, work_dir=str(cfg_dir_as_path), timeout=300, ) elif CFG["container_engine"].lower() == "docker": image = get_docker_image(self.version) self.bmi = BmiClientDocker( image=image, image_port=55555, input_dirs=input_dirs, work_dir=str(cfg_dir_as_path), timeout=300, ) else: raise ValueError( f"Unknown container technology in CFG: {CFG['container_engine']}" ) return str(config_file), str(cfg_dir_as_path)
class Lisflood(AbstractModel[LisfloodForcing]): """eWaterCycle implementation of Lisflood hydrological model. Args: version: pick a version for which an grpc4bmi docker image is available. parameter_set: LISFLOOD input files. Any included forcing data will be ignored. forcing: a LisfloodForcing object. Example: See examples/lisflood.ipynb in `ewatercycle repository <https://github.com/eWaterCycle/ewatercycle>`_ """ available_versions = tuple(version_images.keys()) """Versions for which ewatercycle grpc4bmi docker images are available.""" def __init__( # noqa: D107 self, version: str, parameter_set: ParameterSet, forcing: LisfloodForcing, ): super().__init__(version, parameter_set, forcing) self._check_forcing(forcing) self.cfg = XmlConfig(parameter_set.config) def _get_textvar_value(self, name: str): for textvar in self.cfg.config.iter("textvar"): textvar_name = textvar.attrib["name"] if name == textvar_name: return textvar.get("value") raise KeyError(f"Name {name} not found in the config file.") # unable to subclass with more specialized arguments so ignore type def setup( # type: ignore self, IrrigationEfficiency: str = None, # noqa: N803 start_time: str = None, end_time: str = None, MaskMap: str = None, cfg_dir: str = None, ) -> Tuple[str, str]: """Configure model run. 1. Creates config file and config directory based on the forcing variables and time range. 2. Start bmi container and store as :py:attr:`bmi` Args: IrrigationEfficiency: Field application irrigation efficiency. max 1, ~0.90 drip irrigation, ~0.75 sprinkling start_time: Start time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing start time is used. end_time: End time of model in UTC and ISO format string e.g. 'YYYY-MM-DDTHH:MM:SSZ'. If not given then forcing end time is used. MaskMap: Mask map to use instead of one supplied in parameter set. Path to a NetCDF or pcraster file with same dimensions as parameter set map files and a boolean variable. cfg_dir: a run directory given by user or created for user. Returns: Path to config file and path to config directory """ # TODO forcing can be a part of parameter_set cfg_dir_as_path = None if cfg_dir: cfg_dir_as_path = to_absolute_path(cfg_dir) cfg_dir_as_path = _generate_workdir(cfg_dir_as_path) config_file = self._create_lisflood_config( cfg_dir_as_path, start_time, end_time, IrrigationEfficiency, MaskMap, ) assert self.parameter_set is not None input_dirs = [str(self.parameter_set.directory), str(self.forcing_dir)] if MaskMap is not None: mask_map = to_absolute_path(MaskMap) try: mask_map.relative_to(self.parameter_set.directory) except ValueError: # If not relative add dir input_dirs.append(str(mask_map.parent)) if CFG["container_engine"].lower() == "singularity": image = get_singularity_image(self.version, CFG["singularity_dir"]) self.bmi = BmiClientSingularity( image=str(image), input_dirs=input_dirs, work_dir=str(cfg_dir_as_path), timeout=300, ) elif CFG["container_engine"].lower() == "docker": image = get_docker_image(self.version) self.bmi = BmiClientDocker( image=image, image_port=55555, input_dirs=input_dirs, work_dir=str(cfg_dir_as_path), timeout=300, ) else: raise ValueError( f"Unknown container technology in CFG: {CFG['container_engine']}" ) return str(config_file), str(cfg_dir_as_path) def _check_forcing(self, forcing): """Check forcing argument and get path, start/end time of forcing data.""" # TODO check if mask has same grid as forcing files, # if not warn users to run reindex_forcings if isinstance(forcing, LisfloodForcing): self.forcing = forcing self.forcing_dir = to_absolute_path(forcing.directory) # convert date_strings to datetime objects self._start = get_time(forcing.start_time) self._end = get_time(forcing.end_time) else: raise TypeError(f"Unknown forcing type: {forcing}. " "Please supply a LisfloodForcing object.") def _create_lisflood_config( self, cfg_dir: Path, start_time_iso: str = None, end_time_iso: str = None, IrrigationEfficiency: str = None, # noqa: N803 MaskMap: str = None, ) -> Path: """Create lisflood config file.""" assert self.parameter_set is not None assert self.forcing is not None # overwrite dates if given if start_time_iso is not None: start_time = get_time(start_time_iso) if self._start <= start_time <= self._end: self._start = start_time else: raise ValueError("start_time outside forcing time range") if end_time_iso is not None: end_time = get_time(end_time_iso) if self._start <= end_time <= self._end: self._end = end_time else: raise ValueError("end_time outside forcing time range") settings = { "CalendarDayStart": self._start.strftime("%d/%m/%Y 00:00"), "StepStart": "1", "StepEnd": str((self._end - self._start).days), "PathRoot": str(self.parameter_set.directory), "PathMeteo": str(self.forcing_dir), "PathOut": str(cfg_dir), } if IrrigationEfficiency is not None: settings["IrrigationEfficiency"] = IrrigationEfficiency if MaskMap is not None: mask_map = to_absolute_path(MaskMap) settings["MaskMap"] = str(mask_map.with_suffix("")) for textvar in self.cfg.config.iter("textvar"): textvar_name = textvar.attrib["name"] # general settings for key, value in settings.items(): if key in textvar_name: textvar.set("value", value) # input for lisflood if "PrefixPrecipitation" in textvar_name: textvar.set("value", Path(self.forcing.PrefixPrecipitation).stem) if "PrefixTavg" in textvar_name: textvar.set("value", Path(self.forcing.PrefixTavg).stem) # maps_prefixes dictionary contains lisvap filenames in lisflood config maps_prefixes = { "E0Maps": { "name": "PrefixE0", "value": Path(self.forcing.PrefixE0).stem, }, "ES0Maps": { "name": "PrefixES0", "value": Path(self.forcing.PrefixES0).stem, }, "ET0Maps": { "name": "PrefixET0", "value": Path(self.forcing.PrefixET0).stem, }, } # output of lisvap for map_var, prefix in maps_prefixes.items(): if prefix["name"] in textvar_name: textvar.set("value", prefix["value"]) if map_var in textvar_name: textvar.set("value", f"$(PathMeteo)/$({prefix['name']})") # Write to new setting file lisflood_file = cfg_dir / "lisflood_setting.xml" self.cfg.save(str(lisflood_file)) return lisflood_file def get_value_as_xarray(self, name: str) -> xr.DataArray: """Return the value as xarray object.""" # Get time information time_units = self.bmi.get_time_units() grid = self.bmi.get_var_grid(name) shape = self.bmi.get_grid_shape(grid) # Extract the data and store it in an xarray DataArray return xr.DataArray( data=np.reshape(self.bmi.get_value(name), shape), coords={ "longitude": self.bmi.get_grid_x(grid), "latitude": self.bmi.get_grid_y(grid), "time": num2date(self.bmi.get_current_time(), time_units), }, dims=["latitude", "longitude"], name=name, attrs={"units": self.bmi.get_var_units(name)}, ) def _coords_to_indices(self, name: str, lat: Iterable[float], lon: Iterable[float]) -> Iterable[int]: """Convert lat/lon values to index. Args: lat: Latitudinal value lon: Longitudinal value """ grid_id = self.bmi.get_var_grid(name) shape = self.bmi.get_grid_shape(grid_id) # (len(y), len(x)) grid_lon = self.bmi.get_grid_x(grid_id) # x is longitude grid_lat = self.bmi.get_grid_y(grid_id) # y is latitude indices = [] for point_lon, point_lat in zip(lon, lat): idx_lon, idx_lat = find_closest_point(grid_lon, grid_lat, point_lon, point_lat) idx_flat = cast(int, np.ravel_multi_index((idx_lat, idx_lon), shape)) indices.append(idx_flat) logger.debug( f"Requested point was lon: {point_lon}, lat: {point_lat}; " "closest grid point is " f"{grid_lon[idx_lon]:.2f}, {grid_lat[idx_lat]:.2f}.") return indices @property def parameters(self) -> Iterable[Tuple[str, Any]]: """List the parameters for this model.""" assert self.parameter_set is not None assert self.forcing is not None # TODO fix issue #60 return [ ( "IrrigationEfficiency", self._get_textvar_value("IrrigationEfficiency"), ), ("MaskMap", self._get_textvar_value("MaskMap")), ("start_time", self._start.strftime("%Y-%m-%dT%H:%M:%SZ")), ("end_time", self._end.strftime("%Y-%m-%dT%H:%M:%SZ")), ] def finalize(self) -> None: """Perform tear-down tasks for the model.""" # Finalize function of bmi class of lisflood is kaput, so not calling it del self.bmi