Skip to content

Commit 124ebd5

Browse files
committed
refactor(plot): better shared face finding for HFB plotting
1 parent 905d20b commit 124ebd5

5 files changed

Lines changed: 466 additions & 203 deletions

File tree

autotest/test_gridutil.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
get_disu_kwargs,
99
get_disv_kwargs,
1010
get_lni,
11+
get_shared_face_3d,
1112
uniform_flow_field,
1213
)
1314

@@ -186,3 +187,207 @@ def test_uniform_flow_field(qx, qy, qz, nlay, nrow, ncol):
186187

187188
# TODO: check flowja
188189
# print(flowja.shape)
190+
191+
192+
def test_get_shared_face_3d_structured_horizontal():
193+
"""Test get_shared_face_3d for horizontal barrier in structured grid.
194+
195+
Tests same layer, adjacent cells.
196+
"""
197+
from flopy.discretization import StructuredGrid
198+
199+
# Create a simple 2x2x1 structured grid
200+
nlay, nrow, ncol = 1, 2, 2
201+
delr = np.array([1.0, 1.0])
202+
delc = np.array([1.0, 1.0])
203+
top = np.array([[10.0, 10.0], [10.0, 10.0]])
204+
botm = np.array([[[0.0, 0.0], [0.0, 0.0]]])
205+
206+
grid = StructuredGrid(delc=delc, delr=delr, top=top, botm=botm)
207+
208+
# Test horizontal barrier between cells (0, 0, 0) and (0, 0, 1)
209+
# These are horizontally adjacent
210+
cellid1 = (0, 0, 0)
211+
cellid2 = (0, 0, 1)
212+
213+
face = get_shared_face_3d(grid, cellid1, cellid2)
214+
215+
assert face is not None
216+
assert len(face) == 4 # Vertical face should have 4 vertices
217+
218+
# Check that face is vertical (two z-coordinates: top and bottom)
219+
z_coords = sorted({v[2] for v in face})
220+
assert len(z_coords) == 2
221+
assert z_coords[0] == 0.0 # bottom
222+
assert z_coords[1] == 10.0 # top
223+
224+
# Check x-coordinate is at the shared boundary (x=1.0)
225+
x_coords = {v[0] for v in face}
226+
assert 1.0 in x_coords
227+
228+
229+
def test_get_shared_face_3d_structured_vertical():
230+
"""Test get_shared_face_3d for vertical barrier in structured grid.
231+
232+
Tests different layers, same horizontal position.
233+
"""
234+
from flopy.discretization import StructuredGrid
235+
236+
# Create a simple 2x2x2 structured grid (2 layers)
237+
nlay, nrow, ncol = 2, 2, 2
238+
delr = np.array([1.0, 1.0])
239+
delc = np.array([1.0, 1.0])
240+
top = np.array([[10.0, 10.0], [10.0, 10.0]])
241+
botm = np.array([[[5.0, 5.0], [5.0, 5.0]], [[0.0, 0.0], [0.0, 0.0]]])
242+
243+
grid = StructuredGrid(delc=delc, delr=delr, top=top, botm=botm)
244+
245+
# Test vertical barrier between cells (0, 0, 0) and (1, 0, 0)
246+
# These are vertically adjacent
247+
cellid1 = (0, 0, 0)
248+
cellid2 = (1, 0, 0)
249+
250+
face = get_shared_face_3d(grid, cellid1, cellid2)
251+
252+
assert face is not None
253+
assert len(face) == 2 # Horizontal face should have 2 vertices (the edge)
254+
255+
# Check that face is horizontal (all z-coordinates the same at interface)
256+
z_coords = {v[2] for v in face}
257+
assert len(z_coords) == 1
258+
assert next(iter(z_coords)) == 5.0 # Interface between layers
259+
260+
261+
def test_get_shared_face_3d_vertex():
262+
"""Test get_shared_face_3d for vertex grid."""
263+
from flopy.discretization import VertexGrid
264+
265+
# Create a simple 2-cell vertex grid
266+
nlay = 2
267+
ncpl = 2
268+
269+
# Two square cells side by side
270+
vertices = [
271+
[0, 0.0, 0.0],
272+
[1, 1.0, 0.0],
273+
[2, 2.0, 0.0],
274+
[3, 0.0, 1.0],
275+
[4, 1.0, 1.0],
276+
[5, 2.0, 1.0],
277+
]
278+
279+
cell2d = [
280+
[0, 0.5, 0.5, 4, 0, 1, 4, 3],
281+
[1, 1.5, 0.5, 4, 1, 2, 5, 4],
282+
]
283+
284+
top = np.array([10.0, 10.0])
285+
botm = np.array([[5.0, 5.0], [0.0, 0.0]])
286+
287+
grid = VertexGrid(vertices=vertices, cell2d=cell2d, top=top, botm=botm, nlay=nlay)
288+
289+
# Test horizontal barrier between cells (0, 0) and (0, 1) in same layer
290+
cellid1 = (0, 0)
291+
cellid2 = (0, 1)
292+
293+
face = get_shared_face_3d(grid, cellid1, cellid2)
294+
295+
assert face is not None
296+
assert len(face) == 4 # Vertical face
297+
298+
# Test vertical barrier between cells (0, 0) and (1, 0) in different layers
299+
cellid1 = (0, 0)
300+
cellid2 = (1, 0)
301+
302+
face = get_shared_face_3d(grid, cellid1, cellid2)
303+
304+
assert face is not None
305+
assert len(face) == 2 # Horizontal face
306+
307+
# Check all z-coordinates are the same (interface elevation)
308+
z_coords = {v[2] for v in face}
309+
assert len(z_coords) == 1
310+
assert next(iter(z_coords)) == 5.0
311+
312+
313+
def test_get_shared_face_3d_unstructured():
314+
"""Test get_shared_face_3d for unstructured grid with explicit 3D vertices."""
315+
from flopy.discretization import UnstructuredGrid
316+
317+
# Create a simple 2-cell unstructured grid with 3D vertices
318+
# Two hexahedral cells stacked vertically
319+
vertices = [
320+
# Bottom layer vertices (z=0)
321+
[0, 0.0, 0.0, 0.0],
322+
[1, 1.0, 0.0, 0.0],
323+
[2, 1.0, 1.0, 0.0],
324+
[3, 0.0, 1.0, 0.0],
325+
# Middle layer vertices (z=5)
326+
[4, 0.0, 0.0, 5.0],
327+
[5, 1.0, 0.0, 5.0],
328+
[6, 1.0, 1.0, 5.0],
329+
[7, 0.0, 1.0, 5.0],
330+
# Top layer vertices (z=10)
331+
[8, 0.0, 0.0, 10.0],
332+
[9, 1.0, 0.0, 10.0],
333+
[10, 1.0, 1.0, 10.0],
334+
[11, 0.0, 1.0, 10.0],
335+
]
336+
337+
iverts = [
338+
[0, 1, 2, 3, 4, 5, 6, 7], # Bottom cell
339+
[4, 5, 6, 7, 8, 9, 10, 11], # Top cell
340+
]
341+
342+
xc = np.array([0.5, 0.5])
343+
yc = np.array([0.5, 0.5])
344+
top = np.array([10.0, 10.0])
345+
botm = np.array([0.0, 5.0])
346+
347+
grid = UnstructuredGrid(
348+
vertices=vertices,
349+
iverts=iverts,
350+
xcenters=xc,
351+
ycenters=yc,
352+
top=top,
353+
botm=botm,
354+
ncpl=[2],
355+
)
356+
357+
# Test shared face between two vertically stacked cells
358+
cellid1 = (0,)
359+
cellid2 = (1,)
360+
361+
face = get_shared_face_3d(grid, cellid1, cellid2)
362+
363+
assert face is not None
364+
assert len(face) == 4 # Should have 4 shared vertices at the interface
365+
366+
# Check all shared vertices are at z=5.0 (the interface)
367+
z_coords = {v[2] for v in face}
368+
assert len(z_coords) == 1
369+
assert next(iter(z_coords)) == 5.0
370+
371+
372+
def test_get_shared_face_3d_error_cases():
373+
"""Test error handling in get_shared_face_3d."""
374+
from flopy.discretization import StructuredGrid
375+
376+
nlay, nrow, ncol = 1, 2, 2
377+
delr = np.array([1.0, 1.0])
378+
delc = np.array([1.0, 1.0])
379+
top = np.array([[10.0, 10.0], [10.0, 10.0]])
380+
botm = np.array([[[0.0, 0.0], [0.0, 0.0]]])
381+
382+
grid = StructuredGrid(delc=delc, delr=delr, top=top, botm=botm)
383+
384+
# Test with same cellid
385+
with pytest.raises(ValueError, match="cellid1 and cellid2 must be different"):
386+
get_shared_face_3d(grid, (0, 0, 0), (0, 0, 0))
387+
388+
# Test with non-adjacent cells (should return None)
389+
cellid1 = (0, 0, 0)
390+
cellid2 = (0, 1, 1) # Diagonally opposite, not adjacent
391+
392+
face = get_shared_face_3d(grid, cellid1, cellid2)
393+
assert face is None

flopy/plot/crosssection.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from . import plotutil
1414
from .plotutil import (
1515
get_shared_face_3d,
16-
is_vertical_barrier,
16+
is_vertical,
1717
to_mp7_endpoints,
1818
to_mp7_pathlines,
1919
)
@@ -781,22 +781,6 @@ def plot_grid(self, **kwargs):
781781
ax.add_collection(col)
782782
return col
783783

784-
def _cellid_to_node(self, cellid):
785-
"""
786-
Convert a cellid tuple to a node number.
787-
788-
Parameters
789-
----------
790-
cellid : tuple
791-
Cell identifier
792-
793-
Returns
794-
-------
795-
int
796-
Node number
797-
"""
798-
return self.mg.get_node([cellid])[0]
799-
800784
def _plot_vertical_hfb_lines(self, color=None, **kwargs):
801785
"""
802786
Plot vertical HFBs as lines at layer interfaces.
@@ -848,8 +832,8 @@ def _plot_vertical_hfb_lines(self, color=None, **kwargs):
848832

849833
# Get the x-coordinates along the cross section for this cell
850834
# Need to find this cell in projpts - convert both cellids to nodes
851-
node1 = self._cellid_to_node(cellid1)
852-
node2 = self._cellid_to_node(cellid2)
835+
node1 = self.mg.get_node([cellid1])[0]
836+
node2 = self.mg.get_node([cellid2])[0]
853837

854838
# Get x-coordinates from either node's projection
855839
xcoords = []
@@ -958,11 +942,11 @@ def plot_bc(self, name=None, package=None, kper=0, color=None, head=None, **kwar
958942
cellid1 = tuple(entry["cellid1"])
959943
cellid2 = tuple(entry["cellid2"])
960944

961-
if is_vertical_barrier(self.mg, cellid1, cellid2):
962-
# Store vertical HFBs for line plotting
945+
if not is_vertical(self.mg, cellid1, cellid2):
946+
# horizontal face, vertical barrier
963947
vertical_hfbs.append((cellid1, cellid2))
964948
else:
965-
# Horizontal barriers - plot both cells as patches
949+
# vertical face, horizontal barrier
966950
cellids.append(list(entry["cellid1"]))
967951
cellids.append(list(entry["cellid2"]))
968952

flopy/plot/map.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .plotutil import (
1414
get_shared_face,
1515
get_shared_face_3d,
16-
is_vertical_barrier,
16+
is_vertical,
1717
to_mp7_pathlines,
1818
)
1919

@@ -469,20 +469,18 @@ def _plot_barrier_bc(self, barrier_data, color=None, name=None, **kwargs):
469469
"""
470470
ax = kwargs.pop("ax", self.ax)
471471

472-
# Separate horizontal and vertical barriers
473472
horizontal_line_segments = []
474473
vertical_cell_indices = []
475474

476475
for cellid1, cellid2 in barrier_data:
477476
# Only plot barriers on the current layer (for layered grids)
478477
# For DISU (len==1), plot all barriers since there's no layer filtering
479-
if len(cellid1) >= 2: # DIS or DISV (has layer info)
478+
if len(cellid1) >= 2:
480479
if cellid1[0] != self.layer and cellid2[0] != self.layer:
481480
continue
482481

483-
# Check if this is a vertical barrier
484-
if is_vertical_barrier(self.mg, cellid1, cellid2):
485-
# For vertical barriers, plot the cells on the current layer
482+
# horizontal face, vertical barrier
483+
if not is_vertical(self.mg, cellid1, cellid2):
486484
if len(cellid1) >= 2:
487485
if cellid1[0] == self.layer:
488486
if len(cellid1) == 3:
@@ -499,7 +497,7 @@ def _plot_barrier_bc(self, barrier_data, color=None, name=None, **kwargs):
499497
else:
500498
vertical_cell_indices.append([self.layer, cellid2[1]])
501499
else:
502-
# Horizontal barrier - plot as line on shared face
500+
# vertical face, horizontal barrier
503501
shared_face = get_shared_face(self.mg, cellid1, cellid2)
504502
if shared_face is not None:
505503
horizontal_line_segments.append(shared_face)

0 commit comments

Comments
 (0)