Skip to content

Commit 468a17e

Browse files
Issue #1002 fix MetaSWAP regridding methods for integer data (#1669)
Fixes #1002 # Description Change default methods of MetaSWAP GridData for integer data to ``mode``. # Checklist - [x] Links to correct issue - [x] Update changelog, if changes affect users - [x] PR title starts with ``Issue #nr``, e.g. ``Issue #737`` - [ ] Unit tests were added - [ ] **If feature added**: Added/extended example - [ ] **If feature added**: Added feature to API documentation - [ ] **If pixi.lock was changed**: Ran `pixi run generate-sbom` and committed changes
1 parent 2093c7e commit 468a17e

3 files changed

Lines changed: 155 additions & 103 deletions

File tree

docs/api/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ Fixed
5353
:meth:`imod.mf6.Modflow6Simulation.regrid_like`, and
5454
:meth:`imod.mf6.Modflow6Simulation.clip_box` would not copy
5555
:class:`imod.mf6.ValidationSettings`.
56+
- ``landuse``, ``soil_physical_unit``, ``active`` for :class:`imod.msw.GridData`
57+
are now properly regridded with the ``mode`` statistic when using
58+
:meth:`imod.msw.GridData.regrid_like`.
5659

5760
[1.0.0rc6] - 2025-08-28
5861
-----------------------

examples/user-guide/08-regridding.py

Lines changed: 146 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,27 @@
22
Regridding
33
==========
44
5-
Most MF6 packages have spatial data arrays as input. These arrays
6-
are discrete: they are defined over the simulation grid and contain
7-
values associated to each cell.
5+
Introduction
6+
------------
87
9-
Regridding these package means: create a new package with
10-
spatial data arrays defined over a different grid. Computing what
11-
the values in these new arrays should be, is done by xugrid.
8+
Most MODFLOW 6 packages have spatial data arrays as input. These arrays are
9+
discrete: they are defined over the simulation grid and contain values
10+
associated to each cell.
11+
12+
Regridding these package means: create a new package with spatial data arrays
13+
defined over a different grid. Computing what the values in these new arrays
14+
should be, is done by xugrid.
1215
1316
xugrid will compute a regridded array based on:
1417
1518
- the original array
16-
- the original discretization (this is described in the coordinates of the original arry)
19+
- the original discretization (this is described in the coordinates of the
20+
original arry)
1721
- the new discretization
1822
- a regridding method
1923
20-
More information on the available regridding methods can be found in the xugrid documentation
21-
https://deltares.github.io/xugrid/user_guide.html
24+
More information on the available regridding methods can be found in the xugrid
25+
documentation https://deltares.github.io/xugrid/user_guide.html
2226
2327
The regridding method that should be used depends on the property being
2428
regridded. For example a point-based property (whose value do not depend
@@ -46,13 +50,13 @@
4650
# -----------------------------
4751
#
4852
# In many real-world models, some cells will be inactive or marked as "vertical
49-
# passthrough" (VPT) in the idomain array of the simulation. Some packages require
50-
# that all cells that are inactictive or VPT in idomain are excluded from the
51-
# package as well. An example is the npf package: cells that are inactive or VPT
52-
# in idomain, should not have conductivity data in the npf package. Therefore at
53-
# the end of the regridding process, a final step consists in enforcing
54-
# consistency between those of idomain and all the packages. This is a 2-step
55-
# process:
53+
# passthrough" (VPT) in the idomain array of the simulation. Some packages
54+
# require that all cells that are inactictive or VPT in idomain are excluded
55+
# from the package as well. An example is the :class:`imod.mf6.NodePropertyFlow`
56+
# package: cells that are inactive or VPT in idomain, should not have
57+
# conductivity data in the npf package. Therefore at the end of the regridding
58+
# process, a final step consists in enforcing consistency between those of
59+
# idomain and all the packages. This is a 2-step process:
5660
#
5761
# 1) for cells that do not have inputs in crucial packages like npf or storage,
5862
# idomain will be set to inactive.
@@ -82,8 +86,8 @@
8286
# --------------------------------
8387
#
8488
# The regrid_like function is available on packages, models and simulations.
85-
# When the default methods are acceptable, regridding the whole simulation is the most convenient
86-
# from a user-perspective.
89+
# When the default methods are acceptable, regridding the whole simulation is
90+
# the most convenient from a user-perspective.
8791

8892
import imod
8993

@@ -166,22 +170,25 @@
166170
# When non-default methods are used for one or more packages, these should be
167171
# regridded separately. In that case, the most convenient approach is likely:
168172
#
169-
# - pop the packages that should use non-default methods from the source simulation (the
170-
# popping is optional, and is only recommended for packages whose presence is not
171-
# mandatory for validation.)
172-
# - regrid the source simulation: this takes care of all the packages that should use default methods.
173-
# - regrid the package(s) where you want to use non-standard rergridding methods indivudually starting from the
174-
# packages in the source simulation
175-
# - insert the custom-regridded packages to the
176-
# regridded simulation (or replace the package regridded with default methods with
177-
# the one you just regridded with non-default methods if it was not popped)
178-
#
179-
# In code, consider an example where we want to regrid the recharge package using non default methods
180-
# then we would do the following. First we'll load some example simulation.
181-
# There is a separate example contained in :doc:`/examples/mf6/hondsrug`
182-
# that you should look at if you are interested in the model building
173+
# - pop the packages that should use non-default methods from the source
174+
# simulation (the popping is optional, and is only recommended for packages
175+
# whose presence is not mandatory for validation.)
176+
# - regrid the source simulation: this takes care of all the packages that
177+
# should use default methods.
178+
# - regrid the package(s) where you want to use non-standard rergridding methods
179+
# indivudually starting from the packages in the source simulation
180+
# - insert the custom-regridded packages to the regridded simulation (or replace
181+
# the package regridded with default methods with the one you just regridded
182+
# with non-default methods if it was not popped)
183+
#
184+
# In code, consider an example where we want to regrid the recharge package
185+
# using non default methods then we would do the following. First we'll load
186+
# some example simulation. There is a separate example contained in
187+
# :doc:`/examples/mf6/hondsrug` that you should look at if you are interested in
188+
# the model building
183189

184190
# %%
191+
#
185192
# Set up the input needed for custom regridding. Create a regridder
186193
# weight-cache. This object can (and should) be reused for all the packages that
187194
# undergo custom regridding at this stage.
@@ -192,16 +199,19 @@
192199
regrid_cache
193200

194201
# %%
195-
# Next, we'll remove the recharge package and obtain it as a variable. We'll do this for later use.
202+
#
203+
# Next, we'll remove the recharge package and obtain it as a variable. We'll do
204+
# this for later use.
196205

197206
original_rch_package = original_simulation["GWF"].pop("rch")
198207

199208
original_rch_package
200209

201210
# %%
202-
# Regrid the recharge package with a custom regridder. In this case we opt
203-
# for the centroid locator regridder. This regridder is similar to using a
204-
# "nearest neighbour" lookup.
211+
#
212+
# Regrid the recharge package with a custom regridder. In this case we opt for
213+
# the centroid locator regridder. This regridder is similar to using a "nearest
214+
# neighbour" lookup.
205215
from imod.common.utilities.regrid import RegridderType
206216
from imod.mf6.regrid import RechargeRegridMethod
207217

@@ -226,10 +236,11 @@
226236
# Comparison with histograms
227237
# --------------------------
228238
#
229-
# In the next segment we will compare the input of the models on different grids.
230-
# We advice to always check how your input is regridded. In this example we upscaled grid,
231-
# many input parameters are regridded with a ``mean`` method. This means that their input
232-
# range is reduced, which can be seen in tailings in the histograms becoming shorter
239+
# In the next segment we will compare the input of the models on different
240+
# grids. We advice to always check how your input is regridded. In this example
241+
# we upscaled grid, many input parameters are regridded with a ``mean`` method.
242+
# This means that their input range is reduced, which can be seen in tailings in
243+
# the histograms becoming shorter
233244

234245

235246
def plot_histograms_side_by_side(array_original, array_regridded, title):
@@ -321,6 +332,10 @@ def plot_histograms_side_by_side(array_original, array_regridded, title):
321332
)
322333

323334
# %%
335+
#
336+
# Compare simulation outputs
337+
# --------------------------
338+
#
324339
# Let's compare outputs now.
325340

326341
# Write simulation
@@ -356,40 +371,43 @@ def plot_histograms_side_by_side(array_original, array_regridded, title):
356371
# %%
357372
# A note on regridding conductivity
358373
# ---------------------------------
359-
# In the npf package, it is possible to use for definining the conductivity tensor:
374+
#
375+
# In the npf package, it is possible to use for definining the conductivity
376+
# tensor:
360377
#
361378
# - 1 array (K)
362379
# - 2 arrays (K and K22)
363380
# - 3 arrays (K, K22, K33)
364381
#
365-
# If 1 array is given the tensor
366-
# is called isotropic. Defining only K gives the same behavior as specifying K,
367-
# K22 and K33 with the same value. When regridding, K33 has a default method
368-
# different from that of K and K22, but it can only be applied if K33 exists in
369-
# the source model in the first place. So it is recommended to introduce K33 as a
370-
# separate array in the source model even if it is isotropic.
371-
# Also note that default regridding methods were chosen assuming that K and K22
372-
# are roughly horizontal and K33 roughly vertical. But this may not be the case
373-
# if the input arrays angle2 and/or angle3 have large values.
382+
# If 1 array is given the tensor is called isotropic. Defining only K gives the
383+
# same behavior as specifying K, K22 and K33 with the same value. When
384+
# regridding, K33 has a default method different from that of K and K22, but it
385+
# can only be applied if K33 exists in the source model in the first place. So
386+
# it is recommended to introduce K33 as a separate array in the source model
387+
# even if it is isotropic. Also note that default regridding methods were chosen
388+
# assuming that K and K22 are roughly horizontal and K33 roughly vertical. But
389+
# this may not be the case if the input arrays angle2 and/or angle3 have large
390+
# values.
374391
#
375392
# Regridding boundary conditions
376393
# ------------------------------
394+
#
377395
# Special care must be taken when regridding boundary conditions, and it is
378396
# recommended that users verify the balance output of a regridded simulation and
379397
# compare it to the original model. If the regridded simulation is a good
380-
# representation of the original simulation, the mass contributions on the balance
381-
# by the different boundary conditions should be comparable in both simulations.
382-
# To achieve this, it may be necessary to tweak the input or the regridding
383-
# methods. An example of this is upscaling recharge (so the target grid has
384-
# coarser cells than the source grid). Its default method is averaging, with the
385-
# following rules:
398+
# representation of the original simulation, the mass contributions on the
399+
# balance by the different boundary conditions should be comparable in both
400+
# simulations. To achieve this, it may be necessary to tweak the input or the
401+
# regridding methods. An example of this is upscaling recharge (so the target
402+
# grid has coarser cells than the source grid). Its default method is averaging,
403+
# with the following rules:
386404
#
387405
# - if a cell in the source grid is inactive in the source recharge package
388406
# (meaning no recharge), it will not count when averaging. So if a target cell
389-
# has partial overlap with one source recharge cell, and the rest of the target
390-
# cell has no overlap with any source active recharge cell, it will get the
391-
# recharge of the one cell it has overlap with. But since the target cell is
392-
# larger, this effectively means the regridded recharge will be more in the
407+
# has partial overlap with one source recharge cell, and the rest of the
408+
# target cell has no overlap with any source active recharge cell, it will get
409+
# the recharge of the one cell it has overlap with. But since the target cell
410+
# is larger, this effectively means the regridded recharge will be more in the
393411
# regridded simulation than it was in the source simulation
394412
# - but we do the same regridding this time assigning a zero recharge to cells
395413
# without recharge then the averaging will take the zero-recharge cells into
@@ -398,22 +416,29 @@ def plot_histograms_side_by_side(array_original, array_regridded, title):
398416
#
399417
# A note on regridding transport
400418
# ------------------------------
401-
# Transport simulations can be unstable if constraints related to the grid Peclet
402-
# number and the courant number are exceeded. This can easily happen when
419+
#
420+
# Transport simulations can be unstable if constraints related to the grid
421+
# Peclet number and the courant number are exceeded. This can easily happen when
403422
# regridding. It may be necessary to reduce the simulation's time step size
404423
# especially when downscaling, to prevent numerical issues. Increasing
405424
# dispersivities or the molecular diffusion constant can also help to stabilize
406-
# the simulation. Inversely, when upscaling, a larger time step size can be acceptable.
425+
# the simulation. Inversely, when upscaling, a larger time step size can be
426+
# acceptable.
407427
#
408428
#
409429
# Unsupported packages
410430
# --------------------
431+
#
411432
# Some packages cannot be regridded. This includes the Lake package and the UZF
412-
# package. Such packages should be removed from the simulation before regridding,
413-
# and then new packages should be created by the user and then added to the
414-
# regridded simulation.
433+
# package. Such packages should be removed from the simulation before
434+
# regridding, and then new packages should be created by the user and then added
435+
# to the regridded simulation.
415436
#
437+
# Listing all default regridding methods
438+
# --------------------------------------
416439
#
440+
# MODFLOW 6
441+
# ^^^^^^^^^
417442
# This code snippet prints all default methods:
418443
#
419444
import inspect
@@ -422,44 +447,68 @@ def plot_histograms_side_by_side(array_original, array_regridded, title):
422447

423448
import pandas as pd
424449

425-
regrid_method_setup = {
426-
"package name": [],
427-
"array name": [],
428-
"method name": [],
429-
"function name": [],
430-
}
431-
regrid_method_table = pd.DataFrame(regrid_method_setup)
432-
# Get all classes in the imod.mf6 module (e.g. imod.mf6.NodePropertyFlow,
433-
# imod.mf6.GroundwaterFlowModel, imod.mf6.River)
450+
451+
def collect_regrid_methods(classes: list):
452+
"""Collect all regrid methods from all list of package classes."""
453+
regrid_method_setup = {
454+
"package name": [],
455+
"array name": [],
456+
"method name": [],
457+
"function name": [],
458+
}
459+
regrid_method_table = pd.DataFrame(regrid_method_setup)
460+
counter = 0
461+
for obj in classes:
462+
if hasattr(obj, "_regrid_method"):
463+
package_name = obj.__name__
464+
regrid_methods = asdict(obj.get_regrid_methods())
465+
for array_name in regrid_methods.keys():
466+
method_name = regrid_methods[array_name][0].name
467+
function_name = ""
468+
if len(regrid_methods[array_name]) > 0:
469+
function_name = regrid_methods[array_name][1]
470+
regrid_method_table.loc[counter] = (
471+
package_name,
472+
array_name,
473+
method_name,
474+
function_name,
475+
)
476+
counter = counter + 1
477+
478+
# Set multi index to group with packages
479+
regrid_method_table = regrid_method_table.set_index(["package name", "array name"])
480+
return regrid_method_table
481+
482+
483+
# Get all classes in the imod.mf6 module (e.g.
484+
# :class:`imod.mf6.NodePropertyFlow`, :class:`imod.mf6.GroundwaterFlowModel`,
485+
# :class:`imod.mf6.River`)
434486
mf6_classes = [
435487
obj for _, obj in inspect.getmembers(sys.modules["imod.mf6"], inspect.isclass)
436488
]
437489

438-
counter = 0
439-
for obj in mf6_classes:
440-
if hasattr(obj, "_regrid_method"):
441-
package_name = obj.__name__
442-
regrid_methods = asdict(obj.get_regrid_methods())
443-
for array_name in regrid_methods.keys():
444-
method_name = regrid_methods[array_name][0].name
445-
function_name = ""
446-
if len(regrid_methods[array_name]) > 0:
447-
function_name = regrid_methods[array_name][1]
448-
regrid_method_table.loc[counter] = (
449-
package_name,
450-
array_name,
451-
method_name,
452-
function_name,
453-
)
454-
counter = counter + 1
455-
456-
# Set multi index to group with packages
457-
regrid_method_table = regrid_method_table.set_index(["package name", "array name"])
490+
mf6_regrid_methods = collect_regrid_methods(mf6_classes)
491+
458492
# Pandas by default displays at max 60 rows, which this table exceeds.
459-
nrows = regrid_method_table.shape[0]
493+
nrows = mf6_regrid_methods.shape[0]
460494
# Configure pandas to increase the max amount of displayable rows.
461495
pd.set_option("display.max_rows", nrows + 1)
462496
# Display rows:
463-
regrid_method_table
497+
mf6_regrid_methods
498+
499+
# %%
500+
#
501+
# MetaSWAP
502+
# ^^^^^^^^^
503+
#
504+
# Let's list all default regridding methods for all MetaSWAP packages:
505+
506+
msw_classes = [
507+
obj for _, obj in inspect.getmembers(sys.modules["imod.msw"], inspect.isclass)
508+
]
509+
510+
msw_regrid_methods = collect_regrid_methods(msw_classes)
511+
512+
msw_regrid_methods
464513

465514
# %%

imod/msw/regrid/regrid_schemes.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ class GridDataRegridMethod(DataclassType):
7878
Parameters
7979
----------
8080
area: tuple, default (RegridderType.RELATIVEOVERLAP, "conductance")
81-
landuse: tuple, default (RegridderType.OVERLAP, "mean")
81+
landuse: tuple, default (RegridderType.OVERLAP, "mode")
8282
rootzone_depth: tuple, default (RegridderType.OVERLAP, "mean")
8383
surface_elevation: tuple, default (RegridderType.OVERLAP, "mean")
84-
soil_physical_unit: tuple, default (RegridderType.OVERLAP, "mean")
85-
active: tuple, default (RegridderType.OVERLAP, "mean")
84+
soil_physical_unit: tuple, default (RegridderType.OVERLAP, "mode")
85+
active: tuple, default (RegridderType.OVERLAP, "mode")
8686
8787
Examples
8888
--------
@@ -98,11 +98,11 @@ class GridDataRegridMethod(DataclassType):
9898
"""
9999

100100
area: RegridVarType = (RegridderType.RELATIVEOVERLAP, "conductance")
101-
landuse: RegridVarType = (RegridderType.OVERLAP, "mean")
101+
landuse: RegridVarType = (RegridderType.OVERLAP, "mode")
102102
rootzone_depth: RegridVarType = (RegridderType.OVERLAP, "mean")
103103
surface_elevation: RegridVarType = (RegridderType.OVERLAP, "mean")
104-
soil_physical_unit: RegridVarType = (RegridderType.OVERLAP, "mean")
105-
active: RegridVarType = (RegridderType.OVERLAP, "mean")
104+
soil_physical_unit: RegridVarType = (RegridderType.OVERLAP, "mode")
105+
active: RegridVarType = (RegridderType.OVERLAP, "mode")
106106

107107

108108
@dataclass(config=_CONFIG)

0 commit comments

Comments
 (0)