Skip to content

Commit 5dfdda3

Browse files
authored
add block spec functions (#134)
analogous to fields and fields_dict add blocks and blocks_dict functions which return the block specification
1 parent 5e18ca4 commit 5dfdda3

File tree

14 files changed

+278
-467
lines changed

14 files changed

+278
-467
lines changed

flopy4/mf6/gwf/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
from flopy.discretization.grid import Grid
88
from xattree import xattree
99

10-
from flopy4.mf6.decorators import field
1110
from flopy4.mf6.gwf.chd import Chd
1211
from flopy4.mf6.gwf.dis import Dis
1312
from flopy4.mf6.gwf.ic import Ic
1413
from flopy4.mf6.gwf.npf import Npf
1514
from flopy4.mf6.gwf.oc import Oc
1615
from flopy4.mf6.model import Model
16+
from flopy4.mf6.spec import field
1717
from flopy4.mf6.utils import open_cbc, open_hds
1818

1919
__all__ = ["Gwf", "Chd", "Dis", "Ic", "Npf", "Oc"]

flopy4/mf6/gwf/chd.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from xattree import xattree
88

99
from flopy4.mf6.converters import convert_array
10-
from flopy4.mf6.decorators import array, field
1110
from flopy4.mf6.package import Package
11+
from flopy4.mf6.spec import array, field
1212

1313

1414
@xattree

flopy4/mf6/gwf/dis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from xattree import xattree
66

77
from flopy4.mf6.converters import convert_array
8-
from flopy4.mf6.decorators import array, dim, field
98
from flopy4.mf6.package import Package
9+
from flopy4.mf6.spec import array, dim, field
1010

1111

1212
@xattree

flopy4/mf6/gwf/ic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from xattree import xattree
55

66
from flopy4.mf6.converters import convert_array
7-
from flopy4.mf6.decorators import array, field
87
from flopy4.mf6.package import Package
8+
from flopy4.mf6.spec import array, field
99

1010

1111
@xattree

flopy4/mf6/gwf/npf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from xattree import xattree
88

99
from flopy4.mf6.converters import convert_array
10-
from flopy4.mf6.decorators import array, field
1110
from flopy4.mf6.package import Package
11+
from flopy4.mf6.spec import array, field
1212

1313

1414
@xattree

flopy4/mf6/gwf/oc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from xattree import xattree
88

99
from flopy4.mf6.converters import convert_array
10-
from flopy4.mf6.decorators import array, field
1110
from flopy4.mf6.package import Package
11+
from flopy4.mf6.spec import array, field
1212
from flopy4.utils import to_path
1313

1414

flopy4/mf6/ims.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from xattree import xattree
55

6-
from flopy4.mf6.decorators import field
76
from flopy4.mf6.solution import Solution
7+
from flopy4.mf6.spec import field
88

99

1010
@xattree

flopy4/mf6/decorators.py renamed to flopy4/mf6/spec.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
from attrs import NOTHING
1+
"""
2+
Wrap `xattree` and `attrs` specification utilities for MF6.
3+
These include field decorators and introspection functions.
4+
"""
5+
6+
from attrs import NOTHING, Attribute, fields_dict
27
from xattree import array as xattree_array
38
from xattree import coord as xattree_coord
49
from xattree import dim as xattree_dim
@@ -15,7 +20,7 @@ def field(
1520
metadata=None,
1621
block: str | None = None,
1722
):
18-
"""Create a field."""
23+
"""Define a field."""
1924
if block:
2025
metadata = metadata or {}
2126
metadata["block"] = block
@@ -40,7 +45,7 @@ def dim(
4045
metadata=None,
4146
block: str | None = None,
4247
):
43-
"""Create a dimension field."""
48+
"""Define a dimension field."""
4449
if block:
4550
metadata = metadata or {}
4651
metadata["block"] = block
@@ -63,7 +68,7 @@ def coord(
6368
metadata=None,
6469
block: str | None = None,
6570
):
66-
"""Create a coordinate field."""
71+
"""Define a coordinate field."""
6772
if block:
6873
metadata = metadata or {}
6974
metadata["block"] = block
@@ -87,7 +92,7 @@ def array(
8792
metadata=None,
8893
block: str | None = None,
8994
):
90-
"""Create an array field."""
95+
"""Define an array field."""
9196
if block:
9297
metadata = metadata or {}
9398
metadata["block"] = block
@@ -101,3 +106,44 @@ def array(
101106
eq=eq,
102107
metadata=metadata,
103108
)
109+
110+
111+
Block = dict[str, Attribute]
112+
113+
114+
def _block_sort_key(item) -> int:
115+
k, _ = item
116+
if k == "options":
117+
return 0
118+
elif k == "dimensions":
119+
return 1
120+
elif k == "griddata":
121+
return 2
122+
elif k == "packagedata":
123+
return 3
124+
elif k == "perioddata":
125+
return 4
126+
else:
127+
return 5
128+
129+
130+
def blocks(cls) -> list[list[Attribute]]:
131+
"""Return an ordered list of blocks for a component class."""
132+
return [list(v.values()) for v in blocks_dict(cls).values()]
133+
134+
135+
def blocks_dict(cls) -> dict[str, Block]:
136+
"""
137+
Return an ordered dictionary of blocks for a component class,
138+
whose keys are block names. Each block is a map from variable
139+
(field) name to `attrs.Attribute`.
140+
"""
141+
fields = fields_dict(cls)
142+
fields = {k: v for k, v in fields.items() if "block" in v.metadata}
143+
blocks: dict[str, Block] = {}
144+
for k, v in fields.items():
145+
block = v.metadata["block"]
146+
if block not in blocks:
147+
blocks[block] = {}
148+
blocks[block][k] = v
149+
return dict(sorted(blocks.items(), key=_block_sort_key))

flopy4/mf6/tdis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from xattree import ROOT, xattree
99

1010
from flopy4.mf6.converters import convert_array
11-
from flopy4.mf6.decorators import array, dim, field
1211
from flopy4.mf6.package import Package
12+
from flopy4.mf6.spec import array, dim, field
1313

1414

1515
@xattree

flopy4/mf6/utils/cbc_reader.py

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,7 @@ def open_cbc(
135135
136136
"""
137137
grid = StructuredGridWrapper.from_binary_grid_file(grb_path)
138-
cbc = _open_cbc_dis(
139-
cbc_path, grid, flowja, simulation_start_time, time_unit
140-
)
138+
cbc = _open_cbc_dis(cbc_path, grid, flowja, simulation_start_time, time_unit)
141139
return xr.merge([cbc])
142140

143141

@@ -267,17 +265,13 @@ def read_cbc_headers(
267265
if header["imeth"] == 1:
268266
# Multiply by -1 because ndim3 is stored as a negative for some
269267
# reason. (ndim3 is the integer size of the third dimension)
270-
datasize = (
271-
header["ndim1"] * header["ndim2"] * header["ndim3"] * -1
272-
) * 8
268+
datasize = (header["ndim1"] * header["ndim2"] * header["ndim3"] * -1) * 8
273269
header["pos"] = f.tell()
274270
key = header["text"]
275271
headers[key].append(Imeth1Header(**header))
276272
elif header["imeth"] == 6:
277273
imeth6_header = read_imeth6_header(f)
278-
datasize = imeth6_header["nlist"] * (
279-
8 + imeth6_header["ndat"] * 8
280-
)
274+
datasize = imeth6_header["nlist"] * (8 + imeth6_header["ndat"] * 8)
281275
header["pos"] = f.tell()
282276
# key-format:
283277
# "package type"-"optional_package_variable"_"package name"
@@ -287,11 +281,7 @@ def read_cbc_headers(
287281
# npf-key can be present multiple times in cases of saved
288282
# saturation + specific discharge
289283
if header["text"].startswith("data-"):
290-
key = (
291-
imeth6_header["txt2id2"]
292-
+ "_"
293-
+ header["text"].replace("data-", "")
294-
)
284+
key = imeth6_header["txt2id2"] + "_" + header["text"].replace("data-", "")
295285
headers[key].append(Imeth6Header(**header, **imeth6_header))
296286
else:
297287
raise ValueError(
@@ -332,9 +322,7 @@ def read_imeth6_header(f: BinaryIO) -> dict[str, Any]:
332322
content["txt2id2"] = f.read(16).decode("utf-8").strip().lower()
333323
ndat = struct.unpack("i", f.read(4))[0]
334324
content["ndat"] = ndat
335-
content["auxtxt"] = [
336-
f.read(16).decode("utf-8").strip().lower() for _ in range(ndat - 1)
337-
]
325+
content["auxtxt"] = [f.read(16).decode("utf-8").strip().lower() for _ in range(ndat - 1)]
338326
content["nlist"] = struct.unpack("i", f.read(4))[0]
339327
return content
340328

@@ -345,14 +333,9 @@ def assign_datetime_coords(
345333
time_unit: str | None = "d",
346334
) -> xr.DataArray:
347335
if "time" not in da.coords:
348-
raise ValueError(
349-
"cannot convert time column, "
350-
"because a time column could not be found"
351-
)
336+
raise ValueError("cannot convert time column, because a time column could not be found")
352337

353-
time = pd.Timestamp(simulation_start_time) + pd.to_timedelta(
354-
da["time"], unit=time_unit
355-
)
338+
time = pd.Timestamp(simulation_start_time) + pd.to_timedelta(da["time"], unit=time_unit)
356339
return da.assign_coords(time=time)
357340

358341

@@ -411,9 +394,7 @@ def open_imeth6_budgets(
411394
coords = get_coords(grid)
412395
coords["time"] = time
413396
name = header_list[0].text
414-
return xr.DataArray(
415-
daskarr, coords, ("time", "layer", "y", "x"), name=name
416-
)
397+
return xr.DataArray(daskarr, coords, ("time", "layer", "y", "x"), name=name)
417398

418399

419400
def read_imeth6_budgets_dense(
@@ -470,9 +451,7 @@ def read_imeth6_budgets_dense(
470451
return out.reshape(shape)
471452

472453

473-
def read_imeth6_budgets(
474-
cbc_path: Path, count: int, dtype: np.dtype, pos: int
475-
) -> Any:
454+
def read_imeth6_budgets(cbc_path: Path, count: int, dtype: np.dtype, pos: int) -> Any:
476455
"""
477456
Read the data for an imeth==6 budget section for a single timestep.
478457
@@ -549,9 +528,7 @@ def open_imeth1_budgets(
549528
)
550529

551530

552-
def cbc_open_imeth1_budgets(
553-
cbc_path: Path, header_list: list[Imeth1Header]
554-
) -> xr.DataArray:
531+
def cbc_open_imeth1_budgets(cbc_path: Path, header_list: list[Imeth1Header]) -> xr.DataArray:
555532
"""
556533
Open the data for an imeth==1 budget section. Data is read lazily per
557534
timestep. The cell data is not spatially labelled.
@@ -625,19 +602,15 @@ def dis_open_face_budgets(
625602
front: xr.DataArray of floats with dims ("time", "layer", "y", "x")
626603
lower: xr.DataArray of floats with dims ("time", "layer", "y", "x")
627604
"""
628-
right_index, front_index, lower_index = dis_to_right_front_lower_indices(
629-
grid
630-
)
605+
right_index, front_index, lower_index = dis_to_right_front_lower_indices(grid)
631606
budgets = cbc_open_imeth1_budgets(cbc_path, header_list)
632607
right = dis_extract_face_budgets(budgets, right_index)
633608
front = dis_extract_face_budgets(budgets, front_index)
634609
lower = dis_extract_face_budgets(budgets, lower_index)
635610
return right, front, lower
636611

637612

638-
def dis_extract_face_budgets(
639-
budgets: xr.DataArray, index: xr.DataArray
640-
) -> xr.DataArray:
613+
def dis_extract_face_budgets(budgets: xr.DataArray, index: xr.DataArray) -> xr.DataArray:
641614
"""
642615
Grab right, front, or lower face flows from the flow-ja-face array.
643616

0 commit comments

Comments
 (0)