Skip to content

support xarray conversion for list-based stress packages - #476

Merged
wpbonelli merged 6 commits into
modflowpy:developfrom
wpbonelli:period-lists
Oct 15, 2026
Merged

wpbonelli merged 6 commits into
modflowpy:developfrom
wpbonelli:period-lists

Conversation

@wpbonelli

@wpbonelli wpbonelli commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Package.to_xarray() used to raise for list-based stress packages. Support stress period data conversion to an xarray dataset, in one of two forms, dense or sparse.

Dense

The dense form is the default, where each field becomes a (per, ...) variable, laid out along the rows' primary key, which can be:

  • cell ID (CHD, WEL, TVK) on the grid dims
  • feature number (UZF, SFR ifno) on the referenced table's size dim, e.g. nuzfcells or nreaches. With grid=True it goes on the grid instead, via cellid
  • package index (SPC bndno) on a correspondingly named dim

The typical case for this is a variable living on the grid, e.g. CHD

Dimensions:    (per: 3, layer: 1, row: 3, col: 3)
Coordinates:
    boundname  (per, layer, row, col) object 'a' nan nan ... nan
Data variables:
    head       (per, layer, row, col) float64 1.0 2.0 3e+30 ... 3e+30

Details:

  • Aux columns: one variable per aux name.
  • Array columns: an extra dim named for their size (EVT's pxdp on nseg-1).
  • Keystring packages (SFR, LAK, MAW, LKT, ...): one variable per setting. A setting's own index (SFR's idv) is a dim, and named settings (AUXILIARY conc 1.0) are one variable per name.
  • Missing positions: filled with FILL_DNODATA, FILL_INT64 or None.
  • Periods carry forward as in MF6: a plain period block replaces the previous one, keystring settings each hold until set again, and a period with no block holds the last one.
  • Boundnames are a boundname coordinate.
  • Time-series values are NaN, with the series name in a <var>_ts coordinate (e.g. head_ts).
  • Duplicates: two rows at one position in a period raise. duplicates="sum" | "first" | "last" merges them instead.

Sparse

sparse=True keeps every row, so there are no duplicates to resolve and the round trip is exact. Rows go along an entry dim spanning all periods, with each row's period, key and keystring setting as coordinates, a cellid split by grid dim. The periods attr lists the periods with a block, so an empty block and no block stay distinct. Packages whose rows have no single key (HFB's two cellids, MVR, OC) have only this form. E.g. CHD

Dimensions:       (entry: 3)
Coordinates:
    per           (entry) int64   0 0 2
    cellid_layer  (entry) int64   0 0 0
    cellid_row    (entry) int64   0 0 1
    cellid_col    (entry) int64   0 1 1
    boundname     (entry) object  'a' nan nan
Data variables:
    head          (entry) float64 1.0 2.0 3.0
Attributes:
    periods:  [0 2]

Round trip conversion

Package.from_xarray() inverts either form. From sparse it's exact. From dense, it writes a period block only where a period differs from the previous one, and a keystring row only where a setting changes, so period data become sparse. That gives the same model, not the same rows: row order and settings that repeat a value aren't kept.

wpbonelli and others added 5 commits October 15, 2026 10:35
Package.to_xarray() raised for a list package (CHD, WEL, ...). Put each
column on the grid instead, a (per, ...grid) variable per column and per
aux name, FILL_DNODATA where a period has no boundary, carrying periods
forward as MF6 does. UZF finds its cells through packagedata.
Package.from_xarray() is the inverse.

What has no grid form raises, pointing to to_dataframe(): two rows in a
cell, time-series values, keystring settings (SFR, LAK, MAW, ...), and
rows without exactly one cellid. Boundnames are dropped with a warning.

attrs_to_dataset still keeps the rows as objects in the dataset's attrs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rework to_xarray() for list packages so it needs no per-package code:

- Rows are dense along their key: the grid for a cellid, the referenced
  table's sizing dim for a feature number (SFR's nreaches, UZF's
  nuzfcells), or with grid=True the grid through that table's cellid.
- Keystring packages (SFR, LAK, MAW, LKT, TVK, ...) get a variable per
  setting, carried forward per setting, with a setting's own index (SFR's
  idv) as a dim and named settings (AUXILIARY) as a variable per name.
  LAK's settings land on their own keys' dims (nlakes, noutlets).
- Array columns get a dim named for their size (EVT's nseg-1).
- Boundnames are a coordinate; a time-series value is NaN with its
  series named in a <variable>_ts coordinate.
- Two rows at a position raise, or duplicates="sum"/"first"/"last"
  merges them.
- sparse=True keeps every row; it's the only form of rows with no single
  key (HFB, MVR, OC).

from_xarray() inverts either form. Grid dim names come from one table,
GRID_DIMS, shared with Package.to_dataarray.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the field functions (modflowpy#463, modflowpy#466), a field records its DFN type as
metadata["type"] ("cellid", "array"), not as cellid=/array= flags, and a
list's sizing dim is its shape, not dim=.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A column an option turns on (the DFN's when, e.g. EVT's petm0 with
SURF_RATE_SPECIFIED) is no longer marked optional, so reading rows back
from the xarray form treated it as required. Use _may_be_absent, as
item.py does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wpbonelli wpbonelli added this to the MVP milestone Oct 15, 2026
Say that an entry is one row (an Item), not named row because that's the
grid's dim, and that a column named like a Dataset method (CHD's head)
needs ds["head"].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wpbonelli
wpbonelli marked this pull request as ready for review October 15, 2026 21:09
@wpbonelli
wpbonelli merged commit ca0f2b8 into modflowpy:develop Oct 15, 2026
17 checks passed
@wpbonelli
wpbonelli deleted the period-lists branch October 15, 2026 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant