Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 698b434

Browse files
Issue #1539 fix well cleanup unstructured grids (#1540)
Fixes #1539 # Description Changes the following: - Fix bug where UgridDataArrays where silently converted to regular DataArrays (caused by the call to ``.data_vars``). - Add unstructured grid test case to well tests where easily applicable. # Checklist - [x] Links to correct issue - [x] Update changelog, if changes affect users - [x] PR title starts with ``Issue #nr``, e.g. ``Issue #737`` - [x] Unit tests were added - [ ] **If feature added**: Added/extended example
1 parent 0db7e89 commit 698b434

File tree

3 files changed

+59
-29
lines changed

3 files changed

+59
-29
lines changed

docs/api/changelog.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ Fixed
4040
relative paths for the extra files block.
4141
- :class:`imod.msw.IdfMapping` swapped order of y_grid and x_grid in dictionary
4242
for writing the correct order of coordinates in idf_svat.inp.
43+
- Fixed bug in :meth:`imod.mf6.Well.cleanup` and
44+
:meth:`imod.mf6.LayeredWell.cleanup` which caused an error when called with an
45+
unstructured discretization.
4346

4447
Changed
4548
~~~~~~~

imod/mf6/wel.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,10 @@ def _cleanup(
713713
cleanup_func: Callable,
714714
**cleanup_kwargs,
715715
) -> None:
716+
# Work around mypy error, .data_vars cannot be used with xu.UgridDataset
717+
dict_to_broadcast: dict[str, GridDataArray] = dict(**dis.dataset) # type: ignore
716718
# Top and bottom should be forced to grids with a x, y coordinates
717-
top, bottom = broadcast_to_full_domain(**dict(dis.dataset.data_vars))
719+
top, bottom = broadcast_to_full_domain(**dict_to_broadcast)
718720
# Collect point variable datanames
719721
point_varnames = list(self._write_schemata.keys())
720722
if "concentration" not in self.dataset.keys():

imod/tests/test_mf6/test_mf6_wel.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from imod.mf6.write_context import WriteContext
2424
from imod.schemata import ValidationError
2525
from imod.tests.fixtures.flow_basic_fixture import BasicDisSettings
26+
from imod.typing.grid import ones_like
2627

2728
times = [
2829
datetime(1981, 11, 30),
@@ -34,6 +35,23 @@
3435
]
3536

3637

38+
class BasicGridCases:
39+
@staticmethod
40+
def case_structured(basic_dis):
41+
idomain, top, bottom = basic_dis
42+
return (
43+
imod.mf6.StructuredDiscretization,
44+
idomain,
45+
top,
46+
bottom,
47+
)
48+
49+
@staticmethod
50+
def case_unstructured(basic_unstructured_dis):
51+
idomain, top, bottom = basic_unstructured_dis
52+
return imod.mf6.VerticesDiscretization, idomain, top, bottom
53+
54+
3755
class GridAgnosticWellCases:
3856
def case_well_stationary(self, well_high_lvl_test_data_stationary):
3957
obj = imod.mf6.Well(*well_high_lvl_test_data_stationary)
@@ -330,21 +348,22 @@ def test_to_mf6_pkg__validate_filter_top(well_high_lvl_test_data_stationary):
330348
)
331349

332350

351+
@parametrize_with_cases("dis", cases=BasicGridCases)
333352
def test_to_mf6_pkg__logging_with_message(
334-
tmp_path, basic_dis, well_high_lvl_test_data_transient
353+
tmp_path, dis, well_high_lvl_test_data_transient
335354
):
336355
# Arrange
337356
logfile_path = tmp_path / "logfile.txt"
338-
idomain, top, bottom = basic_dis
357+
_, idomain, top, bottom = dis
339358
modified_well_fixture = list(well_high_lvl_test_data_transient)
340359

341360
# create an idomain where layer 1 is active and layer 2 and 3 are inactive.
342361
# layer 1 has a bottom at -5, layer 2 at -35 and layer 3 at -135
343362
# so only wells that have a filter top above -5 will end up in the simulation
344363
active = idomain == 1
345364

346-
active.loc[1, :, :] = True
347-
active.loc[2:, :, :] = False
365+
active.loc[1, ...] = True
366+
active.loc[2:, ...] = False
348367

349368
# modify the well filter top and filter bottoms so that
350369
# well 0 is not placed
@@ -393,7 +412,7 @@ def test_to_mf6_pkg__logging_with_message(
393412

394413
wel = imod.mf6.Well(*modified_well_fixture)
395414

396-
k = xr.ones_like(idomain)
415+
k = ones_like(idomain)
397416
_ = wel.to_mf6_pkg(active, top, bottom, k)
398417

399418
# the wells that were fully or partially placed should not appear in the log message
@@ -415,15 +434,16 @@ def test_to_mf6_pkg__logging_with_message(
415434
assert "id = 11 x" in log
416435

417436

437+
@parametrize_with_cases("dis", cases=BasicGridCases)
418438
def test_to_mf6_pkg__logging_without_message(
419-
tmp_path, basic_dis, well_high_lvl_test_data_transient
439+
tmp_path, dis, well_high_lvl_test_data_transient
420440
):
421441
# This test activates logging, and then converts a high level well package to
422442
# an MF6 package, in such a way that all the wells can be placed.
423443
# Logging is active, and the log file should not include the "Some wells were not placed"
424444
# message
425445
logfile_path = tmp_path / "logfile.txt"
426-
idomain, top, bottom = basic_dis
446+
_, idomain, top, bottom = dis
427447

428448
with open(logfile_path, "w") as sys.stdout:
429449
imod.logging.configure(
@@ -435,10 +455,10 @@ def test_to_mf6_pkg__logging_without_message(
435455

436456
wel = imod.mf6.Well(*well_high_lvl_test_data_transient)
437457
active = idomain == 1
438-
k = xr.ones_like(idomain)
458+
k = ones_like(idomain)
439459

440-
active.loc[1, :, :] = True
441-
active.loc[2:, :, :] = True
460+
active.loc[1, ...] = True
461+
active.loc[2:, ...] = True
442462

443463
# Act
444464
_ = wel.to_mf6_pkg(active, top, bottom, k)
@@ -448,32 +468,36 @@ def test_to_mf6_pkg__logging_without_message(
448468
assert "Some wells were not placed" not in log
449469

450470

451-
def test_to_mf6_pkg__error_on_all_wells_removed(
452-
basic_dis, well_high_lvl_test_data_transient
453-
):
471+
@parametrize_with_cases("dis", cases=BasicGridCases)
472+
def test_to_mf6_pkg__error_on_all_wells_removed(dis, well_high_lvl_test_data_transient):
454473
"""Drop all wells, then run to_mf6_pkg"""
455-
idomain, top, bottom = basic_dis
474+
_, idomain, top, bottom = dis
456475

457476
wel = imod.mf6.Well(*well_high_lvl_test_data_transient)
458477
wel.dataset = wel.dataset.drop_sel(index=wel.dataset["index"])
459478
active = idomain == 1
460-
k = xr.ones_like(idomain)
479+
k = ones_like(idomain)
461480

462481
with pytest.raises(ValidationError, match="No wells were assigned in package"):
463482
wel.to_mf6_pkg(active, top, bottom, k)
464483

465484

466-
def test_to_mf6_pkg__error_on_well_removal(
467-
basic_dis, well_high_lvl_test_data_transient
468-
):
485+
@parametrize_with_cases("dis", cases=BasicGridCases)
486+
def test_to_mf6_pkg__error_on_well_removal(dis, well_high_lvl_test_data_transient):
469487
"""Set k values at well location x=81 to 1e-3, causing it to be dropped.
470488
Should throw error if error_on_well_removal = True"""
471-
idomain, top, bottom = basic_dis
489+
dis_pkg_type, idomain, top, bottom = dis
472490

473491
wel = imod.mf6.Well(minimum_k=0.9, *well_high_lvl_test_data_transient)
474492
active = idomain == 1
475-
k = xr.ones_like(idomain)
476-
k.loc[{"x": 85.0}] = 1e-3
493+
k = ones_like(idomain).astype(float)
494+
# Set k values at well location x=81 to 1e-3, causing it to be dropped.
495+
if dis_pkg_type is imod.mf6.VerticesDiscretization:
496+
dim = k.ugrid.grid.face_dimension
497+
indices = k.sel(layer=1).ugrid.sel(x=85.0).coords[dim].data
498+
k.loc[{dim: indices}] = 1e-3
499+
else:
500+
k.loc[{"x": 85.0}] = 1e-3
477501

478502
with pytest.raises(ValidationError, match="x = 81"):
479503
wel.to_mf6_pkg(active, top, bottom, k, strict_well_validation=True)
@@ -521,17 +545,18 @@ def test_is_empty(well_high_lvl_test_data_transient):
521545
assert wel_empty.is_empty()
522546

523547

524-
def test_cleanup(basic_dis, well_high_lvl_test_data_transient):
548+
@parametrize_with_cases("dis", cases=BasicGridCases)
549+
def test_cleanup(dis, well_high_lvl_test_data_transient):
525550
# Arrange
526551
wel = imod.mf6.Well(*well_high_lvl_test_data_transient)
527552
ds_original = wel.dataset.copy()
528-
idomain, top, bottom = basic_dis
529-
top = top.isel(layer=0, drop=True)
553+
554+
dis_pkg_type, idomain, top, bottom = dis
555+
if "layer" in top.dims:
556+
top = top.isel(layer=0, drop=True)
530557
deep_offset = 100.0
531-
dis_normal = imod.mf6.StructuredDiscretization(top, bottom, idomain)
532-
dis_deep = imod.mf6.StructuredDiscretization(
533-
top - deep_offset, bottom - deep_offset, idomain
534-
)
558+
dis_normal = dis_pkg_type(top, bottom, idomain)
559+
dis_deep = dis_pkg_type(top - deep_offset, bottom - deep_offset, idomain)
535560

536561
# Nothing to be cleaned up with default discretization
537562
wel.cleanup(dis_normal)

0 commit comments

Comments
 (0)