Skip to content

feat(binaryfile): add write methods#2722

Merged
wpbonelli merged 26 commits intomodflowpy:developfrom
wpbonelli:binary-write
Apr 10, 2026
Merged

feat(binaryfile): add write methods#2722
wpbonelli merged 26 commits intomodflowpy:developfrom
wpbonelli:binary-write

Conversation

@wpbonelli
Copy link
Copy Markdown
Member

@wpbonelli wpbonelli commented Feb 27, 2026

Support writing binary head, budget and grid files.

Class methods

Add write() classmethods to HeadFile and CellBudgetFile. These write a new file with the given data and return an instance with it open.

Head files

There are a few syntax variants

# dict keyed by (kstp, kper)
hds = HeadFile.write(
    'output.hds',
    # totim/pertim inferred (dt = 1.0 / time step)
    data={
        (1, 1): h_t1,
        (1, 2): h_t2,
    }
)

# list
hds = HeadFile.write('output.hds', data=[
    {'data': h_t2, 'kstp': 1, 'kper': 1, 'totim': 10.0, 'pertim': 10.0},
    {'data': h_t2, 'kstp': 1, 'kper': 2, 'totim': 20.0, 'pertim': 10.0},
])

# array/list with time as first dimension
# defaults to sequential stress periods: (1,1), (1,2), (1,3), ...
heads = [h_t1, h_t2, h_t3]  # or np.array([h_t1, h_t2, h_t3])
hds = HeadFile.write('output.hds', heads)

# array/list with custom tdis
hds = HeadFile.write('output.hds', heads, kstpkper=[(1, 1), (2, 1), (3, 1)])

Budget files

Again there are variants. The typical case is to use data with a list, where each entry has "text", "kper", "kstp", and "data" entries, and "data" is an array, typically grid-shaped. But it can be convenient to use text and pass a time-indexed dictionary to data to create a file with a single variable.

# just face flows, dict keyed by (kstp, kper)
cbc = CellBudgetFile.write(
    'output.cbc',
    text='FLOW-JA-FACE',
    nlay=3,
    nrow=10,
    ncol=20,
    data={
        (1, 1): q_t1,
        (1, 2): q_t2,
    }
)

# multiple variables, list
cbc = CellBudgetFile.write(
    'output.cbc',
    data=[
        {'data': q_t1, 'kstp': 1, 'kper': 1, 'totim': 10.0,
         'text': 'FLOW-JA-FACE'},
        {'data': ...},
        ...
    ]
)

# array/list with time dimension (grid-shaped data like storage)
# defaults to sequential stress periods: (1,1), (1,2), (1,3), ...
# grid dimensions inferred from array shape
storage = [s_t1, s_t2, s_t3]  # nlay x nrow x ncol arrays
cbc = CellBudgetFile.write('output.cbc', storage, text='STORAGE')

# for face flows, grid dimensions are required since data is 1D
flows = [q_t1, q_t2]  # 1D arrays
cbc = CellBudgetFile.write(
    'output.cbc',
    flows,
    text='FLOW-JA-FACE',
    nlay=3, nrow=10, ncol=20
)

If only face flows are provided, the grid shape must be specified with nlay/nrow/ncol, nlay/ncpl, or nnodes. If grid-shaped variables are provided, the grid's shape will be inferred.

Instance methods

Add instance export() methods to

  • MfGrdFile
  • HeadFile
  • CellBudgetFile

These copy the contents of an open file to another, optionally filtering by variable and/or time step, or changing the precision.

Grid files

from flopy.mf6.utils.binarygrid_util import MfGrdFile

grb = MfGrdFile("model.grb")
grb.export("copy.grb") # copy to another file
grb.export("diff_prec.grb", precision="single") # different precision

There is no write() method for MfGrdFile, as its signature would have been long and complicated to accommodate all grid types.

Head/budget files

from flopy.utils.binaryfile import HeadFile, CellBudgetFile

# head file
hds = HeadFile("model.hds")
hds.export("copy.hds")  # copy to another file
hds.export("filtered.hds", kstpkper=[(1, 0), (1, 1)])  # filter time steps
hds.export("diff_prec.hds", precision="single")  # different precision

# budget file
cbc = CellBudgetFile("model.cbc")
cbc.export("copy.cbc")
cbc.export("flowja.cbc", text="FLOW-JA-FACE")
cbc.export("bndpkgs.cbc", text=["STORAGE", "CONSTANT HEAD"])
cbc.export("filtered.cbc", kstpkper=[(1, 0)], text="FLOW-JA-FACE")

Close #2717

@wpbonelli wpbonelli added this to the 3.11 milestone Feb 27, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 66.90442% with 232 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.3%. Comparing base (556c088) to head (cd87539).
⚠️ Report is 151 commits behind head on develop.

Files with missing lines Patch % Lines
flopy/utils/binaryfile/__init__.py 64.4% 216 Missing ⚠️
flopy/mf6/utils/binarygrid_util.py 82.4% 13 Missing ⚠️
flopy/utils/utils_def.py 85.0% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2722      +/-   ##
===========================================
+ Coverage     55.5%    72.3%   +16.8%     
===========================================
  Files          644      674      +30     
  Lines       124135   132006    +7871     
===========================================
+ Hits         68947    95561   +26614     
+ Misses       55188    36445   -18743     
Files with missing lines Coverage Δ
flopy/utils/utils_def.py 90.3% <85.0%> (+4.3%) ⬆️
flopy/mf6/utils/binarygrid_util.py 89.8% <82.4%> (-0.9%) ⬇️
flopy/utils/binaryfile/__init__.py 74.5% <64.4%> (-10.0%) ⬇️

... and 569 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@wpbonelli wpbonelli marked this pull request as ready for review February 27, 2026 16:26
@wpbonelli wpbonelli requested a review from jdhughes-dev April 10, 2026 01:19
@wpbonelli wpbonelli merged commit e6951c4 into modflowpy:develop Apr 10, 2026
31 of 32 checks passed
@wpbonelli wpbonelli deleted the binary-write branch April 10, 2026 14:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature: support writing MF6 binary output files

2 participants