From fc24a91b52c49a67df27261bce2ee0f19792dc24 Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Wed, 25 Feb 2026 14:35:37 -0800 Subject: [PATCH 1/3] fix(UcnFile): Add check for modflow-6 GWT concentration file header type --- autotest/test_binaryfile.py | 32 +++++++++++++ flopy/utils/__init__.py | 9 +++- flopy/utils/binaryfile/__init__.py | 75 +++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/autotest/test_binaryfile.py b/autotest/test_binaryfile.py index 59558d98a..1de8a77f9 100644 --- a/autotest/test_binaryfile.py +++ b/autotest/test_binaryfile.py @@ -198,6 +198,38 @@ def test_concentration_build_index(example_data_path): ) +def test_mf6_concentration_build_index(example_data_path): + # test low-level BinaryLayerFile._build_index() method with UCN file + pth = ( + example_data_path + / "mf6/create_tests/test_transport/expected_output/gwt_mst03.ucn" + ) + with UcnFile(pth) as ucn: + pass + assert ucn.nrow == 1 + assert ucn.ncol == 1 + assert ucn.nlay == 1 + assert not hasattr(ucn, "ntrans") + assert ucn.totalbytes == 1680 + assert len(ucn.recordarray) == 28 + assert type(ucn.recordarray) == np.ndarray + assert ucn.recordarray.dtype == np.dtype( + [ + ("kstp", "i4"), + ("kper", "i4"), + ("pertim", "f8"), + ("totim", "f8"), + ("text", "S16"), + ("ncol", "i4"), + ("nrow", "i4"), + ("ilay", "i4"), + ] + ) + + assert np.max(ucn.times) == 4.0 + assert ucn.kstpkper[-1] == (14, 2) + + def test_binaryfile_writeread(function_tmpdir, nwt_model_path): model = "Pr3_MFNWT_lower.nam" ml = flopy.modflow.Modflow.load(model, version="mfnwt", model_ws=nwt_model_path) diff --git a/flopy/utils/__init__.py b/flopy/utils/__init__.py index bd15a59b8..d08b2495c 100644 --- a/flopy/utils/__init__.py +++ b/flopy/utils/__init__.py @@ -23,7 +23,14 @@ from .utl_import import import_optional_dependency # isort:skip from . import get_modflow as get_modflow_module -from .binaryfile import BinaryHeader, CellBudgetFile, HeadFile, HeadUFile, UcnFile +from .binaryfile import ( + BinaryHeader, + CellBudgetFile, + ConcentrationFile, + HeadFile, + HeadUFile, + UcnFile, +) from .check import check from .flopy_io import read_fixed_var, write_fixed_var from .formattedfile import FormattedHeadFile diff --git a/flopy/utils/binaryfile/__init__.py b/flopy/utils/binaryfile/__init__.py index 07027f852..af9230446 100644 --- a/flopy/utils/binaryfile/__init__.py +++ b/flopy/utils/binaryfile/__init__.py @@ -296,6 +296,78 @@ def get_headfile_precision(filename: Union[str, PathLike]): return result +def get_concentration_file_type(filename: Union[str, PathLike], precision): + """ + Method to check header and determine if the concentration file is a MT3D like + file or a MF6 GWT like concentration file + + Parameters + ---------- + filename : str or PathLike + Path of binary MODFLOW file to determine precision. + precision : str + double or single + + Returns + ------- + str + Result will be ucn or head + + """ + f = open(filename, "rb") + f.seek(0, 2) + totalbytes = f.tell() + f.seek(0, 0) # reset to beginning + assert f.tell() == 0 + if totalbytes == 0: + raise ValueError(f"datafile error: file is empty: {filename}") + + floattype = "f4" + if precision == "double": + floattype = "f8" + + # first try mt3d ucn + vartype = [ + ("ntrans", "i4"), + ("kstp", "i4"), + ("kper", "i4"), + ("totim", floattype), + ("text", "S16"), + ] + hdr = binaryread(f, vartype) + + try: + s = hdr[0][4].decode() + if not s.strip().lower().startswith("c"): + success = False + else: + success = True + result = "ucn" + except ValueError: + success = False + + if not success: + f.seek(0) + vartype = [ + ("kstp", " Date: Wed, 25 Feb 2026 14:38:27 -0800 Subject: [PATCH 2/3] linting --- flopy/utils/binaryfile/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flopy/utils/binaryfile/__init__.py b/flopy/utils/binaryfile/__init__.py index af9230446..393f1aae9 100644 --- a/flopy/utils/binaryfile/__init__.py +++ b/flopy/utils/binaryfile/__init__.py @@ -307,7 +307,7 @@ def get_concentration_file_type(filename: Union[str, PathLike], precision): Path of binary MODFLOW file to determine precision. precision : str double or single - + Returns ------- str From 77b8f1e806719e9afb26d77f6483b0407e186d5e Mon Sep 17 00:00:00 2001 From: jlarsen-usgs Date: Wed, 25 Feb 2026 14:42:31 -0800 Subject: [PATCH 3/3] revert change in __init__.py --- flopy/utils/__init__.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/flopy/utils/__init__.py b/flopy/utils/__init__.py index d08b2495c..bd15a59b8 100644 --- a/flopy/utils/__init__.py +++ b/flopy/utils/__init__.py @@ -23,14 +23,7 @@ from .utl_import import import_optional_dependency # isort:skip from . import get_modflow as get_modflow_module -from .binaryfile import ( - BinaryHeader, - CellBudgetFile, - ConcentrationFile, - HeadFile, - HeadUFile, - UcnFile, -) +from .binaryfile import BinaryHeader, CellBudgetFile, HeadFile, HeadUFile, UcnFile from .check import check from .flopy_io import read_fixed_var, write_fixed_var from .formattedfile import FormattedHeadFile