Inspect MODFLOW 6 interactively

pymf6 allows to access all MODFLOW 6 variables at run time. First, we import the class MF6:

from pymf6.mf6 import MF6

We use a simple model:

sim_path = 'examples/head_controlled_well/models/pymf6'

and instantiate the class:

mf6 = MF6(sim_path=sim_path)

The instance has an HTML representation with some meta data:

mf6

MF6

pymf6 configuration data

pymf6 version:1.5.3.
xmipy version:1.3.1
modflowapi version:0.3.0.dev0
ini file path:HOME/pymf6.ini
dll file path:path/to/dll/libmf6.dll
MODFLOW version:6.7.0.dev2

MODFLOW Fortan variable documentation is NOT available.

The same information is available as text:

mf6.info
========================
pymf6 configuration data
========================
pymf6 version: 1.5.3.
xmipy version: 1.3.1
modflowapi version: 0.3.0.dev0
ini file path: HOME/pymf6.ini
dll file path: path/to/dll/libmf6.dll
MODFLOW version: 6.7.0.dev2
MODFLOW Fortan variable documentation is NOT available.

MODFLOW 6 supports multiple simulations. This example only has one:

mf6.simulation
modeltype namefile modelname
gwf6 headconwell.nam HEADCONWELL

The names of the simulations are available as list of strings:

mf6.simulation.model_names
['HEADCONWELL']

The models have their own representations:

mf6.simulation.models
[Model HEADCONWELL 
 15 packages
 49 variables.]

The meta information is also available as a dictionary:

mf6.simulation.models_meta
[{'modeltype': 'gwf6',
  'namefile': 'headconwell.nam',
  'modelname': 'HEADCONWELL'}]

This example has only one solution group:

mf6.simulation.solution_groups
[Solution 1 
 1 packages
 60 variables.]

This is the time discretization is:

mf6.simulation.TDIS

Package TDIS

number of variables: 20

These are the names of the variables:

mf6.simulation.TDIS.var_names
['PERTIMSAV',
 'TOPERTIM',
 'ENDOFSIMULATION',
 'TOTIMSAV',
 'PERTIM',
 'ITMUNI',
 'KSTP',
 'PERLEN',
 'TOTIM',
 'ENDOFPERIOD',
 'NPER',
 'DELTSAV',
 'TOTALSIMTIME',
 'TSMULT',
 'INATS',
 'TOTIMC',
 'DELT',
 'NSTP',
 'READNEWDATA',
 'KPER']

There are scalar values:

mf6.simulation.TDIS.NPER

Variable NPER

value: np.int32(4)

and arrays:

mf6.simulation.TDIS.PERLEN

Variable PERLEN

value: array([ 1., 10., 10., 10.])
shape: (4,)

The docstrings are extracted from the MODFLOW 6 source code from the used MODFLOW version as displayed with mf6.info.

The instance has many variables:

len(mf6.vars)
587

Filter for all TDIS entries:

{k: v for k, v in mf6.vars.items() if 'TDIS' in k}
{'TDIS/PERTIMSAV': array([0.]),
 'TDIS/TOPERTIM': array([0.]),
 'TDIS/TOTIMSAV': array([0.]),
 'TDIS/PERTIM': array([1.]),
 '__INPUT__/SIM/TDIS/NSTP': array([  1, 120, 120, 120], dtype=int32),
 'TDIS/ITMUNI': array([4], dtype=int32),
 'TDIS/KSTP': array([1], dtype=int32),
 'TDIS/PERLEN': array([ 1., 10., 10., 10.]),
 'TDIS/TOTIM': array([1.]),
 'TDIS/NPER': array([4], dtype=int32),
 'TDIS/DELTSAV': array([0.]),
 'TDIS/TOTALSIMTIME': array([31.]),
 '__INPUT__/SIM/TDIS/PERLEN': array([ 1., 10., 10., 10.]),
 'TDIS/TSMULT': array([1., 1., 1., 1.]),
 'TDIS/INATS': array([0], dtype=int32),
 'TDIS/TOTIMC': array([0.]),
 'TDIS/DELT': array([1.]),
 'TDIS/NSTP': array([  1, 120, 120, 120], dtype=int32),
 '__INPUT__/SIM/TDIS/TSMULT': array([1., 1., 1., 1.]),
 '__INPUT__/SIM/TDIS/NPER': array([4], dtype=int32),
 'TDIS/KPER': array([1], dtype=int32)}

Show the first 20:

dict(list(mf6.vars.items())[:20])
{'SLN_1/NCOL': array([100], dtype=int32),
 'HEADCONWELL/CSUB/ISCLOC': array([0], dtype=int32),
 'SLN_1/IMSLINEAR/ID': array([0, 0, 0, ..., 0, 0, 0], dtype=int32),
 'HEADCONWELL/NPF/IK33': array([1], dtype=int32),
 'HEADCONWELL/NPF/IDEWATCV': array([0], dtype=int32),
 'HEADCONWELL/CSUB/INAMEDBOUND': array([0], dtype=int32),
 '__INPUT__/HEADCONWELL/STO/ICONVERT': array([1, 1, 1, ..., 1, 1, 1], dtype=int32),
 'HEADCONWELL/INHFB': array([0], dtype=int32),
 'TDIS/PERTIMSAV': array([0.]),
 'HEADCONWELL/MYWELL/NCOLBND': array([0], dtype=int32),
 'HEADCONWELL/VSC/THERMIVISC': array([0], dtype=int32),
 'HEADCONWELL/VSC/ICONCSET': array([0], dtype=int32),
 'SLN_1/IMSLINEAR/WLU': array([0.]),
 'HEADCONWELL/IC/LASTONPER': array([0], dtype=int32),
 'HEADCONWELL/MVR/IBUDCSV': array([0], dtype=int32),
 'SLN_1/IMSLINEAR/IW': array([0, 0, 0, ..., 0, 0, 0], dtype=int32),
 'SLN_1/IMSLINEAR/ARO': array([0.]),
 'TDIS/TOPERTIM': array([0.]),
 'HEADCONWELL/VSC/IOUT': array([1012], dtype=int32),
 'HEADCONWELL/VSC/LASTONPER': array([0], dtype=int32)}

A simulation can have several models types. These include:

  • gwf6 - flow model

  • gwt6 - constituent transport model

  • gwe6 - energy transport model

They are available as a dictionary:

mf6.models.keys()
dict_keys(['gwf6'])

The example has only a flow models. Let’s get them:

flow_models = mf6.models['gwf6']

MODFLOW supports multiple flow models. An example is the nesting an inner model with finer discretization into an outer model withcoarse discritization. Let’s get the flow model names:

flow_models.keys()
dict_keys(['headconwell'])

This simple simulation has onyl one flow model. We get a reference to thsi model:

gwf = flow_models['headconwell']

We can get a list if all available attributes, removing all names beginning with an underscore because they are internal names or special methods:

[attr for attr in dir(gwf) if not attr.startswith('_')]
['X',
 'allow_convergence',
 'dis_name',
 'dis_type',
 'get_package',
 'kper',
 'kstp',
 'mf6',
 'name',
 'nodetouser',
 'nper',
 'nstp',
 'package_dict',
 'package_list',
 'package_names',
 'package_types',
 'packages',
 'shape',
 'size',
 'solution_id',
 'subcomponent_id',
 'totim',
 'usertonode']

All attributes are available via tab completion, i.e. typing <TAB> after the dot will show the list of available attributes and will narrow it down to match typed characters:

gwf.nper
np.int32(4)

The packages of a model are also available:

gwf.packages
description is_mutable
name
dis DIS Package: DIS False
hfb HFB Package: HFB True
csub CSUB Package: CSUB True
mywell WEL Package: MYWELL True
mvr MVR Package: MVR True
ic IC Package: IC False
vsc VSC Package: VSC True
chd_0 CHD Package: CHD_0 True
buy BUY Package: BUY True
gnc GNC Package: GNC True
sto STO Package: STO False
npf NPF Package: NPF False

Model packages

Packages can be accessed via model.packages.package_name. If package_name is not a valid Python identifier, access with pandas logic model.packages.loc['package_name']. Packages that are mutable (is_mutable is True) can be converted in a mutable boundary condition with .as_mutable_bc().

The well boundary condition is potentially mutable. Let’s try to get a mutable version:

gwf.packages.mywell.as_mutable_bc()
ValueError: No values yet.
Boundary condition MYWELL does not have any values for current stress periods.
Advance to a stress period with values and call this function again.

This doesn’t work yet, because there is no boundary condition in the first, steady-state, stress period.

We create a reference to the model loop:

loop = mf6.model_loop()

and progress to the start of the second stress period:

for model_step in loop:
    if gwf.kper > 0:
        break

Note: Remember that the stress period count is zero-based. Therefore, the stress period with the index 0 is the first.

Now, we are at the beginning of the second stress period:

model_step.state
<States.stress_period_start: 1>

and can create a mutable version of our well boundary conditions:

gwf.packages.chd_0.as_mutable_bc()
nodelist head
0 (0, 0, 0) 1.0
1 (0, 9, 9) 1.0
mywell = gwf.packages.mywell.as_mutable_bc()

There is one well in the middle of the model (1st layer, 5th row, 5th column) with a pumping rate of 0.05 $m^3/s$:

MF6?
Init signature:
MF6(
    sim_path,
    dll_path=None,
    use_modflow_api=True,
    advance_first_step=True,
    verbose=False,
    new_step_only=False,
    do_solution_loop=True,
    _develop=False,
)
Docstring:     
Wrapper around XmiWrapper and modflowapi.

`advance_first_step = True` progresses to the first model step with
model time > 0. This is needed to access any internal values of BCs.
File:           ~/Dev/pymf6/src/pymf6/mf6.py
Type:           type
Subclasses:     

We can progress to the next stress period:

for model_step in loop:
    if gwf.kper > 1:
        break

which has a different pumping rate:

mywell
nodelist q
0 (0, 4, 4) -0.5

We can modify the pumping rate. Let’s make it 10% higher:

mywell.q *= 1.1

The changes are written back into MODFLOW simulation and will be used until for the calculations until MODFLOW reads new values for this boundary condition. This happens at the start of the next stress period or when new time series values are read.

mywell
nodelist q
0 (0, 4, 4) -0.55
model_step.state
<States.stress_period_start: 1>
model_step.simulation_group.model_names
['headconwell']
model_step.simulation_group.kper
np.int32(2)
gwf.totim
np.float64(11.083333333333334)
gwf
HEADCONWELL, 1 Layer, 10 Row, 10 Column model
Packages accessible include: 
  ArrayPackage objects:
    dis: <class 'modflowapi.extensions.pakbase.ApiDisPackage'>
    ic: <class 'modflowapi.extensions.pakbase.ApiIcPackage'>
    sto: <class 'modflowapi.extensions.pakbase.ApiStoPackage'>
    npf: <class 'modflowapi.extensions.pakbase.ApiNpfPackage'>
  ListPackage objects:
    mywell: <class 'modflowapi.extensions.pakbase.ApiWelPackage'>
    chd_0: <class 'modflowapi.extensions.pakbase.ApiChdPackage'>
  AdvancedPackage objects:
    hfb: <class 'modflowapi.extensions.pakbase.ApiHfbPackage'>
    csub: <class 'modflowapi.extensions.pakbase.ApiCsubPackage'>
    mvr: <class 'modflowapi.extensions.pakbase.ApiMvrPackage'>
    vsc: <class 'modflowapi.extensions.pakbase.ApiVscPackage'>
    buy: <class 'modflowapi.extensions.pakbase.ApiBuyPackage'>
    gnc: <class 'modflowapi.extensions.pakbase.ApiGncPackage'>