Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
66800d9
Boyscouting: Get rid of calls to .values
JoerivanEngelen Jul 8, 2026
0473920
Pass id on to Mf6Wel
JoerivanEngelen Jul 9, 2026
7040e13
Support IPF wells for LayeredWell.from_imod5_cap_data
JoerivanEngelen Jul 13, 2026
aa1ab08
Temp add scratch script to try out data reworking
JoerivanEngelen Jul 15, 2026
6405be4
Change order of merging to first merge tables, then to grid and deal …
JoerivanEngelen Jul 16, 2026
fe251dd
Make script work for both subunits
JoerivanEngelen Jul 16, 2026
42948e5
Prototype SprinklingPoints class
JoerivanEngelen Jul 22, 2026
87a5e02
Format
JoerivanEngelen Jul 22, 2026
f088ad2
Fix mypy errors
JoerivanEngelen Aug 12, 2026
eb8a9e2
Convert to set literal
JoerivanEngelen Aug 12, 2026
308b157
Clearer varname for inside_df
JoerivanEngelen Aug 12, 2026
99dba8f
Add comment
JoerivanEngelen Aug 12, 2026
157e91d
Add SprinklingPoints to msw namespace
JoerivanEngelen Aug 12, 2026
ab4815d
Put columns in right order
JoerivanEngelen Aug 12, 2026
e5af555
Start adding unittest
JoerivanEngelen Aug 12, 2026
01f4a5e
Start refactoring test_module: Separate cases from tests
JoerivanEngelen Aug 12, 2026
67058da
Refactor into test for one subunit
JoerivanEngelen Aug 13, 2026
cb7458a
Add SprinklingPoints to the public API
JoerivanEngelen Aug 13, 2026
a7b1d6d
Migrate from_imod5_ipf logic into designated function, update docstri…
JoerivanEngelen Aug 13, 2026
2326477
Format and fix mypy issues by adding some specific typeddicts
JoerivanEngelen Aug 13, 2026
709038c
Finalize renaming to "capacity_p", deal with extra edge case if svat_…
JoerivanEngelen Aug 13, 2026
900de95
Slightly better name
JoerivanEngelen Aug 13, 2026
ab665d2
Add test to write from_imod5_data
JoerivanEngelen Aug 13, 2026
b7fce78
Separate expected case data into separate dataclass
JoerivanEngelen Aug 17, 2026
4f39a69
Start adding unittest for SprinklingPoints with a bunch of test cases…
JoerivanEngelen Aug 17, 2026
4188560
Simplify case where the art grid has a mapping id in an inactive svat
JoerivanEngelen Aug 18, 2026
a5f01ac
Fix edge case that wasn't working and reduce code a lot
JoerivanEngelen Aug 18, 2026
06f40fc
Finish unittest cases and format
JoerivanEngelen Aug 18, 2026
919d438
Ensure y-coords are properly oriented
JoerivanEngelen Aug 19, 2026
07353ca
Clearer name for the mapping id
JoerivanEngelen Aug 19, 2026
cfa5c63
Format
JoerivanEngelen Aug 19, 2026
aa1dc24
Update unittest for LayeredWell.from_imod5_cap_data__points
JoerivanEngelen Aug 19, 2026
31c781a
Implement subunits for art_grid
JoerivanEngelen Aug 19, 2026
6d302d5
Format
JoerivanEngelen Aug 19, 2026
2e35dcb
Add test case where only one subunit is active
JoerivanEngelen Aug 19, 2026
eaa91a1
Merge branch 'master' into issue_#1835_sprinkling_from_ipf
JoerivanEngelen Aug 19, 2026
baaf40a
Update changelog
JoerivanEngelen Aug 19, 2026
c8981d2
Move sprinkling imod5 import utilities into the imod5_converter utili…
JoerivanEngelen Aug 19, 2026
2c741e0
Reduce script to latest method calls
JoerivanEngelen Aug 19, 2026
a938d9e
Deprecate Sprinkling and refactor to keep API stable and keep things …
JoerivanEngelen Aug 20, 2026
e60c949
Update docs about deprecation of Sprinkling and point to SprinklingGrid
JoerivanEngelen Aug 20, 2026
6f85ba1
Provide missing "id" arg to Mf6Wel initiation in tests
JoerivanEngelen Aug 20, 2026
112ae33
Add "id" to optional_data column to ignore fetching it during the _ge…
JoerivanEngelen Aug 20, 2026
88ccf4d
Remove _scratch.py script
JoerivanEngelen Aug 20, 2026
0b5c678
Add sprinkling examples
JoerivanEngelen Aug 20, 2026
351aa88
Add some explanation on what is happening
JoerivanEngelen Aug 20, 2026
471e6f3
Fix crossref in changelog
JoerivanEngelen Aug 20, 2026
409b3fb
Fix typo
JoerivanEngelen Aug 20, 2026
df0867d
Fix docstring about subunits for SprinklingGrid
JoerivanEngelen Aug 20, 2026
c689694
Add experimental note
JoerivanEngelen Aug 20, 2026
e31af4d
Update crossref
JoerivanEngelen Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/api/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,30 @@ The format is based on `Keep a Changelog`_, and this project adheres to
[Unreleased]
------------

Added
~~~~~

- Experimental class :class:`imod.msw.SprinklingPoints` to specify sprinkling
from points for MetaSWAP models, instead of from grid. You can use this to
specify sprinkling wells from IPF files in an iMOD5 CAP dataset with
:meth:`imod.msw.SprinklingPoints.from_imod5_data`.
- :class:`imod.mf6.LayeredWell.from_imod5_cap_data` now also supports loading
wells from IPF files in an iMOD5 CAP dataset.

Fixed
~~~~~

- Fixed resampling in :meth:`imod.mf6.Well.from_imod5_data` and
:meth:`imod.mf6.LayeredWell.from_imod5_data` when simulation timesteps precede
the first well timestep.

Changed
~~~~~~~

- Deprecated :class:`imod.msw.Sprinkling` in favor of
:class:`imod.msw.SprinklingGrid`. Call :class:`imod.msw.SprinklingGrid` to get
the same behavior as you were used to.

[1.1.0] - 2026-08-03
--------------------

Expand Down
17 changes: 12 additions & 5 deletions docs/api/msw.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,18 @@ Grid packages
ScalingFactors.get_regrid_methods
ScalingFactors.write
Sprinkling
Sprinkling.regrid_like
Sprinkling.clip_box
Sprinkling.from_imod5_data
Sprinkling.get_regrid_methods
Sprinkling.write
SprinklingGrid
SprinklingGrid.regrid_like
SprinklingGrid.clip_box
SprinklingGrid.from_imod5_data
SprinklingGrid.get_regrid_methods
SprinklingGrid.write
SprinklingPoints
SprinklingPoints.regrid_like
SprinklingPoints.clip_box
SprinklingPoints.from_imod5_data
SprinklingPoints.get_regrid_methods
SprinklingPoints.write

Initial conditions
==================
Expand Down
7 changes: 6 additions & 1 deletion imod/mf6/mf6_wel_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ class Mf6Wel(BoundaryCondition, IPackage):
_pkg_id = "wel"

_period_data = ("cellid", "rate")
# Workaround that this needs to be ignored in get_period_varnames. This can
# become an _auxiliary_data in the future with some extra work.
_optional_data = ("id",)
_keyword_map = {}
_template = BoundaryCondition._initialize_template(_pkg_id)
_auxiliary_data = {"concentration": "species"}
Expand All @@ -143,6 +146,7 @@ def __init__(
self,
cellid,
rate,
id,
concentration=None,
concentration_boundary_type="aux",
save_flows: Optional[bool] = None,
Expand All @@ -153,6 +157,7 @@ def __init__(
dict_dataset = {
"cellid": cellid,
"rate": rate,
"id": id,
"concentration": concentration,
"concentration_boundary_type": concentration_boundary_type,
"save_flows": save_flows,
Expand All @@ -169,7 +174,7 @@ def _ds_to_arrdict(self, ds):
arrdict: Dict[str, Any] = {}

arrdict["data_vars"] = [
var_name for var_name in ds.data_vars if var_name != "cellid"
var_name for var_name in ds.data_vars if var_name not in ("cellid", "id")
]

dsvar = {}
Expand Down
20 changes: 16 additions & 4 deletions imod/mf6/utilities/imod5_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,16 @@ def fill_missing_layers(


def _well_from_imod5_cap_point_data(cap_data: GridDataDict) -> dict[str, np.ndarray]:
raise NotImplementedError(
"Assigning sprinkling wells with an IPF file is not supported, please specify them as IDF."
)
df_points = cap_data["artificial_recharge_layer"]
data = {}
# Order of columns is x, y, layer, the other columns are irrelevant here.
data["x"] = df_points.iloc[:, 0].to_numpy().astype(float)
data["y"] = df_points.iloc[:, 1].to_numpy().astype(float)
data["layer"] = df_points.iloc[:, 2].to_numpy().astype(int)
data["rate"] = np.zeros_like(data["x"], dtype=float)
data["id"] = df_points.index.to_numpy()

return data


def _well_from_imod5_cap_grid_data(cap_data: GridDataDict) -> dict[str, np.ndarray]:
Expand All @@ -85,7 +92,7 @@ def _well_from_imod5_cap_grid_data(cap_data: GridDataDict) -> dict[str, np.ndarr

def well_from_imod5_cap_data(
imod5_data: Imod5DataDict,
target_dis: IRegridPackage,
target_dis: Optional[IRegridPackage],
regridder_types: DataclassType,
regrid_cache: RegridderWeightsCache,
) -> dict[str, np.ndarray]:
Expand Down Expand Up @@ -121,6 +128,11 @@ def well_from_imod5_cap_data(
if has_ipf_well:
return _well_from_imod5_cap_point_data(cap_data)
else:
if target_dis is None:
raise ValueError(
"target_dis must be provided when converting iMOD5 cap data "
"from grids (IDF)"
)
cap_data_regridded = regrid_imod5_cap_data(
imod5_data, target_dis, regridder_types, regrid_cache
)["cap"]
Expand Down
37 changes: 24 additions & 13 deletions imod/mf6/wel.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ def _assign_dims(arg: Any) -> tuple[Any, ...] | xr.DataArray:
if arg.dims[0] != "time":
arg = arg.transpose()
da = xr.DataArray(
data=arg.values, coords={"time": arg["time"]}, dims=["time", "index"]
data=arg.to_numpy(), coords={"time": arg["time"]}, dims=["time", "index"]
)
return da
elif is_da:
return "index", arg.values
return "index", arg.to_numpy()
else:
return "index", arg

Expand Down Expand Up @@ -347,11 +347,11 @@ class GridAgnosticWell(BoundaryCondition, IPointDataPackage, abc.ABC):

@property
def x(self) -> npt.NDArray[np.float64]:
return self.dataset["x"].values
return self.dataset["x"].to_numpy()

@property
def y(self) -> npt.NDArray[np.float64]:
return self.dataset["y"].values
return self.dataset["y"].to_numpy()

@classmethod
def _is_grid_agnostic_package(cls) -> bool:
Expand Down Expand Up @@ -394,7 +394,9 @@ def _create_dataset_vars(
# Carefully rename the dimension and set coordinates
d_rename = {"index": "ncellid"}
ds_vars = ds_vars.rename_dims(**d_rename).rename_vars(**d_rename)
ds_vars = ds_vars.assign_coords(**{"ncellid": cellid.coords["ncellid"].values})
ds_vars = ds_vars.assign_coords(
**{"ncellid": cellid.coords["ncellid"].to_numpy()}
)

return ds_vars

Expand Down Expand Up @@ -534,9 +536,9 @@ def _to_mf6_pkg(
ds = ds.assign(**data_vars_dict) # type: ignore[arg-type]

ds = remove_inactive(ds, idomain)
ds["save_flows"] = self["save_flows"].values[()]
ds["print_flows"] = self["print_flows"].values[()]
ds["print_input"] = self["print_input"].values[()]
ds["save_flows"] = enforce_scalar(self["save_flows"])
ds["print_flows"] = enforce_scalar(self["print_flows"])
ds["print_input"] = enforce_scalar(self["print_input"])

filtered_final_well_ids = self._gather_filtered_well_ids(ds, wells_df)
if len(filtered_final_well_ids) > 0:
Expand All @@ -546,8 +548,6 @@ def _to_mf6_pkg(
)
logger.log(loglevel=LogLevel.WARNING, message=message_end)

ds = ds.drop_vars("id")

data_vars_dict = {str(k): v for k, v in ds.data_vars.items()}
return Mf6Wel(**data_vars_dict) # type: ignore[arg-type]

Expand Down Expand Up @@ -1080,8 +1080,8 @@ def _find_well_value_at_layer(
if (value is not None) and is_spatial_grid(value):
value = imod.select.points_values(
value,
x=well_dataset["x"].values,
y=well_dataset["y"].values,
x=well_dataset["x"].to_numpy(),
y=well_dataset["y"].to_numpy(),
out_of_bounds="ignore",
)
in_bounds = np.full(well_dataset.sizes["index"], False)
Expand Down Expand Up @@ -1477,7 +1477,7 @@ def _validate_imod5_depth_information(
def from_imod5_cap_data(
cls,
imod5_data: Imod5DataDict,
target_dis: StructuredDiscretization,
target_dis: Optional[StructuredDiscretization] = None,
regridder_types: CapDataWellRegridMethod = CapDataWellRegridMethod(),
regrid_cache: RegridderWeightsCache = RegridderWeightsCache(),
):
Expand Down Expand Up @@ -1517,6 +1517,17 @@ def from_imod5_cap_data(
xarray datasets, under the key of the package type to which it
belongs, as returned by
:func:`imod.formats.prj.open_projectfile_data`.
target_dis: Optional[StructuredDiscretization]
The target discretization to which the data should be regridded.
Only necessary when "artificial_recharge_layer" is an IDF grid,
otherwise ignored.
regridder_types: CapDataWellRegridMethod
The regridder type to use for the regridding of the "artificial_recharge_layer"
and "artificial_recharge_capacity" grids. Only necessary when
"artificial_recharge_layer" is an IDF grid, otherwise ignored.
regrid_cache: RegridderWeightsCache
Cache for storing intermediate regridding results. Only necessary when
"artificial_recharge_layer" is an IDF grid, otherwise ignored.
"""
data = well_from_imod5_cap_data(
imod5_data, target_dis, regridder_types, regrid_cache
Expand Down
4 changes: 3 additions & 1 deletion imod/msw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@
from imod.msw.output_control import TimeOutputControl, VariableOutputControl
from imod.msw.ponding import Ponding
from imod.msw.scaling_factors import ScalingFactors
from imod.msw.sprinkling import Sprinkling

# Import deprecated class Sprinkling to keep public API stable
from imod.msw.sprinkling import Sprinkling, SprinklingGrid, SprinklingPoints
from imod.msw.vegetation import AnnualCropFactors
23 changes: 20 additions & 3 deletions imod/msw/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@
from imod.msw.ponding import Ponding
from imod.msw.regrid.regrid_schemes import CapDataRegridMethod
from imod.msw.scaling_factors import ScalingFactors
from imod.msw.sprinkling import Sprinkling
from imod.msw.sprinkling import SprinklingGrid, SprinklingPoints
from imod.msw.timeutil import to_metaswap_timeformat
from imod.msw.utilities.common import find_in_file_list
from imod.msw.utilities.imod5_converter import (
has_active_scaling_factor,
is_sprinkling_from_points,
)
from imod.msw.utilities.mask import (
MetaSwapActive,
Expand Down Expand Up @@ -302,8 +303,21 @@ def get_pkgkey(
str
The key of the package of type ``pkg_type``.
"""
# Loop over all packages in the model and match based on filename
# attached to the package class. This is a temporary solution to make
# primod stable, where get_pkgkey(Sprinkling) was used and it should
# return a SprinklingGrid or SprinklingPoints package depending on the
# data in the model, while still making Sprinkling behave like
# SprinklingGrid. This can be reverted once primod is updated and had a
# few releases.
#
# The reason why this works for MetaSWAP as each package maps to one
# single unique file, so there can never be a model with both a
# SprinklingGrid and a SprinklingPoints package. The first occurrence of
# the filename is always the correct one.

for pkg_key, pkg in self.items():
if isinstance(pkg, pkg_type):
if pkg._file_name == pkg_type._file_name:
return pkg_key

if not optional_package:
Expand Down Expand Up @@ -830,7 +844,10 @@ def from_imod5_data(
}
model["infiltration"] = Infiltration.from_imod5_data(imod5_masked)
model["ponding"] = Ponding.from_imod5_data(imod5_masked)
model["sprinkling"] = Sprinkling.from_imod5_data(imod5_masked)
if is_sprinkling_from_points(imod5_masked):
model["sprinkling"] = SprinklingPoints.from_imod5_data(imod5_masked)
else:
model["sprinkling"] = SprinklingGrid.from_imod5_data(imod5_masked)
model["meteo_grid"] = MeteoGridCopy.from_imod5_data(imod5_masked)
model["prec_mapping"] = PrecipitationMapping.from_imod5_data(imod5_masked)
model["evt_mapping"] = EvapotranspirationMapping.from_imod5_data(imod5_masked)
Expand Down
22 changes: 22 additions & 0 deletions imod/msw/regrid/regrid_schemes.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,28 @@ class SprinklingRegridMethod(DataclassType):
max_abstraction_surfacewater: RegridVarType = (RegridderType.OVERLAP, "mean")


@dataclass(config=_CONFIG)
class SprinklingPointsRegridMethod(DataclassType):
"""
Object containing regridder methods for the
:class:`imod.msw.SprinklingPoints` package. This can be provided to the
``regrid_like`` method to regrid with custom settings.

Parameters
----------
art_grid: tuple, default (RegridderType.OVERLAP, "mode")

Examples
--------
Regrid with custom settings:

>>> regrid_method = SprinklingPointsRegridMethod(art_grid=(RegridderType.OVERLAP,"min"))
>>> sprinkling.regrid_like(target_grid, RegridderWeightsCache(), regrid_method)
"""
Comment thread
Copilot marked this conversation as resolved.

art_grid: RegridVarType = (RegridderType.OVERLAP, "mode")


@dataclass(config=_CONFIG)
class MeteoGridRegridMethod(DataclassType):
"""
Expand Down
Loading
Loading