gwsource.mf6io#

Read MODFLOW 6 binary grid (.grb) and budget (.cbc) files into a FlowStep.

Node numbering (checked on MF6 6.x nightly with IDOMAIN = 0 cells on DIS and DISU):
- The .grb file stores IA/JA in *user* node numbering for DIS, DISV and DISU. Cells removed by
  IDOMAIN (<= 0) keep an empty row (no diagonal either) and no other row points at them.
- FLOW-JA-FACE has NJA entries aligned with those IA/JA arrays, so it needs no remapping.
- List budget records (IMETH = 6) give the 1-based user node in ID1 and the bound number in ID2.
- Array budget records (IMETH = 1, e.g. STO-SS) hold one value per user node.
So every array is in user numbering; cells with IDOMAIN <= 0 are flagged inactive.

Sign convention: every budget term is positive INTO the cell, including storage (positive =
release from storage). MF6 writes the per-cell residual (sum of all flows into the cell) in the
diagonal of FLOW-JA-FACE; it is not a connection and is ignored here. DATA-* records (DATA-SPDIS,
DATA-SAT) are not flows and are skipped.

Cell area: DELR x DELC (DIS); vertex polygons (DISV, and DISU when vertices were written);
otherwise AREA from the DISU input file read with FloPy, or None if that is not available.

Saturated thickness (for travel times): cell top and bottom and ICELLTYPE come from the .grb
file (DIS/DISV: the top of layer k > 0 is the bottom of layer k - 1). Confined cells
(ICELLTYPE = 0) are fully saturated. Convertible cells use DATA-SAT x (top - bot) when the step
saved DATA-SAT (NPF SAVE_SATURATION); otherwise, if a head file is open, clip(head, bot, top) -
bot, with dry cells (head <= bot, or |head| >= 1e29 for HDRY/HNOFLO) at 0; otherwise None.

Water mover (checked on test model 7, DRN -> MVR -> SFR): a provider package with MOVER writes
two disjoint aquifer-side records, e.g. DRN (the part of the drain outflow that leaves the model)
and DRN-TO-MVR (the part handed to the mover). Both are outflows from the cell and together they
equal the drain's head-dependent flow, so both are kept as sink terms; the cell budget closes
against MF6's own residual with both, and would not close without the -TO-MVR record. Where the
mover sends the water (here into the stream, which may leak it back into the aquifer through
the SFR record) is not followed in a single-model step: gwsource.network routes it when the
receiving package is read as package nodes (read_network / open_simulation with package_nodes=
and the MVR budget file). Records of one package share its name (paknam2); the second and
later records of a package in a step are named '<package>:<text>' (e.g. 'DRN:DRN-TO-MVR') and
keep the package as their parent.

GWF-GWF exchanges (checked with models split by FloPy's Mf6Splitter): with SAVE_FLOWS in the
exchange file, each model's budget has one FLOW-JA-FACE record with IMETH = 6 per exchange,
PAKNAM2 = the exchange name (e.g. 'GWF-GWF_1'), MODELNAM2 = the other model, ID1 = the cell
here, ID2 = the cell in the other model, q positive into the cell here; the other model's record
is its exact negative. Read for one model alone, these are boundary terms: a package with text
'GWF-GWF', the exchange name, partner = the other model and bound = the cell over there.
gwsource.network turns them into edges when both models are read together.

IFLOWFACE (checked on MF6 6.9 dev, RCH with AUXILIARY IFLOWFACE): a package's auxiliary
variables are saved as extra columns of its list budget record (FloPy names the column by the
AUXTXT, here 'IFLOWFACE'). MF6 PRT reads IFLOWFACE from the same budget file, so this column is
what PRT uses. FlowStep.term_iflowface holds it per term (0 for records without it).

Boundnames are not in the budget file. ID2 of a list record is the bound's position (1-based)
in the package's list for the current stress period, so read_boundnames() loads the list input
of every package with BOUNDNAMES through FloPy, per stress period (a period without a PERIOD
block reuses the last list, as in MF6). SFR, LAK, MAW and UZF boundnames come from their
PACKAGEDATA (ID2 = reach, lake, well or UZF cell; also the bound of their package-node terms).
class gwsource.mf6io.ModelFiles(model, grb_file, budget_file, dis_file, dis_type, nam_file=None, tdis_file=None, head_file=None)[source]#

Bases: object

Paths of the files of one GWF model that a FlowStep is read from.
Parameters:
  • model (str)

  • grb_file (Path)

  • budget_file (Path)

  • dis_file (Path)

  • dis_type (str)

  • nam_file (Path | None)

  • tdis_file (Path | None)

  • head_file (Path | None)

class gwsource.mf6io.GridData(grid_type, ia, ja, idomain, shape, area, top=None, bot=None, icelltype=None)[source]#

Bases: object

Connectivity and geometry read from a binary grid file.
Parameters:
gwsource.mf6io.find_model_files(ws, model=None)[source]#
Locate the binary grid and budget files of a GWF model from its name and OC files.
Parameters:
Return type:

ModelFiles

gwsource.mf6io.read_units(files)[source]#
Return {'length': ..., 'time': ...} from DIS LENGTH_UNITS and TDIS TIME_UNITS.
Parameters:

files (ModelFiles)

Return type:

dict[str, str]

gwsource.mf6io.polygon_areas(vertices, iavert, javert)[source]#
Return plan areas of polygons given vertex xy and 0-based CSR vertex lists (shoelace).
Parameters:
Return type:

ndarray

gwsource.mf6io.read_grid(grb_file)[source]#
Read connectivity, IDOMAIN, shape, geometry and ICELLTYPE from a binary grid file.
Parameters:

grb_file (str | Path)

Return type:

GridData

gwsource.mf6io.saturated_thickness(top, bot, icelltype, active, saturation=None, head=None)[source]#
Return per-cell saturated thickness from DATA-SAT (preferred) or heads; 0 if inactive.

Cells with ICELLTYPE = 0 are fully saturated; without ICELLTYPE every cell is convertible.
Parameters:
Return type:

ndarray

gwsource.mf6io.read_disu_area(ws, model)[source]#
Return AREA from a model's DISU input, read with FloPy.
Parameters:
Return type:

ndarray

class gwsource.mf6io.BoundnameTable(periods)[source]#

Bases: object

Boundnames per package and stress period: {PACKAGE: {first kper: names by bound - 1}}.
Parameters:

periods (dict[str, dict[int, ndarray]])

names(package, kper, bounds)[source]#
Return the boundname of each 1-based bound number of a package in stress period kper.
Parameters:
Return type:

ndarray

gwsource.mf6io.read_boundnames(files, ws)[source]#
Load, through FloPy, the boundnames of every package of a model that has BOUNDNAMES.
Parameters:
Return type:

BoundnameTable

gwsource.mf6io.available_steps(budget_file)[source]#
Return the 0-based (kstp, kper) of every time step saved in a budget file.
Parameters:

budget_file (str | Path)

Return type:

list[tuple[int, int]]

class gwsource.mf6io.StepReader(grid, cbc, area=None, boundnames=None, notes=None, heads=None)[source]#

Bases: object

Grid and budget file opened once; reads a FlowStep for any saved time step.

The grid (IA/JA, IDOMAIN, areas) is read once and shared by every FlowStep it returns.
The budget and head files stay open until close() (or the end of a with block); open
handles stop Windows from deleting the model folder.
Parameters:
close()[source]#
Close the budget file and the head file.
Return type:

None

property steps: list[tuple[int, int]]#
0-based (kstp, kper) of every saved time step.
step_length(kstpkper)[source]#
Return the length (DELT) of a saved time step, from the budget record header.
Parameters:

kstpkper (tuple[int, int])

Return type:

float

thickness(kstpkper, saturation, active)[source]#
Return (saturated thickness, its source) from DATA-SAT, else heads, else (None, '').
Parameters:
Return type:

tuple[ndarray | None, str]

read(kstpkper=(0, 0))[source]#
Read one time step into a FlowStep.
Parameters:

kstpkper (tuple[int, int])

Return type:

FlowStep

gwsource.mf6io.open_files(grb_file, budget_file, area=None, head_file=None)[source]#
Open a binary grid file and a budget file (and optionally a head file) once.

The head file is only used for the saturated thickness when a step has no DATA-SAT.
Parameters:
Return type:

StepReader

gwsource.mf6io.open_model(ws, model=None, area=None, boundnames=False)[source]#
Open a GWF model's grid and budget files once; optionally load its boundnames (FloPy).

For DISU grids without vertices the cell area is read from the DISU input (AREA).
Parameters:
Return type:

StepReader

gwsource.mf6io.read_step_files(grb_file, budget_file, kstpkper=(0, 0), area=None)[source]#
Read one time step from a binary grid file and a budget file into a FlowStep.
Parameters:
Return type:

FlowStep

gwsource.mf6io.read_step(ws, model=None, kstpkper=(0, 0), area=None, boundnames=False)[source]#
Read one time step of a GWF model in a simulation folder into a FlowStep.

For DISU grids without vertices the cell area is read from the DISU input (AREA).
boundnames=True also fills FlowStep.term_boundname from the package input (FloPy).
Parameters:
Return type:

FlowStep