Skip to content

Commit 1ad30ae

Browse files
authored
feat(lgr): add from_parent_grid classmethod to Lgr class (#2668)
It's a bit verbose to manually pass all the parent grid props into the Lgr grid initializer, this makes it easier. Also * rename refinement region param (in new method and initializer) to refine_mask * clarify how cells in the child grid are set active/inactive in docstrings * deprecate SimpleRegularGrid class for StructuredGrid
1 parent 3e1b7a9 commit 1ad30ae

File tree

2 files changed

+452
-49
lines changed

2 files changed

+452
-49
lines changed

autotest/test_lgrutil.py

Lines changed: 248 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import numpy as np
2+
import pytest
23

3-
from flopy.utils.lgrutil import Lgr, LgrToDisv
4+
from flopy.discretization import StructuredGrid
5+
from flopy.utils.lgrutil import Lgr, LgrToDisv, SimpleRegularGrid
46

57

6-
def test_lgrutil():
8+
def test_lgr_connections():
79
nlayp = 5
810
nrowp = 5
911
ncolp = 5
@@ -90,7 +92,7 @@ def test_lgrutil():
9092
]
9193

9294

93-
def test_lgrutil2():
95+
def test_lgr_variable_rc_spacing():
9496
# Define parent grid information
9597
xoffp = 0.0
9698
yoffp = 0.0
@@ -145,7 +147,7 @@ def test_lgrutil2():
145147
assert np.allclose(lgr.delc, answer), f"{lgr.delc} /= {answer}"
146148

147149

148-
def test_lgrutil3():
150+
def test_lgr_hanging_vertices():
149151
# Define parent grid information
150152
xoffp = 0.0
151153
yoffp = 0.0
@@ -215,3 +217,245 @@ def test_lgrutil3():
215217
b[1] = -2 * dz
216218
b[2] = -3 * dz
217219
assert np.allclose(gridprops["botm"], b)
220+
221+
222+
def test_lgr_from_parent_grid():
223+
# Create a parent grid with center cells marked for refinement
224+
nlay, nrow, ncol = 1, 7, 7
225+
delr = delc = 100.0 * np.ones(7)
226+
top = np.zeros((nrow, ncol))
227+
botm = -100.0 * np.ones((nlay, nrow, ncol))
228+
idomain = np.ones((nlay, nrow, ncol), dtype=int)
229+
idomain[:, 2:5, 2:5] = 0 # Mark center 3x3 cells for refinement
230+
231+
parent_grid = StructuredGrid(
232+
delr=delr, delc=delc, top=top, botm=botm, idomain=idomain
233+
)
234+
235+
# Create Lgr using the classmethod (no warning - uses new API)
236+
lgr_from_classmethod = Lgr.from_parent_grid(parent_grid, idomain, ncpp=3, ncppl=1)
237+
238+
# Create Lgr using the traditional constructor with deprecated parameter
239+
with pytest.warns(DeprecationWarning, match="idomainp.*deprecated"):
240+
lgr_traditional = Lgr(
241+
nlayp=nlay,
242+
nrowp=nrow,
243+
ncolp=ncol,
244+
delrp=delr,
245+
delcp=delc,
246+
topp=top,
247+
botmp=botm,
248+
idomainp=idomain,
249+
ncpp=3,
250+
ncppl=1,
251+
xllp=0.0,
252+
yllp=0.0,
253+
)
254+
255+
# Verify both methods produce the same results
256+
assert lgr_from_classmethod.get_shape() == lgr_traditional.get_shape()
257+
assert np.allclose(lgr_from_classmethod.delr, lgr_traditional.delr)
258+
assert np.allclose(lgr_from_classmethod.delc, lgr_traditional.delc)
259+
assert np.allclose(lgr_from_classmethod.top, lgr_traditional.top)
260+
assert np.allclose(lgr_from_classmethod.botm, lgr_traditional.botm)
261+
262+
# Verify child grid has expected dimensions (3x3 parent cells refined to 9x9)
263+
assert lgr_from_classmethod.get_shape() == (1, 9, 9)
264+
265+
# Verify gridprops can be generated
266+
gridprops = lgr_from_classmethod.to_disv_gridprops()
267+
assert "ncpl" in gridprops
268+
assert "nvert" in gridprops
269+
assert "vertices" in gridprops
270+
assert "cell2d" in gridprops
271+
assert "nlay" in gridprops
272+
assert "top" in gridprops
273+
assert "botm" in gridprops
274+
275+
# Expected: 40 parent cells + 81 child cells = 121 total cells
276+
assert gridprops["ncpl"] == 121
277+
278+
279+
def test_lgr_deprecation_warnings():
280+
nlay, nrow, ncol = 1, 7, 7
281+
delr = delc = 100.0 * np.ones(7)
282+
top = np.zeros((nrow, ncol))
283+
botm = -100.0 * np.ones((nlay, nrow, ncol))
284+
refine_mask = np.ones((nlay, nrow, ncol))
285+
refine_mask[:, 2:5, 2:5] = 0
286+
287+
# Test deprecated idomainp parameter
288+
with pytest.warns(DeprecationWarning, match="idomainp.*deprecated.*refine_mask"):
289+
lgr = Lgr(
290+
nlayp=nlay,
291+
nrowp=nrow,
292+
ncolp=ncol,
293+
delrp=delr,
294+
delcp=delc,
295+
topp=top,
296+
botmp=botm,
297+
idomainp=refine_mask,
298+
ncpp=3,
299+
)
300+
301+
# Test deprecated idomain attribute access
302+
with pytest.warns(
303+
DeprecationWarning, match="idomain.*attribute.*deprecated.*refine_mask"
304+
):
305+
_ = lgr.idomain
306+
307+
# Verify new API works without warnings
308+
import warnings
309+
310+
with warnings.catch_warnings(record=True) as warning_list:
311+
warnings.simplefilter("always")
312+
lgr_new = Lgr(
313+
nlayp=nlay,
314+
nrowp=nrow,
315+
ncolp=ncol,
316+
delrp=delr,
317+
delcp=delc,
318+
topp=top,
319+
botmp=botm,
320+
refine_mask=refine_mask,
321+
ncpp=3,
322+
)
323+
# Access via new attribute should not warn
324+
_ = lgr_new.refine_mask
325+
326+
# Check no deprecation warnings were raised
327+
deprecation_warnings = [
328+
w for w in warning_list if issubclass(w.category, DeprecationWarning)
329+
]
330+
assert len(deprecation_warnings) == 0, (
331+
"New API should not raise deprecation warnings"
332+
)
333+
334+
335+
def test_lgr_nested_refinement_grandchild():
336+
# Create parent grid (9x9)
337+
nlay, nrow, ncol = 1, 9, 9
338+
delr = delc = 100.0 * np.ones(9)
339+
top = np.zeros((nrow, ncol))
340+
botm = -100.0 * np.ones((nlay, nrow, ncol))
341+
342+
parent_grid = StructuredGrid(
343+
delr=delr, delc=delc, top=top, botm=botm, idomain=np.ones((nlay, nrow, ncol))
344+
)
345+
346+
# First refinement: refine center 3x3 cells of parent
347+
parent_refine_mask = np.ones((nlay, nrow, ncol))
348+
parent_refine_mask[:, 3:6, 3:6] = 0
349+
350+
lgr_child = Lgr.from_parent_grid(parent_grid, parent_refine_mask, ncpp=3)
351+
assert lgr_child.get_shape() == (1, 9, 9)
352+
353+
# Get child grid (SimpleRegularGrid now inherits from StructuredGrid)
354+
child_grid = lgr_child.child
355+
assert isinstance(child_grid, StructuredGrid)
356+
assert (child_grid.nlay, child_grid.nrow, child_grid.ncol) == (1, 9, 9)
357+
358+
# Second refinement: refine center 3x3 cells of child
359+
child_refine_mask = np.ones((child_grid.nlay, child_grid.nrow, child_grid.ncol))
360+
child_refine_mask[:, 3:6, 3:6] = 0
361+
362+
lgr_grandchild = Lgr.from_parent_grid(child_grid, child_refine_mask, ncpp=3)
363+
assert lgr_grandchild.get_shape() == (1, 9, 9)
364+
365+
# Verify grandchild has finer resolution than child
366+
assert np.allclose(lgr_grandchild.delr, lgr_child.delr / 3)
367+
assert np.allclose(lgr_grandchild.delc, lgr_child.delc / 3)
368+
369+
# Verify we can generate gridprops for both
370+
child_gridprops = lgr_child.to_disv_gridprops()
371+
grandchild_gridprops = lgr_grandchild.to_disv_gridprops()
372+
assert "ncpl" in child_gridprops
373+
assert "ncpl" in grandchild_gridprops
374+
375+
376+
def test_lgr_multiple_child_regions():
377+
# Create parent grid (15x15) with two separate areas to refine
378+
nlay, nrow, ncol = 1, 15, 15
379+
delr = delc = 100.0 * np.ones(15)
380+
top = np.zeros((nrow, ncol))
381+
botm = -100.0 * np.ones((nlay, nrow, ncol))
382+
383+
parent_grid = StructuredGrid(
384+
delr=delr, delc=delc, top=top, botm=botm, idomain=np.ones((nlay, nrow, ncol))
385+
)
386+
387+
# Region 1: top-left (2:5, 2:5)
388+
refine_mask_1 = np.ones((nlay, nrow, ncol))
389+
refine_mask_1[:, 2:5, 2:5] = 0
390+
lgr1 = Lgr.from_parent_grid(parent_grid, refine_mask_1, ncpp=3)
391+
392+
# Region 2: bottom-right (10:13, 10:13)
393+
refine_mask_2 = np.ones((nlay, nrow, ncol))
394+
refine_mask_2[:, 10:13, 10:13] = 0
395+
lgr2 = Lgr.from_parent_grid(parent_grid, refine_mask_2, ncpp=3)
396+
397+
# Verify both children have correct shape (3x3 parent cells -> 9x9 child)
398+
assert lgr1.get_shape() == (1, 9, 9)
399+
assert lgr2.get_shape() == (1, 9, 9)
400+
401+
# Verify children are at different locations
402+
assert lgr1.xll == 200.0 # Starts at column 2
403+
assert lgr1.yll == 1000.0 # Starts at row 2 (from top)
404+
assert lgr2.xll == 1000.0 # Starts at column 10
405+
assert lgr2.yll == 200.0 # Starts at row 10
406+
407+
# Verify both have same resolution (refinement of parent)
408+
assert np.allclose(lgr1.delr, lgr2.delr)
409+
assert np.allclose(lgr1.delc, lgr2.delc)
410+
411+
# Verify we can generate gridprops for both
412+
gridprops1 = lgr1.to_disv_gridprops()
413+
gridprops2 = lgr2.to_disv_gridprops()
414+
assert gridprops1["ncpl"] == gridprops2["ncpl"]
415+
416+
417+
def test_simple_regular_grid_deprecation():
418+
nlay, nrow, ncol = 1, 5, 5
419+
delr = delc = 100.0 * np.ones(5)
420+
top = np.zeros((nrow, ncol))
421+
botm = -100.0 * np.ones((nlay, nrow, ncol))
422+
idomain = np.ones((nlay, nrow, ncol), dtype=int)
423+
xorigin = 0.0
424+
yorigin = 0.0
425+
426+
# Test that SimpleRegularGrid instantiation raises deprecation warning
427+
with pytest.warns(
428+
DeprecationWarning, match="SimpleRegularGrid is deprecated.*StructuredGrid"
429+
):
430+
grid = SimpleRegularGrid(
431+
nlay, nrow, ncol, delr, delc, top, botm, idomain, xorigin, yorigin
432+
)
433+
434+
# Verify it's an instance of StructuredGrid
435+
assert isinstance(grid, StructuredGrid)
436+
assert isinstance(grid, SimpleRegularGrid)
437+
438+
# Test that modelgrid property raises deprecation warning
439+
with pytest.warns(
440+
DeprecationWarning, match="modelgrid.*deprecated.*use the instance directly"
441+
):
442+
mg = grid.modelgrid
443+
444+
# Verify modelgrid returns self
445+
assert mg is grid
446+
447+
# Test that get_gridprops_dis6 raises deprecation warning
448+
with pytest.warns(DeprecationWarning, match="get_gridprops_dis6.*deprecated"):
449+
gridprops = grid.get_gridprops_dis6()
450+
451+
# Verify gridprops contains expected keys
452+
assert "nlay" in gridprops
453+
assert "nrow" in gridprops
454+
assert "ncol" in gridprops
455+
assert gridprops["nlay"] == nlay
456+
assert gridprops["nrow"] == nrow
457+
assert gridprops["ncol"] == ncol
458+
459+
# Verify backward compatibility attributes
460+
assert grid.xorigin == xorigin
461+
assert grid.yorigin == yorigin

0 commit comments

Comments
 (0)