Create MODFLOW 6 Model with FloPy

import flopy
import numpy as np

Model names and units

length_units = 'meters'
time_units = 'days'
sim_name = 'cat_ex_1d'
gwf_name = f'gwf_{sim_name}'
gwt_name = f'gwt_{sim_name}'
sim_ws = '../rtmf6/mf6'
concentration_name = 'concentration' # name for concentration
rtmf6_sol_number_name = 'rtmf6_sol_number' # solution number of PHREEQC solution

Make FloPy simulation

sim = flopy.mf6.MFSimulation(sim_name=sim_name, sim_ws=sim_ws)

Time discretization

Number of periods

nper = 1  

Time step (\(days\))

tstep = 0.002 * 0.5

Simulation time (\(days\))

perlen = 0.24 

Number of time steps

nstp = perlen / tstep
nstp
240.0

Create MF6 package

tdis = flopy.mf6.ModflowTdis(
    sim, 
    nper=nper, 
    perioddata=[(perlen, nstp, 1.0)], 
    time_units=time_units)

Flow Model

gwf = flopy.mf6.ModflowGwf(
    sim,
    modelname=gwf_name,
    save_flows=True,
    model_nam_file=f"{gwf_name}.nam",
)

Solver for flow model

# solver parameters
nouter, ninner = 300, 600
hclose, rclose, relax = 1e-6, 1e-6, 1.0


imsgwf = flopy.mf6.ModflowIms(
    sim,
    complexity="complex",
    print_option="SUMMARY",
    outer_dvclose=hclose,
    outer_maximum=nouter,
    under_relaxation="NONE",
    inner_maximum=ninner,
    inner_dvclose=hclose,
    rcloserecord=rclose,
    linear_acceleration="CG",
    scaling_method="NONE",
    reordering_method="NONE",
    relaxation_factor=relax,
    filename=f"{gwf_name}.ims",
)
sim.register_ims_package(imsgwf, [gwf.name])

Model discretization

nlay = 1  # Number of layers
Lx = 0.08 #m Length of domain in x-direction (ncol * delr)
ncol = 40 # Number of columns
nrow = 1  # Number of rows
delr = Lx/ncol # Column spacing along a row ($m$)
dis = flopy.mf6.ModflowGwfdis(
    gwf,
    length_units=length_units,
    nlay=nlay,
    nrow=nrow,
    ncol=ncol,
    delr=delr,
    filename=f"{gwf_name}.dis",
    nogrb=True,
)

Hydraulic properties

k11 = 1.0  # Horizontal hydraulic conductivity ($m/d$)
k33 = k11  # Vertical hydraulic conductivity ($m/d$)
icelltype = 1 # saturated thickness varies with computed head when head is below the cell top
npf = flopy.mf6.ModflowGwfnpf(
    gwf,
    save_flows=True,
    save_saturation=True,
    icelltype=icelltype,
    k=k11,
    k33=k33,
    save_specific_discharge=True,
    filename=f"{gwf_name}.npf",
)

Initial conditions

flopy.mf6.ModflowGwfic(gwf, strt=1, filename=f"{gwf_name}.ic")
package_name = ic
filename = gwf_cat_ex_1d.ic
package_type = ic
model_or_simulation_package = model
model_name = gwf_cat_ex_1d

Block griddata
--------------------
strt
{constant 1}

Injection well

#injection
q = 1 #injection rate m3/d
concentration = 0  # will be replaced
rtmf6_sol_number = 0  # use solution from phreeqcrm/advect.pqi
wel_spd = [[(0, 0, 0), q, concentration, rtmf6_sol_number]] # well stress period data
auxiliary = [
    concentration_name, # name for concentration
    rtmf6_sol_number_name # solution number of PHREEQC solution
] 
wel = flopy.mf6.ModflowGwfwel(
        gwf,
        stress_period_data=wel_spd,
        save_flows=True,
        auxiliary=auxiliary,
        pname='wel',
        filename=f"{gwf_name}.wel"
    )

Constant Head BC

Keep head in last column constant.

Note the zero-based counting.

head = 1
chd_spd = [[(0, 0, ncol - 1), head]]
chd_spd
[[(0, 0, 39), 1]]
chd = flopy.mf6.ModflowGwfchd(
    gwf,
    maxbound=len(chd_spd),
    stress_period_data=chd_spd,
    save_flows=False,
    pname="CHD",
    filename=f"{gwf_name}.chd",
)

Output control of flow model

oc_gwf = flopy.mf6.ModflowGwfoc(
    gwf,
    head_filerecord=f"{gwf_name}.hds",
    budget_filerecord=f"{gwf_name}.cbb",
    headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")],
    saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
    printrecord=[("HEAD", "LAST"), ("BUDGET", "LAST")],
)

Transport model

gwt = flopy.mf6.MFModel(
    sim,
    model_type="gwt6",
    modelname=gwt_name,
    model_nam_file=f"{gwt_name}.nam"
)

Iterative model solution for transport

imsgwt = flopy.mf6.ModflowIms(
    sim,
    print_option="SUMMARY",
    outer_dvclose=hclose,
    outer_maximum=nouter,
    under_relaxation="NONE",
    inner_maximum=ninner,
    inner_dvclose=hclose,
    rcloserecord=rclose,
    linear_acceleration="BICGSTAB",
    scaling_method="NONE",
    reordering_method="NONE",
    relaxation_factor=relax,
    filename=f"{gwt_name}.ims",
)
sim.register_ims_package(imsgwt, [gwt.name])

Model discretization for transport model

gwt_dis = flopy.mf6.ModflowGwtdis(
    gwt,
    length_units=length_units,
    nlay=nlay,
    nrow=nrow,
    ncol=ncol,
    delr=delr,
    filename=f"{gwt_name}.dis",
    nogrb=True,
)

Initial condition for transport

gwt_ic = flopy.mf6.ModflowGwtic(
    gwt, 
    strt=1,  # rtmf6 solution number
    filename=f"{gwt_name}.ic")

Source-sink mixing

sourcerecarray = ['wel', 'aux', concentration_name]

ssm = flopy.mf6.ModflowGwtssm(
    gwt, 
    sources=sourcerecarray, 
    save_flows=True,
    print_flows=True,
    filename=f"{gwt_name}.ssm"
)

Advection

adv = flopy.mf6.ModflowGwtadv(
    gwt,
    scheme="tvd",
)

Dispersion

Longitudinal dispersivity (\(m\))

dispersivity = 0.002

Transverse vertical dispersivity (\(m\))

transverse_vertical_dispersivity = dispersivity * 0.1
dsp = flopy.mf6.ModflowGwtdsp(
            gwt,
            xt3d_off=True,
            alh=dispersivity,
            ath1=transverse_vertical_dispersivity,
            atv=transverse_vertical_dispersivity, 
            filename=f"{gwt_name}.dsp",
        )

Transport mass storage

first_order_decay = None
porosity = 1


mst = flopy.mf6.ModflowGwtmst(
    gwt,
    porosity=porosity,
    first_order_decay=first_order_decay,
    filename=f"{gwt_name}.mst",
)

Add observation well for concentration

lay = 0
obs_dict = {
    f'{gwt_name}.obs.csv': 
    [(
        'concentration',
        concentration_name, 
        (lay, nrow // 2, ncol - 2)
     )
    ]
}
flopy.mf6.ModflowUtlobs(
    gwt, print_input=False, continuous=obs_dict
)
package_name = obs_0
filename = gwt_cat_ex_1d.obs
package_type = obs
model_or_simulation_package = model
model_name = gwt_cat_ex_1d

Block options
--------------------
print_input
{internal}
(False)


Block continuous
--------------------
continuous
{internal}
(rec.array([('concentration', 'concentration', (0, 0, 38), None)],
          dtype=[('obsname', 'O'), ('obstype', 'O'), ('id', 'O'), ('id2', 'O')]))

Output control package for transport

oc_gwt = flopy.mf6.ModflowGwtoc(
    gwt,
    budget_filerecord=f"{gwt_name}.cbb",
    concentration_filerecord=f"{gwt_name}.ucn",
    concentrationprintrecord=[
        ("COLUMNS", 10, "WIDTH", 15, "DIGITS", 10, "GENERAL")
    ],
    saverecord=[("CONCENTRATION", "ALL"), 
                ("BUDGET", "ALL")
                ],
    printrecord=[("CONCENTRATION", "ALL"), 
                 ("BUDGET", "ALL")
                    ],
)

Fow-transport exchange

flopy.mf6.ModflowGwfgwt(
    sim,
    exgtype="GWF6-GWT6",
    exgmnamea=gwf_name,
    exgmnameb=gwt_name,
    filename=f"{sim_name}.gwfgwt",
)
package_name = cat_ex_1d.gwfgwt
filename = cat_ex_1d.gwfgwt
package_type = gwfgwt
model_or_simulation_package = simulation
simulation_name = cat_ex_1d

Write simulation

sim.write_simulation()
writing simulation...
  writing simulation name file...
  writing simulation tdis package...
  writing solution package ims_-1...
  writing solution package ims_0...
  writing package cat_ex_1d.gwfgwt...
  writing model gwf_cat_ex_1d...
    writing model name file...
    writing package dis...
    writing package npf...
    writing package ic...
    writing package wel...
INFORMATION: maxbound in ('gwf6', 'wel', 'dimensions') changed to 1 based on size of stress_period_data
    writing package chd...
    writing package oc...
  writing model gwt_cat_ex_1d...
    writing model name file...
    writing package dis...
    writing package ic...
    writing package ssm...
    writing package adv...
    writing package dsp...
    writing package mst...
    writing package obs_0...
    writing package oc...