Source code for imod.msw.sprinkling

from typing import TextIO

import numpy as np
import pandas as pd
import xarray as xr

from imod.mf6.dis import StructuredDiscretization
from imod.mf6.interfaces.iregridpackage import IRegridPackage
from imod.mf6.mf6_wel_adapter import Mf6Wel
from imod.msw.fixed_format import VariableMetaData
from imod.msw.pkgbase import MetaSwapPackage
from imod.msw.regrid.regrid_schemes import SprinklingRegridMethod
from imod.msw.utilities.common import concat_imod5
from imod.typing import GridDataDict, Imod5DataDict, IntArray, SelSettingsType
from imod.typing.grid import zeros_like


def _ravel_per_subunit(da: xr.DataArray) -> np.ndarray:
    # per defined well element, all subunits
    array_out = da.to_numpy().ravel()
    # per defined well element, per defined subunits
    return array_out[np.isfinite(array_out)]


def _sprinkling_data_from_imod5_ipf(cap_data: GridDataDict) -> GridDataDict:
    raise NotImplementedError(
        "Assigning sprinkling wells with an IPF file is not supported, please specify them as IDF."
    )


def _sprinkling_data_from_imod5_grid(cap_data: GridDataDict) -> GridDataDict:
    drop_layer_kwargs: SelSettingsType = {
        "layer": 0,
        "drop": True,
        "missing_dims": "ignore",
    }
    type = cap_data["artificial_recharge"].isel(**drop_layer_kwargs)
    capacity = cap_data["artificial_recharge_capacity"].isel(**drop_layer_kwargs)

    from_groundwater = type == 1
    from_surfacewater = type == 2
    is_active = type != 0

    zero_where_active = zeros_like(type).where(is_active)

    # Add zero where active, to have active cells set to 0.0.
    max_abstraction_groundwater_rural = zero_where_active.where(
        ~from_groundwater, capacity
    )
    max_abstraction_surfacewater_rural = zero_where_active.where(
        ~from_surfacewater, capacity
    )

    # No sprinkling for urban environments
    max_abstraction_urban = zero_where_active

    data = {}
    data["max_abstraction_groundwater"] = concat_imod5(
        max_abstraction_groundwater_rural, max_abstraction_urban
    )
    data["max_abstraction_surfacewater"] = concat_imod5(
        max_abstraction_surfacewater_rural, max_abstraction_urban
    )
    return data


[docs] class Sprinkling(MetaSwapPackage, IRegridPackage): """ This contains the sprinkling capacities of links between SVAT units and groundwater/surface water locations. This class is responsible for the file `scap_svat.inp` Parameters ---------- max_abstraction_groundwater: array of floats (xr.DataArray) Describes the maximum abstraction of groundwater to SVAT units in m3 per day. This array must not have a subunit coordinate. max_abstraction_surfacewater: array of floats (xr.DataArray) Describes the maximum abstraction of surfacewater to SVAT units in m3 per day. This array must not have a subunit coordinate. """ _file_name = "scap_svat.inp" _metadata_dict = { "svat": VariableMetaData(10, 1, 99999999, int), "max_abstraction_groundwater_mm_d": VariableMetaData(8, None, None, str), "max_abstraction_surfacewater_mm_d": VariableMetaData(8, None, None, str), "max_abstraction_groundwater": VariableMetaData(8, 0.0, 1e9, float), "max_abstraction_surfacewater": VariableMetaData(8, 0.0, 1e9, float), "svat_groundwater": VariableMetaData(10, 1, 99999999, int), "layer": VariableMetaData(6, 1, 9999, int), "trajectory": VariableMetaData(10, None, None, str), } _with_subunit = ( "max_abstraction_groundwater", "max_abstraction_surfacewater", ) _without_subunit = () _to_fill = ( "max_abstraction_groundwater_mm_d", "max_abstraction_surfacewater_mm_d", "trajectory", ) _regrid_method = SprinklingRegridMethod()
[docs] def __init__( self, max_abstraction_groundwater: xr.DataArray, max_abstraction_surfacewater: xr.DataArray, ): super().__init__() self.dataset["max_abstraction_groundwater"] = max_abstraction_groundwater self.dataset["max_abstraction_surfacewater"] = max_abstraction_surfacewater self._pkgcheck()
def _render( self, file: TextIO, index: IntArray, svat: xr.DataArray, mf6_dis: StructuredDiscretization, mf6_well: Mf6Wel, ): if not isinstance(mf6_well, Mf6Wel): raise TypeError(rf"well not of type 'Mf6Wel', got '{type(mf6_well)}'") well_cellid = mf6_well["cellid"] well_layer = well_cellid.sel(dim_cellid="layer").data well_row = well_cellid.sel(dim_cellid="row").data - 1 well_column = well_cellid.sel(dim_cellid="column").data - 1 max_rate_per_svat = self.dataset["max_abstraction_groundwater"].where(svat > 0) well_layer_per_svat = xr.full_like(max_rate_per_svat, np.nan) well_layer_per_svat.values[:, well_row, well_column] = well_layer is_active_per_svat = (max_rate_per_svat > 0) & well_layer_per_svat.notnull() layer_active = well_layer_per_svat.where(is_active_per_svat) layer_source = _ravel_per_subunit(layer_active).astype(dtype=np.int32) svat_active = svat.where(is_active_per_svat) svat_source_target = _ravel_per_subunit(svat_active).astype(dtype=np.int32) data_dict: dict[str, str | np.ndarray] = { "svat": svat_source_target, "layer": layer_source, "svat_groundwater": svat_source_target, } for var in self._with_subunit: data_with_well = self.dataset[var].where(is_active_per_svat) data_dict[var] = _ravel_per_subunit(data_with_well) for var in self._to_fill: data_dict[var] = "" dataframe = pd.DataFrame( data=data_dict, columns=list(self._metadata_dict.keys()) ) self._check_range(dataframe) return self.write_dataframe_fixed_width(file, dataframe)
[docs] @classmethod def from_imod5_data(cls, imod5_data: Imod5DataDict) -> "Sprinkling": """ Import sprinkling data from imod5 data. Abstraction data for sprinkling is defined in iMOD5 either with grids (IDF) or points (IPF) combined with a grid. Depending on the type, the method does different conversions: - grids (IDF) The ``"artifical_recharge_layer"`` variable was defined as grid (IDF), this grid defines in which layer a groundwater abstraction well should be placed. The ``"artificial_recharge"`` grid contains types which point to the type of abstraction: * 0: no abstraction * 1: groundwater abstraction * 2: surfacewater abstraction The ``"artificial_recharge_capacity"`` grid/constant defines the capacity of each groundwater or surfacewater abstraction. This is an ``1:1`` mapping: Each grid cell maps to a separate well. - points with grid (IPF & IDF) The ``"artifical_recharge_layer"`` variable was defined as point data (IPF), this table contains wellids with an abstraction capacity and layer. The ``"artificial_recharge"`` grid contains a mapping of grid cells to wellids in the point data. The ``"artificial_recharge_capacity"`` is ignored as the abstraction capacity is already defined in the point data. This is an ``n:1`` mapping: multiple grid cells can map to one well. Parameters ---------- imod5_data: dict[str, dict[str, GridDataArray]] dictionary containing the arrays mentioned in the project file as xarray datasets, under the key of the package type to which it belongs, as returned by :func:`imod.formats.prj.open_projectfile_data`. Returns ------- Sprinkling package """ cap_data = imod5_data["cap"] if isinstance(cap_data["artificial_recharge_layer"], pd.DataFrame): data = _sprinkling_data_from_imod5_ipf(cap_data) else: data = _sprinkling_data_from_imod5_grid(cap_data) return cls(**data)