From 170c951626c6b32d968419eaedf2068f0d2b55b6 Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Mon, 28 Sep 2026 05:20:36 -0700 Subject: [PATCH 1/2] fix(mflistfile): parse budget values missing the exponent "E" Fortran E/ES edit descriptors without an exponent width drop the "E" for three-digit exponents (e.g. 8.5159-100). MF6 list files can contain such values, which caused ListBudget to fail parsing the budget. Accept these values when parsing budget lines. Close #2855 Co-Authored-By: Claude Opus 5.5 --- autotest/test_listbudget.py | 61 +++++++++++++++++++++++++++++++++++++ flopy/utils/mflistfile.py | 31 ++++++++++++------- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/autotest/test_listbudget.py b/autotest/test_listbudget.py index aa6a586f69..fd510a38f0 100644 --- a/autotest/test_listbudget.py +++ b/autotest/test_listbudget.py @@ -141,3 +141,64 @@ def test_mtlist(example_data_path): assert len(w) == 1, len(w) assert w[0].category == UserWarning, w[0] assert "error parsing SW mass budget" in str(w[0].message) + + +MF6_BUDGET_MISSING_E = """ + MASS BUDGET FOR ENTIRE MODEL AT END OF TIME STEP 1, STRESS PERIOD 1 + ------------------------------------------------------------------------------ + + CUMULATIVE MASS M RATES FOR THIS TIME STEP M/T + ------------------ ------------------------ + + IN: IN: + --- --- + STORAGE-AQUEOUS = 1.6172E-08 STORAGE-AQUEOUS = 1.6172E-08 + SRC = 60.3962 SRC = 60.3962 + FLOW-JA-FACE = 3.9204E-103 FLOW-JA-FACE = 3.9204E-103 + + TOTAL IN = 60.3962 TOTAL IN = 60.3962 + + OUT: OUT: + ---- ---- + STORAGE-AQUEOUS = 60.3063 STORAGE-AQUEOUS = 60.3063 + SRC = 0.0000 SRC = 0.0000 + FLOW-JA-FACE = 8.5159-100 FLOW-JA-FACE = 8.5159-100 + + TOTAL OUT = 60.3962 TOTAL OUT = 60.3962 + + IN - OUT = -3.1974E-13 IN - OUT = -3.1974E-13 + + PERCENT DISCREPANCY = -0.00 PERCENT DISCREPANCY = -0.00 +""" + + +def test_mf6listfile_three_digit_exponent_missing_e(function_tmpdir): + # Fortran drops the "E" for three-digit exponents unless an exponent + # width is specified, see https://github.com/modflowpy/flopy/issues/2855 + list_file = function_tmpdir / "gwt.lst" + list_file.write_text(MF6_BUDGET_MISSING_E) + mflist = Mf6ListBudget(list_file, budgetkey="MASS BUDGET") + + inc, cum = mflist.get_budget() + assert "FLOW-JA-FACE_OUT" in inc.dtype.names + assert inc["STORAGE-AQUEOUS_OUT"][0] == pytest.approx(60.3063) + assert cum["TOTAL_OUT"][0] == pytest.approx(60.3962) + + +@pytest.mark.parametrize( + "value, expected", + [ + ("8.5159-100", 8.5159e-100), + ("-8.5159-100", -8.5159e-100), + ("1.0000+100", 1.0e100), + ("-1.2000+150", -1.2e150), + ("3.9204E-103", 3.9204e-103), + ("60.3962", 60.3962), + ], +) +def test_mf6listfile_parse_budget_line_missing_e(value, expected): + line = f" FLOW-JA-FACE = {value:>16} FLOW-JA-FACE = {value:>16}" + entry, flux, cumu = Mf6ListBudget._parse_budget_line(None, line) + assert entry == "FLOW-JA-FACE" + assert flux == expected + assert cumu == expected diff --git a/flopy/utils/mflistfile.py b/flopy/utils/mflistfile.py index 18ae848de6..93581e784e 100644 --- a/flopy/utils/mflistfile.py +++ b/flopy/utils/mflistfile.py @@ -16,6 +16,24 @@ from ..utils.flopy_io import get_ts_sp from ..utils.utils_def import totim_to_datetime +# Fortran E/ES edit descriptors without an exponent width drop the "E" +# when the exponent has three digits, e.g. 8.5159E-100 -> 8.5159-100 +_MISSING_E = re.compile(r"^([+-]?\d*\.?\d+)([+-]\d{3})$") + + +def _parse_float(s): + """Parse a float from a list file, tolerating a missing "E" and NaN.""" + s = s.strip() + try: + return float(s) + except ValueError: + m = _MISSING_E.match(s) + if m: + return float(f"{m.group(1)}E{m.group(2)}") + if "NAN" in s.upper(): + return np.nan + return None + class ListBudget: """ @@ -825,17 +843,8 @@ def _parse_budget_line(self, line): idx = line2.index("=") + 1 fx_str = line2[idx:].split()[0].strip() - flux, cumu = None, None - try: - cumu = float(cu_str) - except: - if "NAN" in cu_str.strip().upper(): - cumu = np.nan - try: - flux = float(fx_str) - except: - if "NAN" in fx_str.strip().upper(): - flux = np.nan + cumu = _parse_float(cu_str) + flux = _parse_float(fx_str) return entry, flux, cumu def _get_totim(self, ts, sp, seekpoint): From 5f3f15e4431f6081214066556ab7f194ea36a61a Mon Sep 17 00:00:00 2001 From: wpbonelli Date: Mon, 28 Sep 2026 12:46:30 -0700 Subject: [PATCH 2/2] test(mflistfile): drop redundant end-to-end missing "E" test The parametrized _parse_budget_line test covers the fix, and float32 budget arrays prevent the end-to-end test from checking parsed values. Co-Authored-By: Claude Opus 5.5 --- autotest/test_listbudget.py | 42 ------------------------------------- 1 file changed, 42 deletions(-) diff --git a/autotest/test_listbudget.py b/autotest/test_listbudget.py index fd510a38f0..47f4382374 100644 --- a/autotest/test_listbudget.py +++ b/autotest/test_listbudget.py @@ -143,48 +143,6 @@ def test_mtlist(example_data_path): assert "error parsing SW mass budget" in str(w[0].message) -MF6_BUDGET_MISSING_E = """ - MASS BUDGET FOR ENTIRE MODEL AT END OF TIME STEP 1, STRESS PERIOD 1 - ------------------------------------------------------------------------------ - - CUMULATIVE MASS M RATES FOR THIS TIME STEP M/T - ------------------ ------------------------ - - IN: IN: - --- --- - STORAGE-AQUEOUS = 1.6172E-08 STORAGE-AQUEOUS = 1.6172E-08 - SRC = 60.3962 SRC = 60.3962 - FLOW-JA-FACE = 3.9204E-103 FLOW-JA-FACE = 3.9204E-103 - - TOTAL IN = 60.3962 TOTAL IN = 60.3962 - - OUT: OUT: - ---- ---- - STORAGE-AQUEOUS = 60.3063 STORAGE-AQUEOUS = 60.3063 - SRC = 0.0000 SRC = 0.0000 - FLOW-JA-FACE = 8.5159-100 FLOW-JA-FACE = 8.5159-100 - - TOTAL OUT = 60.3962 TOTAL OUT = 60.3962 - - IN - OUT = -3.1974E-13 IN - OUT = -3.1974E-13 - - PERCENT DISCREPANCY = -0.00 PERCENT DISCREPANCY = -0.00 -""" - - -def test_mf6listfile_three_digit_exponent_missing_e(function_tmpdir): - # Fortran drops the "E" for three-digit exponents unless an exponent - # width is specified, see https://github.com/modflowpy/flopy/issues/2855 - list_file = function_tmpdir / "gwt.lst" - list_file.write_text(MF6_BUDGET_MISSING_E) - mflist = Mf6ListBudget(list_file, budgetkey="MASS BUDGET") - - inc, cum = mflist.get_budget() - assert "FLOW-JA-FACE_OUT" in inc.dtype.names - assert inc["STORAGE-AQUEOUS_OUT"][0] == pytest.approx(60.3063) - assert cum["TOTAL_OUT"][0] == pytest.approx(60.3962) - - @pytest.mark.parametrize( "value, expected", [