SFR package example

Demonstrates functionality of Flopy SFR module using the example documented by Prudic and others (2004):

Problem description:

  • Grid dimensions: 1 Layer, 15 Rows, 10 Columns
  • Stress periods: 1 steady
  • Flow package: LPF
  • Stress packages: SFR, GHB, EVT, RCH
  • Solver: SIP


In [1]:
import sys
import platform
import os
import numpy as np
import glob
import shutil
import matplotlib as mpl
import matplotlib.pyplot as plt
import flopy
import flopy.utils.binaryfile as bf

In [2]:
#Set name of MODFLOW exe
#  assumes executable is in users path statement
exe_name = 'mf2005'
if platform.system() == 'Windows':
    exe_name = 'mf2005.exe'

% matplotlib inline

mpl.rcParams['figure.figsize'] = (11, 8.5)

copy over the example files to the working directory


In [3]:
path = 'data'
gpth = os.path.join('..', 'data', 'mf2005_test', 'test1ss.*')
for f in glob.glob(gpth):
    shutil.copy(f, path)

Load example dataset, skipping the SFR package


In [4]:
m = flopy.modflow.Modflow.load('test1ss.nam', version='mf2005', exe_name=exe_name, 
                               model_ws=path, load_only=['ghb', 'evt', 'rch', 'dis', 'bas6', 'oc', 'sip', 'lpf'])

Read pre-prepared reach and segment data into numpy recarrays using numpy.genfromtxt()

Reach data (Item 2 in the SFR input instructions), are input and stored in a numpy record array
http://docs.scipy.org/doc/numpy/reference/generated/numpy.recarray.html
This allows for reach data to be indexed by their variable names, as described in the SFR input instructions.

For more information on Item 2, see the Online Guide to MODFLOW:
http://water.usgs.gov/nrp/gwsoftware/modflow2000/MFDOC/index.html?sfr.htm


In [5]:
rpth = os.path.join('..', 'data', 'sfr_examples', 'test1ss_reach_data.csv')
reach_data = np.genfromtxt(rpth, delimiter=',', names=True)
reach_data


Out[5]:
array([(0.0, 0.0, 0.0, 1.0, 1.0, 4500.0),
       (0.0, 1.0, 1.0, 1.0, 2.0, 7000.0),
       (0.0, 2.0, 2.0, 1.0, 3.0, 6000.0),
       (0.0, 2.0, 3.0, 1.0, 4.0, 5550.0),
       (0.0, 3.0, 4.0, 2.0, 1.0, 6500.0),
       (0.0, 4.0, 5.0, 2.0, 2.0, 5000.0),
       (0.0, 5.0, 5.0, 2.0, 3.0, 5000.0),
       (0.0, 6.0, 5.0, 2.0, 4.0, 5000.0),
       (0.0, 7.0, 5.0, 2.0, 5.0, 5000.0),
       (0.0, 2.0, 4.0, 3.0, 1.0, 5000.0),
       (0.0, 2.0, 5.0, 3.0, 2.0, 5000.0),
       (0.0, 2.0, 6.0, 3.0, 3.0, 4500.0),
       (0.0, 3.0, 7.0, 3.0, 4.0, 6000.0),
       (0.0, 4.0, 7.0, 3.0, 5.0, 5000.0),
       (0.0, 5.0, 7.0, 3.0, 6.0, 2000.0),
       (0.0, 4.0, 9.0, 4.0, 1.0, 2500.0),
       (0.0, 4.0, 8.0, 4.0, 2.0, 5000.0),
       (0.0, 5.0, 7.0, 4.0, 3.0, 3500.0),
       (0.0, 5.0, 7.0, 5.0, 1.0, 4000.0),
       (0.0, 6.0, 6.0, 5.0, 2.0, 5000.0),
       (0.0, 7.0, 6.0, 5.0, 3.0, 3500.0),
       (0.0, 7.0, 5.0, 5.0, 4.0, 2500.0),
       (0.0, 8.0, 5.0, 6.0, 1.0, 5000.0),
       (0.0, 9.0, 6.0, 6.0, 2.0, 5000.0),
       (0.0, 10.0, 6.0, 6.0, 3.0, 5000.0),
       (0.0, 11.0, 6.0, 6.0, 4.0, 5000.0),
       (0.0, 12.0, 6.0, 6.0, 5.0, 2000.0),
       (0.0, 13.0, 8.0, 7.0, 1.0, 5000.0),
       (0.0, 12.0, 7.0, 7.0, 2.0, 5500.0),
       (0.0, 12.0, 6.0, 7.0, 3.0, 5000.0),
       (0.0, 12.0, 5.0, 8.0, 1.0, 5000.0),
       (0.0, 12.0, 4.0, 8.0, 2.0, 5000.0),
       (0.0, 12.0, 3.0, 8.0, 3.0, 5000.0),
       (0.0, 12.0, 2.0, 8.0, 4.0, 5000.0),
       (0.0, 12.0, 1.0, 8.0, 5.0, 5000.0),
       (0.0, 12.0, 0.0, 8.0, 6.0, 3000.0)], 
      dtype=[('k', '<f8'), ('i', '<f8'), ('j', '<f8'), ('iseg', '<f8'), ('ireach', '<f8'), ('rchlen', '<f8')])

Segment Data structure

Segment data are input and stored in a dictionary of record arrays, which


In [6]:
spth = os.path.join('..', 'data', 'sfr_examples', 'test1ss_segment_data.csv')
ss_segment_data = np.genfromtxt(spth, delimiter=',', names=True)
segment_data = {0: ss_segment_data}
segment_data[0][0:1]['width1']


Out[6]:
array([ 0.])

define dataset 6e (channel flow data) for segment 1

dataset 6e is stored in a nested dictionary keyed by stress period and segment,
with a list of the following lists defined for each segment with icalc == 4
FLOWTAB(1) FLOWTAB(2) ... FLOWTAB(NSTRPTS)
DPTHTAB(1) DPTHTAB(2) ... DPTHTAB(NSTRPTS)
WDTHTAB(1) WDTHTAB(2) ... WDTHTAB(NSTRPTS)


In [7]:
channel_flow_data = {0: {1: [[0.5, 1.0, 2.0, 4.0, 7.0, 10.0, 20.0, 30.0, 50.0, 75.0, 100.0],
                             [0.25, 0.4, 0.55, 0.7, 0.8, 0.9, 1.1, 1.25, 1.4, 1.7, 2.6],
                             [3.0, 3.5, 4.2, 5.3, 7.0, 8.5, 12.0, 14.0, 17.0, 20.0, 22.0]]}}

define dataset 6d (channel geometry data) for segments 7 and 8

dataset 6d is stored in a nested dictionary keyed by stress period and segment,
with a list of the following lists defined for each segment with icalc == 4
FLOWTAB(1) FLOWTAB(2) ... FLOWTAB(NSTRPTS)
DPTHTAB(1) DPTHTAB(2) ... DPTHTAB(NSTRPTS)
WDTHTAB(1) WDTHTAB(2) ... WDTHTAB(NSTRPTS)


In [8]:
channel_geometry_data = {0: {7: [[0.0, 10.0, 80.0, 100.0, 150.0, 170.0, 240.0, 250.0],
                                 [20.0, 13.0, 10.0, 2.0, 0.0, 10.0, 13.0, 20.0]],
                             8: [[0.0, 10.0, 80.0, 100.0, 150.0, 170.0, 240.0, 250.0],
                                 [25.0, 17.0, 13.0, 4.0, 0.0, 10.0, 16.0, 20.0]]}}

Define SFR package variables


In [9]:
nstrm = len(reach_data) # number of reaches
nss = len(segment_data[0]) # number of segments
nsfrpar = 0 # number of parameters (not supported)
nparseg = 0
const = 1.486 # constant for manning's equation, units of cfs
dleak = 0.0001 # closure tolerance for stream stage computation
istcb1 = 53 # flag for writing SFR output to cell-by-cell budget (on unit 53)
istcb2 = 81 # flag for writing SFR output to text file
dataset_5 = {0: [nss, 0, 0]} # dataset 5 (see online guide)

Instantiate SFR package

Input arguments generally follow the variable names defined in the Online Guide to MODFLOW


In [10]:
sfr = flopy.modflow.ModflowSfr2(m, nstrm=nstrm, nss=nss, const=const, dleak=dleak, istcb1=istcb1, istcb2=istcb2, 
                                reach_data=reach_data,
                                segment_data=segment_data,
                                channel_geometry_data=channel_geometry_data,
                                channel_flow_data=channel_flow_data,
                                dataset_5=dataset_5)

In [11]:
sfr.reach_data[0:1]


Out[11]:
rec.array([ (1, 0, 0, 0, 1, 1, 4500.0, -10000000000.0, -10000000000.0, -10000000000.0, -10000000000.0, -10000000000, -10000000000.0, -10000000000.0, -10000000000.0, 1, 2)], 
          dtype=[('node', '<i8'), ('k', '<i8'), ('i', '<i8'), ('j', '<i8'), ('iseg', '<i8'), ('ireach', '<i8'), ('rchlen', '<f4'), ('strtop', '<f4'), ('slope', '<f4'), ('strthick', '<f4'), ('strhc1', '<f4'), ('thts', '<i8'), ('thti', '<f4'), ('eps', '<f4'), ('uhc', '<f4'), ('reachID', '<i8'), ('outreach', '<i8')])

Plot the SFR segments

any column in the reach_data array can be plotted using the key argument


In [12]:
sfr.plot(key='iseg');


Check the SFR dataset for errors


In [13]:
chk = sfr.check()


Checking for continuity in segment and reach numbering...
passed.

Checking for increasing segment numbers in downstream direction...
passed.

Checking for circular routing...
passed.

Checking for model cells with multiple non-zero SFR conductances...
3 model cells with multiple non-zero SFR conductances found.
This may lead to circular routing between collocated reaches.
Nodes with overlapping conductances:
node	k	i	j	iseg	ireach	rchlen
76	0	7	5	2	5	5000.000000
58	0	5	7	3	6	2000.000000
58	0	5	7	4	3	3500.000000
58	0	5	7	5	1	4000.000000
76	0	7	5	5	4	2500.000000
127	0	12	6	6	5	2000.000000
127	0	12	6	7	3	5000.000000

Checking segment_data for downstream rises in streambed elevation...
passed.

Checking reach_data for downstream rises in streambed elevation...
Reach strtop not specified for nstrm=36, reachinput=False and isfropt=0
passed.

Checking reach_data for inconsistencies between streambed elevations and the model grid...
Reach strtop, strthick not specified for nstrm=36, reachinput=False and isfropt=0
passed.

Checking segment_data for inconsistencies between segment end elevations and the model grid...
passed.

Checking for streambed slopes of less than 0.0001...
slope not specified for isfropt=0
passed.

Checking for streambed slopes of greater than 1.0...
slope not specified for isfropt=0
passed.


In [14]:
m.external_fnames = [os.path.split(f)[1] for f in m.external_fnames]
m.external_fnames


Out[14]:
['test1ss.flw',
 'test1ss.sg1',
 'test1ss.sg2',
 'test1ss.sg3',
 'test1ss.sg4',
 'test1ss.sg5',
 'test1ss.sg6',
 'test1ss.sg7',
 'test1ss.sg8',
 'test1ss.dvsg9']

In [15]:
m.write_input()

In [16]:
m.run_model()


FloPy is using the following executable to run the model: /Users/jdhughes/Documents/Development/bin/mac/mf2005

                                  MODFLOW-2005     
    U.S. GEOLOGICAL SURVEY MODULAR FINITE-DIFFERENCE GROUND-WATER FLOW MODEL
                             Version 1.12.00 x/x/2015                        

 Using NAME file: test1ss.nam 
 Run start date and time (yyyy/mm/dd hh:mm:ss): 2016/06/27 16:26:15

 Solving:  Stress period:     1    Time step:     1    Ground-Water Flow Eqn.
 Run end date and time (yyyy/mm/dd hh:mm:ss): 2016/06/27 16:26:15
 Elapsed run time:  0.308 Seconds

 FAILED TO MEET SOLVER CONVERGENCE CRITERIA            1  TIME(S)
Out[16]:
(False, [])

Look at results


In [17]:
sfr_outfile = os.path.join('..', 'data', 'sfr_examples', 'test1ss.flw')
names =  ["layer", "row", "column", "segment", "reach", "Qin", 
          "Qaquifer", "Qout", "Qovr", "Qprecip", "Qet", "stage", "depth", "width", "Cond", "gradient"]

Read results into numpy array using genfromtxt


In [18]:
sfrresults = np.genfromtxt(sfr_outfile, skip_header=8, names=names, dtype=None)
sfrresults[0:1]


Out[18]:
array([ (1, 1, 1, 1, 1, 25.0, 0.7923, 24.208, 0.0, 0.0, 0.0, 1094.22, 1.174, 12.98, 0.5843, 0.452)], 
      dtype=[('layer', '<i8'), ('row', '<i8'), ('column', '<i8'), ('segment', '<i8'), ('reach', '<i8'), ('Qin', '<f8'), ('Qaquifer', '<f8'), ('Qout', '<f8'), ('Qovr', '<f8'), ('Qprecip', '<f8'), ('Qet', '<f8'), ('stage', '<f8'), ('depth', '<f8'), ('width', '<f8'), ('Cond', '<f8'), ('gradient', '<f8')])

Read results into pandas dataframe

  • requires the pandas library

In [19]:
import pandas as pd
df = pd.read_csv(sfr_outfile, delim_whitespace=True, skiprows=8, names=names, header=None)
df


Out[19]:
layer row column segment reach Qin Qaquifer Qout Qovr Qprecip Qet stage depth width Cond gradient
0 1 1 1 1 1 25.0000 0.79230 24.2080 0 0 0 1094.220 1.17400 12.980 0.5843 0.45200
1 1 2 2 1 2 24.2080 2.14080 22.0670 0 0 0 1089.210 1.15200 12.680 0.8878 0.80380
2 1 3 3 1 3 22.0670 2.99090 19.0760 0 0 0 1083.530 1.11000 12.130 0.7278 1.37000
3 1 3 4 1 4 19.0760 2.55380 16.5220 0 0 0 1078.470 1.06400 11.320 0.6285 1.35500
4 1 4 5 2 1 6.5222 2.70580 3.8163 0 0 0 1072.400 0.46900 12.000 0.7800 1.15600
5 1 5 6 2 2 3.8163 1.99560 1.8208 0 0 0 1066.840 0.32600 12.000 0.6000 1.10900
6 1 6 6 2 3 1.8208 1.82080 0.0000 0 0 0 1061.850 0.05643 12.000 0.6000 1.01900
7 1 7 6 2 4 0.0000 0.00000 0.0000 0 0 0 1057.080 0.00000 12.000 0.6000 1.00000
8 1 8 6 2 5 0.0000 0.00000 0.0000 0 0 0 1052.360 0.00000 12.000 0.6000 1.00000
9 1 3 5 3 1 10.0000 2.18710 7.8129 0 0 0 1075.550 1.90900 9.636 0.7227 1.51300
10 1 3 6 3 2 7.8129 1.36970 6.4432 0 0 0 1072.640 1.72700 8.909 0.6682 1.02500
11 1 3 7 3 3 6.4432 0.42730 6.0159 0 0 0 1069.870 1.55500 8.218 0.5547 0.38510
12 1 4 8 3 4 6.0159 0.61019 5.4057 0 0 0 1066.820 1.36400 7.455 0.6709 0.45480
13 1 5 8 3 5 5.4057 0.80307 4.6026 0 0 0 1063.620 1.16400 6.655 0.4991 0.80450
14 1 6 8 3 6 4.6026 0.45886 4.1438 0 0 0 1061.580 1.03600 6.145 0.1844 1.24400
15 1 5 10 4 1 10.0000 0.90525 9.0947 0 0 0 1078.350 0.62100 10.000 0.2500 1.20700
16 1 5 9 4 2 9.0947 1.78350 7.3113 0 0 0 1071.480 0.56700 10.000 0.5000 1.18900
17 1 6 8 4 3 7.3113 1.22570 6.0855 0 0 0 1063.680 0.50210 10.000 0.3500 1.16700
18 1 6 8 5 1 10.2290 -0.25525 10.4850 0 0 0 1058.680 0.67990 15.450 0.6180 -0.13770
19 1 7 7 5 2 10.4850 0.74225 9.7423 0 0 0 1054.170 0.67430 15.230 0.7616 0.32490
20 1 8 7 5 3 9.7423 1.04410 8.6982 0 0 0 1049.900 0.65280 14.410 0.5043 0.69010
21 1 8 6 5 4 8.6982 0.15040 8.5478 0 0 0 1046.890 0.63770 13.840 0.3460 0.14490
22 1 9 6 6 1 8.5478 1.37410 7.1736 0 0 0 1043.340 0.60990 12.000 0.6000 0.76340
23 1 10 7 6 2 7.1736 1.33000 5.8436 0 0 0 1038.730 0.54460 12.000 0.6000 0.73890
24 1 11 7 6 3 5.8436 1.21580 4.6279 0 0 0 1034.110 0.47790 12.000 0.6000 0.67540
25 1 12 7 6 4 4.6279 0.56873 4.0591 0 0 0 1029.520 0.42720 12.000 0.6000 0.31600
26 1 13 7 6 5 4.0591 -0.66107 4.7202 0 0 0 1026.340 0.43000 12.000 0.2400 -0.91810
27 1 14 9 7 1 150.0000 2.95600 147.0400 0 0 0 1039.860 2.28300 55.280 5.5910 0.17620
28 1 13 8 7 2 147.0400 4.05460 142.9900 0 0 0 1034.760 2.26500 55.190 6.1400 0.22010
29 1 13 7 7 3 142.9900 3.18190 139.8100 0 0 0 1029.660 2.24500 55.100 5.5720 0.19030
30 1 13 6 8 1 144.5300 1.56850 142.9600 0 0 0 1024.650 2.77200 40.200 4.0960 0.12760
31 1 13 5 8 2 142.9600 -1.25360 144.2100 0 0 0 1018.400 2.77100 40.180 4.0950 -0.10210
32 1 13 4 8 3 144.2100 -2.96590 147.1800 0 0 0 1012.160 2.78600 40.400 4.1170 -0.24010
33 1 13 3 8 4 147.1800 -3.05000 150.2300 0 0 0 1005.930 2.80800 40.710 4.1490 -0.24510
34 1 13 2 8 5 150.2300 -1.11190 151.3400 0 0 0 999.697 2.82200 40.920 4.1700 -0.08887
35 1 13 1 8 6 151.3400 0.41336 150.9300 0 0 0 994.700 2.82500 40.960 2.5040 0.05502

Plot streamflow and stream/aquifer interactions for a segment


In [20]:
inds = df.segment == 3
ax = df.ix[inds, ['Qin', 'Qaquifer', 'Qout']].plot(x=df.reach[inds])
ax.set_ylabel('Flow, in cubic feet per second')
ax.set_xlabel('SFR reach')


Out[20]:
<matplotlib.text.Text at 0x115fd75c0>

Look at stage, model top, and streambed top


In [21]:
streambed_top = m.sfr.segment_data[0][m.sfr.segment_data[0].nseg == 3][['elevup', 'elevdn']][0]
streambed_top


Out[21]:
(1075.0, 1060.0)

In [22]:
df['model_top'] = m.dis.top.array[df.row.values - 1, df.column.values -1]
fig, ax = plt.subplots()
plt.plot([1, 6], list(streambed_top), label='streambed top')
ax = df.ix[inds, ['stage', 'model_top']].plot(ax=ax, x=df.reach[inds])
ax.set_ylabel('Elevation, in feet')
plt.legend()


Out[22]:
<matplotlib.legend.Legend at 0x1160bde80>

Get SFR leakage results from cell budget file


In [23]:
bpth = os.path.join('data', 'test1ss.cbc')
cbbobj = bf.CellBudgetFile(bpth)
cbbobj.list_records()


(1, 1, b'              ET', 10, 15, 1, 0, 0.0, 0.0, 0.0)
(1, 1, b' HEAD DEP BOUNDS', 10, 15, 1, 0, 0.0, 0.0, 0.0)
(1, 1, b'  STREAM LEAKAGE', 10, 15, 1, 0, 0.0, 0.0, 0.0)

In [24]:
sfrleak = cbbobj.get_data(text='  STREAM LEAKAGE')[0]
sfrleak[sfrleak == 0] = np.nan # remove zero values

Plot leakage in plan view


In [25]:
im = plt.imshow(sfrleak[0], interpolation='none', cmap='coolwarm', vmin = -3, vmax=3)
cb = plt.colorbar(im, label='SFR Leakage, in cubic feet per second');


Plot total streamflow


In [26]:
sfrQ = sfrleak[0].copy()
sfrQ[sfrQ == 0] = np.nan
sfrQ[df.row.values-1, df.column.values-1] = df[['Qin', 'Qout']].mean(axis=1).values
im = plt.imshow(sfrQ, interpolation='none')
plt.colorbar(im, label='Streamflow, in cubic feet per second');



In [ ]: