Skip to content

Commit 2b44c3f

Browse files
mjrenomjreno
authored andcommitted
add welg, rcha baseline
1 parent 23ccde2 commit 2b44c3f

File tree

6 files changed

+267
-34
lines changed

6 files changed

+267
-34
lines changed

docs/examples/twri.py

Lines changed: 114 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88

99
import flopy4
1010

11+
12+
def plot_contourf(head, workspace):
13+
import matplotlib.pyplot as plt
14+
15+
# Plot head results
16+
plt.figure(figsize=(10, 6))
17+
head.isel(layer=0, time=0).plot.contourf()
18+
plt.title("Filled Contour Plot TWRI Head")
19+
plt.xlabel("x")
20+
plt.ylabel("y")
21+
plt.grid(True)
22+
plt.savefig(workspace / "head.png", dpi=300, bbox_inches="tight")
23+
plt.close()
24+
25+
1126
# Timing
1227
time = flopy4.mf6.utils.time.Time.from_timestamps(
1328
["2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04"]
@@ -28,7 +43,7 @@
2843
grid = flopy4.mf6.utils.grid.StructuredGrid(
2944
nlay=nlay, nrow=nrow, ncol=ncol, top=top, botm=bottom, delr=delr, delc=delc, idomain=idomain
3045
)
31-
dims = {"nper": nper, **dict(grid.dataset.sizes)} # TODO: temporary
46+
dims = {"nper": nper, "ncpl": nrow * ncol, **dict(grid.dataset.sizes)} # TODO: temporary
3247

3348
# Grid discretization
3449
# TODO: xorigin, yorigin
@@ -84,9 +99,10 @@
8499
)
85100

86101
# Uniform recharge on the top layer
87-
rch_rate = np.stack(np.full((nlay, nrow, ncol), flopy4.mf6.constants.FILL_DNODATA))
102+
rch_rate = np.full((nlay, nrow, ncol), flopy4.mf6.constants.FILL_DNODATA)
88103
rate = np.repeat(np.expand_dims(rch_rate, axis=0), repeats=nper, axis=0)
89-
rate[0, 0, :] = 3.0e-8
104+
# rate[0, 0, :, :] = 3.0e-8
105+
rate[0, 0, ...] = 3.0e-8
90106
rch = flopy4.mf6.gwf.Rch(recharge=rate.reshape(nper, -1), dims=dims)
91107

92108
# Output control
@@ -103,24 +119,24 @@
103119
# Wells scattered throughout the model
104120
wel_q = -5.0
105121
wel_nodes = [
106-
[2, 4, 10, -5.0],
107-
[1, 3, 5, -5.0],
108-
[1, 5, 11, -5.0],
109-
[0, 8, 7, -5.0],
110-
[0, 8, 9, -5.0],
111-
[0, 8, 11, -5.0],
112-
[0, 8, 13, -5.0],
113-
[0, 10, 7, -5.0],
114-
[0, 10, 9, -5.0],
115-
[0, 10, 11, -5.0],
116-
[0, 10, 13, -5.0],
117-
[0, 12, 7, -5.0],
118-
[0, 12, 9, -5.0],
119-
[0, 12, 11, -5.0],
120-
[0, 12, 13, -5.0],
122+
[2, 4, 10],
123+
[1, 3, 5],
124+
[1, 5, 11],
125+
[0, 8, 7],
126+
[0, 8, 9],
127+
[0, 8, 11],
128+
[0, 8, 13],
129+
[0, 10, 7],
130+
[0, 10, 9],
131+
[0, 10, 11],
132+
[0, 10, 13],
133+
[0, 12, 7],
134+
[0, 12, 9],
135+
[0, 12, 11],
136+
[0, 12, 13],
121137
]
122138
wel = flopy4.mf6.gwf.Wel(
123-
q={"*": {(layer, row, col): wel_q for layer, row, col, wel_q in wel_nodes}},
139+
q={"*": {(layer, row, col): wel_q for layer, row, col in wel_nodes}},
124140
dims=dims,
125141
)
126142

@@ -181,4 +197,82 @@
181197
)
182198

183199
# Plot head results
184-
head.isel(layer=0, time=0).plot.contourf()
200+
plot_contourf(head, workspace)
201+
202+
# update simulation with array based inputs
203+
LAYER_NODATA = np.full((nrow, ncol), flopy4.mf6.constants.FILL_DNODATA, dtype=float)
204+
GRID_NODATA = np.full((nlay, nrow, ncol), flopy4.mf6.constants.FILL_DNODATA, dtype=float)
205+
206+
head = np.repeat(np.expand_dims(GRID_NODATA, axis=0), repeats=nper, axis=0)
207+
for i in range(nrow):
208+
for k in range(nlay - 1):
209+
head[0, k, i, 0] = 0.0
210+
chdg = flopy4.mf6.gwf.Chdg(
211+
print_input=True,
212+
print_flows=True,
213+
save_flows=True,
214+
head=head.reshape(nper, -1),
215+
dims=dims,
216+
)
217+
218+
# Drain in the center left of the model
219+
elev = np.repeat(np.expand_dims(GRID_NODATA, axis=0), repeats=nper, axis=0)
220+
cond = np.repeat(np.expand_dims(GRID_NODATA, axis=0), repeats=nper, axis=0)
221+
for j in range(9):
222+
elev[0, 0, 7, j + 1] = elevation[j]
223+
cond[0, 0, 7, j + 1] = conductance
224+
drng = flopy4.mf6.gwf.Drng(
225+
print_input=True,
226+
print_flows=True,
227+
save_flows=True,
228+
elev=elev.reshape(nper, -1),
229+
cond=cond.reshape(nper, -1),
230+
dims=dims,
231+
)
232+
233+
# well
234+
q = np.repeat(np.expand_dims(GRID_NODATA, axis=0), repeats=nper, axis=0)
235+
for layer, row, col in wel_nodes:
236+
q[0, layer, row, col] = wel_q
237+
welg = flopy4.mf6.gwf.Welg(
238+
q=q.reshape(nper, -1),
239+
dims=dims,
240+
)
241+
242+
# recharge
243+
recharge = np.repeat(np.expand_dims(LAYER_NODATA, axis=0), repeats=nper, axis=0)
244+
recharge[0, ...] = 3.0e-8
245+
# recharge[0, 0, 0] = 3.0e-7
246+
# print(recharge)
247+
# rch = flopy4.mf6.gwf.Rcha(irch=1, recharge=recharge.reshape(nper, -1), dims=dims)
248+
rcha = flopy4.mf6.gwf.Rcha(recharge=recharge.reshape(nper, -1), dims=dims)
249+
250+
# remove list based inputs
251+
del gwf.chd[0]
252+
del gwf.drn[0]
253+
del gwf.wel[0]
254+
del gwf.rch[0]
255+
256+
# add array based inputs
257+
gwf.chdg = [chdg]
258+
gwf.drng = [drng]
259+
gwf.welg = [welg]
260+
gwf.rcha = [rcha]
261+
262+
# create new workspace
263+
workspace = Path(__file__).parent / "twri2"
264+
workspace.mkdir(parents=True, exist_ok=True)
265+
sim.workspace = workspace
266+
sim.__attrs_post_init__()
267+
268+
sim.write()
269+
sim.run()
270+
271+
# Load head results
272+
head = flopy4.mf6.utils.open_hds(
273+
workspace / f"{gwf.name}.hds",
274+
workspace / f"{gwf.name}.dis.grb",
275+
)
276+
277+
# Plot head results
278+
plot_contourf(head, workspace)

flopy4/mf6/binding.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def _get_binding_type(component: Component) -> str:
3434
return f"{component.slntype}6"
3535
else:
3636
if len(cls_name) == 4 and (cls_name[3] == "g" or cls_name[3] == "a"):
37-
return f"{cls_name[0:3]}6"
37+
return f"{cls_name[0:3].upper()}6"
3838
return f"{cls_name.upper()}6"
3939

4040
def _get_binding_terms(component: Component) -> tuple[str, ...] | None:

flopy4/mf6/converter/unstructure.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def _unstructure_block_param(
190190
blocks[block_name][field_name] = tuple(field_value.values.tolist())
191191
case xr.DataArray() if "nper" in field_value.dims:
192192
has_spatial_dims = any(
193-
dim in field_value.dims for dim in ["nlay", "nrow", "ncol", "nodes"]
193+
dim in field_value.dims for dim in ["nlay", "nrow", "ncol", "ncpl", "nodes"]
194194
)
195195
if has_spatial_dims:
196196
field_value = _hack_structured_grid_dims(
@@ -216,19 +216,14 @@ def _unstructure_block_param(
216216

217217
def unstructure_component(value: Component) -> dict[str, Any]:
218218
xatspec = xattree.get_xatspec(type(value))
219-
if "readarraygrid" in xatspec.attrs:
220-
return _unstructure_grid_component(value)
221-
elif "readasarrays" in xatspec.attrs:
222-
return _unstructure_layer_component(value)
219+
if "readarraygrid" in xatspec.attrs or "readasarrays" in xatspec.attrs:
220+
# return _unstructure_array_component(value)
221+
return _unstructure_array_component(value)
223222
else:
224223
return _unstructure_component(value)
225224

226225

227-
def _unstructure_layer_component(value: Component) -> dict[str, Any]:
228-
return {}
229-
230-
231-
def _unstructure_grid_component(value: Component) -> dict[str, Any]:
226+
def _unstructure_array_component(value: Component) -> dict[str, Any]:
232227
blockspec = dict(sorted(value.dfn.blocks.items(), key=block_sort_key)) # type: ignore
233228
blocks: dict[str, dict[str, Any]] = {}
234229
xatspec = xattree.get_xatspec(type(value))
@@ -243,7 +238,6 @@ def _unstructure_grid_component(value: Component) -> dict[str, Any]:
243238
for block_name, block in blockspec.items():
244239
period_data = {} # type: ignore
245240
period_blocks = {} # type: ignore
246-
period_block_name = None
247241

248242
if block_name not in blocks:
249243
blocks[block_name] = {}
@@ -279,6 +273,7 @@ def _unstructure_component(value: Component) -> dict[str, Any]:
279273
data = xattree.asdict(value)
280274

281275
# create child component binding blocks
276+
# TODO: this can add certain block keys out of order
282277
blocks.update(_make_binding_blocks(value))
283278

284279
# process blocks in order, unstructuring fields as needed,
@@ -287,7 +282,6 @@ def _unstructure_component(value: Component) -> dict[str, Any]:
287282
for block_name, block in blockspec.items():
288283
period_data = {} # type: ignore
289284
period_blocks = {} # type: ignore
290-
period_block_name = None
291285

292286
if block_name not in blocks:
293287
blocks[block_name] = {}
@@ -335,6 +329,10 @@ def _unstructure_component(value: Component) -> dict[str, Any]:
335329
if perioddata := blocks.get("perioddata", None):
336330
blocks["perioddata"] = {"perioddata": xr.Dataset(perioddata)}
337331

332+
# TODO: this fixes out of order blocks (e.g. model namefile) from
333+
# blocks.update() child binding call above
334+
# blocks = dict(sorted(blocks.items(), key=block_sort_key))
335+
338336
# total temporary hack! manually set solutiongroup 1.
339337
# TODO still need to support multiple..
340338
if "solutiongroup" in blocks:

flopy4/mf6/gwf/__init__.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,31 @@
1616
from flopy4.mf6.gwf.npf import Npf
1717
from flopy4.mf6.gwf.oc import Oc
1818
from flopy4.mf6.gwf.rch import Rch
19+
from flopy4.mf6.gwf.rcha import Rcha
1920
from flopy4.mf6.gwf.sto import Sto
2021
from flopy4.mf6.gwf.wel import Wel
22+
from flopy4.mf6.gwf.welg import Welg
2123
from flopy4.mf6.model import Model
2224
from flopy4.mf6.spec import field, path
2325
from flopy4.mf6.utils import open_cbc, open_hds
2426
from flopy4.utils import to_path
2527

26-
__all__ = ["Gwf", "Chd", "Chdg", "Dis", "Drn", "Drng", "Ic", "Npf", "Oc", "Rch", "Sto", "Wel"]
28+
__all__ = [
29+
"Gwf",
30+
"Chd",
31+
"Chdg",
32+
"Dis",
33+
"Drn",
34+
"Drng",
35+
"Ic",
36+
"Npf",
37+
"Oc",
38+
"Rch",
39+
"Rcha",
40+
"Sto",
41+
"Wel",
42+
"Welg",
43+
]
2744

2845

2946
def convert_grid(value):
@@ -82,10 +99,13 @@ def budget(self):
8299
sto: Sto | None = field(block="packages", default=None)
83100
chd: list[Chd] = field(block="packages")
84101
chdg: list[Chdg] = field(block="packages")
102+
# chd: List[Union[Chd, Chdg]] = field(block="packages", factory=List)
85103
drn: list[Drn] = field(block="packages")
86104
drng: list[Drng] = field(block="packages")
87105
rch: list[Rch] = field(block="packages")
106+
rcha: list[Rcha] = field(block="packages")
88107
wel: list[Wel] = field(block="packages")
108+
welg: list[Welg] = field(block="packages")
89109
output: Output = attrs.field(
90110
default=attrs.Factory(lambda self: Gwf.Output(self), takes_self=True)
91111
)

flopy4/mf6/gwf/rcha.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from pathlib import Path
2+
from typing import ClassVar, Optional
3+
4+
import numpy as np
5+
6+
# from attrs import Converter
7+
from numpy.typing import NDArray
8+
from xattree import xattree
9+
10+
# from flopy4.mf6.converter import structure_array
11+
from flopy4.mf6.package import Package
12+
from flopy4.mf6.spec import array, field, path
13+
14+
# from flopy4.mf6.utils.grid import update_maxbound
15+
from flopy4.utils import to_path
16+
17+
18+
@xattree
19+
class Rcha(Package):
20+
multi_package: ClassVar[bool] = True
21+
fixed_cell: bool = field(block="options", default=False)
22+
auxiliary: Optional[list[str]] = array(block="options", default=None)
23+
auxmultname: Optional[str] = field(block="options", default=None)
24+
print_input: bool = field(block="options", default=False)
25+
print_flows: bool = field(block="options", default=False)
26+
readasarrays: bool = field(block="options", default=True)
27+
save_flows: bool = field(block="options", default=False)
28+
tas_filerecord: Optional[Path] = path(
29+
block="options", default=None, converter=to_path, inout="filein"
30+
)
31+
obs_filerecord: Optional[Path] = path(
32+
block="options", default=None, converter=to_path, inout="fileout"
33+
)
34+
# maxbound: Optional[int] = field(block="dimensions", default=None, init=False)
35+
irch: Optional[NDArray[np.int64]] = array(
36+
block="period",
37+
dims=(
38+
"nper",
39+
"ncpl",
40+
),
41+
default=None,
42+
# converter=Converter(structure_array, takes_self=True, takes_field=True),
43+
# on_setattr=update_maxbound,
44+
)
45+
recharge: Optional[NDArray[np.float64]] = array(
46+
block="period",
47+
dims=(
48+
"nper",
49+
"ncpl",
50+
),
51+
default=None,
52+
# converter=Converter(structure_array, takes_self=True, takes_field=True),
53+
# on_setattr=update_maxbound,
54+
)
55+
aux: Optional[NDArray[np.float64]] = array(
56+
block="period",
57+
dims=(
58+
"nper",
59+
"ncpl",
60+
),
61+
default=None,
62+
# converter=Converter(structure_array, takes_self=True, takes_field=True),
63+
# on_setattr=update_maxbound,
64+
)

0 commit comments

Comments
 (0)