This page was generated from ex-gwt-uzt-2d.py. It's also available as a notebook.

Two-Dimensional Test of Unsaturated-Saturated Transport

This problem tests transport in the unsaturated zone. This example originally appeared in Morway et al (2013).

Initial setup

Import dependencies, define the example name and workspace, and read settings from environment variables.

[1]:
import os
import pathlib as pl
from pprint import pformat

import flopy
import git
import matplotlib.pyplot as plt
import numpy as np
from flopy.plot.styles import styles
from modflow_devtools.misc import get_env, timed

# Example name and workspace paths. If this example is running
# in the git repository, use the folder structure described in
# the README. Otherwise just use the current working directory.
try:
    root = pl.Path(git.Repo(".", search_parent_directories=True).working_dir)
except:
    root = None
workspace = root / "examples" if root else pl.Path.cwd()
figs_path = root / "figures" if root else pl.Path.cwd()

# Settings from environment variables
write = get_env("WRITE", True)
run = get_env("RUN", True)
plot = get_env("PLOT", True)
plot_show = get_env("PLOT_SHOW", True)
plot_save = get_env("PLOT_SAVE", True)

Define parameters

Define model units, parameters and other settings.

[2]:
# Set scenario parameters (make sure there is at least one blank line before next item)
# This entire dictionary is passed to _build_models()_ using the kwargs argument
parameters = {
    "ex-gwt-uzt-2d-a": {
        "longitudinal_dispersivity": [0.5],
        "ratio_horizontal_to_longitudinal_dispersivity": [0.4],
        "ratio_vertical_to_longitudinal_dispersivity": [0.4],
    },
    "ex-gwt-uzt-2d-b": {
        "longitudinal_dispersivity": [
            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.5,
            0.5,
            0.5,
            0.5,
            0.5,
            0.5,
            0.5,
            0.5,
        ],
        "ratio_horizontal_to_longitudinal_dispersivity": [
            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.4,
            0.4,
            0.4,
            0.4,
            0.4,
            0.4,
            0.4,
            0.4,
        ],
        "ratio_vertical_to_longitudinal_dispersivity": [
            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.4,
            0.4,
            0.4,
            0.4,
            0.4,
            0.4,
            0.4,
            0.4,
        ],
    },
}

# Scenario parameter units
#
# add parameter_units to add units to the scenario parameter table that is automatically
# built and used by the .tex input
parameter_units = {
    "longitudinal_dispersivity": "$m$",
    "ratio_horizontal_to_longitudinal_dispersivity": "unitless",
}

# Model units
length_units = "meters"
time_units = "days"

# Model parameters
nlay = 20  # Number of layers
nrow = 1  # Number of rows
ncol = 40  # Number of columns
delr = 0.25  # Column width ($m$)
delc = 0.25  # Row width ($m$)
delz = 0.25  # Layer thickness ($m$)
top = 5.0  # Top of the model ($m$)
k1 = 2.5  # Horizontal hydraulic conductivity ($m/day$)
vk = 0.5  # Vertical hydraulic conductivity ($m/day$)
Ss = 1e-5  # Aquifer storativity
sy = 0.35  # Specific yield
thtr = 0.1  # Residual water content
thts = 0.45  # Saturated water content
thti = 0.105  # Initial water content
eps = 4.0  # Brooks-Corey Epsilon
finf = 0.1  # Infiltration rate ($m/d$)
cfinf = 1.0  # Concentration of recharge in select cells($ppm$)
prsity = 0.45  # Porosity
al = 0.5  # Longitudinal dispersivity ($ft$)
trpt = 0.4  # Ratio of horizontal transverse dispersivity to longitudinal dispersivity
trpv = 0.4  # Ratio of vertical transverse dispersivity to longitudinal dispersivity
perlen = 60.0  # Simulation time ($days$)

# Additional model input
botm = [top - delz * k for k in range(1, nlay + 1)]
perlen = [perlen]
nper = len(perlen)
nstp = [120]
tsmult = [1.0]
laytyp = icelltype = 1
iconvert = 1
# Hydraulic Conductivity
hk = k1 * np.ones((nlay, nrow, ncol), dtype=float)
hk33 = vk * np.ones((nlay, nrow, ncol), dtype=float)
# Starting Heads:
s0 = np.empty((nrow * ncol), dtype=float)
strt = np.zeros((nlay, nrow, ncol), dtype=float)
for k in range(nlay):
    strt[k] = s0

# Active model domain
ibound = np.ones((nlay, nrow, ncol), dtype=int)
ibound_mf2k5 = ibound.copy()
ibound_mf2k5[0:15, 0, 0] = 0
ibound_mf2k5[0:15, 0, -1] = 0
ibound_mf2k5[0:11, 0, :] = 0  # upper lays w/ UZF don't need to be active
# ibound_mf2k5[ 0:11, 0, 0:3] = ibound_mf2k5[ 0:11, 0, 37:40] =  0
ibound_mf2k5[13:20, 0, 0] = -1
ibound_mf2k5[13:20, 0, -1] = -1
idomain = np.ones_like(ibound)
idomain[0:13, 0, 0] = idomain[0:13, 0, -1] = 0
strt = np.ones((nlay, nrow, ncol), dtype=float) * 1.625
sconc = 0.0

# UZF related boundary conditions
nuztop = 3
iuzfopt = 2
irunflg = 0
ietflg = 0
iuzfcb1 = 0
iuzfcb2 = 0
ntrail2 = 25
nsets2 = 80
iuzfbnd = np.ones((nrow, ncol), dtype=int)
iuzfbnd[0, 0] = iuzfbnd[0, ncol - 1] = 0
# Fixed properties
surfdep = 0.00001
vks = k1
thtr = 0.1
thts = 0.45
thti = 0.105
eps = 4.0
# UZF boundary stresses
finf_mfnwt = np.ones((nrow, ncol), dtype=float) * finf
finf_mfnwt[0, 0] = finf_mfnwt[0, ncol - 1] = 0  # Shut off the outer cells
pet = 0.0
extdp = 0.0
extwc = 0.0
ha = 0.0
hroot = 0.0
rootact = 0.0
packagedata = []
uztpackagedata = []
pd0 = []
for k in range(nlay):
    for j in range(1, ncol - 1):  # outside columns are not UZF cells
        iuzno = k * (ncol - 2) + (j - 1)  # in flopy, iuzno is 0-based
        if k == 0:
            lflag = 1
            surfdep = 0.1
        else:
            lflag = 0
            surfdep = 0.001
        ivertcon = iuzno + len(range(1, ncol - 1))
        # adjust if on bottom layer (no underlying conn.)
        # remember, setting ivertcon=0, which means there is no vertical
        # connection results in vertical connection being equal to 1 in the
        # model input...SET equal to -1 to compensate
        if k == nlay - 1:
            ivertcon = -1
        # <iuzno> <cellid(ncelldim)> <landflag> <ivertcon> <surfdep> <vks> <thtr> <thts> <thti> <eps> [<boundname>]
        uz = [
            iuzno,
            (k, 0, j),
            lflag,
            ivertcon,
            surfdep,
            vk,
            thtr,
            thts,
            thti,
            eps,
        ]
        packagedata.append(uz)

        # now some transport book keeping
        transp = (iuzno, 0.0)  # sets the starting concentration
        uztpackagedata.append(transp)

        # Enter boundary conditions only for land surface cells
        if lflag:
            pd0.append((iuzno, finf, pet, extdp, extwc, ha, hroot, rootact))

nuzfcells = len(packagedata)
uzf_perioddata = {0: pd0}

# Transport related
mixelm = -1  # -1: TVD; 0: FD
dmcoef = 0.0  # ft^2/day

# Solver settings
nouter, ninner = 100, 300
hclose, rclose, relax = 1e-6, 1e-6, 1.0
percel = 1.0
itrack = 3
wd = 0.5
dceps = 1.0e-5
nplane = 0
npl = 0
nph = 10
npmin = 2
npmax = 20
dchmoc = 1.0e-3
nlsink = nplane
npsink = nph

Model setup

Define functions to build models, write input files, and run the simulation.

[3]:
def build_models(
    sim_name,
    mixelm=0,
    longitudinal_dispersivity=[0.5],
    ratio_horizontal_to_longitudinal_dispersivity=[0.4],
    ratio_vertical_to_longitudinal_dispersivity=[0.4],
    silent=False,
):
    print(f"Building mf-nwt model...{sim_name}")
    model_ws = os.path.join(workspace, sim_name, "mt3d")
    modelname_mf = "uzt-2d-mf"

    # Instantiate the MODFLOW model
    mf = flopy.modflow.Modflow(
        modelname=modelname_mf,
        model_ws=model_ws,
        exe_name="mfnwt",
        version="mfnwt",
    )

    # Instantiate discretization package
    # units: itmuni=4 (days), lenuni=2 (m)
    flopy.modflow.ModflowDis(
        mf,
        nlay=nlay,
        nrow=nrow,
        ncol=ncol,
        delr=delr,
        delc=delc,
        top=top,
        nper=nper,
        botm=botm,
        perlen=perlen,
        nstp=nstp,
        steady=[False],
        itmuni=4,
        lenuni=2,
    )

    # Instantiate basic package
    flopy.modflow.ModflowBas(mf, ibound=ibound_mf2k5, strt=strt)

    # Instantiate layer property flow package
    flopy.modflow.ModflowUpw(mf, hk=k1, vka=vk, sy=sy, ss=Ss, laytyp=laytyp)

    # Instantiate unsaturated-zone flow package
    flopy.modflow.ModflowUzf1(
        mf,
        nuztop=nuztop,
        iuzfopt=iuzfopt,
        irunflg=irunflg,
        ietflg=ietflg,
        ipakcb=iuzfcb1,
        iuzfcb2=iuzfcb2,
        ntrail2=ntrail2,
        nsets=nsets2,
        surfdep=0.1,
        iuzfbnd=iuzfbnd,
        eps=eps,
        thts=thts,
        thti=thti,
        thtr=thtr,
        finf=finf_mfnwt,
        specifythti=True,
        specifythtr=True,
    )

    # Instantiate output control (OC) package
    flopy.modflow.ModflowOc(
        mf,
        # stress_period_data={(0, nstp[0] - 1): ["save head", "save budget"]}
        stress_period_data={(0, nstp[0] - 1): ["save head", "save budget"]},
    )

    # Instantiate solver package
    flopy.modflow.ModflowNwt(mf)

    # Instantiate link mass transport package (for writing linker file)
    flopy.modflow.ModflowLmt(mf, package_flows=["UZF"])

    # Transport
    print(f"Building mt3d-usgs model...{sim_name}")

    modelname_mt = "uzt-2d-mt"
    mt = flopy.mt3d.Mt3dms(
        modelname=modelname_mt,
        model_ws=model_ws,
        exe_name="mt3dusgs",
        modflowmodel=mf,
        version="mt3d-usgs",
    )

    # Instantiate basic transport package
    flopy.mt3d.Mt3dBtn(
        mt,
        icbund=1,
        prsity=prsity,
        sconc=sconc,
        nper=nper,
        perlen=perlen,
        timprs=np.arange(1, 121),
        dt0=0.05,
    )

    # Instatiate the advection package
    flopy.mt3d.Mt3dAdv(
        mt,
        mixelm=mixelm,
        dceps=dceps,
        nplane=nplane,
        npl=npl,
        nph=nph,
        npmin=npmin,
        npmax=npmax,
        nlsink=nlsink,
        npsink=npsink,
        percel=percel,
        itrack=itrack,
        wd=wd,
    )

    # Instantiate the dispersion package
    if len(longitudinal_dispersivity) > 1:
        disp = np.ones_like(ibound_mf2k5)
        trpt = []
        trpv = []
        for i, (dispx, ratio1, ratio2) in enumerate(
            zip(
                longitudinal_dispersivity,
                ratio_horizontal_to_longitudinal_dispersivity,
                ratio_vertical_to_longitudinal_dispersivity,
            )
        ):
            disp[i, :, :] = dispx
            trpt.append(ratio1)
            trpv.append(ratio2)
        trpt = np.array(trpt)
        trpv = np.array(trpv)
    else:
        # Dispersion
        disp = longitudinal_dispersivity[0]
        trpt = ratio_horizontal_to_longitudinal_dispersivity[0]
        trpv = ratio_vertical_to_longitudinal_dispersivity[0]
    flopy.mt3d.Mt3dDsp(mt, al=disp, trpt=trpt, trpv=trpv, dmcoef=dmcoef)

    # Instantiate source/sink mixing package; -1 below indicates constant
    # concentration boundary condition (the form of this input is specific
    # to MT3DMS and doesn't carry over to MF6)
    cnc0_left = [(k, 0, 0, 0.0, 1) for k in range(0, nlay)]
    cnc0_right = [(k, 0, ncol - 1, 0.0, 1) for k in range(0, nlay)]
    cnc0 = cnc0_left + cnc0_right
    ssmspd = {0: cnc0}
    mxss = len(cnc0)
    flopy.mt3d.Mt3dSsm(mt, mxss=mxss, stress_period_data=ssmspd)

    # Instantiate unsaturated zone tranport package
    cuzinf = np.zeros((nrow, ncol), dtype=int)
    cuzinf[0, 15:25] = 1.0
    uzt = flopy.mt3d.Mt3dUzt(mt, iuzfbnd=iuzfbnd, iet=0, cuzinf=cuzinf)

    # Instantiate the GCG solver in MT3DMS
    flopy.mt3d.Mt3dGcg(mt, iter1=2000, isolve=3, ncrs=1)

    # MODFLOW 6
    print(f"Building mf6gwt model...{sim_name}")

    name = "uzt-2d-mf6"
    gwfname = "gwf-" + name
    sim_ws = os.path.join(workspace, sim_name)
    sim = flopy.mf6.MFSimulation(sim_name=sim_name, sim_ws=sim_ws, exe_name="mf6")

    # How much output to write?
    from flopy.mf6.mfbase import VerbosityLevel

    sim.simulation_data.verbosity_level = VerbosityLevel.quiet
    sim.name_file.memory_print_option = "all"

    # Instantiating MODFLOW 6 time discretization
    tdis_rc = []
    for i in range(nper):
        tdis_rc.append((perlen[i], nstp[i], tsmult[i]))
    flopy.mf6.ModflowTdis(sim, nper=nper, perioddata=tdis_rc, time_units=time_units)

    # Instantiating MODFLOW 6 groundwater flow model
    gwf = flopy.mf6.ModflowGwf(
        sim,
        modelname=gwfname,
        save_flows=True,
        newtonoptions="newton",
        model_nam_file=f"{gwfname}.nam",
    )

    # Instantiating MODFLOW 6 solver for flow model
    imsgwf = flopy.mf6.ModflowIms(
        sim,
        print_option="summary",
        complexity="complex",
        no_ptcrecord="all",
        outer_dvclose=1.0e-4,
        outer_maximum=2000,
        under_relaxation="dbd",
        linear_acceleration="BICGSTAB",
        under_relaxation_theta=0.7,
        under_relaxation_kappa=0.08,
        under_relaxation_gamma=0.05,
        under_relaxation_momentum=0.0,
        backtracking_number=20,
        backtracking_tolerance=2.0,
        backtracking_reduction_factor=0.2,
        backtracking_residual_limit=5.0e-4,
        inner_dvclose=1.0e-5,
        rcloserecord="0.0001 relative_rclose",
        inner_maximum=100,
        relaxation_factor=0.0,
        number_orthogonalizations=2,
        preconditioner_levels=8,
        preconditioner_drop_tolerance=0.001,
        filename=f"{gwfname}.ims",
    )
    sim.register_ims_package(imsgwf, [gwf.name])

    # Instantiating MODFLOW 6 discretization package
    flopy.mf6.ModflowGwfdis(
        gwf,
        length_units=length_units,
        nlay=nlay,
        nrow=nrow,
        ncol=ncol,
        delr=delr,
        delc=delc,
        top=top,
        botm=botm,
        idomain=idomain,
        filename=f"{gwfname}.dis",
    )

    # Instantiating MODFLOW 6 initial conditions package for flow model
    flopy.mf6.ModflowGwfic(gwf, strt=strt, filename=f"{gwfname}.ic")

    # Instantiating MODFLOW 6 node-property flow package
    flopy.mf6.ModflowGwfnpf(
        gwf,
        save_flows=False,
        icelltype=laytyp,
        k=hk,
        k33=hk33,
        save_specific_discharge=True,
        filename=f"{gwfname}.npf",
    )

    # Instantiate storage package
    flopy.mf6.ModflowGwfsto(
        gwf,
        ss=Ss,
        sy=sy,
        iconvert=iconvert,
        filename=f"{gwfname}.sto",
    )

    # Instantiate constant head package
    chdspd = []
    # Model domain is symmetric
    for k in np.arange(nlay):
        if botm[k] <= strt[k, 0, 0]:
            #              (l, r,      c),              head, conc
            chdspd.append([(k, 0, 0), strt[k, 0, 0], 0.0])
            chdspd.append([(k, 0, ncol - 1), strt[k, 0, ncol - 1], 0.0])
    chdspd = {0: chdspd}
    flopy.mf6.modflow.mfgwfchd.ModflowGwfchd(
        gwf,
        maxbound=len(chdspd),
        stress_period_data=chdspd,
        save_flows=False,
        auxiliary="CONCENTRATION",
        pname="CHD-1",
        filename=f"{gwfname}.chd",
    )

    # Instantiate unsaturated zone flow package
    flopy.mf6.ModflowGwfuzf(
        gwf,
        nuzfcells=nuzfcells,
        ntrailwaves=15,
        nwavesets=40,
        print_flows=True,
        packagedata=packagedata,
        perioddata=uzf_perioddata,
        pname="UZF-1",
        budget_filerecord=f"{gwfname}.uzf.bud",
    )

    # Instantiate output control package
    flopy.mf6.ModflowGwfoc(
        gwf,
        budget_filerecord=f"{gwfname}.bud",
        head_filerecord=f"{gwfname}.hds",
        headprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")],
        saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
        printrecord=[("HEAD", "LAST"), ("BUDGET", "ALL")],
    )

    # Instantiating MODFLOW 6 groundwater transport package
    gwtname = "gwt-" + name
    gwt = flopy.mf6.MFModel(
        sim,
        model_type="gwt6",
        modelname=gwtname,
        model_nam_file=f"{gwtname}.nam",
    )
    gwt.name_file.save_flows = True

    # create iterative model solution and register the gwt model with it
    imsgwt = flopy.mf6.ModflowIms(
        sim,
        print_option="summary",
        complexity="complex",
        no_ptcrecord="all",
        outer_dvclose=1.0e-4,
        outer_maximum=2000,
        under_relaxation="dbd",
        linear_acceleration="BICGSTAB",
        under_relaxation_theta=0.7,
        under_relaxation_kappa=0.08,
        under_relaxation_gamma=0.05,
        under_relaxation_momentum=0.0,
        backtracking_number=20,
        backtracking_tolerance=2.0,
        backtracking_reduction_factor=0.2,
        backtracking_residual_limit=5.0e-4,
        inner_dvclose=1.0e-5,
        rcloserecord="0.0001 relative_rclose",
        inner_maximum=100,
        relaxation_factor=0.0,
        number_orthogonalizations=2,
        preconditioner_levels=8,
        preconditioner_drop_tolerance=0.001,
        filename=f"{gwtname}.ims",
    )
    sim.register_ims_package(imsgwt, [gwt.name])

    # Instantiating MODFLOW 6 transport discretization package
    flopy.mf6.ModflowGwtdis(
        gwt,
        nlay=nlay,
        nrow=nrow,
        ncol=ncol,
        delr=delr,
        delc=delc,
        top=top,
        botm=botm,
        idomain=idomain,
        filename=f"{gwtname}.dis",
    )

    # Instantiating MODFLOW 6 transport initial concentrations
    flopy.mf6.ModflowGwtic(gwt, strt=sconc, filename=f"{gwtname}.ic")

    # Instantiating MODFLOW 6 transport advection package
    if mixelm >= 0:
        scheme = "UPSTREAM"
    elif mixelm == -1:
        scheme = "TVD"
    else:
        raise Exception()
    flopy.mf6.ModflowGwtadv(gwt, scheme=scheme, filename=f"{gwtname}.adv")

    # Instantiating MODFLOW 6 transport dispersion package
    if len(longitudinal_dispersivity) > 1:
        disp = np.ones_like(ibound_mf2k5)
        ath1 = np.ones_like(ibound_mf2k5)
        atv = np.ones_like(ibound_mf2k5)
        for i, (dispx, ratio1, ratio2) in enumerate(
            zip(
                longitudinal_dispersivity,
                ratio_horizontal_to_longitudinal_dispersivity,
                ratio_vertical_to_longitudinal_dispersivity,
            )
        ):
            disp[i, :, :] = dispx
            ath1[i, :, :] = dispx * ratio1
            atv[i, :, :] = dispx * ratio2
    else:
        # Dispersion
        disp = longitudinal_dispersivity[0]
        ath1 = (
            longitudinal_dispersivity[0]
            * ratio_horizontal_to_longitudinal_dispersivity[0]
        )
        atv = (
            longitudinal_dispersivity[0]
            * ratio_vertical_to_longitudinal_dispersivity[0]
        )

    if al != 0:
        flopy.mf6.ModflowGwtdsp(
            gwt,
            alh=disp,
            ath1=ath1,
            atv=atv,
            pname="DSP-1",
            filename=f"{gwtname}.dsp",
        )

    # Instantiating MODFLOW 6 transport mass storage package
    flopy.mf6.ModflowGwtmst(
        gwt,
        porosity=prsity,
        first_order_decay=False,
        decay=None,
        decay_sorbed=None,
        sorption=None,
        bulk_density=None,
        distcoef=None,
        pname="MST-1",
        filename=f"{gwtname}.mst",
    )

    # Instantiating MODFLOW 6 transport source-sink mixing package
    sourcerecarray = [("CHD-1", "AUX", "CONCENTRATION")]
    flopy.mf6.ModflowGwtssm(
        gwt,
        sources=sourcerecarray,
        print_flows=True,
        filename=f"{gwtname}.ssm",
    )

    # Instantiate unsaturated zone transport package.
    #  * use iuzno to set the concentration of infiltrating water
    #  * only set the middle 10 cells at the top of the domain
    #  * first and last 15 cells have concentration of 0
    pd0 = []
    for i in range(14, 24):
        pd0.append((i, "INFILTRATION", 1.0))
    uztperioddata = {0: pd0}
    flopy.mf6.modflow.ModflowGwtuzt(
        gwt,
        save_flows=True,
        print_input=True,
        print_flows=True,
        print_concentration=True,
        concentration_filerecord=gwtname + ".uzt.bin",
        budget_filerecord=gwtname + ".uzt.bud",
        packagedata=uztpackagedata,
        uztperioddata=uztperioddata,
        pname="UZF-1",
        filename=f"{gwtname}.uzt",
    )

    # Instantiating MODFLOW 6 transport output control package
    flopy.mf6.ModflowGwtoc(
        gwt,
        budget_filerecord=f"{gwtname}.cbc",
        concentration_filerecord=f"{gwtname}.ucn",
        concentrationprintrecord=[("COLUMNS", 10, "WIDTH", 15, "DIGITS", 6, "GENERAL")],
        saverecord=[("CONCENTRATION", "ALL")],
        printrecord=[("CONCENTRATION", "LAST"), ("BUDGET", "ALL")],
        filename=f"{gwtname}.oc",
    )

    # Instantiating MODFLOW 6 flow-transport exchange mechanism
    flopy.mf6.ModflowGwfgwt(
        sim,
        exgtype="GWF6-GWT6",
        exgmnamea=gwfname,
        exgmnameb=gwtname,
        filename=f"{name}.gwfgwt",
    )
    return mf, mt, sim


def write_models(mf2k5, mt3d, sim, silent=True):
    mf2k5.write_input()
    mt3d.write_input()
    sim.write_simulation(silent=silent)


@timed
def run_models(mf2k5, mt3d, sim, silent=True):
    success, buff = mf2k5.run_model(silent=silent, report=True)
    assert success, pformat(buff)
    success, buff = mt3d.run_model(
        silent=silent, normal_msg="Program completed", report=True
    )
    assert success, pformat(buff)
    success, buff = sim.run_simulation(silent=silent, report=True)
    assert success, pformat(buff)

Plotting results

Define functions to plot model results.

[4]:
# Figure properties
figure_size = (6, 4)


def plot_results(mf2k5, mt3d, mf6, idx, ax=None):
    print("Plotting model results...")
    mt3d_out_path = mt3d.model_ws
    mf6_out_path = mf6.simulation_data.mfpath.get_sim_path()
    mf6.simulation_data.mfpath.get_sim_path()

    # Get the MF-NWT heads
    fname_mfnwt = os.path.join(mt3d_out_path, "uzt-2d-mf.hds")
    hds_mfnwt = flopy.utils.HeadFile(fname_mfnwt)
    hds = hds_mfnwt.get_alldata()
    # Make list of verticies for plotting the saturated zone as a polygon
    # Start by adding fixed locations
    satzn = []
    satzn.append([40 * delr, 0])
    satzn.append([0 * delr, 0])
    for j in range(ncol):
        hd_in_col = hds[0, :, 0, j].max()
        if j == 0:
            satzn.append([j * delr, hd_in_col])
        elif j == ncol - 1:
            satzn.append([(j + 1) * delr, hd_in_col])
        else:
            satzn.append([(j * delr) + (delr / 2), hd_in_col])

    poly_pts = np.array(satzn)

    # Get the MT3DMS concentration output
    fname_mt3d = os.path.join(mt3d_out_path, "MT3D001.UCN")
    ucnobj_mt3d = flopy.utils.UcnFile(fname_mt3d)
    times_mt3d = ucnobj_mt3d.get_times()
    conc_mt3d = ucnobj_mt3d.get_alldata()

    # get the MODFLOW 6 results from the UZF package and the GWT model
    gwt = mf6.get_model("gwt-uzt-2d-mf6")
    uzconc_mf6 = gwt.uzf.output.concentration().get_alldata()
    mf6_satconc = gwt.output.concentration().get_alldata()

    uzconc_mf6_shpd = []
    for i in np.arange(0, uzconc_mf6.shape[0]):
        tmp = uzconc_mf6[i, 0, 0, :].reshape((20, 1, 38))
        # insert column of np.nan on left and right sides of reshaped array
        tmp2 = np.insert(tmp, 0, np.nan, axis=2)
        tmp_apnd = np.empty((tmp2.shape[0], 1, 1))
        tmp_apnd[:] = np.nan
        tmp3 = np.append(tmp2, tmp_apnd, axis=2)
        uzconc_mf6_shpd.append(tmp3)

    uzconc_mf6_shpd = np.array(uzconc_mf6_shpd)
    i = 0
    hds_bool = np.zeros_like(hds[0, :, i, :])
    for k in range(nlay):
        for j in range(ncol):
            if hds[0, k, i, j] > ((nlay - 1) - k) * 0.25:
                hds_bool[k, j] = 1

    combined_concs = np.where(
        hds_bool > 0,
        mf6_satconc[-1, :, 0, :],
        uzconc_mf6_shpd[-1, :, 0, :],
    )

    combined_conc_3d = combined_concs[np.newaxis, :, np.newaxis, :]

    contourLevels = np.arange(0.2, 1.01, 0.2)

    # Create figure for scenario
    with styles.USGSPlot() as fs:
        sim_name = mf6.name
        plt.rcParams["lines.dashed_pattern"] = [5.0, 5.0]

        axWasNone = False
        if ax is None:
            fig = plt.figure(figsize=figure_size, dpi=300, tight_layout=True)
            ax = fig.add_subplot(1, 1, 1, aspect="equal")
            axWasNone = True

        mx = flopy.plot.PlotCrossSection(ax=ax, model=mf2k5, line={"row": 0})
        mx.plot_grid(color="0.5", alpha=0.1)

        cs1 = mx.contour_array(
            conc_mt3d[-1],
            colors="k",
            linestyles="-",
            levels=contourLevels[0:4],
            masked_values=[1.0e30],
        )
        plt.clabel(cs1, fmt=r"%4.2f")
        plt.fill(poly_pts[:, 0], poly_pts[:, 1], alpha=0.2)
        cs2 = mx.contour_array(
            combined_conc_3d[0],
            levels=contourLevels[0:4],
            colors="red",
            linestyles="--",
        )
        plt.clabel(cs2, fmt=r"%4.2f")

        labels = ["MF-NWT/MT3D-USGS", "MODFLOW 6"]
        lines = [cs1.collections[0], cs2.collections[0]]
        for x in np.linspace(0.25, 9.75, 11):
            ax.arrow(
                x,
                5.25,
                0.0,
                -0.25,
                head_width=0.05,
                head_length=0.1,
                fc="b",
                ec="b",
                clip_on=False,
                length_includes_head=True,
            )

        ax.arrow(0.25, 5.25, 9.5, 0.0, fc="b", ec="b", clip_on=False)
        ax.annotate(
            "Infiltration = 0.10 m/day",
            xy=(0.025, 1.06),
            xycoords="axes fraction",
            clip_on=False,
        )
        for x in np.linspace(3.75, 6.25, 6):
            ax.arrow(
                x,
                5.5,
                0.0,
                -0.5,
                head_width=0.05,
                head_length=0.1,
                fc="r",
                ec="r",
                clip_on=False,
                length_includes_head=True,
            )

        ax.arrow(3.75, 5.5, 2.5, 0.0, fc="b", ec="r", clip_on=False)
        ax.annotate(
            "Concentration = 1.00 mg/L",
            xy=(0.38, 1.12),
            xycoords="axes fraction",
            clip_on=False,
        )
        ax.legend(lines, labels, loc="upper left")

        title = "Unsaturated/saturated zone concentration X-section, time = 60 days"
        letter = chr(ord("@") + idx + 1)
        # styles.heading(letter=letter, heading=title)
        plt.tight_layout()

        if plot_show:
            plt.show()
        if plot_save:
            fpth = figs_path / "{}{}".format(
                sim_name,
                ".png",
            )
            fig.savefig(fpth)

Running the example

Define and invoke a function to run the example scenario, then plot results.

[5]:
def scenario(idx, silent=True):
    key = list(parameters.keys())[idx]
    parameter_dict = parameters[key]
    mf2k5, mt3d, sim = build_models(key, mixelm=mixelm, **parameter_dict)
    if write:
        write_models(mf2k5, mt3d, sim, silent=silent)
    if run:
        run_models(mf2k5, mt3d, sim, silent=silent)
    if plot:
        plot_results(mf2k5, mt3d, sim, idx)
[6]:
scenario(0, silent=True)
Building mf-nwt model...ex-gwt-uzt-2d-a
Building mt3d-usgs model...ex-gwt-uzt-2d-a
Building mf6gwt model...ex-gwt-uzt-2d-a
<flopy.mf6.data.mfstructure.MFDataItemStructure object at 0x7fe3ebba2eb0>
<flopy.mf6.data.mfstructure.MFDataItemStructure object at 0x7fe3ebba2eb0>
run_models took 11956.24 ms
Plotting model results...
/tmp/ipykernel_10501/333023305.py:102: MatplotlibDeprecationWarning: The collections attribute was deprecated in Matplotlib 3.8 and will be removed in 3.10.
  lines = [cs1.collections[0], cs2.collections[0]]
/tmp/ipykernel_10501/333023305.py:102: MatplotlibDeprecationWarning: The collections attribute was deprecated in Matplotlib 3.8 and will be removed in 3.10.
  lines = [cs1.collections[0], cs2.collections[0]]
../_images/_notebooks_ex-gwt-uzt-2d_11_4.png
[7]:
scenario(1, silent=True)
Building mf-nwt model...ex-gwt-uzt-2d-b
Building mt3d-usgs model...ex-gwt-uzt-2d-b
Building mf6gwt model...ex-gwt-uzt-2d-b
<flopy.mf6.data.mfstructure.MFDataItemStructure object at 0x7fe3ebba2eb0>
<flopy.mf6.data.mfstructure.MFDataItemStructure object at 0x7fe3ebba2eb0>
run_models took 11366.13 ms
Plotting model results...
/tmp/ipykernel_10501/333023305.py:102: MatplotlibDeprecationWarning: The collections attribute was deprecated in Matplotlib 3.8 and will be removed in 3.10.
  lines = [cs1.collections[0], cs2.collections[0]]
/tmp/ipykernel_10501/333023305.py:102: MatplotlibDeprecationWarning: The collections attribute was deprecated in Matplotlib 3.8 and will be removed in 3.10.
  lines = [cs1.collections[0], cs2.collections[0]]
../_images/_notebooks_ex-gwt-uzt-2d_12_3.png