Flopy3 Boundary Conditions

Flopy3 has a new way to enter boundary conditions for some MODFLOW packages. These changes are substantial. Boundary condtions can now be entered as a list of boundaries, as a numpy recarray, or as a dictionary. These different styles are described in this notebook.

Flopy3 also now requires zero-based input. This means that all boundaries are entered in zero-based layer, row, and column indicies. This means that older Flopy scripts will need to be modified to account for this change. If you are familiar with Python, this should be natural, but if not, then it may take some time to get used to zero-based numbering. Flopy3 users submit all information in zero-based form, and Flopy3 converts this to the one-based form required by MODFLOW.

The following MODFLOW packages are affected by this change:

  • Well
  • Drain
  • River
  • General-Head Boundary
  • Time-Variant Constant Head

This notebook explains the different ways to enter these types of boundary conditions.


In [1]:
#begin by importing flopy
import os
import sys
import numpy as np
flopypath = '../..'
if flopypath not in sys.path:
    sys.path.append(flopypath)
import flopy

workspace = os.path.join('data')
#make sure workspace directory exists
if not os.path.exists(workspace):
    os.makedirs(workspace)

List of Boundaries

Boundary condition information is passed to a package constructor as stress_period_data. In its simplest form, stress_period_data can be a list of individual boundaries, which themselves are lists. The following shows a simple example for a MODFLOW River Package boundary:


In [2]:
stress_period_data = [
                      [2, 3, 4, 10.7, 5000., -5.7],   #layer, row, column, stage conductance, river bottom
                      [2, 3, 5, 10.7, 5000., -5.7],   #layer, row, column, stage conductance, river bottom
                      [2, 3, 6, 10.7, 5000., -5.7],   #layer, row, column, stage conductance, river bottom
                     ]
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=stress_period_data)
m.write_input()

If we look at the River Package created here, you see that the layer, row, and column numbers have been increased by one.


In [3]:
!more 'data/test.riv'


=# RIV for MODFLOW, generated by Flopy.
         3         0
         3         0 # stress period 0
         3         4         5 10.700000 5000.000000 -5.700000
         3         4         6 10.700000 5000.000000 -5.700000
         3         4         7 10.700000 5000.000000 -5.700000
>

If this model had more than one stress period, then Flopy will assume that this boundary condition information applies until the end of the simulation


In [4]:
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
dis = flopy.modflow.ModflowDis(m, nper=3)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=stress_period_data)
m.write_input()
!more 'data/test.riv'


=# RIV for MODFLOW, generated by Flopy.
         3         0
         3         0 # stress period 0
         3         4         5 10.700000 5000.000000 -5.700000
         3         4         6 10.700000 5000.000000 -5.700000
         3         4         7 10.700000 5000.000000 -5.700000
        -1         0 # stress period 1
        -1         0 # stress period 2
>

Recarray of Boundaries

Numpy allows the use of recarrays, which are numpy arrays in which each column of the array may be given a different type. Boundary conditions can be entered as recarrays. Information on the structure of the recarray for a boundary condition package can be obtained from that particular package. The structure of the recarray is contained in the dtype.


In [5]:
riv_dtype = flopy.modflow.ModflowRiv.get_default_dtype()
print riv_dtype


[('k', '<i8'), ('i', '<i8'), ('j', '<i8'), ('stage', '<f4'), ('cond', '<f4'), ('rbot', '<f4')]

Now that we know the structure of the recarray that we want to create, we can create a new one as follows.


In [6]:
stress_period_data = np.zeros((3), dtype=riv_dtype)
stress_period_data = stress_period_data.view(np.recarray)
print 'stress_period_data: ', stress_period_data
print 'type is: ', type(stress_period_data)


stress_period_data:  [(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)]
type is:  <class 'numpy.core.records.recarray'>

We can then fill the recarray with our boundary conditions.


In [7]:
stress_period_data[0] = (2, 3, 4, 10.7, 5000., -5.7)
stress_period_data[1] = (2, 3, 5, 10.7, 5000., -5.7)
stress_period_data[2] = (2, 3, 6, 10.7, 5000., -5.7)
print stress_period_data


[(2, 3, 4, 10.699999809265137, 5000.0, -5.699999809265137)
 (2, 3, 5, 10.699999809265137, 5000.0, -5.699999809265137)
 (2, 3, 6, 10.699999809265137, 5000.0, -5.699999809265137)]

In [8]:
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=stress_period_data)
m.write_input()
!more 'data/test.riv'


=# RIV for MODFLOW, generated by Flopy.
         3         0
         3         0 # stress period 0
         3         4         5 10.700000 5000.000000 -5.700000
         3         4         6 10.700000 5000.000000 -5.700000
         3         4         7 10.700000 5000.000000 -5.700000
>

As before, if we have multiple stress periods, then this recarray will apply to all of them.


In [9]:
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
dis = flopy.modflow.ModflowDis(m, nper=3)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=stress_period_data)
m.write_input()
!more 'data/test.riv'


=# RIV for MODFLOW, generated by Flopy.
         3         0
         3         0 # stress period 0
         3         4         5 10.700000 5000.000000 -5.700000
         3         4         6 10.700000 5000.000000 -5.700000
         3         4         7 10.700000 5000.000000 -5.700000
        -1         0 # stress period 1
        -1         0 # stress period 2
>

Dictionary of Boundaries

The power of the new functionality in Flopy3 is the ability to specify a dictionary for stress_period_data. If specified as a dictionary, the key is the stress period number (as a zero-based number), and the value is either a nested list, an integer value of 0 or -1, or a recarray for that stress period.

Let's say that we want to use the following schedule for our rivers:

  1. No rivers in stress period zero
  2. Rivers specified by a list in stress period 1
  3. No rivers
  4. No rivers
  5. No rivers
  6. Rivers specified by a recarray
  7. Same recarray rivers
  8. Same recarray rivers
  9. Same recarray rivers

In [10]:
sp1 = [
       [2, 3, 4, 10.7, 5000., -5.7],   #layer, row, column, stage conductance, river bottom
       [2, 3, 5, 10.7, 5000., -5.7],   #layer, row, column, stage conductance, river bottom
       [2, 3, 6, 10.7, 5000., -5.7],   #layer, row, column, stage conductance, river bottom
      ]
print sp1


[[2, 3, 4, 10.7, 5000.0, -5.7], [2, 3, 5, 10.7, 5000.0, -5.7], [2, 3, 6, 10.7, 5000.0, -5.7]]

In [11]:
riv_dtype = flopy.modflow.ModflowRiv.get_default_dtype()
sp5 = np.zeros((3), dtype=riv_dtype)
sp5 = sp5.view(np.recarray)
sp5[0] = (2, 3, 4, 20.7, 5000., -5.7)
sp5[1] = (2, 3, 5, 20.7, 5000., -5.7)
sp5[2] = (2, 3, 6, 20.7, 5000., -5.7)
print sp5


[(2, 3, 4, 20.700000762939453, 5000.0, -5.699999809265137)
 (2, 3, 5, 20.700000762939453, 5000.0, -5.699999809265137)
 (2, 3, 6, 20.700000762939453, 5000.0, -5.699999809265137)]

In [12]:
sp_dict = {0:0, 1:sp1, 2:0, 5:sp5}
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
dis = flopy.modflow.ModflowDis(m, nper=8)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=sp_dict)
m.write_input()
!more 'data/test.riv'


=# RIV for MODFLOW, generated by Flopy.
         3         0
         0         0 # stress period 0
         3         0 # stress period 1
         3         4         5 10.700000 5000.000000 -5.700000
         3         4         6 10.700000 5000.000000 -5.700000
         3         4         7 10.700000 5000.000000 -5.700000
         0         0 # stress period 2
        -1         0 # stress period 3
        -1         0 # stress period 4
         3         0 # stress period 5
         3         4         5 20.700001 5000.000000 -5.700000
         3         4         6 20.700001 5000.000000 -5.700000
         3         4         7 20.700001 5000.000000 -5.700000
        -1         0 # stress period 6
        -1         0 # stress period 7
>

MODFLOW Auxiliary Variables

Flopy3 works with MODFLOW auxiliary variables by allowing the recarray to contain additional columns of information. The auxiliary variables must be specified as package options as shown in the example below.

In this example, we also add a string in the last column of the list in order to name each boundary condition. In this case, however, we do not include boundname as an auxiliary variable as MODFLOW would try to read it as a floating point number.


In [13]:
#create an empty array with an iface auxiliary variable at the end
riva_dtype = [('k', '<i8'), ('i', '<i8'), ('j', '<i8'), 
              ('stage', '<f4'), ('cond', '<f4'), ('rbot', '<f4'), 
              ('iface', '<i4'), ('boundname', object)]
riva_dtype = np.dtype(riva_dtype)
stress_period_data = np.zeros((3), dtype=riva_dtype)
stress_period_data = stress_period_data.view(np.recarray)
print 'stress_period_data: ', stress_period_data
print 'type is: ', type(stress_period_data)


stress_period_data:  [(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)]
type is:  <class 'numpy.core.records.recarray'>

In [14]:
stress_period_data[0] = (2, 3, 4, 10.7, 5000., -5.7, 1, 'riv1')
stress_period_data[1] = (2, 3, 5, 10.7, 5000., -5.7, 2, 'riv2')
stress_period_data[2] = (2, 3, 6, 10.7, 5000., -5.7, 3, 'riv3')
print stress_period_data


[(2, 3, 4, 10.699999809265137, 5000.0, -5.699999809265137, 1, 'riv1')
 (2, 3, 5, 10.699999809265137, 5000.0, -5.699999809265137, 2, 'riv2')
 (2, 3, 6, 10.699999809265137, 5000.0, -5.699999809265137, 3, 'riv3')]

In [15]:
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=stress_period_data, dtype=riva_dtype, options=['aux iface'])
m.write_input()
!more 'data/test.riv'


=# RIV for MODFLOW, generated by Flopy.
         3         0 aux iface
         3         0 # stress period 0
         3         4         5 10.700000 5000.000000 -5.700000         1 riv1
         3         4         6 10.700000 5000.000000 -5.700000         2 riv2
         3         4         7 10.700000 5000.000000 -5.700000         3 riv3
>

Working with Unstructured Grids

Flopy3 can create an unstructured grid boundary condition package for MODFLOW-USG. This can be done by specifying a custom dtype for the recarray. The following shows an example of how that can be done.


In [16]:
#create an empty array based on nodenumber instead of layer, row, and column
rivu_dtype = [('nodenumber', '<i8'), ('stage', '<f4'), ('cond', '<f4'), ('rbot', '<f4')]
rivu_dtype = np.dtype(rivu_dtype)
stress_period_data = np.zeros((3), dtype=rivu_dtype)
stress_period_data = stress_period_data.view(np.recarray)
print 'stress_period_data: ', stress_period_data
print 'type is: ', type(stress_period_data)


stress_period_data:  [(0, 0.0, 0.0, 0.0) (0, 0.0, 0.0, 0.0) (0, 0.0, 0.0, 0.0)]
type is:  <class 'numpy.core.records.recarray'>

In [17]:
stress_period_data[0] = (77, 10.7, 5000., -5.7)
stress_period_data[1] = (245, 10.7, 5000., -5.7)
stress_period_data[2] = (450034, 10.7, 5000., -5.7)
print stress_period_data


[(77, 10.699999809265137, 5000.0, -5.699999809265137)
 (245, 10.699999809265137, 5000.0, -5.699999809265137)
 (450034, 10.699999809265137, 5000.0, -5.699999809265137)]

In [18]:
m = flopy.modflow.Modflow(modelname='test', model_ws=workspace)
riv = flopy.modflow.ModflowRiv(m, stress_period_data=stress_period_data, dtype=rivu_dtype)
m.write_input()
print workspace
!more 'data/test.riv'


data
=# RIV for MODFLOW, generated by Flopy.
         3         0
         3         0 # stress period 0
        77 10.700000 5000.000000 -5.700000
       245 10.700000 5000.000000 -5.700000
    450034 10.700000 5000.000000 -5.700000
>

In [18]: