This page was generated from mf6_mnw2_tutorial01.py. It's also available as a notebook.

Working with the Multi-node Well (MNW2) Package

[1]:
import os
[2]:
import sys
from tempfile import TemporaryDirectory

import numpy as np
import pandas as pd

import flopy

print(sys.version)
print(f"numpy version: {np.__version__}")
print(f"pandas version: {pd.__version__}")
print(f"flopy version: {flopy.__version__}")
3.9.19 (main, Mar 20 2024, 16:40:02)
[GCC 11.4.0]
numpy version: 1.26.4
pandas version: 2.2.2
flopy version: 3.7.0.dev0

Make an MNW2 package from scratch

[3]:
# 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.)

[4]:
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
[4]:
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

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) for i in [0, 1]}
stress_period_data
[6]:
{0:    per wellid  qdes
 0    0  well1   0.0
 2    0  well2   0.0,
 1:    per wellid    qdes
 1    1  well1   100.0
 3    1  well2  1000.0}

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
)
/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/flopy/mbase.py:659: UserWarning: Unit 34 of package MNW2 already in use.
  warn(
/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/flopy/mbase.py:668: UserWarning: Two packages of the same type, Replacing existing 'MNW2' package.
  warn(

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 0x7f1cac1992b0>,
 'well2': <flopy.modflow.mfmnw2.Mnw at 0x7f1c7eb1e0a0>}
[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
)
/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/flopy/mbase.py:659: UserWarning: Unit 34 of package MNW2 already in use.
  warn(
/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/flopy/mbase.py:668: UserWarning: Two packages of the same type, Replacing existing 'MNW2' package.
  warn(
[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 argument use_tables=True. To write the package file using the Mnw objects (ignoring the tables), use mnw2.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.7.0.dev0
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 0x7f1c7ea48d60>}
[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(
    os.path.join(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