コード例 #1
0
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
コード例 #2
0
    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
コード例 #3
0
ファイル: wflow.py プロジェクト: eWaterCycle/ewatercycle
 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']}")
コード例 #4
0
ファイル: pcrglobwb.py プロジェクト: eWaterCycle/ewatercycle
    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']}"
            )
コード例 #5
0
    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)
コード例 #6
0
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")),
        ]
コード例 #7
0
    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)
コード例 #8
0
 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))
コード例 #9
0
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
コード例 #10
0
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
コード例 #11
0
    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)
コード例 #12
0
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