Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions autotest/test_listbudget.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,54 @@ def test_mf6listfile_parse_budget_line_missing_e(value, expected):
assert entry == "FLOW-JA-FACE"
assert flux == expected
assert cumu == expected


MF6_BUDGET_TEMPLATE = """
VOLUME BUDGET FOR ENTIRE MODEL AT END OF TIME STEP 1, STRESS PERIOD {kper}
------------------------------------------------------------------------------

CUMULATIVE VOLUME L**3 RATES FOR THIS TIME STEP L**3/T

IN: IN:
--- ---
STO = 100.0000 STO = 100.0000
CHD = {chd:>8} CHD = {chd:>8}

TOTAL IN = 100.0000 TOTAL IN = 100.0000

OUT: OUT:
---- ----
STO = 100.0000 STO = 100.0000
CHD = 0.0000 CHD = 0.0000

TOTAL OUT = 100.0000 TOTAL OUT = 100.0000

IN - OUT = 0.0000 IN - OUT = 0.0000

PERCENT DISCREPANCY = 0.00 PERCENT DISCREPANCY = 0.00
"""


def test_mf6listfile_unparseable_first_entry(function_tmpdir):
# see https://github.com/modflowpy/flopy/issues/2856
list_file = function_tmpdir / "gwf.lst"
list_file.write_text(
MF6_BUDGET_TEMPLATE.format(kper=1, chd="********")
+ MF6_BUDGET_TEMPLATE.format(kper=2, chd="0.0000")
)
match = r"first budget.*CHD \(IN\) at time step 1, stress period 1: .*\*{8}"
with pytest.raises(ValueError, match=match):
Mf6ListBudget(list_file)


def test_mf6listfile_unparseable_later_entry(function_tmpdir):
list_file = function_tmpdir / "gwf.lst"
list_file.write_text(
MF6_BUDGET_TEMPLATE.format(kper=1, chd="0.0000")
+ MF6_BUDGET_TEMPLATE.format(kper=2, chd="********")
)
mflist = Mf6ListBudget(list_file)
inc, cum = mflist.get_budget()
assert inc["STO_IN"][0] == pytest.approx(100.0)
assert np.isnan(inc["STO_IN"][1])
assert np.isnan(cum["CHD_IN"][1])
90 changes: 53 additions & 37 deletions flopy/utils/mflistfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,20 +630,23 @@ def _build_index(self, maxentries):
def _get_index(self, maxentries):
# --parse through the file looking for matches and parsing ts and sp
idxs = []
l_count = 1
l_count = 0
while True:
seekpoint = self.f.tell()
line = self.f.readline()
l_count += 1
if line == "":
break
if self.budgetkey in line:
for _ in range(self.tssp_lines):
line = self.f.readline()
l_count += 1
try:
ts, sp = get_ts_sp(line)
except:
except Exception:
print(
"unable to cast ts,sp on line number", l_count, " line: ", line
"could not parse time step and stress period "
f"on line {l_count}: {line.strip()!r}"
)
break

Expand Down Expand Up @@ -684,14 +687,13 @@ def _set_entries(self):
raise Exception(f"entries already set:{self.entries}")
if not self.idx_map:
raise Exception("must call build_index before call set_entries")
ts, sp, seekpoint = self.idx_map[0]
try:
incdict, cumdict = self._get_sp(
self.idx_map[0][0], self.idx_map[0][1], self.idx_map[0][2]
)
except:
raise Exception(
"unable to read budget information from first entry in list file"
)
incdict, cumdict = self._get_sp(ts, sp, seekpoint)
except Exception as e:
raise ValueError(
f"unable to read first budget in list file {self.file_name}: {e}"
) from e
self.entries = incdict.keys()
null_entries = {}
incdict = {}
Expand Down Expand Up @@ -759,17 +761,24 @@ def _load(self, maxentries=None):

return

def _sp_failed(self, msg):
# entries are read from the first budget, so until they are set
# there are no null entries to fall back on
if not self.entries:
raise ValueError(msg)
print(msg)
return self.null_entries

def _get_sp(self, ts, sp, seekpoint):
self.f.seek(seekpoint)
# --read to the start of the "in" budget information
while True:
line = self.f.readline()
if line == "":
print(
"end of file found while seeking budget "
f"information for ts,sp: {ts} {sp}"
return self._sp_failed(
"end of file found while reading budget "
f"at time step {ts}, stress period {sp}"
)
return self.null_entries

# --if there are two '=' in this line, then it is a budget line
if len(re.findall(r"=", line)) == 2:
Expand All @@ -781,27 +790,19 @@ def _get_sp(self, ts, sp, seekpoint):
entrydict = {}
while True:
if line == "":
print(
"end of file found while seeking budget "
f"information for ts,sp: {ts} {sp}"
return self._sp_failed(
"end of file found while reading budget "
f"at time step {ts}, stress period {sp}"
)
return self.null_entries
if len(re.findall(r"=", line)) == 2:
try:
entry, flux, cumu = self._parse_budget_line(line)
except Exception:
print("error parsing budget line in ts,sp", ts, sp)
return self.null_entries
if flux is None:
print(
"error casting in flux for", entry, " to float in ts,sp", ts, sp
)
return self.null_entries
if cumu is None:
print(
"error casting in cumu for", entry, " to float in ts,sp", ts, sp
return self._sp_failed(
"could not parse budget line at time step "
f"{ts}, stress period {sp}: {line.strip()!r}"
)
return self.null_entries
label = entry
if entry.endswith(tag.upper()):
if " - " in entry.upper():
key = entry.replace(" ", "")
Expand All @@ -818,6 +819,12 @@ def _get_sp(self, ts, sp, seekpoint):
else:
entrydict[entry] = 0
key = f"{entry}_{tag}"
label = f"{label} ({tag})"
if flux is None or cumu is None:
return self._sp_failed(
f"could not parse value for {label} at time step {ts}, "
f"stress period {sp}: {line.strip()!r}"
)
incdict[key] = flux
cumdict[key] = cumu
else:
Expand Down Expand Up @@ -856,8 +863,8 @@ def _get_totim(self, ts, sp, seekpoint):
ihead += 1
if line == "":
print(
"end of file found while seeking budget "
f"information for ts,sp: {ts} {sp}"
"end of file found while reading time summary "
f"at time step {ts}, stress period {sp}"
)
return np.nan, np.nan, np.nan
elif (
Expand All @@ -873,28 +880,37 @@ def _get_totim(self, ts, sp, seekpoint):
translen = self._parse_time_line(line)
line = self.f.readline()
if translen is None:
print("error parsing translen for ts,sp", ts, sp)
print(
f"could not parse transport step length at time step {ts}, "
f"stress period {sp}"
)
return np.nan, np.nan, np.nan

tslen = self._parse_time_line(line)
if tslen is None:
print("error parsing tslen for ts,sp", ts, sp)
print(
f"could not parse time step length at time step {ts}, "
f"stress period {sp}"
)
return np.nan, np.nan, np.nan

sptim = self._parse_time_line(self.f.readline())
if sptim is None:
print("error parsing sptim for ts,sp", ts, sp)
print(
f"could not parse stress period time at time step {ts}, "
f"stress period {sp}"
)
return np.nan, np.nan, np.nan

totim = self._parse_time_line(self.f.readline())
if totim is None:
print("error parsing totim for ts,sp", ts, sp)
print(f"could not parse total time at time step {ts}, stress period {sp}")
return np.nan, np.nan, np.nan
return tslen, sptim, totim

def _parse_time_line(self, line):
if line == "":
print("end of file found while parsing time information")
print("end of file found while reading time summary")
return None
try:
time_str = line[self.time_line_idx :]
Expand All @@ -910,7 +926,7 @@ def _parse_time_line(self, line):
idx = 0
tval = float(raw[idx])
except:
print("error parsing tslen information: ", time_str)
print(f"could not parse time value: {line.strip()!r}")
return None
return tval

Expand Down
Loading