Skip to content

Commit 66b4423

Browse files
authored
fix(mnw1): fix mnw1 load (#2752)
* add tests for the PR changes
1 parent 2eaad43 commit 66b4423

3 files changed

Lines changed: 216 additions & 6 deletions

File tree

autotest/test_mnw.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,181 @@ def test_checks(mnw2_examples_path):
441441
assert "MNWI package present without MNW2 package." in ".".join(
442442
chk.summary_array.desc
443443
)
444+
445+
446+
def test_mnw1_add_flag(function_tmpdir):
447+
"""Test MNW1 ADD flag parsing and writing"""
448+
mnw1_str = """ 120 -90 0 REFERENCE SP = 2
449+
Well model will use SKIN
450+
#
451+
2 SP 1
452+
1 1 1 0 100 0.5 1 SITE:Well-A
453+
1 2 2 0 200 0.5 1
454+
# Multi-node switch Switch to specify Hlim Auxiliary
455+
2 ADD
456+
1 1 1 -100.0 100 0.5 1 DD 50 1.e16 1 SITE:Well-A
457+
1 2 2 -200.0 200 0.5 1 DD 50 1.e16 1
458+
#
459+
1
460+
1 1 1 -100.0 100 0.5 1 DD 50 1.e16 1 SITE:Well-A
461+
#
462+
1
463+
1 1 1 -100.0 100 0.5 1 DD 50 1.e16 1 SITE:Well-A
464+
""" # noqa: E501
465+
ws = function_tmpdir
466+
fpth = ws / "test_add.mnw"
467+
with open(fpth, "w") as f:
468+
f.write(mnw1_str)
469+
470+
# Create minimal model files
471+
disstr = """1 1 1 3 1 1
472+
0 0 0
473+
constant 1
474+
constant 1
475+
constant 0 top of model
476+
constant -1 bottom of layer 1
477+
1. 1 1. Tr
478+
1. 1 1. Tr
479+
1. 1 1. Tr
480+
""" # noqa: E501
481+
dis_path = ws / "test_add.dis"
482+
with open(dis_path, "w") as f:
483+
f.write(disstr)
484+
485+
basstr = """ 0 0 1 1 1 1 0 0
486+
1 1 1
487+
0.
488+
""" # noqa: E501
489+
bas_path = ws / "test_add.bas"
490+
with open(bas_path, "w") as f:
491+
f.write(basstr)
492+
493+
namstr = """lst 101 test_add.lst
494+
dis 102 test_add.dis
495+
bas6 103 test_add.bas
496+
mnw1 104 test_add.mnw""" # noqa: E501
497+
nam_path = ws / "test_add.nam"
498+
with open(nam_path, "w") as f:
499+
f.write(namstr)
500+
501+
# Load and test ADD flag parsing
502+
m = Modflow.load(
503+
"test_add.nam",
504+
model_ws=ws,
505+
load_only=["mnw1"],
506+
verbose=True,
507+
forgive=False,
508+
)
509+
510+
# Verify add parameter was loaded correctly
511+
assert hasattr(m.mnw1, "add"), "MNW1 package missing 'add' attribute"
512+
assert len(m.mnw1.add) == 3, (
513+
f"Expected 3 stress periods in add, got {len(m.mnw1.add)}"
514+
)
515+
assert m.mnw1.add[0] is False, "SP 1: add should be False"
516+
assert m.mnw1.add[1] is True, "SP 2: add should be True"
517+
assert m.mnw1.add[2] is False, "SP 3: add should be False"
518+
519+
# Write to new file and verify round-trip
520+
m.mnw1.fn_path = ws / "test_add_write.mnw"
521+
m.mnw1.write_file()
522+
523+
# Create a new name file pointing to the written MNW file
524+
namstr_write = """lst 101 test_add_write.lst
525+
dis 102 test_add.dis
526+
bas6 103 test_add.bas
527+
mnw1 104 test_add_write.mnw""" # noqa: E501
528+
with open(ws / "test_add_write.nam", "w") as f:
529+
f.write(namstr_write)
530+
531+
# Reload the model with the written MNW file
532+
m2 = Modflow.load(
533+
"test_add_write.nam",
534+
model_ws=ws,
535+
load_only=["mnw1"],
536+
verbose=False,
537+
forgive=False,
538+
)
539+
540+
# Verify add flag was preserved after write and reload
541+
assert hasattr(m2.mnw1, "add"), "Reloaded MNW1 package missing 'add' attribute"
542+
assert m2.mnw1.add[0] is False, "Reloaded SP 1: add should be False"
543+
assert m2.mnw1.add[1] is True, "Reloaded SP 2: add should be True"
544+
assert m2.mnw1.add[2] is False, "Reloaded SP 3: add should be False"
545+
546+
547+
def test_mnw1_stress_period_no_wells(function_tmpdir):
548+
"""Test MNW1 loading with stress periods that have no wells (itmp <= 0)"""
549+
# This tests the fix for handling empty stress periods in the load routine
550+
mnw1_str = """ 984 2 0 10000 !! Item 1: MXMNW IWL2CB IWELPT
551+
SKIN !! Item 2: LOSSTYPE
552+
#
553+
1 # Item 4, SP 1
554+
# lay row col Q rw Skin Hlim Href QWZN
555+
1 1 1 -10.0 0.5 2.0 0.5 1.e9 0 SITE:Well-A
556+
# stress period 2 has no wells (itmp=0) - should be handled without error
557+
0 # Item 4, SP 2
558+
1 # Item 4, SP 3
559+
# Multi-node switch Switch to specify Hlim Auxiliary
560+
# | as difference from Href definitions
561+
# lay row col Q rw Skin Hlim Href QWZN
562+
1 1 1 -100.0 0.5 1.0 DD 50 1.e16 1 SITE:Well-A
563+
""" # noqa: E501
564+
ws = function_tmpdir
565+
fpth = ws / "test_no_wells.mnw"
566+
with open(fpth, "w") as f:
567+
f.write(mnw1_str)
568+
569+
# Create minimal model files with only 2 stress periods (to match the MNW file)
570+
disstr = """1 1 1 3 1 1
571+
0 0 0
572+
constant 1
573+
constant 1
574+
constant 0 top of model
575+
constant -1 bottom of layer 1
576+
1. 1 1. Tr
577+
1. 1 1. Tr
578+
1. 1 1. Tr
579+
1. 1 1. Tr
580+
""" # noqa: E501
581+
dis_path = ws / "test_no_wells.dis"
582+
with open(dis_path, "w") as f:
583+
f.write(disstr)
584+
585+
basstr = """ 0 0 1 1 1 1 0 0
586+
1 1 1
587+
0.
588+
""" # noqa: E501
589+
bas_path = ws / "test_no_wells.bas"
590+
with open(bas_path, "w") as f:
591+
f.write(basstr)
592+
593+
namstr = """lst 101 test_no_wells.lst
594+
dis 102 test_no_wells.dis
595+
bas6 103 test_no_wells.bas
596+
mnw1 104 test_no_wells.mnw""" # noqa: E501
597+
nam_path = ws / "test_no_wells.nam"
598+
with open(nam_path, "w") as f:
599+
f.write(namstr)
600+
601+
# Load the file - should not fail when SP 2 has no wells (itmp=0)
602+
m = Modflow.load(
603+
"test_no_wells.nam",
604+
model_ws=ws,
605+
load_only=["mnw1"],
606+
verbose=False,
607+
forgive=False,
608+
)
609+
610+
# Verify the package loaded successfully
611+
assert m.mnw1 is not None, "MNW1 package failed to load"
612+
assert m.mnw1.stress_period_data is not None, "stress_period_data is None"
613+
614+
# Verify SP 1 has wells
615+
assert len(m.mnw1.stress_period_data[0]) >= 1, "SP 1 should have wells"
616+
617+
# Verify SP 2 has no wells
618+
assert len(m.mnw1.stress_period_data[1]) == 0, "SP 2 should have no wells"
619+
620+
# Verify SP 3 has wells
621+
assert len(m.mnw1.stress_period_data[2]) >= 1, "SP 3 should have wells"

flopy/modflow/mfmnw1.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class ModflowMnw1(Package):
3737
if None, these optional external filenames and unit numbers are not written out
3838
itmp : array
3939
number of wells to be simulated for each stress period (shape : (NPER))
40+
add : bool, list of bool, or None
41+
flag to indicate whether the number of wells specified in itmp should be
42+
added to the number of wells from previous stress periods (True) or if
43+
the number of wells specified in itmp is the total number of wells for
44+
that stress period (False)
4045
lay_row_col_qdes_mn_multi : list of arrays
4146
lay, row, col, qdes, and MN or MULTI flag for all well nodes
4247
(length : NPER)
@@ -88,6 +93,7 @@ def __init__(
8893
kspref=1,
8994
wel1_bynode_qsum=None,
9095
losstype="skin",
96+
add=None,
9197
stress_period_data=None,
9298
dtype=None,
9399
mnwname=None,
@@ -125,6 +131,13 @@ def __init__(
125131
# nested list containing file names, unit numbers, and ALLTIME flag for
126132
# auxiliary output, e.g. [['test.ByNode',92,'ALLTIME']]
127133
self.wel1_bynode_qsum = wel1_bynode_qsum
134+
if add is None:
135+
add = [False] * self.nper
136+
elif isinstance(add, bool):
137+
add = [add] * self.nper
138+
elif isinstance(add, np.ndarray):
139+
add = add.tolist()
140+
self.add = add
128141
if dtype is not None:
129142
self.dtype = dtype
130143
else:
@@ -215,14 +228,16 @@ def load(cls, f, model, nper=None, gwt=False, nsol=1, ext_unit_dict=None):
215228
qfrcmx_default = None
216229
qcut_default = ""
217230

218-
# not sure what 'add' means
219-
add = True if "add" in line.lower() else False
220-
231+
add = []
221232
for per in range(nper):
222233
if per > 0:
223234
line = skipcomments(next(f), f)
224-
add = True if "add" in line.lower() else False
225235
itmp = int(line_parse(line)[0])
236+
if itmp < 1:
237+
tadd = False
238+
else:
239+
tadd = True if "add" in line.lower() else False
240+
add.append(tadd)
226241
if itmp > 0:
227242
# dataset 5
228243
data, qfrcmn_default, qfrcmx_default, qcut_default = _parse_5(
@@ -235,6 +250,8 @@ def load(cls, f, model, nper=None, gwt=False, nsol=1, ext_unit_dict=None):
235250
for n in dtype.descr:
236251
spd[n[0]] = tmp[n[0]]
237252
stress_period_data[per] = spd
253+
else:
254+
stress_period_data[per] = ModflowMnw1.get_empty_stress_period_data(0)
238255

239256
if openfile:
240257
f.close()
@@ -248,6 +265,7 @@ def load(cls, f, model, nper=None, gwt=False, nsol=1, ext_unit_dict=None):
248265
kspref=kspref,
249266
wel1_bynode_qsum=wel1_bynode_qsum,
250267
losstype=losstype,
268+
add=add,
251269
stress_period_data=stress_period_data,
252270
)
253271

@@ -303,10 +321,18 @@ def write_file(self):
303321
"FILE:%s QSUM:%-10i %s\n" % (each[0], int(each[1]), each[2])
304322
)
305323

324+
# process additional data for Section 4 (itmp and ADD flag)
325+
additional_data = []
326+
for per in range(self.nper):
327+
if self.add[per]:
328+
additional_data.append(" ADD ")
329+
else:
330+
additional_data.append(" ")
331+
306332
spd = self.stress_period_data.drop("mnw_no")
307333
# force write_transient to keep the list arrays internal because MNW1
308334
# doesn't allow open/close
309-
spd.write_transient(f, forceInternal=True)
335+
spd.write_transient(f, forceInternal=True, additional_data=additional_data)
310336

311337
# -Un-numbered section PREFIX:MNWNAME
312338
if self.mnwname:

flopy/utils/util_list.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,7 @@ def write_transient(
697697
forceInternal=False,
698698
write_header=True,
699699
cln_data=None,
700+
additional_data=None,
700701
):
701702
# forceInternal overrides isExternal (set below) for cases where
702703
# external arrays are not supported (oh hello MNW1!)
@@ -744,8 +745,13 @@ def write_transient(
744745
kper_vtype = int
745746

746747
if write_header:
747-
if cln_data is None:
748+
if cln_data is None and additional_data is None:
748749
f.write(f" {itmp:9d} {0:9d} # stress period {kper + 1}\n")
750+
elif additional_data is not None:
751+
f.write(
752+
f" {itmp:9d} {additional_data[kper]} "
753+
+ f"# stress period {kper + 1}\n"
754+
)
749755
elif cln_data.get_itmp(kper) is None:
750756
f.write(f" {itmp:9d} {0:9d} # stress period {kper + 1}\n")
751757
else:

0 commit comments

Comments
 (0)