Note
Working with the Multi-node Well (MNW2) Package
[1]:
import sys
import os
from tempfile import TemporaryDirectory
import numpy as np
try:
import pandas as pd
except:
pass
# run installed version of flopy or add local path
try:
import flopy
except:
fpth = os.path.abspath(os.path.join("..", ".."))
sys.path.append(fpth)
import flopy
print(sys.version)
print("numpy version: {}".format(np.__version__))
try:
print("pandas version: {}".format(pd.__version__))
except:
pass
print("flopy version: {}".format(flopy.__version__))
3.10.10 | packaged by conda-forge | (main, Mar 24 2023, 20:08:06) [GCC 11.3.0]
numpy version: 1.24.3
pandas version: 2.0.1
flopy version: 3.3.7
Make an MNW2 package from scratch
[2]:
# temporary directory
temp_dir = TemporaryDirectory()
model_ws = temp_dir.name
m = flopy.modflow.Modflow("mnw2example", model_ws=model_ws)
dis = flopy.modflow.ModflowDis(
nrow=5, ncol=5, nlay=3, nper=3, top=10, botm=0, model=m
)
MNW2 information by node
(this could be prepared externally from well reconds and read in from a csv or excel file) * this table has two multi-node wells, the first (well1) consisting of two nodes that are manually specified (where the variable rw is specified by node) * node that some variables that are constant for the whole well are also included (losstype, zpump, etc.)
[3]:
node_data = pd.DataFrame(
[
[1, 1, 9.5, 7.1, "well1", "skin", -1, 0, 0, 0, 1.0, 2.0, 5.0, 6.2],
[1, 1, 7.1, 5.1, "well1", "skin", -1, 0, 0, 0, 0.5, 2.0, 5.0, 6.2],
[3, 3, 9.1, 3.7, "well2", "skin", -1, 0, 0, 0, 1.0, 2.0, 5.0, 4.1],
],
columns=[
"i",
"j",
"ztop",
"zbotm",
"wellid",
"losstype",
"pumploc",
"qlimit",
"ppflag",
"pumpcap",
"rw",
"rskin",
"kskin",
"zpump",
],
)
node_data
[3]:
i | j | ztop | zbotm | wellid | losstype | pumploc | qlimit | ppflag | pumpcap | rw | rskin | kskin | zpump | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 9.5 | 7.1 | well1 | skin | -1 | 0 | 0 | 0 | 1.0 | 2.0 | 5.0 | 6.2 |
1 | 1 | 1 | 7.1 | 5.1 | well1 | skin | -1 | 0 | 0 | 0 | 0.5 | 2.0 | 5.0 | 6.2 |
2 | 3 | 3 | 9.1 | 3.7 | well2 | skin | -1 | 0 | 0 | 0 | 1.0 | 2.0 | 5.0 | 4.1 |
convert the DataFrame to a recarray for compatibility with flopy
[4]:
node_data = node_data.to_records()
node_data
[4]:
rec.array([(0, 1, 1, 9.5, 7.1, 'well1', 'skin', -1, 0, 0, 0, 1. , 2., 5., 6.2),
(1, 1, 1, 7.1, 5.1, 'well1', 'skin', -1, 0, 0, 0, 0.5, 2., 5., 6.2),
(2, 3, 3, 9.1, 3.7, 'well2', 'skin', -1, 0, 0, 0, 1. , 2., 5., 4.1)],
dtype=[('index', '<i8'), ('i', '<i8'), ('j', '<i8'), ('ztop', '<f8'), ('zbotm', '<f8'), ('wellid', 'O'), ('losstype', 'O'), ('pumploc', '<i8'), ('qlimit', '<i8'), ('ppflag', '<i8'), ('pumpcap', '<i8'), ('rw', '<f8'), ('rskin', '<f8'), ('kskin', '<f8'), ('zpump', '<f8')])
Stress period information
(could also be developed externally)
[5]:
stress_period_data = pd.DataFrame(
[
[0, "well1", 0],
[1, "well1", 100.0],
[0, "well2", 0],
[1, "well2", 1000.0],
],
columns=["per", "wellid", "qdes"],
)
stress_period_data
[5]:
per | wellid | qdes | |
---|---|---|---|
0 | 0 | well1 | 0.0 |
1 | 1 | well1 | 100.0 |
2 | 0 | well2 | 0.0 |
3 | 1 | well2 | 1000.0 |
[6]:
pers = stress_period_data.groupby("per")
stress_period_data = {i: pers.get_group(i).to_records() for i in [0, 1]}
stress_period_data
[6]:
{0: rec.array([(0, 0, 'well1', 0.), (2, 0, 'well2', 0.)],
dtype=[('index', '<i8'), ('per', '<i8'), ('wellid', 'O'), ('qdes', '<f8')]),
1: rec.array([(1, 1, 'well1', 100.), (3, 1, 'well2', 1000.)],
dtype=[('index', '<i8'), ('per', '<i8'), ('wellid', 'O'), ('qdes', '<f8')])}
Make ModflowMnw2
package object
note that extraneous columns in node_data and stress_period_data are ignored
if itmp is positive, it must equal the number of active wells being specified in
stress_period_data
, otherwise the package class will raise an error.
[7]:
mnw2 = flopy.modflow.ModflowMnw2(
model=m,
mnwmax=2,
node_data=node_data,
stress_period_data=stress_period_data,
itmp=[2, 2, -1], # reuse second per pumping for last stress period
)
[8]:
# "nodtot" is computed automatically
mnw2.nodtot
[8]:
3
[9]:
pd.DataFrame(mnw2.node_data)
[9]:
k | i | j | ztop | zbotm | wellid | losstype | pumploc | qlimit | ppflag | ... | hlim | qcut | qfrcmn | qfrcmx | hlift | liftq0 | liftqmax | hwtol | liftn | qn | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | 9.5 | 7.1 | well1 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 0 | 1 | 1 | 7.1 | 5.1 | well1 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 0 | 3 | 3 | 9.1 | 3.7 | well2 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 rows × 33 columns
[10]:
pd.DataFrame(mnw2.stress_period_data[0])
[10]:
k | i | j | wellid | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | well1 | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
1 | 0 | 3 | 3 | well2 | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
[11]:
pd.DataFrame(mnw2.stress_period_data[1])
[11]:
k | i | j | wellid | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | well1 | 100.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
1 | 0 | 3 | 3 | well2 | 1000.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
[12]:
tmp = flopy.modflow.ModflowMnw2(
model=m,
itmp=[1, 1, -1], # reuse second per pumping for last stress period
)
empty node_data
and stress_period_data
tables can also be generated by the package class, and then filled
[13]:
node_data = tmp.get_empty_node_data(3)
node_data
[13]:
rec.array([(0, 0, 0, 0., 0., 0, 0, 0, 0, 0, 0, 0., 0., 0., 0., 0., 0., 0., 0., 0, 0, 0, 0., 0., 0, 0., 0., 0., 0., 0., 0., 0., 0.),
(0, 0, 0, 0., 0., 0, 0, 0, 0, 0, 0, 0., 0., 0., 0., 0., 0., 0., 0., 0, 0, 0, 0., 0., 0, 0., 0., 0., 0., 0., 0., 0., 0.),
(0, 0, 0, 0., 0., 0, 0, 0, 0, 0, 0, 0., 0., 0., 0., 0., 0., 0., 0., 0, 0, 0, 0., 0., 0, 0., 0., 0., 0., 0., 0., 0., 0.)],
dtype=[('k', '<i8'), ('i', '<i8'), ('j', '<i8'), ('ztop', '<f4'), ('zbotm', '<f4'), ('wellid', 'O'), ('losstype', 'O'), ('pumploc', '<i8'), ('qlimit', '<i8'), ('ppflag', '<i8'), ('pumpcap', '<i8'), ('rw', '<f4'), ('rskin', '<f4'), ('kskin', '<f4'), ('B', '<f4'), ('C', '<f4'), ('P', '<f4'), ('cwc', '<f4'), ('pp', '<f4'), ('pumplay', '<i8'), ('pumprow', '<i8'), ('pumpcol', '<i8'), ('zpump', '<f4'), ('hlim', '<f4'), ('qcut', '<i8'), ('qfrcmn', '<f4'), ('qfrcmx', '<f4'), ('hlift', '<f4'), ('liftq0', '<f4'), ('liftqmax', '<f4'), ('hwtol', '<f4'), ('liftn', '<f4'), ('qn', '<f4')])
Mnw objects
at the base of the flopy mnw2 module is the Mnw object class, which describes a single multi-node well. A list or dict of Mnw objects can be used to build a package (using the example above):
flopy.modflow.ModflowMnw2(model=m, mnwmax=2,
mnw=<dict or list of Mnw objects>,
itmp=[1, 1, -1], # reuse second per pumping for last stress period
)
or if node_data and stress_period_data are supplied, the Mnw objects are created on initialization of the ModflowMnw2 class instance, and assigned to the .mnw
attribute, as items in a dictionary keyed by wellid
.
[14]:
mnw2.mnw
[14]:
{'well1': <flopy.modflow.mfmnw2.Mnw at 0x7fca685edae0>,
'well2': <flopy.modflow.mfmnw2.Mnw at 0x7fca685ed660>}
[15]:
mnw2.mnw["well1"].rw
[15]:
[1.0, 0.5]
Note that Mnw object attributes for variables that vary by node are lists (e.g. rw
above)
Each Mnw object has its own node_data
and stress_period_data
[16]:
pd.DataFrame(mnw2.mnw["well1"].node_data)
[16]:
k | i | j | ztop | zbotm | wellid | losstype | pumploc | qlimit | ppflag | ... | hlim | qcut | qfrcmn | qfrcmx | hlift | liftq0 | liftqmax | hwtol | liftn | qn | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | 9.5 | 7.1 | well1 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 0 | 1 | 1 | 7.1 | 5.1 | well1 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 rows × 33 columns
Instead of a dict keyed by stress period, Mnw.stress_period_data is a recarray with pumping data listed by stress period for that well
note that data for period 2, where
itmp
< 1, is shown (was copied from s.p. 1 during construction of the Mnw object)
[17]:
pd.DataFrame(mnw2.mnw["well2"].stress_period_data)
[17]:
k | i | j | per | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 3 | 3 | 0 | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
1 | 0 | 3 | 3 | 1 | 1000.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
2 | 0 | 3 | 3 | 1 | 1000.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
Build the same package using only the Mnw
objects
[18]:
mnw2fromobj = flopy.modflow.ModflowMnw2(
model=m,
mnwmax=2,
mnw=mnw2.mnw,
itmp=[2, 2, -1], # reuse second per pumping for last stress period
)
[19]:
pd.DataFrame(mnw2fromobj.node_data)
[19]:
k | i | j | ztop | zbotm | wellid | losstype | pumploc | qlimit | ppflag | ... | hlim | qcut | qfrcmn | qfrcmx | hlift | liftq0 | liftqmax | hwtol | liftn | qn | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | 9.5 | 7.1 | well1 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 | 0 | 1 | 1 | 7.1 | 5.1 | well1 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
2 | 0 | 3 | 3 | 9.1 | 3.7 | well2 | skin | -1 | 0 | 0 | ... | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
3 rows × 33 columns
[20]:
pd.DataFrame(mnw2fromobj.stress_period_data[0])
[20]:
k | i | j | wellid | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | well1 | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
1 | 0 | 3 | 3 | well2 | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
[21]:
pd.DataFrame(mnw2fromobj.stress_period_data[1])
[21]:
k | i | j | wellid | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1 | well1 | 100.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
1 | 0 | 3 | 3 | well2 | 1000.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
By default, the node_data
and stress_period_data
tables attached to the ModflowMnw2
package class are definitive
on writing of the package output (
mnw2.write_file()
), the Mnw objects are regenerated from the tables. This setting is controlled by the default argumentuse_tables=True
. To write the package file using the Mnw objects (ignoring the tables), usemnw2.write_file(use_tables=False)
.
[22]:
per1 = flopy.modflow.ModflowMnw2.get_empty_stress_period_data(itmp=2)
per1
[22]:
rec.array([(0, 0, 0, 0, 0., 0, 0., 0., 0, 0., 0.),
(0, 0, 0, 0, 0., 0, 0., 0., 0, 0., 0.)],
dtype=[('k', '<i8'), ('i', '<i8'), ('j', '<i8'), ('wellid', 'O'), ('qdes', '<f4'), ('capmult', '<i8'), ('cprime', '<f4'), ('hlim', '<f4'), ('qcut', '<i8'), ('qfrcmn', '<f4'), ('qfrcmx', '<f4')])
Write an MNW2 package file and inspect the results
[23]:
mnw2.write_file(os.path.join(model_ws, "test.mnw2"))
[24]:
junk = [
print(l.strip("\n"))
for l in open(os.path.join(model_ws, "test.mnw2")).readlines()
]
# MNW2 package for MODFLOW-2005 generated by Flopy 3.3.7
2 0 0
well1 -2
skin -1 0 0 0
-1.0000000E+00 2.0000000E+00 5.0000000E+00
9.5000000E+00 7.0999999E+00 2 2 1.0000000E+00
7.0999999E+00 5.0999999E+00 2 2 5.0000000E-01
6.1999998E+00
well2 -1
skin -1 0 0 0
1.0000000E+00 2.0000000E+00 5.0000000E+00
9.1000004E+00 3.7000000E+00 4 4
4.0999999E+00
2 Stress Period 1
well1 0.0000000E+00
well2 0.0000000E+00
2 Stress Period 2
well1 1.0000000E+02
well2 1.0000000E+03
-1 Stress Period 3
Load some example MNW2 packages
[25]:
path = os.path.join("..", "..", "examples", "data", "mnw2_examples")
m = flopy.modflow.Modflow("MNW2-Fig28", model_ws=model_ws)
dis = flopy.modflow.ModflowDis.load(os.path.join(path, "MNW2-Fig28.dis"), m)
[26]:
m.get_package_list()
[26]:
['DIS']
[27]:
mnw2pth = os.path.join(path, "MNW2-Fig28.mnw2")
mnw2 = flopy.modflow.ModflowMnw2.load(mnw2pth, m)
[28]:
pd.DataFrame(mnw2.node_data)
[28]:
k | i | j | ztop | zbotm | wellid | losstype | pumploc | qlimit | ppflag | ... | hlim | qcut | qfrcmn | qfrcmx | hlift | liftq0 | liftqmax | hwtol | liftn | qn | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 29 | 40 | -5.0 | -65.0 | well-a | skin | 0 | 1 | 0 | ... | -7.5 | -1 | 0.1 | 0.15 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
1 rows × 33 columns
[29]:
pd.DataFrame(mnw2.stress_period_data[0])
[29]:
k | i | j | wellid | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 29 | 40 | well-a | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
[30]:
mnw2.mnw
[30]:
{'well-a': <flopy.modflow.mfmnw2.Mnw at 0x7fca9607f970>}
[31]:
pd.DataFrame(mnw2.mnw["well-a"].stress_period_data)
[31]:
k | i | j | per | qdes | capmult | cprime | hlim | qcut | qfrcmn | qfrcmx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 29 | 40 | 0 | 0.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
1 | 0 | 29 | 40 | 1 | -10000.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
2 | 0 | 29 | 40 | 2 | -10000.0 | 0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 |
[32]:
path = os.path.join("..", "..", "examples", "data", "mnw2_examples")
m = flopy.modflow.Modflow("br", model_ws=model_ws)
mnw2 = flopy.modflow.ModflowMnw2.load(path + "/BadRiver_cal.mnw2", m)
[33]:
df = pd.DataFrame(mnw2.node_data)
df.loc[:, df.sum(axis=0) != 0]
[33]:
i | j | ztop | zbotm | wellid | losstype | pumploc | rw | rskin | kskin | zpump | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 294 | 503 | 181.630005 | 161.630005 | br_birch1 | skin | -1 | 1.0 | 2.0 | 10.0 | 162.630005 |
1 | 295 | 503 | 179.119995 | 159.119995 | br_birch2 | skin | -1 | 1.0 | 2.0 | 10.0 | 160.119995 |
2 | 175 | 342 | 400.220001 | 312.220001 | br_diaperville1 | skin | -1 | 1.0 | 2.0 | 10.0 | 313.220001 |
3 | 174 | 342 | 399.119995 | 312.119995 | br_diaperville2 | skin | -1 | 1.0 | 2.0 | 10.0 | 313.119995 |
4 | 248 | 454 | 565.200012 | 555.200012 | br_franks1 | skin | -1 | 1.0 | 2.0 | 10.0 | 556.200012 |
5 | 249 | 453 | 564.419983 | 554.419983 | br_franks2 | skin | -1 | 1.0 | 2.0 | 10.0 | 555.419983 |
6 | 180 | 396 | 453.959991 | 447.959991 | br_odanah1 | skin | -1 | 1.0 | 2.0 | 10.0 | 448.959991 |
7 | 181 | 395 | 450.559998 | 444.559998 | br_odanah2 | skin | -1 | 1.0 | 2.0 | 10.0 | 445.559998 |
8 | 181 | 395 | 380.489990 | 371.489990 | br_odanah3 | skin | -1 | 1.0 | 2.0 | 10.0 | 372.489990 |
9 | 180 | 396 | 450.739990 | 444.739990 | br_odanah4 | skin | -1 | 1.0 | 2.0 | 10.0 | 445.739990 |
10 | 170 | 350 | 475.410004 | 472.410004 | br_oldschool | skin | -1 | 1.0 | 2.0 | 10.0 | 473.410004 |
11 | 172 | 312 | 377.410004 | 348.410004 | br_recycle | skin | -1 | 1.0 | 2.0 | 10.0 | 349.410004 |
12 | 216 | 412 | 562.200012 | 542.200012 | br_unspec | skin | -1 | 1.0 | 2.0 | 10.0 | 543.200012 |
13 | 516 | 424 | 1079.910034 | 1072.910034 | cfsp_ncp1 | skin | -1 | 1.0 | 2.0 | 10.0 | 1073.910034 |
14 | 515 | 424 | 1077.900024 | 1074.900024 | cfsp_ncp2 | skin | -1 | 1.0 | 2.0 | 10.0 | 1075.900024 |
15 | 539 | 415 | 1093.010010 | 1003.010010 | cfsp_of | skin | -1 | 1.0 | 2.0 | 10.0 | 1004.010010 |
16 | 360 | 2 | 706.330017 | 699.330017 | hayward_bait_n | skin | -1 | 1.0 | 2.0 | 10.0 | 700.330017 |
17 | 360 | 6 | 705.289978 | 698.289978 | hayward_bait_ne | skin | -1 | 1.0 | 2.0 | 10.0 | 699.289978 |
18 | 362 | 4 | 696.979980 | 689.979980 | hayward_bait_of | skin | -1 | 1.0 | 2.0 | 10.0 | 690.979980 |
19 | 456 | 699 | 1352.050049 | 1342.050049 | ironbelt2 | skin | -1 | 1.0 | 2.0 | 10.0 | 1343.050049 |
20 | 456 | 699 | 1350.439941 | 1343.439941 | ironbelt3 | skin | -1 | 1.0 | 2.0 | 10.0 | 1344.439941 |
21 | 599 | 414 | 1198.839966 | 1188.839966 | mellen2 | skin | -1 | 1.0 | 2.0 | 10.0 | 1189.839966 |
22 | 576 | 408 | 1151.599976 | 1131.599976 | mellen3 | skin | -1 | 1.0 | 2.0 | 10.0 | 1132.599976 |
23 | 253 | 149 | 622.179993 | 602.179993 | milestone | skin | -1 | 1.0 | 2.0 | 10.0 | 603.179993 |
24 | 208 | 148 | 520.119995 | 500.119995 | nsp | skin | -1 | 1.0 | 2.0 | 10.0 | 501.119995 |
25 | 80 | 162 | 535.299988 | 30.299999 | washburn1 | skin | -1 | 1.0 | 2.0 | 10.0 | 31.299999 |
26 | 91 | 143 | 541.280029 | -67.720001 | washburn2 | skin | -1 | 1.0 | 2.0 | 10.0 | -66.720001 |
[34]:
try:
# ignore PermissionError on Windows
temp_dir.cleanup()
except:
pass