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 modelgwt6- constituent transport modelgwe6- 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'>