Skip to content

Commit b1c8c7a

Browse files
authored
feat(CellBudgetFile): support aux vars in get_ts() method (#2568)
Add a parameter 'variable' to the CellBudgetFile.get_ts() method defaulting to "q"
1 parent d452b78 commit b1c8c7a

File tree

2 files changed

+157
-31
lines changed

2 files changed

+157
-31
lines changed

autotest/test_cellbudgetfile.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,105 @@ def test_read_mf6_budgetfile(example_data_path):
656656
assert len(rch_zone_1) == 120 * 3 + 1
657657
assert len(rch_zone_2) == 120 * 3 + 1
658658
assert len(rch_zone_3) == 120 * 3 + 1
659+
660+
661+
def test_cellbudgetfile_get_ts_auxiliary_variables(example_data_path):
662+
from flopy.discretization import StructuredGrid
663+
664+
mf2005_model_path = example_data_path / "mf2005_test"
665+
cbc_fname = mf2005_model_path / "test1tr.gitcbc"
666+
667+
# modelgrid for test1tr (1 layer, 15 rows, 10 cols)
668+
modelgrid = StructuredGrid(nrow=15, ncol=10, nlay=1)
669+
670+
with CellBudgetFile(cbc_fname, modelgrid=modelgrid) as v:
671+
node = 54 - 1
672+
k = node // (15 * 10)
673+
i = (node % (15 * 10)) // 10
674+
j = node % 10
675+
676+
ts_default = v.get_ts(idx=(k, i, j), text="WELLS")
677+
ts_q_explicit = v.get_ts(idx=(k, i, j), text="WELLS", variable="q")
678+
assert ts_default.shape[1] == 2 # time + 1 data column
679+
np.testing.assert_array_equal(ts_default, ts_q_explicit)
680+
681+
ts_iface = v.get_ts(idx=(k, i, j), text="WELLS", variable="IFACE")
682+
assert ts_iface.shape[1] == 2 # time + 1 data column
683+
assert not np.array_equal(ts_default[:, 1], ts_iface[:, 1])
684+
np.testing.assert_array_equal(ts_default[:, 0], ts_iface[:, 0])
685+
686+
687+
@pytest.mark.requires_exe("mf6")
688+
def test_cellbudgetfile_get_ts_spdis_auxiliary_variables(function_tmpdir):
689+
from flopy.mf6 import (
690+
MFSimulation,
691+
ModflowGwf,
692+
ModflowGwfchd,
693+
ModflowGwfdis,
694+
ModflowGwfic,
695+
ModflowGwfnpf,
696+
ModflowGwfoc,
697+
ModflowIms,
698+
ModflowTdis,
699+
)
700+
701+
sim_name = "spdis_test"
702+
sim = MFSimulation(sim_name=sim_name, sim_ws=function_tmpdir, exe_name="mf6")
703+
tdis = ModflowTdis(sim, nper=2, perioddata=[(1.0, 1, 1.0), (1.0, 1, 1.0)])
704+
ims = ModflowIms(sim)
705+
gwf = ModflowGwf(sim, modelname=sim_name, save_flows=True)
706+
nrow, ncol, nlay = 5, 5, 1
707+
dis = ModflowGwfdis(
708+
gwf,
709+
nrow=nrow,
710+
ncol=ncol,
711+
nlay=nlay,
712+
delr=10.0,
713+
delc=10.0,
714+
top=10.0,
715+
botm=[0.0],
716+
)
717+
ic = ModflowGwfic(gwf, strt=5.0)
718+
npf = ModflowGwfnpf(gwf, k=1.0, save_specific_discharge=True)
719+
chd_spd = [[(0, 0, 0), 10.0], [(0, 4, 4), 0.0]]
720+
chd = ModflowGwfchd(gwf, stress_period_data=chd_spd)
721+
budget_file = f"{sim_name}.cbc"
722+
head_file = f"{sim_name}.hds"
723+
oc = ModflowGwfoc(
724+
gwf,
725+
budget_filerecord=budget_file,
726+
head_filerecord=head_file,
727+
saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
728+
)
729+
730+
sim.write_simulation()
731+
success, _ = sim.run_simulation(silent=True)
732+
733+
cbc_file = function_tmpdir / budget_file
734+
with CellBudgetFile(cbc_file, modelgrid=gwf.modelgrid) as cbc:
735+
spdis_data = cbc.get_data(text="DATA-SPDIS")
736+
if len(spdis_data) == 0:
737+
pytest.skip("No DATA-SPDIS records found")
738+
739+
available_fields = list(spdis_data[0].dtype.names)
740+
expected_fields = ["node", "q", "qx", "qy", "qz"]
741+
742+
for field in ["qx", "qy", "qz"]:
743+
assert field in available_fields, (
744+
f"Expected field '{field}' not found in {available_fields}"
745+
)
746+
747+
test_cell = (0, 2, 2)
748+
ts_q = cbc.get_ts(idx=test_cell, text="DATA-SPDIS", variable="q")
749+
ts_qx = cbc.get_ts(idx=test_cell, text="DATA-SPDIS", variable="qx")
750+
ts_qy = cbc.get_ts(idx=test_cell, text="DATA-SPDIS", variable="qy")
751+
ts_qz = cbc.get_ts(idx=test_cell, text="DATA-SPDIS", variable="qz")
752+
753+
assert ts_q.shape[1] == 2 # time + 1 data column
754+
assert ts_qx.shape[1] == 2
755+
assert ts_qy.shape[1] == 2
756+
assert ts_qz.shape[1] == 2
757+
758+
np.testing.assert_array_equal(ts_q[:, 0], ts_qx[:, 0])
759+
np.testing.assert_array_equal(ts_q[:, 0], ts_qy[:, 0])
760+
np.testing.assert_array_equal(ts_q[:, 0], ts_qz[:, 0])

flopy/utils/binaryfile/__init__.py

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,7 +1604,7 @@ def get_data(
16041604
if t
16051605
]
16061606

1607-
def get_ts(self, idx, text=None, times=None):
1607+
def get_ts(self, idx, text=None, times=None, variable="q"):
16081608
"""
16091609
Get a time series from the binary budget file.
16101610
@@ -1619,6 +1619,11 @@ def get_ts(self, idx, text=None, times=None):
16191619
'RIVER LEAKAGE', 'STORAGE', 'FLOW RIGHT FACE', etc.
16201620
times : iterable of floats
16211621
List of times to from which to get time series.
1622+
variable : str, optional
1623+
Variable name to extract from the budget record. Default is 'q'.
1624+
For records with auxiliary variables (e.g., 'DATA-SPDIS', 'DATA-SAT'),
1625+
this can be used to access auxiliary data. Examples include 'qx',
1626+
'qy', 'qz' for specific discharge components.
16221627
16231628
Returns
16241629
-------
@@ -1638,6 +1643,9 @@ def get_ts(self, idx, text=None, times=None):
16381643
Examples
16391644
--------
16401645
1646+
>>> # Get specific discharge x-component from DATA-SPDIS record
1647+
>>> ts = cbb.get_ts(idx=(0, 5, 5), text='DATA-SPDIS', variable='qx')
1648+
16411649
"""
16421650
# issue exception if text not provided
16431651
if text is None:
@@ -1669,45 +1677,61 @@ def get_ts(self, idx, text=None, times=None):
16691677
for idx, t in enumerate(timesint):
16701678
result[idx, 0] = t
16711679

1680+
full3d = True
16721681
for itim, k in enumerate(kstpkper):
1673-
try:
1674-
v = self.get_data(kstpkper=k, text=text, full3D=True)
1675-
# skip missing data - required for storage
1676-
if len(v) > 0:
1682+
if full3d:
1683+
try:
1684+
v = self.get_data(kstpkper=k, text=text, full3D=True)
1685+
1686+
# skip missing data - required for storage
1687+
if len(v) == 0:
1688+
continue
1689+
16771690
v = v[0]
16781691
istat = 1
16791692
for k, i, j in kijlist:
16801693
result[itim, istat] = v[k, i, j].copy()
16811694
istat += 1
1682-
except ValueError:
1695+
continue
1696+
except ValueError:
1697+
full3d = False
1698+
if not full3d:
16831699
v = self.get_data(kstpkper=k, text=text)
1700+
16841701
# skip missing data - required for storage
1685-
if len(v) > 0:
1686-
if self.modelgrid is None:
1687-
s = (
1688-
"A modelgrid instance must be provided during "
1689-
"instantiation to get IMETH=6 timeseries data"
1702+
if len(v) == 0:
1703+
continue
1704+
1705+
if self.modelgrid is None:
1706+
s = (
1707+
"A modelgrid instance must be provided during "
1708+
"instantiation to get IMETH=6 timeseries data"
1709+
)
1710+
raise ValueError(s)
1711+
1712+
if self.modelgrid.grid_type == "structured":
1713+
ndx = [
1714+
lrc[0] * (self.modelgrid.nrow * self.modelgrid.ncol)
1715+
+ lrc[1] * self.modelgrid.ncol
1716+
+ (lrc[2] + 1)
1717+
for lrc in kijlist
1718+
]
1719+
else:
1720+
ndx = [
1721+
lrc[0] * self.modelgrid.ncpl + (lrc[-1] + 1) for lrc in kijlist
1722+
]
1723+
1724+
for vv in v:
1725+
available = vv.dtype.names
1726+
if variable not in available:
1727+
raise ValueError(
1728+
f"Variable '{variable}' not found in budget record. "
1729+
f"Available variables: {list(available)}"
16901730
)
1691-
raise AssertionError(s)
1692-
1693-
if self.modelgrid.grid_type == "structured":
1694-
ndx = [
1695-
lrc[0] * (self.modelgrid.nrow * self.modelgrid.ncol)
1696-
+ lrc[1] * self.modelgrid.ncol
1697-
+ (lrc[2] + 1)
1698-
for lrc in kijlist
1699-
]
1700-
else:
1701-
ndx = [
1702-
lrc[0] * self.modelgrid.ncpl + (lrc[-1] + 1)
1703-
for lrc in kijlist
1704-
]
1705-
1706-
for vv in v:
1707-
field = vv.dtype.names[2]
1708-
dix = np.asarray(np.isin(vv["node"], ndx)).nonzero()[0]
1709-
if len(dix) > 0:
1710-
result[itim, 1:] = vv[field][dix]
1731+
1732+
dix = np.asarray(np.isin(vv["node"], ndx)).nonzero()[0]
1733+
if len(dix) > 0:
1734+
result[itim, 1:] = vv[variable][dix]
17111735

17121736
return result
17131737

0 commit comments

Comments
 (0)