Skip to content

Commit 942a4cd

Browse files
authored
fix(mp7particledata): add global_xy option for to_coords/to_prp (#2405)
Following PRT's expectation of model coordinates, convert to model coords by default if the grid has georef info. Add `global_xy` option to disable this and emit global coords. Default is False. Note: method does get slow when it has to do the vertices conversion for lots of points. This can undoubtedly be optimized by first computing vertices in model coordinates or something along those lines. But I figured that was outside the scope for this PR.
1 parent c3884ce commit 942a4cd

File tree

3 files changed

+75
-17
lines changed

3 files changed

+75
-17
lines changed

autotest/test_grid_cases.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
class GridCases:
1212
@staticmethod
13-
def structured_small():
13+
def structured_small(xoff=0.0, yoff=0.0):
1414
nlay, nrow, ncol = 3, 2, 3
1515
delc = 1.0 * np.ones(nrow, dtype=float)
1616
delr = 1.0 * np.ones(ncol, dtype=float)
@@ -29,6 +29,8 @@ def structured_small():
2929
top=top,
3030
botm=botm,
3131
idomain=idomain,
32+
xoff=xoff,
33+
yoff=yoff,
3234
)
3335

3436
@staticmethod

autotest/test_particledata.py

+27
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,33 @@ def test_particledata_to_prp_dis_1():
245245
assert np.isclose(rpt[6], minz + (exp[ci][5] * (maxz - minz)))
246246

247247

248+
def test_particledata_to_prp_dis_1_global_xy():
249+
# model grid
250+
xoff = 100.0
251+
yoff = 300.0
252+
grid = GridCases().structured_small(xoff=xoff, yoff=yoff)
253+
254+
# particle data
255+
cells = [(0, 1, 1), (0, 1, 2)]
256+
part_data = ParticleData(partlocs=cells, structured=True)
257+
258+
# convert to global coordinates
259+
rpts_prt_model_coords = flatten(list(part_data.to_prp(grid)))
260+
rpts_prt_global_coords = flatten(list(part_data.to_prp(grid, global_xy=True)))
261+
262+
# check global and model coords
263+
# x
264+
assert np.all(
265+
np.array(rpts_prt_global_coords)[:, -3] - xoff
266+
== np.array(rpts_prt_model_coords)[:, -3]
267+
)
268+
# y
269+
assert np.all(
270+
np.array(rpts_prt_global_coords)[:, -2] - yoff
271+
== np.array(rpts_prt_model_coords)[:, -2]
272+
)
273+
274+
248275
def test_particledata_to_prp_dis_9():
249276
# minimal structured grid
250277
grid = GridCases().structured_small()

flopy/modpath/mp7particledata.py

+45-16
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ def write(self, f=None):
362362
for v in d:
363363
f.write(fmt.format(*v))
364364

365-
def to_coords(self, grid, localz=False) -> Iterator[tuple]:
365+
def to_coords(self, grid, localz=False, global_xy=False) -> Iterator[tuple]:
366366
"""
367367
Compute particle coordinates on the given grid.
368368
@@ -397,9 +397,12 @@ def cvt_z(p, k, i, j):
397397
span = mx - mn
398398
return mn + span * p
399399

400-
def convert(row) -> tuple[float, float, float]:
400+
def convert(row, global_xy=False) -> tuple[float, float, float]:
401401
verts = grid.get_cell_vertices(row.i, row.j)
402-
xs, ys = list(zip(*verts))
402+
if global_xy:
403+
xs, ys = list(zip(*verts))
404+
else:
405+
xs, ys = grid.get_local_coords(*np.array(verts).T)
403406
return [
404407
cvt_xy(row.localx, xs),
405408
cvt_xy(row.localy, ys),
@@ -421,19 +424,22 @@ def cvt_z(p, nn):
421424
span = mx - mn
422425
return mn + span * p
423426

424-
def convert(row) -> tuple[float, float, float]:
427+
def convert(row, global_xy=False) -> tuple[float, float, float]:
425428
verts = grid.get_cell_vertices(row.node)
426-
xs, ys = list(zip(*verts))
429+
if global_xy:
430+
xs, ys = list(zip(*verts))
431+
else:
432+
xs, ys = grid.get_local_coords(*np.array(verts).T)
427433
return [
428434
cvt_xy(row.localx, xs),
429435
cvt_xy(row.localy, ys),
430436
row.localz if localz else cvt_z(row.localz, row.node),
431437
]
432438

433439
for t in self.particledata.itertuples():
434-
yield convert(t)
440+
yield convert(t, global_xy=global_xy)
435441

436-
def to_prp(self, grid, localz=False) -> Iterator[tuple]:
442+
def to_prp(self, grid, localz=False, global_xy=False) -> Iterator[tuple]:
437443
"""
438444
Convert particle data to PRT particle release point (PRP)
439445
package data entries for the given grid. A model grid is
@@ -447,6 +453,8 @@ def to_prp(self, grid, localz=False) -> Iterator[tuple]:
447453
The grid on which to locate particle release points.
448454
localz : bool, optional
449455
Whether to return local z coordinates.
456+
global_xy : bool, optional
457+
Whether to return global x and y coordinates, default is False.
450458
451459
Returns
452460
-------
@@ -459,7 +467,7 @@ def to_prp(self, grid, localz=False) -> Iterator[tuple]:
459467
for i, (t, c) in enumerate(
460468
zip(
461469
self.particledata.itertuples(index=False),
462-
self.to_coords(grid, localz),
470+
self.to_coords(grid, localz, global_xy=global_xy),
463471
)
464472
):
465473
row = [i] # release point index (irpt)
@@ -778,10 +786,14 @@ def write(self, f=None):
778786
)
779787

780788

781-
def get_extent(grid, k=None, i=None, j=None, nn=None, localz=False) -> Extent:
789+
def get_extent(
790+
grid, k=None, i=None, j=None, nn=None, localz=False, global_xy=False
791+
) -> Extent:
782792
# get cell coords and span in each dimension
783793
if not (k is None or i is None or j is None):
784794
verts = grid.get_cell_vertices(i, j)
795+
if not global_xy and grid._has_ref_coordinates:
796+
verts = list(zip(*grid.get_local_coords(*np.array(verts).T)))
785797
minz, maxz = (
786798
(0, 1)
787799
if localz
@@ -792,6 +804,8 @@ def get_extent(grid, k=None, i=None, j=None, nn=None, localz=False) -> Extent:
792804
)
793805
elif nn is not None:
794806
verts = grid.get_cell_vertices(nn)
807+
if not global_xy and grid._has_ref_coordinates:
808+
verts = list(zip(*grid.get_local_coords(*np.array(verts).T)))
795809
if grid.grid_type == "structured":
796810
k, i, j = grid.get_lrc([nn])[0]
797811
minz, maxz = (
@@ -967,7 +981,14 @@ def get_cell_release_points(subdivisiondata, cellid, extent) -> Iterator[tuple]:
967981

968982

969983
def get_release_points(
970-
subdivisiondata, grid, k=None, i=None, j=None, nn=None, localz=False
984+
subdivisiondata,
985+
grid,
986+
k=None,
987+
i=None,
988+
j=None,
989+
nn=None,
990+
localz=False,
991+
global_xy=False,
971992
) -> Iterator[tuple]:
972993
"""
973994
Get MODPATH 7 release point tuples for the given cell.
@@ -980,7 +1001,7 @@ def get_release_points(
9801001
)
9811002

9821003
cellid = [k, i, j] if nn is None else [nn]
983-
extent = get_extent(grid, k, i, j, nn, localz)
1004+
extent = get_extent(grid, k, i, j, nn, localz, global_xy=global_xy)
9841005

9851006
if isinstance(subdivisiondata, FaceDataType):
9861007
return get_face_release_points(subdivisiondata, cellid, extent)
@@ -1351,7 +1372,7 @@ def write(self, f=None):
13511372
line += "\n"
13521373
f.write(line)
13531374

1354-
def to_coords(self, grid, localz=False) -> Iterator[tuple]:
1375+
def to_coords(self, grid, localz=False, global_xy=False) -> Iterator[tuple]:
13551376
"""
13561377
Compute global particle coordinates on the given grid.
13571378
@@ -1361,6 +1382,8 @@ def to_coords(self, grid, localz=False) -> Iterator[tuple]:
13611382
The grid on which to locate particle release points.
13621383
localz : bool, optional
13631384
Whether to return local z coordinates.
1385+
global_xy : bool, optional
1386+
Whether to return global x, y coordinates. Default is False.
13641387
13651388
Returns
13661389
-------
@@ -1369,23 +1392,27 @@ def to_coords(self, grid, localz=False) -> Iterator[tuple]:
13691392

13701393
for sd in self.subdivisiondata:
13711394
for nd in self.nodedata:
1372-
for rpt in get_release_points(sd, grid, nn=int(nd[0]), localz=localz):
1395+
for rpt in get_release_points(
1396+
sd, grid, nn=int(nd[0]), localz=localz, global_xy=global_xy
1397+
):
13731398
yield (*rpt[1:4],)
13741399

1375-
def to_prp(self, grid, localz=False) -> Iterator[tuple]:
1400+
def to_prp(self, grid, localz=False, global_xy=False) -> Iterator[tuple]:
13761401
"""
13771402
Convert particle data to PRT particle release point (PRP)
13781403
package data entries for the given grid. A model grid is
13791404
required because MODPATH supports several ways to specify
13801405
particle release locations by cell ID and subdivision info
1381-
or local coordinates, but PRT expects global coordinates.
1406+
or local coordinates, but PRT expects model coordinates, by default.
13821407
13831408
Parameters
13841409
----------
13851410
grid : flopy.discretization.grid.Grid
13861411
The grid on which to locate particle release points.
13871412
localz : bool, optional
13881413
Whether to return local z coordinates.
1414+
global_xy : bool, optional
1415+
Whether to return global x, y coordinates. Default is False.
13891416
13901417
Returns
13911418
-------
@@ -1396,7 +1423,9 @@ def to_prp(self, grid, localz=False) -> Iterator[tuple]:
13961423
for sd in self.subdivisiondata:
13971424
for nd in self.nodedata:
13981425
for irpt, rpt in enumerate(
1399-
get_release_points(sd, grid, nn=int(nd[0]), localz=localz)
1426+
get_release_points(
1427+
sd, grid, nn=int(nd[0]), localz=localz, global_xy=global_xy
1428+
)
14001429
):
14011430
row = [irpt]
14021431
if grid.grid_type == "structured":

0 commit comments

Comments
 (0)