diff --git a/imod/msw/infiltration.py b/imod/msw/infiltration.py index f8a6b2c6e..f9d441af1 100644 --- a/imod/msw/infiltration.py +++ b/imod/msw/infiltration.py @@ -1,10 +1,14 @@ import xarray as xr +from imod.mf6.interfaces.iregridpackage import IRegridPackage +from imod.mf6.utilities.regrid import ( + RegridderType, +) from imod.msw.fixed_format import VariableMetaData from imod.msw.pkgbase import MetaSwapPackage -class Infiltration(MetaSwapPackage): +class Infiltration(MetaSwapPackage, IRegridPackage): """ This contains the infiltration data. @@ -54,6 +58,21 @@ class Infiltration(MetaSwapPackage): ) _to_fill = () + _regrid_method = { + "infiltration_capacity": (RegridderType.OVERLAP, "mean"), + "downward_resistance": (RegridderType.OVERLAP, "mean"), + "upward_resistance": ( + RegridderType.OVERLAP, + "mean", + ), + "longitudinal_vertical": ( + RegridderType.OVERLAP, + "mean", + ), + "bottom_resistance": (RegridderType.OVERLAP, "mean"), + "extra_storage_coefficient": (RegridderType.OVERLAP, "mean"), + } + def __init__( self, infiltration_capacity: xr.DataArray, diff --git a/imod/msw/initial_conditions.py b/imod/msw/initial_conditions.py index 166f47ba6..49275e93d 100644 --- a/imod/msw/initial_conditions.py +++ b/imod/msw/initial_conditions.py @@ -1,17 +1,20 @@ import pathlib import shutil +from imod.mf6.interfaces.iregridpackage import IRegridPackage from imod.msw.fixed_format import VariableMetaData from imod.msw.pkgbase import MetaSwapPackage -class InitialConditionsEquilibrium(MetaSwapPackage): +class InitialConditionsEquilibrium(MetaSwapPackage, IRegridPackage): """ Use an equilibrium profile to initialize the model. This class is responsible for the file `init_svat.inp` """ + _regrid_method = {} + _file_name = "init_svat.inp" _option = "Equilibrium" _metadata_dict = {} @@ -23,7 +26,7 @@ def _render(self, file, *args): file.write(self._option + "\n") -class InitialConditionsRootzonePressureHead(MetaSwapPackage): +class InitialConditionsRootzonePressureHead(MetaSwapPackage, IRegridPackage): """ Use the pF-value of the root zone pressure head as initial condition. @@ -40,6 +43,7 @@ class InitialConditionsRootzonePressureHead(MetaSwapPackage): _metadata_dict = { "initial_pF": VariableMetaData(6, 0.0, 6.0, float), } + _regrid_method = {} def __init__(self, initial_pF=2.2): super().__init__() @@ -53,7 +57,7 @@ def _render(self, file, *args): self.write_dataframe_fixed_width(file, dataframe) -class InitialConditionsPercolation(MetaSwapPackage): +class InitialConditionsPercolation(MetaSwapPackage, IRegridPackage): """ The precipitation intensity at the starting time (iybg, tdbg in PARA_SIM.INP) is used for initializing the percolation flux in the profiles. @@ -68,6 +72,7 @@ class InitialConditionsPercolation(MetaSwapPackage): _file_name = "init_svat.inp" _option = "MeteoInputP" _metadata_dict = {} + _regrid_method = {} def __init__(self): super().__init__() diff --git a/imod/msw/landuse.py b/imod/msw/landuse.py index cc9d31a05..429715448 100644 --- a/imod/msw/landuse.py +++ b/imod/msw/landuse.py @@ -1,8 +1,9 @@ +from imod.mf6.interfaces.iregridpackage import IRegridPackage from imod.msw.fixed_format import VariableMetaData from imod.msw.pkgbase import MetaSwapPackage -class LanduseOptions(MetaSwapPackage): +class LanduseOptions(MetaSwapPackage, IRegridPackage): """ Land use options. This object is responsible for luse_svat.inp @@ -53,6 +54,12 @@ class LanduseOptions(MetaSwapPackage): Hoyningen. interception_intercept: array of floats (xr.DataArray) Intercept of the interception evaporation curve. Pun unintended. + interception_capacity_per_LAI_Rutter: this initializer argument is for + internal use only. Users can ignore it and should only provide + interception_capacity_per_LAI + interception_capacity_per_LAI_VonHoyningen: this initializer argument is for + internal use only. Users can ignore it and should only provide + interception_capacity_per_LAI Notes ----- @@ -116,6 +123,8 @@ class LanduseOptions(MetaSwapPackage): _file_name = "luse_svat.inp" + _regrid_method = {} + def __init__( self, landuse_name, @@ -137,8 +146,10 @@ def __init__( start_sprinkling_season, end_sprinkling_season, interception_option, - interception_capacity_per_LAI, - interception_intercept, + interception_capacity_per_LAI=None, + interception_capacity_per_LAI_Rutter=None, + interception_capacity_per_LAI_VonHoyningen=None, + interception_intercept=None, ): super().__init__() self.dataset["landuse_name"] = landuse_name @@ -160,12 +171,22 @@ def __init__( self.dataset["start_sprinkling_season"] = start_sprinkling_season self.dataset["end_sprinkling_season"] = end_sprinkling_season self.dataset["interception_option"] = interception_option - self.dataset["interception_capacity_per_LAI_Rutter"] = ( - interception_capacity_per_LAI - ) - self.dataset["interception_capacity_per_LAI_VonHoyningen"] = ( - interception_capacity_per_LAI - ) + if interception_capacity_per_LAI is not None: + self.dataset["interception_capacity_per_LAI_Rutter"] = ( + interception_capacity_per_LAI + ) + else: + self.dataset["interception_capacity_per_LAI_Rutter"] = ( + interception_capacity_per_LAI_Rutter + ) + if interception_capacity_per_LAI is not None: + self.dataset["interception_capacity_per_LAI_VonHoyningen"] = ( + interception_capacity_per_LAI + ) + else: + self.dataset["interception_capacity_per_LAI_VonHoyningen"] = ( + interception_capacity_per_LAI_VonHoyningen + ) self.dataset["interception_intercept"] = interception_intercept self._pkgcheck() diff --git a/imod/msw/pkgbase.py b/imod/msw/pkgbase.py index aa3b20b40..71691d12c 100644 --- a/imod/msw/pkgbase.py +++ b/imod/msw/pkgbase.py @@ -1,12 +1,18 @@ import abc from pathlib import Path -from typing import Union +from typing import Any, Optional, Tuple, Union import numpy as np import pandas as pd import xarray as xr +from imod.mf6.utilities.regrid import ( + RegridderType, + RegridderWeightsCache, + _regrid_like, +) from imod.msw.fixed_format import format_fixed_width +from imod.typing.grid import GridDataArray, GridDataset class MetaSwapPackage(abc.ABC): @@ -28,6 +34,14 @@ def __getitem__(self, key): def __setitem__(self, key, value): self.dataset.__setitem__(key, value) + @property + def dataset(self) -> GridDataset: + return self._dataset + + @dataset.setter + def dataset(self, value): + self._dataset = value + def isel(self): raise NotImplementedError( f"Selection on packages not yet supported. " @@ -131,3 +145,49 @@ def _pkgcheck(self): f"Variable '{var}' in {self.__class__} should not " "contain 'subunit' coordinate" ) + + def _valid(self, value): + return True + + def get_non_grid_data(self, grid_names: list[str]) -> dict[str, Any]: + """ + This function copies the attributes of a dataset that are scalars, such as options. + + parameters + ---------- + grid_names: list of str + the names of the attribbutes of a dataset that are grids. + """ + result = {} + all_non_grid_data = list(self.dataset.keys()) + for name in ( + gridname for gridname in grid_names if gridname in all_non_grid_data + ): + all_non_grid_data.remove(name) + for name in all_non_grid_data: + result[name] = self.dataset[name] + return result + + @property + def auxiliary_data_fields(self) -> dict[str, str]: + return {} + + def is_regridding_supported(self) -> bool: + return + + def regrid_like( + self, + target_grid: GridDataArray, + regrid_context: RegridderWeightsCache, + regridder_types: Optional[dict[str, Tuple[RegridderType, str]]] = None, + ) -> "MetaSwapPackage": + try: + result = _regrid_like(self, target_grid, regrid_context, regridder_types) + except ValueError as e: + raise e + except Exception as e: + raise ValueError(f"package could not be regridded:{e}") + return result + + def get_regrid_methods(self) -> Optional[dict[str, Tuple[RegridderType, str]]]: + return self._regrid_method diff --git a/imod/msw/ponding.py b/imod/msw/ponding.py index c378afb42..c871f0a37 100644 --- a/imod/msw/ponding.py +++ b/imod/msw/ponding.py @@ -1,10 +1,14 @@ import pandas as pd +from imod.mf6.interfaces.iregridpackage import IRegridPackage +from imod.mf6.utilities.regrid import ( + RegridderType, +) from imod.msw.fixed_format import VariableMetaData from imod.msw.pkgbase import MetaSwapPackage -class Ponding(MetaSwapPackage): +class Ponding(MetaSwapPackage, IRegridPackage): """ Set ponding related parameters for MetaSWAP. This class is responsible for the svat2swnr_roff.inp file. Currently, we do not support ponds coupled to @@ -39,6 +43,15 @@ class Ponding(MetaSwapPackage): _without_subunit = () _to_fill = () + _regrid_method = { + "ponding_depth": (RegridderType.OVERLAP, "mean"), + "runon_resistance": (RegridderType.OVERLAP, "mean"), + "runoff_resistance": ( + RegridderType.OVERLAP, + "mean", + ), + } + def __init__(self, ponding_depth, runon_resistance, runoff_resistance) -> None: super().__init__() self.dataset["ponding_depth"] = ponding_depth diff --git a/imod/msw/scaling_factors.py b/imod/msw/scaling_factors.py index c3be7e7d1..6100c99e3 100644 --- a/imod/msw/scaling_factors.py +++ b/imod/msw/scaling_factors.py @@ -1,8 +1,12 @@ +from imod.mf6.interfaces.iregridpackage import IRegridPackage +from imod.mf6.utilities.regrid import ( + RegridderType, +) from imod.msw.fixed_format import VariableMetaData from imod.msw.pkgbase import MetaSwapPackage -class ScalingFactors(MetaSwapPackage): +class ScalingFactors(MetaSwapPackage, IRegridPackage): """ This package allows you to do three things: 1. Set scaling factors for some inputs in the soil physical database, @@ -58,6 +62,19 @@ class ScalingFactors(MetaSwapPackage): _without_subunit = ("depth_perched_water_table",) _to_fill = () + _regrid_method = { + "scale_soil_moisture": (RegridderType.OVERLAP, "mean"), + "scale_hydraulic_conductivity": (RegridderType.OVERLAP, "mean"), + "scale_pressure_head": ( + RegridderType.OVERLAP, + "mean", + ), + "depth_perched_water_table": ( + RegridderType.OVERLAP, + "mean", + ), + } + def __init__( self, scale_soil_moisture, diff --git a/imod/msw/vegetation.py b/imod/msw/vegetation.py index fcc725740..232124ed7 100644 --- a/imod/msw/vegetation.py +++ b/imod/msw/vegetation.py @@ -1,11 +1,12 @@ import numpy as np import xarray as xr +from imod.mf6.interfaces.iregridpackage import IRegridPackage from imod.msw.fixed_format import VariableMetaData from imod.msw.pkgbase import MetaSwapPackage -class AnnualCropFactors(MetaSwapPackage): +class AnnualCropFactors(MetaSwapPackage, IRegridPackage): """ For each vegetation type specify a yearly trend in vegetation factors and interception characteristics. These are used if WOFOST is not used. @@ -52,6 +53,8 @@ class AnnualCropFactors(MetaSwapPackage): "ponding_factor": VariableMetaData(8, 0.01, 10.0, float), } + _regrid_method = {} + def __init__( self, soil_cover: xr.DataArray, diff --git a/imod/tests/test_msw/test_annual_crop_factors.py b/imod/tests/test_msw/test_annual_crop_factors.py new file mode 100644 index 000000000..77ea39379 --- /dev/null +++ b/imod/tests/test_msw/test_annual_crop_factors.py @@ -0,0 +1,65 @@ +import numpy as np +import xarray as xr + +from imod.mf6.utilities.regrid import ( + RegridderWeightsCache, +) +from imod.msw import AnnualCropFactors + + +def setup_cropfactors(): + day_of_year = np.arange(1, 367) + vegetation_index = np.arange(1, 4) + + coords = {"day_of_year": day_of_year, "vegetation_index": vegetation_index} + soil_cover = xr.DataArray( + data=np.zeros(day_of_year.shape + vegetation_index.shape), + coords=coords, + dims=("day_of_year", "vegetation_index"), + ) + soil_cover[132:254, :] = 1.0 + leaf_area_index = soil_cover * 3 + + vegetation_factor = xr.zeros_like(soil_cover) + vegetation_factor[132:142, :] = 0.7 + vegetation_factor[142:152, :] = 0.9 + vegetation_factor[152:162, :] = 1.0 + vegetation_factor[162:192, :] = 1.2 + vegetation_factor[192:244, :] = 1.1 + vegetation_factor[244:254, :] = 0.7 + + cropfactors = AnnualCropFactors( + soil_cover=soil_cover, + leaf_area_index=leaf_area_index, + interception_capacity=xr.zeros_like(soil_cover), + vegetation_factor=vegetation_factor, + interception_factor=xr.ones_like(soil_cover), + bare_soil_factor=xr.ones_like(soil_cover), + ponding_factor=xr.ones_like(soil_cover), + ) + return cropfactors + + +def get_new_grid(): + x = [i for i in range(100)] + y = [i for i in range(100, 0, -1)] + subunit = [0, 1] + dx = 0.5 + dy = 0.5 + # fmt: off + new_grid = xr.DataArray( + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} + ) + new_grid.values[:,:,:] = 1 + return new_grid + + +def test_cropfactor_regrid(): + crop_factors = setup_cropfactors() + new_grid = get_new_grid() + + regrid_context = RegridderWeightsCache(new_grid, new_grid) + regridded = crop_factors.regrid_like(new_grid, regrid_context) + + assert regridded.dataset.equals(crop_factors.dataset) diff --git a/imod/tests/test_msw/test_infiltration.py b/imod/tests/test_msw/test_infiltration.py index 74517c6d5..e1bac2cad 100644 --- a/imod/tests/test_msw/test_infiltration.py +++ b/imod/tests/test_msw/test_infiltration.py @@ -8,10 +8,73 @@ from numpy import nan from numpy.testing import assert_almost_equal, assert_equal +from imod.mf6.utilities.regrid import ( + RegridderWeightsCache, +) from imod.msw import Infiltration from imod.msw.fixed_format import format_fixed_width +def setup_infiltration_package(subunit, y, x, dy, dx): + infiltration_capacity = xr.DataArray( + np.array( + [ + [[0.5, 0.5, 0.5], [nan, nan, nan], [1.0, 1.0, 1.0]], + [[0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [nan, nan, nan]], + ] + ), + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy}, + ) + + downward_resistance = xr.DataArray( + np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]), + dims=("y", "x"), + coords={"y": y, "x": x, "dx": dx, "dy": dy}, + ) + + upward_resistance = xr.DataArray( + np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]), + dims=("y", "x"), + coords={"y": y, "x": x, "dx": dx, "dy": dy}, + ) + + bottom_resistance = xr.DataArray( + np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]), + dims=("y", "x"), + coords={"y": y, "x": x, "dx": dx, "dy": dy}, + ) + + extra_storage_coefficient = xr.DataArray( + np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]]), + dims=("y", "x"), + coords={"y": y, "x": x, "dx": dx, "dy": dy}, + ) + + svat = xr.DataArray( + np.array( + [ + [[0, 1, 0], [0, 0, 0], [0, 2, 0]], + [[0, 3, 0], [0, 4, 0], [0, 0, 0]], + ] + ), + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy}, + ) + # fmt: on + index = (svat != 0).values.ravel() + + infiltration = Infiltration( + infiltration_capacity, + downward_resistance, + upward_resistance, + bottom_resistance, + extra_storage_coefficient, + ) + + return infiltration, svat, index + + @given( floats( Infiltration._metadata_dict["infiltration_capacity"].min_value, @@ -119,83 +182,7 @@ def test_simple_model(fixed_format_parser): dx = 1.0 dy = 1.0 # fmt: off - infiltration_capacity = xr.DataArray( - np.array( - [ - [[0.5, 0.5, 0.5], - [nan, nan, nan], - [1.0, 1.0, 1.0]], - - [[0.5, 0.5, 0.5], - [1.0, 1.0, 1.0], - [nan, nan, nan]], - ] - ), - dims=("subunit", "y", "x"), - coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} - ) - - downward_resistance = xr.DataArray( - np.array( - [[1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0]]), - dims=("y", "x"), - coords={"y": y, "x": x, "dx": dx, "dy": dy} - ) - - upward_resistance = xr.DataArray( - np.array( - [[1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0]]), - dims=("y", "x"), - coords={"y": y, "x": x, "dx": dx, "dy": dy} - ) - - bottom_resistance = xr.DataArray( - np.array( - [[1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0]]), - dims=("y", "x"), - coords={"y": y, "x": x, "dx": dx, "dy": dy} - ) - - extra_storage_coefficient = xr.DataArray( - np.array( - [[0.1, 0.2, 0.3], - [0.4, 0.5, 0.6], - [0.7, 0.8, 0.9]]), - dims=("y", "x"), - coords={"y": y, "x": x, "dx": dx, "dy": dy} - ) - - svat = xr.DataArray( - np.array( - [ - [[0, 1, 0], - [0, 0, 0], - [0, 2, 0]], - - [[0, 3, 0], - [0, 4, 0], - [0, 0, 0]], - ] - ), - dims=("subunit", "y", "x"), - coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} - ) - # fmt: on - index = (svat != 0).values.ravel() - - infiltration = Infiltration( - infiltration_capacity, - downward_resistance, - upward_resistance, - bottom_resistance, - extra_storage_coefficient, - ) + infiltration, svat, index = setup_infiltration_package(subunit, y, x, dy, dx) with tempfile.TemporaryDirectory() as output_dir: output_dir = Path(output_dir) @@ -215,3 +202,30 @@ def test_simple_model(fixed_format_parser): assert_almost_equal( results["extra_storage_coefficient"], np.array([0.2, 0.8, 0.2, 0.5]) ) + + +def test_regrid(): + x = [1.0, 2.0, 3.0] + y = [3.0, 2.0, 1.0] + subunit = [0, 1] + dx = 1.0 + dy = 1.0 + + infiltration, _, _ = setup_infiltration_package(subunit, y, x, dy, dx) + + x = [1.0, 1.5, 2.0, 2.5, 3.0] + y = [3.0, 2.5, 2.0, 1.5, 1.0] + subunit = [0, 1] + dx = 0.5 + dy = 0.5 + # fmt: off + new_grid = xr.DataArray( + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} + ) + new_grid.values[:,:,:] = 1 + + regrid_context = RegridderWeightsCache(infiltration.dataset["infiltration_capacity"], new_grid) + regridded = infiltration.regrid_like(new_grid, regrid_context ) + assert_almost_equal(regridded.dataset.coords["x"].values, x) + assert_almost_equal(regridded.dataset.coords["y"].values, y) diff --git a/imod/tests/test_msw/test_initial_conditions.py b/imod/tests/test_msw/test_initial_conditions.py index 6d70ba649..d990d39d0 100644 --- a/imod/tests/test_msw/test_initial_conditions.py +++ b/imod/tests/test_msw/test_initial_conditions.py @@ -2,13 +2,33 @@ from pathlib import Path import pytest +import xarray as xr +from imod.mf6.utilities.regrid import ( + RegridderWeightsCache, +) from imod.msw import ( InitialConditionsEquilibrium, InitialConditionsPercolation, InitialConditionsRootzonePressureHead, InitialConditionsSavedState, ) +from imod.typing.grid import is_empty + + +def get_new_grid(): + x = [1.0, 1.5, 2.0, 2.5, 3.0] + y = [3.0, 2.5, 2.0, 1.5, 1.0] + subunit = [0, 1] + dx = 0.5 + dy = 0.5 + # fmt: off + new_grid = xr.DataArray( + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} + ) + new_grid.values[:,:,:] = 1 + return new_grid def test_initial_conditions_equilibrium(): @@ -25,6 +45,18 @@ def test_initial_conditions_equilibrium(): assert lines == ["Equilibrium\n"] +def test_initial_conditions_equilibrium_regrid(): + ic = InitialConditionsEquilibrium() + + # fmt: off + new_grid = get_new_grid() + + regrid_context = RegridderWeightsCache(new_grid, new_grid) + regridded = ic.regrid_like(new_grid, regrid_context ) + + assert is_empty(regridded.dataset) + + def test_initial_conditions_percolation(): ic = InitialConditionsPercolation() dummy = None, None @@ -39,6 +71,16 @@ def test_initial_conditions_percolation(): assert lines == ["MeteoInputP\n"] +def test_initial_conditions_percolation_regrid(): + ic = InitialConditionsPercolation() + + new_grid = get_new_grid() + + regrid_context = RegridderWeightsCache(new_grid, new_grid) + regridded = ic.regrid_like(new_grid, regrid_context) + assert is_empty(regridded.dataset) + + def test_initial_conditions_rootzone_pressure_head(): ic = InitialConditionsRootzonePressureHead(2.2) dummy = None, None @@ -53,6 +95,16 @@ def test_initial_conditions_rootzone_pressure_head(): assert lines == ["Rootzone_pF\n", " 2.200\n"] +def test_initial_conditions_rootzone_regrid(): + ic = InitialConditionsRootzonePressureHead(2.2) + + new_grid = get_new_grid() + + regrid_context = RegridderWeightsCache(new_grid, new_grid) + regridded = ic.regrid_like(new_grid, regrid_context) + assert regridded.dataset["initial_pF"] == 2.2 + + def test_initial_conditions_saved_state(): dummy = None, None with tempfile.TemporaryDirectory() as output_dir: @@ -75,3 +127,11 @@ def test_initial_conditions_saved_state_no_file(): output_dir = Path(output_dir) with pytest.raises(FileNotFoundError): ic.write(output_dir, *dummy) + + +def test_initial_conditions_saved_state_regrid(): + with tempfile.TemporaryDirectory() as output_dir: + output_dir = Path(output_dir) + ic = InitialConditionsSavedState(output_dir / "foo.out") + + assert not ic.is_regridding_supported() diff --git a/imod/tests/test_msw/test_landuse_options.py b/imod/tests/test_msw/test_landuse_options.py index 5b1f20eea..58d62832c 100644 --- a/imod/tests/test_msw/test_landuse_options.py +++ b/imod/tests/test_msw/test_landuse_options.py @@ -5,10 +5,13 @@ import xarray as xr from numpy.testing import assert_almost_equal, assert_equal +from imod.mf6.utilities.regrid import ( + RegridderWeightsCache, +) from imod.msw import LanduseOptions -def test_landuse_options(fixed_format_parser): +def create_landuse_dict(): landuse_index = np.arange(1, 4) names = ["grassland", "maize", "potatoes"] @@ -43,7 +46,26 @@ def test_landuse_options(fixed_format_parser): interception_capacity_per_LAI=xr.zeros_like(lu), interception_intercept=xr.ones_like(lu), ) + return options + + +def get_new_grid(): + x = [1.0, 1.5, 2.0, 2.5, 3.0] + y = [3.0, 2.5, 2.0, 1.5, 1.0] + subunit = [0, 1] + dx = 0.5 + dy = 0.5 + # fmt: off + new_grid = xr.DataArray( + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} + ) + new_grid.values[:,:,:] = 1 + return new_grid + +def test_landuse_options(fixed_format_parser): + options = create_landuse_dict() lu_options = LanduseOptions(**options) with tempfile.TemporaryDirectory() as output_dir: @@ -67,3 +89,17 @@ def test_landuse_options(fixed_format_parser): assert_almost_equal(results[option], value.values) else: assert_equal(results[option], value.values) + + +def test_landuse_options_regrid(): + new_grid = get_new_grid() + options = create_landuse_dict() + lu_options = LanduseOptions(**options) + + regrid_context = RegridderWeightsCache(new_grid, new_grid) + regridded_land_use = lu_options.regrid_like(new_grid, regrid_context) + + assert len(regridded_land_use.dataset.coords.keys()) == 1 + assert np.all( + regridded_land_use.dataset.coords["landuse_index"].values == np.array([1, 2, 3]) + ) diff --git a/imod/tests/test_msw/test_ponding.py b/imod/tests/test_msw/test_ponding.py index 455b43f39..8e8d6e406 100644 --- a/imod/tests/test_msw/test_ponding.py +++ b/imod/tests/test_msw/test_ponding.py @@ -6,10 +6,13 @@ from numpy import nan from numpy.testing import assert_almost_equal, assert_equal +from imod.mf6.utilities.regrid import ( + RegridderWeightsCache, +) from imod.msw import Ponding -def test_simple_model(fixed_format_parser): +def setup_ponding(): x = [1.0, 2.0, 3.0] y = [1.0, 2.0, 3.0] subunit = [0, 1] @@ -71,7 +74,26 @@ def test_simple_model(fixed_format_parser): runoff_resistance=runoff_resistance, runon_resistance=runoff_resistance, ) + return ponding, index, svat + + +def get_new_grid(): + x = [1.0, 1.5, 2.0, 2.5, 3.0] + y = [3.0, 2.5, 2.0, 1.5, 1.0] + subunit = [0, 1] + dx = 0.5 + dy = 0.5 + # fmt: off + new_grid = xr.DataArray( + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} + ) + new_grid.values[:,:,:] = 1 + return new_grid + +def test_simple_model(fixed_format_parser): + ponding, index, svat = setup_ponding() with tempfile.TemporaryDirectory() as output_dir: output_dir = Path(output_dir) ponding.write(output_dir, index, svat) @@ -85,3 +107,16 @@ def test_simple_model(fixed_format_parser): assert_almost_equal(results["ponding_depth"], np.array([0.5, 1.0, 0.5, 1.0])) assert_almost_equal(results["runoff_resistance"], np.array([0.5, 1.0, 0.5, 1.0])) assert_almost_equal(results["runoff_resistance"], np.array([0.5, 1.0, 0.5, 1.0])) + + +def test_regrid_ponding(): + ponding, index, svat = setup_ponding() + new_grid = get_new_grid() + + old_grid = ponding.dataset["ponding_depth"].isel(subunit=0) + regrid_context = RegridderWeightsCache(old_grid, new_grid) + + regridded_ponding = ponding.regrid_like(new_grid, regrid_context) + + assert np.all(regridded_ponding.dataset["x"].values == new_grid["x"].values) + assert np.all(regridded_ponding.dataset["y"].values == new_grid["y"].values) diff --git a/imod/tests/test_msw/test_scaling_factors.py b/imod/tests/test_msw/test_scaling_factors.py index ff755d8d2..946be5b64 100644 --- a/imod/tests/test_msw/test_scaling_factors.py +++ b/imod/tests/test_msw/test_scaling_factors.py @@ -6,10 +6,13 @@ from numpy import nan from numpy.testing import assert_almost_equal, assert_equal +from imod.mf6.utilities.regrid import ( + RegridderWeightsCache, +) from imod.msw import ScalingFactors -def test_simple_model(fixed_format_parser): +def setup_scaling_factor(): x = [1.0, 2.0, 3.0] y = [1.0, 2.0, 3.0] subunit = [0, 1] @@ -67,6 +70,27 @@ def test_simple_model(fixed_format_parser): depth_perched_water_table=depth_perched_water_table, ) + return scaling_factors, index, svat + + +def get_new_grid(): + x = [1.0, 1.5, 2.0, 2.5, 3.0] + y = [3.0, 2.5, 2.0, 1.5, 1.0] + subunit = [0, 1] + dx = 0.5 + dy = 0.5 + # fmt: off + new_grid = xr.DataArray( + dims=("subunit", "y", "x"), + coords={"subunit": subunit, "y": y, "x": x, "dx": dx, "dy": dy} + ) + new_grid.values[:,:,:] = 1 + return new_grid + + +def test_simple_model(fixed_format_parser): + scaling_factors, index, svat = setup_scaling_factor() + with tempfile.TemporaryDirectory() as output_dir: output_dir = Path(output_dir) scaling_factors.write(output_dir, index, svat) @@ -84,3 +108,16 @@ def test_simple_model(fixed_format_parser): assert_almost_equal( results["depth_perched_water_table"], np.array([0.5, 1.0, 0.5, 0.7]) ) + + +def test_regrid_scaling_factor(fixed_format_parser): + scaling_factors, index, svat = setup_scaling_factor() + new_grid = get_new_grid() + + old_grid = scaling_factors.dataset["scale_soil_moisture"].isel(subunit=0) + regrid_context = RegridderWeightsCache(old_grid, new_grid) + + regridded_scaling_factor = scaling_factors.regrid_like(new_grid, regrid_context) + + assert np.all(regridded_scaling_factor.dataset["x"].values == new_grid["x"].values) + assert np.all(regridded_scaling_factor.dataset["y"].values == new_grid["y"].values) diff --git a/imod/typing/grid.py b/imod/typing/grid.py index 2a74d7b25..d157626c6 100644 --- a/imod/typing/grid.py +++ b/imod/typing/grid.py @@ -390,3 +390,13 @@ def decorator(*args, **kwargs): return x return decorator + + +@typedispatch +def is_empty(object: xr.Dataset) -> bool: + return len(object.keys()) == 0 + + +@typedispatch +def is_empty(object: object) -> bool: + return False