Skip to content

Commit 3d5eec4

Browse files
committed
add from_tokens interface for record types
1 parent d2c6ec3 commit 3d5eec4

20 files changed

Lines changed: 415 additions & 39 deletions

File tree

flopy4/mf6/codec/writer/filters.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,16 @@ def dataset2list(value: xr.Dataset):
243243
yield (*name.split("_"), val) # type: ignore
244244

245245
else:
246-
vals = []
247-
for name in value.data_vars.keys():
248-
val = value[name]
249-
val = val.item() if val.shape == () else val
250-
vals.append(val)
251-
yield tuple(vals)
246+
row: list[Any] = []
247+
for name, da in value.data_vars.items():
248+
val = da.item() if da.shape == () else da
249+
if kw := da.attrs.get("row_keyword", False):
250+
if val:
251+
row.append(kw if isinstance(kw, str) else str(name).upper())
252+
else:
253+
row.extend(da.attrs.get("prefix", ()))
254+
row.append(val)
255+
yield tuple(row)
252256
return
253257

254258
combined_mask: Any = None
@@ -266,15 +270,20 @@ def dataset2list(value: xr.Dataset):
266270
for name in value.data_vars.keys():
267271
val = value[name][tuple(idx[i] for idx in indices)]
268272
val = val.item() if val.shape == () else val
269-
yield (*name.split("_"), val) # type: ignore
273+
yield (*str(name).split("_"), val) # type: ignore
270274
else:
271-
vals = []
272-
for name in value.data_vars.keys():
273-
val = value[name][tuple(idx[i] for idx in indices)]
275+
row2: list[Any] = []
276+
for name, da in value.data_vars.items():
277+
val = da[tuple(idx[i] for idx in indices)]
274278
val = val.item() if val.shape == () else val
275-
vals.append(val)
279+
if kw := da.attrs.get("row_keyword", False):
280+
if val:
281+
row2.append(kw if isinstance(kw, str) else str(name).upper())
282+
else:
283+
row2.extend(da.attrs.get("prefix", ()))
284+
row2.append(val)
276285
if has_spatial_dims:
277286
cellid = tuple(idx[i] + 1 for idx in indices)
278-
yield cellid + tuple(vals)
287+
yield tuple(cellid) + tuple(row2)
279288
else:
280-
yield tuple(vals)
289+
yield tuple(row2)

flopy4/mf6/converter/egress/unstructure.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,14 @@ def _unstructure_block_param(
248248
for n, v in dat.items():
249249
period_data[n] = v
250250
else:
251+
arr_spec = xatspec.arrays.get(field_name)
252+
field_meta = (arr_spec.metadata or {}) if arr_spec is not None else {}
253+
if "prefix" in field_meta or "row_keyword" in field_meta:
254+
field_value = field_value.copy()
255+
if "prefix" in field_meta:
256+
field_value.attrs["prefix"] = field_meta["prefix"]
257+
if "row_keyword" in field_meta:
258+
field_value.attrs["row_keyword"] = field_meta["row_keyword"]
251259
blocks[block_name][field_name] = field_value
252260
case _:
253261
blocks[block_name][field_name] = field_value
@@ -306,15 +314,17 @@ def _unstructure_array_component(value: Component) -> dict[str, Any]:
306314

307315
# Block names that MF6 rejects if present but empty.
308316
# These blocks should only be written when they contain data.
309-
_SKIP_IF_EMPTY = frozenset({"dimensions", "tracktimes"})
317+
_SKIP_IF_EMPTY = frozenset({"dimensions", "fileinput", "tracktimes"})
310318

311319
# Block names whose fields are list columns (one array per column, same dim)
312320
# rather than independent grid arrays. Only these blocks are auto-combined
313321
# into an xr.Dataset for row-per-record output. griddata-style blocks must
314322
# NOT be in this set — their fields are written individually with
315323
# INTERNAL/CONSTANT/NETCDF format.
316324
# "sources" is the SSM sources block (pname/srctype/auxname per-row tabular input).
317-
_LIST_BLOCK_NAMES = frozenset({"packagedata", "packages", "perioddata", "sources", "table"})
325+
_LIST_BLOCK_NAMES = frozenset(
326+
{"packagedata", "packages", "perioddata", "sources", "fileinput", "table"}
327+
)
318328

319329

320330
def _unstructure_component(value: Component) -> dict[str, Any]:

flopy4/mf6/gwe/oc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111

1212
from flopy4.mf6.converter import structure_array
1313
from flopy4.mf6.package import Package
14+
from flopy4.mf6.record import Record
1415
from flopy4.mf6.spec import array, field, path
1516
from flopy4.utils import to_path
1617

1718

1819
@xattree(kw_only=True)
1920
class Oc(Package):
2021
@attrs.define
21-
class Temperatureprint:
22+
class Temperatureprint(Record):
2223
_keyword: ClassVar[str] = "temperature"
2324
_extra_tokens: ClassVar[tuple[str, ...]] = ("PRINT_FORMAT",)
2425

flopy4/mf6/gwe/ssm.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class Ssm(Package):
2020
save_flows: bool = field(
2121
block="options", default=False, longname="save calculated flows to budget file"
2222
)
23-
# TODO: fileinput — could not determine list column dimension
2423
nsources: Optional[int] = dim(block="__dim__", coord=False, default=None)
2524
pname: Optional[NDArray[np.object_]] = array(
2625
block="sources",
@@ -43,3 +42,26 @@ class Ssm(Package):
4342
converter=Converter(structure_array, takes_self=True, takes_field=True),
4443
longname="auxiliary variable name",
4544
)
45+
nfileinput: Optional[int] = dim(block="__dim__", coord=False, default=None)
46+
fi_pname: Optional[NDArray[np.object_]] = array(
47+
block="fileinput",
48+
dims=("nfileinput",),
49+
default=None,
50+
converter=Converter(structure_array, takes_self=True, takes_field=True),
51+
longname="package name",
52+
)
53+
fi_spc6_filename: Optional[NDArray[np.object_]] = array(
54+
block="fileinput",
55+
dims=("nfileinput",),
56+
default=None,
57+
converter=Converter(structure_array, takes_self=True, takes_field=True),
58+
longname="spc6 file name",
59+
prefix=("SPC6", "FILEIN"),
60+
)
61+
fi_mixed: Optional[NDArray[np.bool_]] = array(
62+
block="fileinput",
63+
dims=("nfileinput",),
64+
default=None,
65+
longname="mixed keyword",
66+
row_keyword="MIXED",
67+
)

flopy4/mf6/gwf/npf.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,27 @@
1111

1212
from flopy4.mf6.converter import structure_array
1313
from flopy4.mf6.package import Package
14+
from flopy4.mf6.record import Record
1415
from flopy4.mf6.spec import array, field, path
1516
from flopy4.utils import to_path
1617

1718

1819
@xattree(kw_only=True)
1920
class Npf(Package):
2021
@attrs.define
21-
class Cvoptions:
22+
class Cvoptions(Record):
2223
_keyword: ClassVar[str] = "variablecv"
2324
dewatered: Optional[bool] = attrs.field(default=None)
2425

2526
@attrs.define
26-
class Rewet:
27+
class Rewet(Record):
2728
_keyword: ClassVar[str] = "rewet"
2829
wetfct: float = attrs.field(metadata={"tagged": True})
2930
iwetit: int = attrs.field(metadata={"tagged": True})
3031
ihdwet: int = attrs.field(metadata={"tagged": True})
3132

3233
@attrs.define
33-
class Xt3doptions:
34+
class Xt3doptions(Record):
3435
_keyword: ClassVar[str] = "xt3d"
3536
rhs: Optional[bool] = attrs.field(default=None)
3637

flopy4/mf6/gwf/oc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111

1212
from flopy4.mf6.converter import structure_array
1313
from flopy4.mf6.package import Package
14+
from flopy4.mf6.record import Record
1415
from flopy4.mf6.spec import array, field, path
1516
from flopy4.utils import to_path
1617

1718

1819
@xattree(kw_only=True)
1920
class Oc(Package):
2021
@attrs.define
21-
class Headprint:
22+
class Headprint(Record):
2223
_keyword: ClassVar[str] = "head"
2324
_extra_tokens: ClassVar[tuple[str, ...]] = ("PRINT_FORMAT",)
2425
format_: str = attrs.field()

flopy4/mf6/gwt/ist.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from flopy4.mf6.converter import structure_array
1313
from flopy4.mf6.package import Package
14+
from flopy4.mf6.record import Record
1415
from flopy4.mf6.spec import array, field, path
1516
from flopy4.utils import to_path
1617

@@ -20,7 +21,7 @@ class Ist(Package):
2021
multi_package: ClassVar[bool] = True
2122

2223
@attrs.define
23-
class Cimprint:
24+
class Cimprint(Record):
2425
_keyword: ClassVar[str] = "cim"
2526
_extra_tokens: ClassVar[tuple[str, ...]] = ("PRINT_FORMAT",)
2627

flopy4/mf6/gwt/oc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111

1212
from flopy4.mf6.converter import structure_array
1313
from flopy4.mf6.package import Package
14+
from flopy4.mf6.record import Record
1415
from flopy4.mf6.spec import array, field, path
1516
from flopy4.utils import to_path
1617

1718

1819
@xattree(kw_only=True)
1920
class Oc(Package):
2021
@attrs.define
21-
class Concentrationprint:
22+
class Concentrationprint(Record):
2223
_keyword: ClassVar[str] = "concentration"
2324
_extra_tokens: ClassVar[tuple[str, ...]] = ("PRINT_FORMAT",)
2425

flopy4/mf6/gwt/ssm.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ class Ssm(Package):
2020
save_flows: bool = field(
2121
block="options", default=False, longname="save calculated flows to budget file"
2222
)
23-
# TODO: fileinput — could not determine list column dimension
2423
nsources: Optional[int] = dim(block="__dim__", coord=False, default=None)
2524
pname: Optional[NDArray[np.object_]] = array(
2625
block="sources",
@@ -43,3 +42,26 @@ class Ssm(Package):
4342
converter=Converter(structure_array, takes_self=True, takes_field=True),
4443
longname="auxiliary variable name",
4544
)
45+
nfileinput: Optional[int] = dim(block="__dim__", coord=False, default=None)
46+
fi_pname: Optional[NDArray[np.object_]] = array(
47+
block="fileinput",
48+
dims=("nfileinput",),
49+
default=None,
50+
converter=Converter(structure_array, takes_self=True, takes_field=True),
51+
longname="package name",
52+
)
53+
fi_spc6_filename: Optional[NDArray[np.object_]] = array(
54+
block="fileinput",
55+
dims=("nfileinput",),
56+
default=None,
57+
converter=Converter(structure_array, takes_self=True, takes_field=True),
58+
longname="spc6 file name",
59+
prefix=("SPC6", "FILEIN"),
60+
)
61+
fi_mixed: Optional[NDArray[np.bool_]] = array(
62+
block="fileinput",
63+
dims=("nfileinput",),
64+
default=None,
65+
longname="mixed keyword",
66+
row_keyword="MIXED",
67+
)

flopy4/mf6/ims.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import attrs
77
from xattree import xattree
88

9+
from flopy4.mf6.record import Record
910
from flopy4.mf6.solution import Solution
1011
from flopy4.mf6.spec import field, path
1112
from flopy4.utils import to_path
@@ -16,12 +17,12 @@ class Ims(Solution):
1617
slntype: ClassVar[str] = "ims"
1718

1819
@attrs.define
19-
class NoPtc:
20+
class NoPtc(Record):
2021
_keyword: ClassVar[str] = "no_ptc"
2122
no_ptc_option: Optional[str] = attrs.field(default=None)
2223

2324
@attrs.define
24-
class Rclose:
25+
class Rclose(Record):
2526
_keyword: ClassVar[str] = ""
2627
inner_rclose: float = attrs.field(metadata={"tagged": True})
2728
rclose_option: Optional[str] = attrs.field(default=None)

0 commit comments

Comments
 (0)