Skip to content

Commit d127a0d

Browse files
authored
fix(io): extending and testing ascii writes (#186)
1 parent 6e6e7cc commit d127a0d

File tree

7 files changed

+178
-24
lines changed

7 files changed

+178
-24
lines changed

flopy4/mf6/codec/writer/filters.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,14 @@ def data2keystring(value: dict | xr.Dataset):
260260
return
261261

262262
for field_name in value.data_vars.keys():
263+
name = (
264+
field_name.replace("_", " ").upper()
265+
if np.issubdtype(value.data_vars[field_name].dtype, np.str_)
266+
else field_name.upper()
267+
)
263268
field_val = value[field_name]
264269
if hasattr(field_val, "item"):
265270
val = field_val.item()
266271
else:
267272
val = field_val
268-
yield (field_name.upper(), val)
273+
yield (name, val)

flopy4/mf6/codec/writer/templates/macros.jinja

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{% set inset = " " %}
2+
13
{% macro field(name, value) %}
24
{% set format = value|field_format %}
35
{% if format in ['keyword', 'integer', 'double precision', 'string'] %}
@@ -15,7 +17,7 @@
1517

1618
{% macro scalar(name, value) %}
1719
{% set format = value|field_format %}
18-
{% if value is not none %}{{ name.upper() }}{% if format != 'keyword' %} {{ value }}{% endif %}{% endif %}
20+
{% if value is not none %}{{ inset ~ name.upper() }}{% if format != 'keyword' %} {{ value }}{% endif %}{% endif %}
1921
{% endmacro %}
2022

2123
{% macro keystring(name, value) %}
@@ -30,7 +32,7 @@
3032
{{ field_name.upper() }} {{ field(field_value) }}
3133
{%- endfor %}
3234
{% else %}
33-
{{ value|join(" ") }}
35+
{{ inset ~ value|join(" ") }}
3436
{%- endif %}
3537
{% endmacro %}
3638

@@ -55,6 +57,6 @@ OPEN/CLOSE {{ value }}
5557

5658
{% macro list(name, value) %}
5759
{% for row in (value|data2list) %}
58-
{{ row|join(" ") }}
60+
{{ inset ~ row|join(" ") }}
5961
{% endfor %}
6062
{% endmacro %}

flopy4/mf6/converter.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from collections.abc import MutableMapping
1+
from collections.abc import Iterable, MutableMapping
22
from datetime import datetime
33
from pathlib import Path
44
from typing import Any
@@ -47,6 +47,8 @@ def _get_binding_type(component: Component) -> str:
4747
cls_name = component.__class__.__name__
4848
if isinstance(component, Exchange):
4949
return f"{'-'.join([cls_name[:2], cls_name[3:]]).upper()}6"
50+
elif isinstance(component, Solution):
51+
return f"{component.slntype}6"
5052
else:
5153
return f"{cls_name.upper()}6"
5254

@@ -107,7 +109,7 @@ def unstructure_component(value: Component) -> dict[str, Any]:
107109
for comp in field_value.values()
108110
if comp is not None
109111
]
110-
elif isinstance(field_value, (list, tuple)):
112+
elif isinstance(field_value, Iterable):
111113
components = [
112114
_Binding.from_component(comp).to_tuple()
113115
for comp in field_value
@@ -170,16 +172,16 @@ def unstructure_component(value: Component) -> dict[str, Any]:
170172
(
171173
field_value.sizes["nper"],
172174
parent.dims["nlay"],
173-
parent.dims["ncol"],
174175
parent.dims["nrow"],
176+
parent.dims["ncol"],
175177
)
176178
),
177-
dims=("nper", "nlay", "ncol", "nrow"),
179+
dims=("nper", "nlay", "nrow", "ncol"),
178180
coords={
179181
"nper": field_value.coords["nper"],
180182
"nlay": range(parent.dims["nlay"]),
181-
"ncol": range(parent.dims["ncol"]),
182183
"nrow": range(parent.dims["nrow"]),
184+
"ncol": range(parent.dims["ncol"]),
183185
},
184186
name=field_value.name,
185187
)
@@ -189,12 +191,28 @@ def unstructure_component(value: Component) -> dict[str, Any]:
189191
for kper in range(field_value.sizes["nper"])
190192
}
191193
else:
192-
if block_name not in period_data:
193-
period_data[block_name] = {}
194-
period_data[block_name][field_name] = field_value # type: ignore
194+
if (
195+
# TODO: refactor
196+
# field_name == "save_budget"
197+
# or field_name == "save_head"
198+
# or field_name == "print_budget"
199+
# or field_name == "print_head"
200+
np.issubdtype(field_value.dtype, np.str_)
201+
):
202+
period_data[field_name] = {
203+
kper: field_value[kper] for kper in range(field_value.sizes["nper"])
204+
}
205+
else:
206+
if block_name not in period_data:
207+
period_data[block_name] = {}
208+
period_data[block_name][field_name] = field_value # type: ignore
195209
else:
196210
if field_value is not None:
197-
blocks[block_name][field_name] = field_value
211+
if isinstance(field_value, bool):
212+
if field_value:
213+
blocks[block_name][field_name] = field_value
214+
else:
215+
blocks[block_name][field_name] = field_value
198216

199217
if block_name in period_data and isinstance(period_data[block_name], dict):
200218
dataset = xr.Dataset(period_data[block_name])

flopy4/mf6/ims.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@
1111
@xattree
1212
class Ims(Solution):
1313
solution_package: ClassVar[Sln] = Sln(abbr="ims", pattern="*")
14+
slntype: ClassVar[str] = "ims"
1415

16+
mxiter: Optional[int] = field(default=1)
1517
print_option: Optional[str] = field(block="options", default=None)
1618
complexity: str = field(block="options", default="simple")
1719
csv_outer_output_file: Optional[Path] = field(default=None, block="options")
1820
csv_inner_output_file: Optional[Path] = field(block="options", default=None)
1921
no_ptc: bool = field(default=False, block="options")
2022
no_ptc_option: Optional[str] = field(default=None, block="options")
2123
ats_outer_maximum_fraction: Optional[float] = field(block="options", default=None)
22-
outer_dvclose: Optional[float] = field(default=None, block="options")
23-
outer_maximum: Optional[int] = field(default=None, block="options")
24-
under_relaxation: Optional[str] = field(default=None, block="options")
24+
outer_dvclose: Optional[float] = field(default=None, block="nonlinear")
25+
outer_maximum: Optional[int] = field(default=None, block="nonlinear")
26+
under_relaxation: Optional[str] = field(default=None, block="nonlinear")
2527
under_relaxation_gamma: Optional[float] = field(block="nonlinear", default=None)
2628
under_relaxation_theta: Optional[float] = field(block="nonlinear", default=None)
2729
under_relaxation_kappa: Optional[float] = field(block="nonlinear", default=None)

flopy4/mf6/solution.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
from abc import ABC
2+
from pathlib import Path
3+
from typing import ClassVar, Optional
24

35
import attrs
4-
from xattree import xattree
6+
from xattree import field, xattree
57

68
from flopy4.mf6.package import Package
79

810

911
@xattree
1012
class Solution(Package, ABC):
13+
slntype: ClassVar[str] = "sln"
14+
15+
slnfname: Optional[Path] = field(default=None) # type: ignore
1116
models: list[str] = attrs.field(default=attrs.Factory(list))
17+
18+
def default_filename(self) -> str:
19+
return str(self.slnfname) if self.slnfname else f"solution.{self.slntype.lower()}"

test/test_component.py

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -274,13 +274,135 @@ def test_ims_dfn():
274274
assert "inner_maximum" in set(dfn["linear"].keys())
275275

276276

277+
def test_gwf_chd01(function_tmpdir):
278+
sim_name = "chd01"
279+
gwf_name = "gwf_chd01"
280+
time = ModelTime(perlen=[5.0], nstp=[1], tsmult=[1.0], time_units="days")
281+
282+
ims = Ims(
283+
slnfname="sln1.ims",
284+
models=[gwf_name],
285+
print_option="summary",
286+
outer_dvclose=1.00000000e-06,
287+
outer_maximum=100,
288+
under_relaxation="none",
289+
inner_maximum=300,
290+
inner_dvclose=1.00000000e-06,
291+
inner_rclose=1.00000000e-06,
292+
linear_acceleration="cg",
293+
relaxation_factor=1.0,
294+
scaling_method="none",
295+
reordering_method="none",
296+
)
297+
298+
sim = Simulation(
299+
tdis=time,
300+
workspace=function_tmpdir,
301+
name=sim_name,
302+
solutions={"ims": ims},
303+
)
304+
305+
dis = Dis(
306+
nlay=1,
307+
nrow=1,
308+
ncol=100,
309+
delr=1.0,
310+
delc=1.0,
311+
top=1.0,
312+
botm=0.0,
313+
idomain=1,
314+
)
315+
316+
gwf = Gwf(parent=sim, save_flows=True, dis=dis, name=gwf_name)
317+
318+
ic = Ic(parent=gwf, strt=1.0)
319+
320+
oc = Oc(
321+
parent=gwf,
322+
budget_file=f"{gwf_name}.cbc",
323+
head_file=f"{gwf_name}.hds",
324+
# COLUMNS 10 WIDTH 15 DIGITS 6 GENERAL
325+
save_head=["last"],
326+
# save_head={0: "last"},
327+
save_budget=["last"],
328+
print_head=["last"],
329+
print_budget=["last"],
330+
)
331+
332+
npf = Npf(
333+
parent=gwf,
334+
save_specific_discharge=True,
335+
k=1.0,
336+
k33=1.0,
337+
icelltype=0,
338+
)
339+
340+
chd = Chd(
341+
parent=gwf,
342+
print_flows=True,
343+
head={0: {(0, 0, 0): 1.0, (0, 0, 99): 0.0}},
344+
name="chd-1",
345+
)
346+
347+
sim.write()
348+
sim.run()
349+
350+
351+
def test_quickstart(function_tmpdir):
352+
sim_name = "quickstart"
353+
gwf_name = "mymodel"
354+
time = ModelTime(perlen=[1.0], nstp=[1], tsmult=[1.0])
355+
ims = Ims(models=[gwf_name])
356+
dis = Dis(
357+
nlay=1,
358+
nrow=10,
359+
ncol=10,
360+
top=1.0,
361+
botm=0.0,
362+
)
363+
sim = Simulation(
364+
tdis=time,
365+
workspace=function_tmpdir,
366+
name=sim_name,
367+
solutions={"ims": ims},
368+
)
369+
gwf = Gwf(parent=sim, dis=dis, name=gwf_name)
370+
ic = Ic(parent=gwf)
371+
oc = Oc(
372+
parent=gwf,
373+
budget_file=f"{gwf_name}.bud",
374+
head_file=f"{gwf_name}.hds",
375+
save_head=["all"],
376+
save_budget=["all"],
377+
)
378+
npf = Npf(parent=gwf, icelltype=0, k=1.0)
379+
chd = Chd(parent=gwf, head={0: {(0, 0, 0): 1.0, (0, 9, 9): 0.0}})
380+
381+
sim.write()
382+
sim.run()
383+
384+
277385
def test_write_ascii(function_tmpdir):
278386
sim_name = "sim"
279-
time = ModelTime(perlen=[1.0], nstp=[1], tsmult=[1.0])
280-
grid = StructuredGrid(nlay=1, nrow=10, ncol=10)
281-
sim = Simulation(tdis=time, workspace=function_tmpdir, name=sim_name)
282387
gwf_name = "gwf"
283-
gwf = Gwf(parent=sim, dis=grid, name=gwf_name)
388+
time = ModelTime(perlen=[1.0], nstp=[1], tsmult=[1.0])
389+
ims = Ims(models=[gwf_name])
390+
dis = Dis(
391+
nlay=1,
392+
nrow=10,
393+
ncol=10,
394+
delr=1.0,
395+
delc=1.0,
396+
top=1.0,
397+
botm=0.0,
398+
)
399+
sim = Simulation(
400+
tdis=time,
401+
workspace=function_tmpdir,
402+
name=sim_name,
403+
solutions={"ims": ims},
404+
)
405+
gwf = Gwf(parent=sim, dis=dis, name=gwf_name)
284406
ic = Ic(parent=gwf)
285407
oc = Oc(parent=gwf)
286408
npf = Npf(parent=gwf)

test/test_interface.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,6 @@ def test_flopy3_package(tmp_path):
242242

243243

244244
def norun_test_flopy3_cbd_small(tmp_path):
245-
import sys
246-
247-
sys.path.append("/home/mjreno/.clone/usgs/flopy/autotest")
248245
from test_grid_cases import GridCases
249246

250247
time = ModelTime(perlen=[1.0], nstp=[1], tsmult=[1.0])

0 commit comments

Comments
 (0)