Skip to content

Commit 569d636

Browse files
committed
refactor(grid): consistent API for get_cell_vertices
1 parent 8d8097a commit 569d636

File tree

4 files changed

+245
-24
lines changed

4 files changed

+245
-24
lines changed

autotest/test_grid.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
centroid_of_polygon,
2626
to_cvfd,
2727
)
28+
from flopy.utils.gridutil import get_disu_kwargs, get_disv_kwargs
2829
from flopy.utils.lgrutil import Lgr
2930
from flopy.utils.triangle import Triangle
3031
from flopy.utils.voronoi import VoronoiGrid
@@ -190,6 +191,92 @@ def test_get_cell_vertices():
190191
mg.get_cell_vertices(nn=0)
191192

192193

194+
def test_structured_grid_get_cell_vertices():
195+
"""Test StructuredGrid.get_cell_vertices() with various input forms"""
196+
delr, delc = np.array([10.0] * 3), np.array([10.0] * 4)
197+
sg = StructuredGrid(delr=delr, delc=delc)
198+
199+
# Test node kwarg
200+
v1 = sg.get_cell_vertices(node=0)
201+
expected = [
202+
(np.float64(0.0), np.float64(40.0)),
203+
(np.float64(10.0), np.float64(40.0)),
204+
(np.float64(10.0), np.float64(30.0)),
205+
(np.float64(0.0), np.float64(30.0)),
206+
]
207+
assert v1 == expected
208+
209+
# Test positional args (i, j)
210+
v2 = sg.get_cell_vertices(3, 0)
211+
212+
# Test cellid as tuple (i, j)
213+
v3 = sg.get_cell_vertices((3, 0))
214+
assert v2 == v3, "Positional and tuple forms should match"
215+
216+
# Test cellid as 3-element tuple (layer, i, j) - layer ignored
217+
v4 = sg.get_cell_vertices(cellid=(0, 3, 0))
218+
assert v2 == v4, "2-element and 3-element forms should match"
219+
220+
# Test named i, j kwargs (backward compatibility)
221+
v5 = sg.get_cell_vertices(i=3, j=0)
222+
assert v2 == v5, "Named i,j should match"
223+
224+
225+
def test_vertex_grid_get_cell_vertices():
226+
"""Test VertexGrid.get_cell_vertices() with various input forms"""
227+
disv_props = get_disv_kwargs(2, 10, 10, 10.0, 10.0, 100.0, [50.0, 0.0])
228+
# Remove nvert which is not needed for VertexGrid constructor
229+
disv_props.pop("nvert", None)
230+
vg = VertexGrid(**disv_props)
231+
232+
# Test cell2d index as positional arg
233+
v1 = vg.get_cell_vertices(5)
234+
235+
# Test (layer, cell2d) tuple - layer ignored for 2D vertices
236+
v2 = vg.get_cell_vertices((0, 5))
237+
assert v1 == v2, "cell2d and (layer, cell2d) should match"
238+
239+
# Test named cellid kwarg
240+
v3 = vg.get_cell_vertices(cellid=5)
241+
assert v1 == v3, "Positional and kwarg should match"
242+
243+
# Test node number (>= ncpl, should be converted to cell2d)
244+
# Node 105 = layer 1, cell2d 5 (ncpl=100)
245+
v4 = vg.get_cell_vertices(node=105)
246+
247+
# Verify it's the same as (1, 5)
248+
v5 = vg.get_cell_vertices((1, 5))
249+
assert v4 == v5, "Node and (layer, cell2d) should match"
250+
251+
252+
def test_unstructured_grid_get_cell_vertices():
253+
"""Test UnstructuredGrid.get_cell_vertices() with various input forms"""
254+
disu_props = get_disu_kwargs(
255+
1, 10, 10, 10.0, 10.0, 100.0, [0.0], return_vertices=True
256+
)
257+
# Extract only the parameters needed for UnstructuredGrid
258+
ug = UnstructuredGrid(
259+
vertices=disu_props["vertices"],
260+
cell2d=disu_props["cell2d"],
261+
top=disu_props["top"],
262+
)
263+
264+
# Test node as positional arg
265+
v1 = ug.get_cell_vertices(5)
266+
267+
# Test (node,) single-element tuple
268+
v2 = ug.get_cell_vertices((5,))
269+
assert v1 == v2, "Int and tuple forms should match"
270+
271+
# Test node kwarg
272+
v3 = ug.get_cell_vertices(node=5)
273+
assert v1 == v3, "cellid and node should match"
274+
275+
# Test cellid kwarg
276+
v4 = ug.get_cell_vertices(cellid=5)
277+
assert v1 == v4, "Positional and kwarg should match"
278+
279+
193280
def test_get_lrc_get_node():
194281
nlay, nrow, ncol = 3, 4, 5
195282
nnodes = nlay * nrow * ncol

flopy/discretization/structuredgrid.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,15 +1148,20 @@ def get_cell_vertices(self, *args, **kwargs):
11481148
11491149
Parameters
11501150
----------
1151+
cellid : int or tuple, optional
1152+
Cell identifier. Can be:
1153+
- node number (int)
1154+
- (row, col) tuple
1155+
- (layer, row, col) tuple (layer is ignored, vertices are 2D)
11511156
node : int, optional
1152-
Node index, mutually exclusive with i and j
1157+
Node index, mutually exclusive with cellid, i, and j
11531158
i, j : int, optional
1154-
Row and column index, mutually exclusive with node
1159+
Row and column index, mutually exclusive with cellid and node
11551160
11561161
Returns
11571162
-------
11581163
list
1159-
list of tuples with x,y coordinates to cell vertices
1164+
list of (x, y) cell vertex coordinates
11601165
11611166
Examples
11621167
--------
@@ -1168,26 +1173,63 @@ def get_cell_vertices(self, *args, **kwargs):
11681173
[(0.0, 40.0), (10.0, 40.0), (10.0, 30.0), (0.0, 30.0)]
11691174
>>> sg.get_cell_vertices(3, 0)
11701175
[(0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)]
1176+
>>> sg.get_cell_vertices((3, 0))
1177+
[(0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)]
1178+
>>> sg.get_cell_vertices(cellid=(0, 3, 0))
1179+
[(0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)]
11711180
"""
11721181
if kwargs:
11731182
if args:
11741183
raise TypeError("mixed positional and keyword arguments not supported")
1184+
elif "cellid" in kwargs:
1185+
cellid = kwargs.pop("cellid")
1186+
# Handle cellid as int, tuple of 2, or tuple of 3
1187+
if isinstance(cellid, (tuple, list)):
1188+
if len(cellid) == 2:
1189+
i, j = cellid
1190+
elif len(cellid) == 3:
1191+
# (layer, row, col) - ignore layer
1192+
_, i, j = cellid
1193+
else:
1194+
raise ValueError(
1195+
f"cellid tuple must have 2 or 3 elements, got {len(cellid)}"
1196+
)
1197+
else:
1198+
# cellid is a node number
1199+
_, i, j = self.get_lrc(cellid)[0]
11751200
elif "node" in kwargs:
11761201
_, i, j = self.get_lrc(kwargs.pop("node"))[0]
11771202
elif "i" in kwargs and "j" in kwargs:
11781203
i = kwargs.pop("i")
11791204
j = kwargs.pop("j")
1205+
else:
1206+
raise TypeError(
1207+
"expected cellid, node, or i and j as keyword arguments"
1208+
)
11801209
if kwargs:
11811210
unused = ", ".join(kwargs.keys())
11821211
raise TypeError(f"unused keyword arguments: {unused}")
11831212
elif len(args) == 0:
11841213
raise TypeError("expected one or more arguments")
1185-
1186-
if len(args) == 1:
1187-
_, i, j = self.get_lrc(args[0])[0]
1214+
elif len(args) == 1:
1215+
# Single arg could be node number or (row, col) or (layer, row, col) tuple
1216+
arg = args[0]
1217+
if isinstance(arg, (tuple, list)):
1218+
if len(arg) == 2:
1219+
i, j = arg
1220+
elif len(arg) == 3:
1221+
# (layer, row, col) - ignore layer
1222+
_, i, j = arg
1223+
else:
1224+
raise ValueError(
1225+
f"cellid tuple must have 2 or 3 elements, got {len(arg)}"
1226+
)
1227+
else:
1228+
# Node number
1229+
_, i, j = self.get_lrc(arg)[0]
11881230
elif len(args) == 2:
11891231
i, j = args
1190-
elif len(args) > 2:
1232+
else:
11911233
raise TypeError("too many arguments")
11921234

11931235
self._copy_cache = False

flopy/discretization/unstructuredgrid.py

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -847,16 +847,60 @@ def top_botm(self):
847847
new_botm = np.expand_dims(self._botm, 0)
848848
return np.concatenate((new_top, new_botm), axis=0)
849849

850-
def get_cell_vertices(self, cellid):
850+
def get_cell_vertices(self, cellid=None, node=None):
851851
"""
852-
Method to get a set of cell vertices for a single cell
853-
used in the Shapefile export utilities
854-
:param cellid: (int) cellid number
852+
Get a set of cell vertices for a single cell.
853+
854+
Parameters
855+
----------
856+
cellid : int or tuple, optional
857+
Cell identifier. Can be:
858+
- node number (int)
859+
- (node,) single-element tuple
860+
node : int, optional
861+
Node number, mutually exclusive with cellid
862+
855863
Returns
856-
------- list of x,y cell vertices
864+
-------
865+
list
866+
list of (x, y) cell vertex coordinates
867+
868+
Examples
869+
--------
870+
>>> import flopy
871+
>>> from flopy.utils.gridutil import get_disu_kwargs
872+
>>> disu_props = get_disu_kwargs(1, 10, 10, 1.0, 1.0, 1.0, [0.0])
873+
>>> ug = flopy.discretization.UnstructuredGrid(**disu_props)
874+
>>> ug.get_cell_vertices(5) # node number
875+
>>> ug.get_cell_vertices((5,)) # (node,) tuple
876+
>>> ug.get_cell_vertices(node=5) # explicit node kwarg
877+
>>> ug.get_cell_vertices(cellid=5) # explicit cellid kwarg
857878
"""
879+
# Handle arguments
880+
if cellid is not None and node is not None:
881+
raise ValueError("cellid and node are mutually exclusive")
882+
883+
if cellid is None and node is None:
884+
raise TypeError("expected cellid or node argument")
885+
886+
# Use cellid if provided, otherwise use node
887+
if node is not None:
888+
node_idx = node
889+
else:
890+
node_idx = cellid
891+
892+
# Handle tuple form
893+
if isinstance(node_idx, (tuple, list)):
894+
if len(node_idx) == 1:
895+
node_idx = node_idx[0]
896+
else:
897+
raise ValueError(
898+
f"cellid tuple must have 1 element for "
899+
f"unstructured grids, got {len(node_idx)}"
900+
)
901+
858902
self._copy_cache = False
859-
cell_vert = list(zip(self.xvertices[cellid], self.yvertices[cellid]))
903+
cell_vert = list(zip(self.xvertices[node_idx], self.yvertices[node_idx]))
860904
self._copy_cache = True
861905
return cell_vert
862906

flopy/discretization/vertexgrid.py

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -474,23 +474,71 @@ def intersect(self, x, y, z=None, local=False, forgive=False):
474474
results.astype(int) if np.all(valid_mask) else results,
475475
)
476476

477-
def get_cell_vertices(self, cellid):
477+
def get_cell_vertices(self, cellid=None, node=None):
478478
"""
479-
Method to get a set of cell vertices for a single cell
480-
used in the Shapefile export utilities
481-
:param cellid: (int) cellid number
479+
Get a set of cell vertices for a single cell.
480+
481+
Parameters
482+
----------
483+
cellid : int or tuple, optional
484+
Cell identifier. Can be:
485+
- cell2d index (int, 0 to ncpl-1)
486+
- node number (int, >= ncpl) - will be converted to cell2d
487+
- (cell2d,) single-element tuple
488+
- (layer, cell2d) tuple (layer is ignored, vertices are 2D)
489+
node : int, optional
490+
Node number, mutually exclusive with cellid
491+
482492
Returns
483-
------- list of x,y cell vertices
493+
-------
494+
list
495+
list of (x, y) cell vertex coordinates
496+
497+
Examples
498+
--------
499+
>>> import flopy
500+
>>> from flopy.utils.gridutil import get_disv_kwargs
501+
>>> disv_props = get_disv_kwargs(1, 10, 10, 1.0, 1.0, 1.0, [0.0])
502+
>>> vg = flopy.discretization.VertexGrid(**disv_props)
503+
>>> vg.get_cell_vertices(5) # cell2d index
504+
>>> vg.get_cell_vertices((0, 5)) # (layer, cell2d) tuple
505+
>>> vg.get_cell_vertices(node=105) # node number
506+
>>> vg.get_cell_vertices(cellid=(1, 5)) # explicit cellid kwarg
484507
"""
485-
while cellid >= self.ncpl:
486-
if cellid > self.nnodes:
487-
err = f"cellid {cellid} out of index for size {self.nnodes}"
488-
raise IndexError(err)
508+
# Handle arguments
509+
if cellid is not None and node is not None:
510+
raise ValueError("cellid and node are mutually exclusive")
511+
512+
if cellid is None and node is None:
513+
raise TypeError("expected cellid or node argument")
514+
515+
# Use cellid if provided, otherwise use node
516+
if node is not None:
517+
cell2d = node
518+
else:
519+
cell2d = cellid
520+
521+
# Handle tuple forms
522+
if isinstance(cell2d, (tuple, list)):
523+
if len(cell2d) == 1:
524+
# (cell2d,) or (node,)
525+
cell2d = cell2d[0]
526+
elif len(cell2d) == 2:
527+
# (layer, cell2d) - ignore layer since vertices are 2D
528+
_, cell2d = cell2d
529+
else:
530+
raise ValueError(
531+
f"cellid tuple must have 1 or 2 elements, got {len(cell2d)}"
532+
)
489533

490-
cellid -= self.ncpl
534+
# Convert node to cell2d if necessary
535+
while cell2d >= self.ncpl:
536+
if cell2d > self.nnodes:
537+
raise IndexError(f"cellid {cell2d} out of index for size {self.nnodes}")
538+
cell2d -= self.ncpl
491539

492540
self._copy_cache = False
493-
cell_verts = list(zip(self.xvertices[cellid], self.yvertices[cellid]))
541+
cell_verts = list(zip(self.xvertices[cell2d], self.yvertices[cell2d]))
494542
self._copy_cache = True
495543
return cell_verts
496544

0 commit comments

Comments
 (0)