Skip to content

Commit f867c90

Browse files
committed
refactor(grid): consistent API for get_cell_vertices
1 parent 9060146 commit f867c90

4 files changed

Lines changed: 246 additions & 24 deletions

File tree

autotest/test_grid.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
gridlist_to_disv_gridprops,
2626
to_cvfd,
2727
)
28+
from flopy.utils.gridutil import get_disu_kwargs, get_disv_kwargs
2829
from flopy.utils.triangle import Triangle
2930
from flopy.utils.voronoi import VoronoiGrid
3031

@@ -178,6 +179,92 @@ def test_get_cell_vertices():
178179
mg.get_cell_vertices(nn=0)
179180

180181

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

flopy/discretization/structuredgrid.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -975,15 +975,20 @@ def get_cell_vertices(self, *args, **kwargs):
975975
976976
Parameters
977977
----------
978+
cellid : int or tuple, optional
979+
Cell identifier. Can be:
980+
- node number (int)
981+
- (row, col) tuple
982+
- (layer, row, col) tuple (layer is ignored, vertices are 2D)
978983
node : int, optional
979-
Node index, mutually exclusive with i and j
984+
Node index, mutually exclusive with cellid, i, and j
980985
i, j : int, optional
981-
Row and column index, mutually exclusive with node
986+
Row and column index, mutually exclusive with cellid and node
982987
983988
Returns
984989
-------
985990
list
986-
list of tuples with x,y coordinates to cell vertices
991+
list of (x, y) cell vertex coordinates
987992
988993
Examples
989994
--------
@@ -995,26 +1000,63 @@ def get_cell_vertices(self, *args, **kwargs):
9951000
[(0.0, 40.0), (10.0, 40.0), (10.0, 30.0), (0.0, 30.0)]
9961001
>>> sg.get_cell_vertices(3, 0)
9971002
[(0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)]
1003+
>>> sg.get_cell_vertices((3, 0))
1004+
[(0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)]
1005+
>>> sg.get_cell_vertices(cellid=(0, 3, 0))
1006+
[(0.0, 10.0), (10.0, 10.0), (10.0, 0.0), (0.0, 0.0)]
9981007
"""
9991008
if kwargs:
10001009
if args:
10011010
raise TypeError("mixed positional and keyword arguments not supported")
1011+
elif "cellid" in kwargs:
1012+
cellid = kwargs.pop("cellid")
1013+
# Handle cellid as int, tuple of 2, or tuple of 3
1014+
if isinstance(cellid, (tuple, list)):
1015+
if len(cellid) == 2:
1016+
i, j = cellid
1017+
elif len(cellid) == 3:
1018+
# (layer, row, col) - ignore layer
1019+
_, i, j = cellid
1020+
else:
1021+
raise ValueError(
1022+
f"cellid tuple must have 2 or 3 elements, got {len(cellid)}"
1023+
)
1024+
else:
1025+
# cellid is a node number
1026+
_, i, j = self.get_lrc(cellid)[0]
10021027
elif "node" in kwargs:
10031028
_, i, j = self.get_lrc(kwargs.pop("node"))[0]
10041029
elif "i" in kwargs and "j" in kwargs:
10051030
i = kwargs.pop("i")
10061031
j = kwargs.pop("j")
1032+
else:
1033+
raise TypeError(
1034+
"expected cellid, node, or i and j as keyword arguments"
1035+
)
10071036
if kwargs:
10081037
unused = ", ".join(kwargs.keys())
10091038
raise TypeError(f"unused keyword arguments: {unused}")
10101039
elif len(args) == 0:
10111040
raise TypeError("expected one or more arguments")
1012-
1013-
if len(args) == 1:
1014-
_, i, j = self.get_lrc(args[0])[0]
1041+
elif len(args) == 1:
1042+
# Single arg could be node number or (row, col) or (layer, row, col) tuple
1043+
arg = args[0]
1044+
if isinstance(arg, (tuple, list)):
1045+
if len(arg) == 2:
1046+
i, j = arg
1047+
elif len(arg) == 3:
1048+
# (layer, row, col) - ignore layer
1049+
_, i, j = arg
1050+
else:
1051+
raise ValueError(
1052+
f"cellid tuple must have 2 or 3 elements, got {len(arg)}"
1053+
)
1054+
else:
1055+
# Node number
1056+
_, i, j = self.get_lrc(arg)[0]
10151057
elif len(args) == 2:
10161058
i, j = args
1017-
elif len(args) > 2:
1059+
else:
10181060
raise TypeError("too many arguments")
10191061

10201062
self._copy_cache = False

flopy/discretization/unstructuredgrid.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import copy
22
import os
3+
import warnings
34
from typing import Union
45

56
import numpy as np
@@ -792,16 +793,60 @@ def top_botm(self):
792793
new_botm = np.expand_dims(self._botm, 0)
793794
return np.concatenate((new_top, new_botm), axis=0)
794795

795-
def get_cell_vertices(self, cellid):
796+
def get_cell_vertices(self, cellid=None, node=None):
796797
"""
797-
Method to get a set of cell vertices for a single cell
798-
used in the Shapefile export utilities
799-
:param cellid: (int) cellid number
798+
Get a set of cell vertices for a single cell.
799+
800+
Parameters
801+
----------
802+
cellid : int or tuple, optional
803+
Cell identifier. Can be:
804+
- node number (int)
805+
- (node,) single-element tuple
806+
node : int, optional
807+
Node number, mutually exclusive with cellid
808+
800809
Returns
801-
------- list of x,y cell vertices
810+
-------
811+
list
812+
list of (x, y) cell vertex coordinates
813+
814+
Examples
815+
--------
816+
>>> import flopy
817+
>>> from flopy.utils.gridutil import get_disu_kwargs
818+
>>> disu_props = get_disu_kwargs(1, 10, 10, 1.0, 1.0, 1.0, [0.0])
819+
>>> ug = flopy.discretization.UnstructuredGrid(**disu_props)
820+
>>> ug.get_cell_vertices(5) # node number
821+
>>> ug.get_cell_vertices((5,)) # (node,) tuple
822+
>>> ug.get_cell_vertices(node=5) # explicit node kwarg
823+
>>> ug.get_cell_vertices(cellid=5) # explicit cellid kwarg
802824
"""
825+
# Handle arguments
826+
if cellid is not None and node is not None:
827+
raise ValueError("cellid and node are mutually exclusive")
828+
829+
if cellid is None and node is None:
830+
raise TypeError("expected cellid or node argument")
831+
832+
# Use cellid if provided, otherwise use node
833+
if node is not None:
834+
node_idx = node
835+
else:
836+
node_idx = cellid
837+
838+
# Handle tuple form
839+
if isinstance(node_idx, (tuple, list)):
840+
if len(node_idx) == 1:
841+
node_idx = node_idx[0]
842+
else:
843+
raise ValueError(
844+
f"cellid tuple must have 1 element for "
845+
f"unstructured grids, got {len(node_idx)}"
846+
)
847+
803848
self._copy_cache = False
804-
cell_vert = list(zip(self.xvertices[cellid], self.yvertices[cellid]))
849+
cell_vert = list(zip(self.xvertices[node_idx], self.yvertices[node_idx]))
805850
self._copy_cache = True
806851
return cell_vert
807852

flopy/discretization/vertexgrid.py

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -413,23 +413,71 @@ def intersect(self, x, y, z=None, local=False, forgive=False):
413413

414414
raise Exception("point given is outside of the model area")
415415

416-
def get_cell_vertices(self, cellid):
416+
def get_cell_vertices(self, cellid=None, node=None):
417417
"""
418-
Method to get a set of cell vertices for a single cell
419-
used in the Shapefile export utilities
420-
:param cellid: (int) cellid number
418+
Get a set of cell vertices for a single cell.
419+
420+
Parameters
421+
----------
422+
cellid : int or tuple, optional
423+
Cell identifier. Can be:
424+
- cell2d index (int, 0 to ncpl-1)
425+
- node number (int, >= ncpl) - will be converted to cell2d
426+
- (cell2d,) single-element tuple
427+
- (layer, cell2d) tuple (layer is ignored, vertices are 2D)
428+
node : int, optional
429+
Node number, mutually exclusive with cellid
430+
421431
Returns
422-
------- list of x,y cell vertices
432+
-------
433+
list
434+
list of (x, y) cell vertex coordinates
435+
436+
Examples
437+
--------
438+
>>> import flopy
439+
>>> from flopy.utils.gridutil import get_disv_kwargs
440+
>>> disv_props = get_disv_kwargs(1, 10, 10, 1.0, 1.0, 1.0, [0.0])
441+
>>> vg = flopy.discretization.VertexGrid(**disv_props)
442+
>>> vg.get_cell_vertices(5) # cell2d index
443+
>>> vg.get_cell_vertices((0, 5)) # (layer, cell2d) tuple
444+
>>> vg.get_cell_vertices(node=105) # node number
445+
>>> vg.get_cell_vertices(cellid=(1, 5)) # explicit cellid kwarg
423446
"""
424-
while cellid >= self.ncpl:
425-
if cellid > self.nnodes:
426-
err = f"cellid {cellid} out of index for size {self.nnodes}"
427-
raise IndexError(err)
447+
# Handle arguments
448+
if cellid is not None and node is not None:
449+
raise ValueError("cellid and node are mutually exclusive")
450+
451+
if cellid is None and node is None:
452+
raise TypeError("expected cellid or node argument")
453+
454+
# Use cellid if provided, otherwise use node
455+
if node is not None:
456+
cell2d = node
457+
else:
458+
cell2d = cellid
459+
460+
# Handle tuple forms
461+
if isinstance(cell2d, (tuple, list)):
462+
if len(cell2d) == 1:
463+
# (cell2d,) or (node,)
464+
cell2d = cell2d[0]
465+
elif len(cell2d) == 2:
466+
# (layer, cell2d) - ignore layer since vertices are 2D
467+
_, cell2d = cell2d
468+
else:
469+
raise ValueError(
470+
f"cellid tuple must have 1 or 2 elements, got {len(cell2d)}"
471+
)
428472

429-
cellid -= self.ncpl
473+
# Convert node to cell2d if necessary
474+
while cell2d >= self.ncpl:
475+
if cell2d > self.nnodes:
476+
raise IndexError(f"cellid {cell2d} out of index for size {self.nnodes}")
477+
cell2d -= self.ncpl
430478

431479
self._copy_cache = False
432-
cell_verts = list(zip(self.xvertices[cellid], self.yvertices[cellid]))
480+
cell_verts = list(zip(self.xvertices[cell2d], self.yvertices[cell2d]))
433481
self._copy_cache = True
434482
return cell_verts
435483

0 commit comments

Comments
 (0)