This page was generated from ex-gwt-synthetic-valley.py. It's also available as a notebook.

Synthetic Valley Problem

This problem is described in Hughes and others (2023).

Initial setup

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

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

import flopy
import flopy.plot.styles as styles
import git
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
import pooch
import shapely
from flopy.discretization import VertexGrid
from flopy.utils.triangle import Triangle
from flopy.utils.voronoi import VoronoiGrid
from matplotlib import colors
from modflow_devtools.misc import get_env, timed
from shapely.geometry import LineString, Polygon

# 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)

# Groundwater 2023 utilities

geometries = {
    "sv_boundary": """0.0 0.0
    0.0 20000.0
    12500.0 20000.0
    12500.0 0.0""",
    "sv_river": """4250.0 8750.0
    4250.0 0.0""",
    "sv_river_box": """3500.0 0.0
    3500.0 9500.0
    5000.0 9500.0
    5000.0 0.0""",
    "sv_wells": """7250. 17250.
    7750. 2750.
    2750 3750.""",
    "sv_lake": """1500. 18500.
    3500. 18500.
    3500. 15500.
    4000. 15500.
    4000. 14500.
    4500. 14500.
    4500. 12000.
    2500. 12000.
    2500. 12500.
    2000. 12500.
    2000. 14000.
    1500. 14000.
    1500. 15000.
    1000. 15000.
    1000. 18000.
    1500. 18000.""",
}


def string2geom(geostring, conversion=None):
    if conversion is None:
        multiplier = 1.0
    else:
        multiplier = float(conversion)
    res = []
    for line in geostring.split("\n"):
        line = line.strip()
        line = line.split(" ")
        x = float(line[0]) * multiplier
        y = float(line[1]) * multiplier
        res.append((x, y))
    return res


def densify_geometry(line, step, keep_internal_nodes=True):
    xy = []  # list of tuple of coordinates
    lines_strings = []
    if keep_internal_nodes:
        for idx in range(1, len(line)):
            lines_strings.append(shapely.geometry.LineString(line[idx - 1 : idx + 1]))
    else:
        lines_strings = [shapely.geometry.LineString(line)]

    for line_string in lines_strings:
        length_m = line_string.length  # get the length
        for distance in np.arange(0, length_m + step, step):
            point = line_string.interpolate(distance)
            xy_tuple = (point.x, point.y)
            if xy_tuple not in xy:
                xy.append(xy_tuple)
        # make sure the end point is in xy
        if keep_internal_nodes:
            xy_tuple = line_string.coords[-1]
            if xy_tuple not in xy:
                xy.append(xy_tuple)

    return xy


def circle_function(center=(0, 0), radius=1.0, dtheta=10.0):
    angles = np.arange(0.0, 360.0, dtheta) * np.pi / 180.0
    xpts = center[0] + np.cos(angles) * radius
    ypts = center[1] + np.sin(angles) * radius
    return np.array([(x, y) for x, y in zip(xpts, ypts)])


# 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.
sim_name = "ex-gwt-synthetic-valley"
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()
data_path = pl.Path(f"../data/{sim_name}")
data_path = data_path if data_path.is_dir() else pl.Path.cwd()

# Conversion factors
ft2m = 1.0 / 3.28081
ft3tom3 = 1.0 * ft2m * ft2m * ft2m
ftpd2cmpy = 1000.0 * 365.25 * ft2m
mpd2cmpy = 100.0 * 365.25
mpd2inpy = 12.0 * 365.25 * 3.28081

Model setup

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

[2]:
# Model units
length_units = "meters"
time_units = "days"

# Model parameters
pertim = 10957.5  # Simulation length ($d$)
ntransport_steps = 60  # Number of transport time steps
nlay = 6  # Number of layers
rainfall = 0.0025  # Rainfall ($m/d$)
evaporation = 0.0019  # Potential evaporation ($m/d$)
sfr_length_conversion = 1.0  # SFR package length unit conversion
sfr_time_conversion = 86400.0  # SFR package time conversion
sfr_width = 3.048  # Stream width ($m$)
sfr_bedthick = 0.3048  # Stream bed thickness ($m$)
sfr_mann = 0.030  # Stream Manning's roughness coefficient
lake_bedleak = 0.0013  # Lake bed leakance ($1/d$)
lak_length_conversion = 1.0  # LAK package length unit conversion
lak_time_conversion = 86400.0  # LAK package time conversion
drn_kv = 0.03048  # Drain vertical hydraulic conductivity ($m/d$)
drn_bed_thickness = 0.3048  # Drain bed thickness ($m$)
drn_depth = 0.3048  # Drain linear scaling depth ($m$)
alpha_l = 75.0  # Longitudinal dispersivity ($m$)
alpha_th = 7.5  # Transverse horizontal dispersivity ($m$)
porosity = 0.2  # Aquifer porosity (unitless)
confining_porosity = 0.4  # Confining unit porosity (unitless)

[3]:
# voronoi grid properties
maximum_area = 150.0 * 150.0
well_dv = 300.0
boundary_refinement = 100.0
river_refinement = 25.0
lake_refinement = 30.0
max_boundary_area = boundary_refinement * boundary_refinement
max_river_area = river_refinement * river_refinement
max_lake_area = lake_refinement * lake_refinement

boundary_polygon = string2geom(geometries["sv_boundary"], conversion=ft2m)
bp = np.array(boundary_polygon)
bp_densify = np.array(densify_geometry(bp, boundary_refinement))

river_polyline = string2geom(geometries["sv_river"], conversion=ft2m)
sg = np.array(river_polyline)
sg_densify = np.array(densify_geometry(sg, river_refinement))

river_boundary = string2geom(geometries["sv_river_box"], conversion=ft2m)
rb = np.array(river_boundary)
rb_densify = np.array(densify_geometry(rb, river_refinement))

lake_polygon = string2geom(geometries["sv_lake"], conversion=ft2m)
lake_plot = string2geom(geometries["sv_lake"], conversion=ft2m)
lake_plot += [lake_plot[0]]
lake_plot = np.array(lake_plot)
lp = np.array(lake_polygon)
lp_densify = np.array(densify_geometry(lp, lake_refinement))

well_points = string2geom(geometries["sv_wells"], conversion=ft2m)
wp = np.array(well_points)
[4]:
# create the voronoi grid
temp_path = pl.Path("temp/triangle_data")
temp_path.mkdir(parents=True, exist_ok=True)
tri = Triangle(
    angle=30,
    nodes=sg_densify,
    model_ws=temp_path,
)
tri.add_polygon(bp_densify)
tri.add_polygon(rb_densify)
tri.add_polygon(lp_densify)
tri.add_region((10, 10), attribute=10, maximum_area=max_boundary_area)
tri.add_region(
    (3050.0, 3050.0),
    attribute=10,
    maximum_area=max_boundary_area,
)
tri.add_region((900.0, 4600.0), attribute=11, maximum_area=max_lake_area)
tri.add_region((1200.0, 150.0), attribute=10, maximum_area=max_river_area)
for idx, w in enumerate(wp):
    center = (w[0], w[1])
    tri.add_polygon(circle_function(center=center, radius=100.0))
    tri.add_region(center, attribute=idx, maximum_area=500.0)
tri.build(verbose=False)
vor = VoronoiGrid(tri)
[5]:
# create a vertex grid from the voronoi grid
gridprops = vor.get_gridprops_vertexgrid()
idomain_vor = np.ones((1, vor.ncpl), dtype=int)
voronoi_grid = VertexGrid(**gridprops, nlay=1, idomain=idomain_vor)
[6]:
# load raster data files
fname = "k_aq_SI.tif"
fpath = pooch.retrieve(
    url=f"https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/{sim_name}/{fname}",
    fname=fname,
    path=data_path,
    known_hash="md5:d233e5c393ab6c029c63860d73818856",
)
kaq = flopy.utils.Raster.load(fpath)

fname = "k_clay_SI.tif"
fpath = pooch.retrieve(
    url=f"https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/{sim_name}/{fname}",
    fname=fname,
    path=data_path,
    known_hash="md5:a08999c37f42b35884468e4ef896d5f9",
)
kclay = flopy.utils.Raster.load(fpath)

fname = "top_SI.tif"
fpath = pooch.retrieve(
    url=f"https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/{sim_name}/{fname}",
    fname=fname,
    path=data_path,
    known_hash="md5:781155bdcc2b9914e1cad6b10de0e9c7",
)
top_base = flopy.utils.Raster.load(fpath)

fname = "bottom_SI.tif"
fpath = pooch.retrieve(
    url=f"https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/{sim_name}/{fname}",
    fname=fname,
    path=data_path,
    known_hash="md5:00b4a39fbf5180e65c0367cdb6f15c93",
)
bot = flopy.utils.Raster.load(fpath)

fname = "lake_location_SI.tif"
fpath = pooch.retrieve(
    url=f"https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/{sim_name}/{fname}",
    fname=fname,
    path=data_path,
    known_hash="md5:38600d6f0eef7c033ede278252dc6343",
)
lake_location = flopy.utils.Raster.load(fpath)
Downloading data from 'https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/ex-gwt-synthetic-valley/k_aq_SI.tif' to file '/home/runner/work/modflow6-examples/modflow6-examples/.doc/_notebooks/k_aq_SI.tif'.
Downloading data from 'https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/ex-gwt-synthetic-valley/k_clay_SI.tif' to file '/home/runner/work/modflow6-examples/modflow6-examples/.doc/_notebooks/k_clay_SI.tif'.
Downloading data from 'https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/ex-gwt-synthetic-valley/top_SI.tif' to file '/home/runner/work/modflow6-examples/modflow6-examples/.doc/_notebooks/top_SI.tif'.
Downloading data from 'https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/ex-gwt-synthetic-valley/bottom_SI.tif' to file '/home/runner/work/modflow6-examples/modflow6-examples/.doc/_notebooks/bottom_SI.tif'.
Downloading data from 'https://github.com/MODFLOW-USGS/modflow6-examples/raw/master/data/ex-gwt-synthetic-valley/lake_location_SI.tif' to file '/home/runner/work/modflow6-examples/modflow6-examples/.doc/_notebooks/lake_location_SI.tif'.
[7]:
# a few variables for plotting
xcv, ycv = voronoi_grid.xcellcenters, voronoi_grid.ycellcenters
x0 = x1 = sg[:, 0].min()
y0, y1 = sg[:, 1].max(), sg[:, 1].min()
top_range = (0, 20)
top_levels = np.arange(0, 25, 5)
head_range = (-1, 5)
head_levels = np.arange(1, head_range[1] + 1, 1)
extent = voronoi_grid.extent
[8]:
# intersect the rasters with the vertex grid
top_vg = top_base.resample_to_grid(
    voronoi_grid,
    band=top_base.bands[0],
    method="linear",
    extrapolate_edges=True,
)
bot_vg = bot.resample_to_grid(
    voronoi_grid,
    band=bot.bands[0],
    method="linear",
    extrapolate_edges=True,
)
lake_cells_vg = lake_location.resample_to_grid(
    voronoi_grid,
    band=lake_location.bands[0],
    method="nearest",
    extrapolate_edges=True,
)
kaq_vg = kaq.resample_to_grid(
    voronoi_grid,
    band=kaq.bands[0],
    method="nearest",
    extrapolate_edges=True,
)
kclay_vg = kclay.resample_to_grid(
    voronoi_grid,
    band=kclay.bands[0],
    method="nearest",
)
[9]:
# create confining unit location map
kclay_loc_vg = np.zeros(kclay_vg.shape, dtype=int)
kclay_loc_vg[kclay_vg < 60.0] = 1
idomain_2 = np.ones(kclay_vg.shape, dtype=int)
idomain_2[kclay_loc_vg == 0] = -1

# set the porosity based on the clay location
porosity_2 = np.full(kclay_vg.shape, porosity, dtype=float)
porosity_2[kclay_loc_vg == 1] = confining_porosity
[10]:
# set the bottom of each layer
bot_l2 = np.full(bot_vg.shape, -51.0 * ft2m, dtype=float)
bot_l3 = np.full(bot_vg.shape, -100.0 * ft2m, dtype=float)
bot_l4 = bot_vg + 0.5 * (bot_l3 - bot_vg)
# set the bottom of the 3rd layer in areas where the confining unit exists
bot_l2[idomain_2] = -50.0 * ft2m
# create a list with bottom data
botm = [-5.0 * ft2m, -50.0 * ft2m, bot_l2, -100.0 * ft2m, bot_l4, bot_vg]
[11]:
# create a modelgrid for the lake
lake_grid_top = np.full((vor.ncpl), 50.0, dtype=float)
lake_vg_grid = flopy.discretization.VertexGrid(
    **gridprops,
    nlay=1,
    idomain=idomain_vor,
    top=lake_grid_top,
    botm=top_vg.reshape(1, vor.ncpl),
)
[12]:
# intersect stream features with the grid
ixs = flopy.utils.GridIntersect(voronoi_grid, method="vertex")
sg_result = ixs.intersect(LineString(sg_densify), sort_by_cellid=False)

# build sfr package datasets
sfr_plt_array = np.zeros(voronoi_grid.ncpl, dtype=int)
sfr_nodes = np.arange(0, sg_result.shape[0])
gwf_nodes = sg_result["cellids"][::-1]
sfr_lengths = sg_result["lengths"][::-1]
total_cond = 1800000.0 * ft3tom3
sfr_hk = total_cond * sfr_bedthick / (sfr_width * sfr_lengths.sum())
b0, b1 = -0.3 * ft2m, -2.05 * ft2m
sfr_slope = -0.0002
cum_dist = np.zeros(sfr_nodes.shape, dtype=float)
cum_dist[0] = 0.5 * sfr_lengths[0]
for idx in range(1, sfr_nodes.shape[0]):
    cum_dist[idx] = cum_dist[idx - 1] + 0.5 * (sfr_lengths[idx - 1] + sfr_lengths[idx])
sfr_bot = b0 + sfr_slope * cum_dist
sfr_conn = []
for idx, node in enumerate(sfr_nodes):
    iconn = [node]
    if idx > 0:
        iconn.append(sfr_nodes[idx - 1])
    if idx < sfr_nodes.shape[0] - 1:
        iconn.append(-sfr_nodes[idx + 1])
    sfr_conn.append(iconn)

# <rno> <cellid(ncelldim)> <rlen> <rwid> <rgrd> <rtp> <rbth> <rhk> <man> <ncon> <ustrf> <ndv>
sfrpak_data = []
for idx, (cellid, rlen, rtp) in enumerate(zip(gwf_nodes, sfr_lengths, sfr_bot)):
    sfr_plt_array[cellid] = 1
    sfrpak_data.append(
        (
            idx,
            (
                0,
                cellid,
            ),
            rlen,
            sfr_width,
            -sfr_slope,
            rtp,
            sfr_bedthick,
            sfr_hk,
            sfr_mann,
            (len(sfr_conn[idx]) - 1),
            1.0,
            0,
        )
    )
sfr_spd = [(node, "rainfall", rainfall) for node in sfr_nodes] + [
    (node, "evaporation", evaporation) for node in sfr_nodes
]
[13]:
# build lake package datasets
lake_ic = 11.3 * ft2m
idx = np.where(lake_cells_vg == 1.0)

lake_map = np.ones(voronoi_grid.ncpl, dtype=int) * -1
lake_map[idx] = 0

(idomain, lakpak_dict, lak_connections) = flopy.mf6.utils.get_lak_connections(
    voronoi_grid,
    lake_map,
    bedleak=lake_bedleak,
)

# add concentration to lake data as aux
lakpak_data = [(0, lake_ic, lakpak_dict[0], 1.0)]
lake_spd = [
    (0, "rainfall", rainfall),
    (0, "evaporation", evaporation),
]
[14]:
# build drain package datasets
areas = []
for idx in range(voronoi_grid.ncpl):
    vertices = np.array(voronoi_grid.get_cell_vertices(idx))
    area = Polygon(vertices).area
    areas.append(area)
drn_spd = []
for idx, elev in enumerate(top_vg):
    if lake_cells_vg[idx] > 0:
        cond = drn_kv * areas[idx] / drn_bed_thickness
        drn_spd.append([(0, idx), elev, cond, -drn_depth])
[15]:
# build well package datasets
well_loc = []
for x, y in well_points:
    well_loc.append(voronoi_grid.intersect(x, y))

# first well is Virginia City well site 2
# second well is Reilly well
# third well is Virginia City well site 1
well_boundnames = ["P3", "P1", "P2"]
rates = [-1900.0, -7600.0, -7600.0]
welspd = [
    [nlay - 1, cellid, rates[idx], well_boundnames[idx]]
    for idx, cellid in enumerate(well_loc)
]

Model setup

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

[16]:
def build_mf6gwf(sim_folder):
    print(f"Building mf6gwf model...{sim_folder}")
    name = "flow"
    sim_ws = os.path.join(workspace, sim_folder, "mf6gwf")
    sim = flopy.mf6.MFSimulation(
        sim_name=name,
        sim_ws=sim_ws,
        exe_name="mf6",
        continue_=True,
    )
    tdis = flopy.mf6.ModflowTdis(sim, time_units="days", perioddata=((pertim, 1, 1.0),))
    ims = flopy.mf6.ModflowIms(
        sim,
        print_option="all",
        complexity="simple",
        linear_acceleration="bicgstab",
    )
    gwf = flopy.mf6.ModflowGwf(
        sim,
        modelname=name,
        save_flows=True,
        newtonoptions="NEWTON UNDER_RELAXATION",
    )
    dis = flopy.mf6.ModflowGwfdisv(
        gwf,
        length_units="meters",
        nlay=nlay,
        ncpl=vor.ncpl,
        nvert=vor.nverts,
        top=top_vg,
        botm=botm,
        vertices=vor.get_disv_gridprops()["vertices"],
        cell2d=vor.get_disv_gridprops()["cell2d"],
        idomain=[1, 1, idomain_2, 1, 1, 1],
    )
    ic = flopy.mf6.ModflowGwfic(gwf, strt=11.0)
    npf = flopy.mf6.ModflowGwfnpf(
        gwf,
        xt3doptions=True,
        save_specific_discharge=True,
        save_saturation=True,
        icelltype=[1, 0, 0, 0, 0, 0],
        k=[kaq_vg, kaq_vg, kclay_vg, kaq_vg, kaq_vg, kaq_vg],
        k33=[
            0.25 * kaq_vg,
            0.25 * kaq_vg,
            kclay_vg,
            0.25 * kaq_vg,
            0.25 * kaq_vg,
            0.25 * kaq_vg,
        ],
    )
    rch = flopy.mf6.ModflowGwfrcha(gwf, recharge=rainfall)
    evt = flopy.mf6.ModflowGwfevta(gwf, surface=top_vg, rate=evaporation, depth=1.0)
    wel = flopy.mf6.ModflowGwfwel(gwf, stress_period_data=welspd, boundnames=True)
    drn = flopy.mf6.ModflowGwfdrn(
        gwf,
        auxiliary=["depth"],
        auxdepthname="depth",
        stress_period_data=drn_spd,
    )
    sfr = flopy.mf6.ModflowGwfsfr(
        gwf,
        print_stage=True,
        print_flows=True,
        length_conversion=sfr_length_conversion,
        time_conversion=sfr_time_conversion,
        stage_filerecord=f"{name}.sfr.stage.bin",
        budget_filerecord=f"{name}.sfr.cbc",
        nreaches=len(sfrpak_data),
        packagedata=sfrpak_data,
        connectiondata=sfr_conn,
        perioddata=sfr_spd,
    )
    lak = flopy.mf6.ModflowGwflak(
        gwf,
        pname="LAK-1",
        time_conversion=lak_time_conversion,
        length_conversion=lak_length_conversion,
        auxiliary=["concentration"],
        print_stage=True,
        print_flows=True,
        stage_filerecord=f"{name}.lak.stage.bin",
        budget_filerecord=f"{name}.lak.cbc",
        nlakes=1,
        packagedata=lakpak_data,
        connectiondata=lak_connections,
        perioddata=lake_spd,
    )
    oc = flopy.mf6.ModflowGwfoc(
        gwf,
        head_filerecord=name + ".hds",
        budget_filerecord=name + ".cbc",
        saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
        printrecord=[("BUDGET", "ALL")],
    )
    return sim


def build_mf6gwt(sim_folder):
    print(f"Building mf6gwt model...{sim_folder}")
    name = "trans"
    sim_ws = os.path.join(workspace, sim_folder, "mf6gwt")
    sim = flopy.mf6.MFSimulation(
        sim_name=name,
        sim_ws=sim_ws,
        exe_name="mf6",
        continue_=True,
    )
    tdis = flopy.mf6.ModflowTdis(
        sim, time_units="days", perioddata=((pertim, ntransport_steps, 1.0),)
    )
    ims = flopy.mf6.ModflowIms(
        sim,
        print_option="all",
        complexity="simple",
        linear_acceleration="bicgstab",
    )
    gwt = flopy.mf6.ModflowGwt(
        sim,
        modelname=name,
    )
    dis = flopy.mf6.ModflowGwtdisv(
        gwt,
        length_units="meters",
        nlay=nlay,
        ncpl=vor.ncpl,
        nvert=vor.nverts,
        top=top_vg,
        botm=botm,
        vertices=vor.get_disv_gridprops()["vertices"],
        cell2d=vor.get_disv_gridprops()["cell2d"],
        idomain=[1, 1, idomain_2, 1, 1, 1],
    )
    ic = flopy.mf6.ModflowGwtic(gwt, strt=0.0)
    adv = flopy.mf6.ModflowGwtadv(
        gwt,
        scheme="tvd",
    )
    dsp = flopy.mf6.ModflowGwtdsp(
        gwt,
        diffc=0.0e-12,
        alh=alpha_l,
        ath1=alpha_th,
    )
    mst = flopy.mf6.ModflowGwtmst(
        gwt,
        porosity=[
            porosity_2,
            porosity,
            porosity_2,
            porosity,
            porosity,
            porosity,
        ],
    )
    pd = [
        ("GWFHEAD", "../mf6gwf/flow.hds", None),
        ("GWFBUDGET", "../mf6gwf/flow.cbc", None),
    ]
    fmi = flopy.mf6.ModflowGwtfmi(gwt, packagedata=pd)
    sourcerecarray = [
        ("LAK-1", "AUX", "CONCENTRATION"),
    ]
    ssm = flopy.mf6.ModflowGwtssm(gwt, sources=sourcerecarray)

    oc = flopy.mf6.ModflowGwtoc(
        gwt,
        concentration_filerecord=f"{name}.ucn",
        saverecord=[
            ("CONCENTRATION", "LAST"),
        ],
        printrecord=[("BUDGET", "ALL")],
    )
    return sim


def build_models(sim_name):
    sim_mf6gwf = build_mf6gwf(sim_name)
    sim_mf6gwt = build_mf6gwt(sim_name)
    sim_mf2005 = None  # build_mf2005(sim_name)
    sim_mt3dms = None  # build_mt3dms(sim_name, sim_mf2005)
    return sim_mf6gwf, sim_mf6gwt, sim_mf2005, sim_mt3dms


def write_models(sims, silent=True):
    sim_mf6gwf, sim_mf6gwt, sim_mf2005, sim_mt3dms = sims
    sim_mf6gwf.write_simulation(silent=silent)
    sim_mf6gwt.write_simulation(silent=silent)


@timed
def run_models(sims, silent=True):
    sim_mf6gwf, sim_mf6gwt, sim_mf2005, sim_mt3dms = sims
    success, buff = sim_mf6gwf.run_simulation(silent=silent, report=True)
    assert success, pformat(buff)
    success, buff = sim_mf6gwt.run_simulation(silent=silent, report=True)
    assert success, pformat(buff)

Plotting results

Define functions to plot model results.

[17]:
# Figure properties
two_panel_figsize = (17.15 / 2.541, 0.8333 * 17.15 / 2.541)
one_panel_figsize = (8.25 / 2.541, 13.25 / 2.541)
six_panel_figsize = (17.15 / 2.541, 1.4 * 0.8333 * 17.15 / 2.541)
levels = np.arange(10, 110, 10)
contour_color = "black"
contour_style = "--"
sv_contour_dict = {
    "linewidths": 0.5,
    "colors": contour_color,
    "linestyles": contour_style,
}
sv_contour_dict = {
    "linewidths": 0.5,
    "colors": contour_color,
    "linestyles": contour_style,
}
sv_gwt_contour_dict = {
    "linewidths": 0.75,
    "colors": contour_color,
    "linestyles": contour_style,
}
contour_label_dict = {
    "linewidth": 0.5,
    "color": contour_color,
    "linestyle": contour_style,
}
contour_gwt_label_dict = {
    "linewidth": 0.75,
    "color": contour_color,
    "linestyle": contour_style,
}
clabel_dict = {
    "inline": True,
    "fmt": "%1.0f",
    "fontsize": 6,
    "inline_spacing": 0.5,
}
font_dict = {"fontsize": 5, "color": "black"}
grid_dict = {"lw": 0.25, "color": "0.5"}
arrowprops = dict(
    arrowstyle="-",
    edgecolor="red",
    lw=0.5,
    shrinkA=0.15,
    shrinkB=0.15,
)
river_dict = {"color": "blue", "linestyle": "-", "linewidth": 1}
lake_cmap = colors.ListedColormap(["cyan"])
clay_cmap = colors.ListedColormap(["brown"])


def plot_wells(ax=None, ms=None):
    if ax is None:
        ax = plt.gca()
    ax.plot(wp[:, 0], wp[:, 1], "ro", ms=ms)
    return ax


def plot_river(ax=None):
    if ax is None:
        ax = plt.gca()
    ax.plot(sg_densify[:, 0], sg_densify[:, 1], **river_dict)
    return ax


def plot_lake(ax=None, lw=0.5, color="cyan", marker=None, densify=False):
    if ax is None:
        ax = plt.gca()
    if densify:
        arr = lp_densify
    else:
        arr = lake_plot
    ax.plot(arr[:, 0], arr[:, 1], ls="-", color=color, lw=lw, marker=marker)
    return ax


def set_ticklabels(
    ax,
    fmt="{:.1f}",
    skip_xticklabels=False,
    skip_yticklabels=False,
    skip_xlabel=False,
    skip_ylabel=False,
    xticks=None,
    yticks=None,
):
    if xticks is None:
        labels = [ax.get_xticks().tolist()]
    else:
        ax.set_xticks(xticks, labels=[str(value) for value in xticks])
        labels = [xticks]

    if yticks is None:
        labels += [ax.get_yticks().tolist()]
    else:
        ax.set_yticks(yticks, labels=[str(value) for value in yticks])
        labels += [yticks]

    for idx, label in enumerate(labels):
        for jdx, value in enumerate(label):
            labels[idx][jdx] = fmt.format(float(value) / 1000.0)

    if skip_xticklabels:
        ax.set_xticklabels([])
    else:
        ax.xaxis.set_major_locator(mticker.FixedLocator(ax.get_xticks()))
        ax.set_xticklabels(labels[0])

    if skip_yticklabels:
        ax.set_yticklabels([])
    else:
        ax.yaxis.set_major_locator(mticker.FixedLocator(ax.get_yticks()))
        ax.set_yticklabels(labels[1])

    if not skip_xlabel:
        ax.set_xlabel("x position (km)")
    if not skip_ylabel:
        ax.set_ylabel("y position (km)")


def plot_well_labels(ax):
    for xy, name in zip(well_points, well_boundnames):
        styles.add_annotation(
            ax=ax,
            text=name,
            xy=xy,
            xytext=(-15, 10),
            bold=False,
            textcoords="offset points",
            arrowprops=arrowprops,
        )


def plot_feature_labels(ax):
    styles.add_text(
        ax=ax,
        text="Blue\nLake",
        x=610,
        y=5000.0,
        transform=False,
        bold=False,
        ha="center",
        va="center",
    )
    styles.add_text(
        ax=ax,
        text="Straight River",
        x=1425,
        y=1500.0,
        transform=False,
        bold=False,
        va="center",
        ha="center",
        rotation=90,
    )
    plot_well_labels(ax)


def plot_results(sims, idx):
    print("Plotting model results...")
    plot_river_mapping(sims, idx)
    plot_head_results(sims, idx)
    plot_conc_results(sims)


def plot_river_mapping(sims, idx):
    print("Plotting river mapping...")
    sim_mf6gwf, _, _, _ = sims
    sim_ws = sim_mf6gwf.simulation_data.mfpath.get_sim_path()
    dv = 100.0  # m

    with styles.USGSMap():
        fig = plt.figure(figsize=one_panel_figsize, constrained_layout=False)
        gs = gridspec.GridSpec(ncols=1, nrows=24, figure=fig)
        ax0 = fig.add_subplot(gs[:18])
        ax_leg = fig.add_subplot(gs[18:])

        ax = ax0
        ax.set_aspect("equal", "box")
        mm = flopy.plot.PlotMapView(modelgrid=voronoi_grid, ax=ax)
        mm.plot_array(
            kclay_loc_vg,
            masked_values=[
                0,
            ],
            cmap=clay_cmap,
            alpha=0.5,
        )
        mm.plot_array(
            lake_cells_vg,
            masked_values=[
                0,
            ],
            cmap=lake_cmap,
            alpha=0.5,
        )
        mm.plot_grid(**grid_dict)
        plot_river(ax)
        plot_wells(ax, ms=3)
        plot_feature_labels(ax)

        xticks = np.arange(mm.extent[0], mm.extent[1], 1000.0).tolist()
        yticks = np.arange(mm.extent[2], mm.extent[3], 1000.0).tolist()
        set_ticklabels(ax, fmt="{:.0f}", xticks=xticks, yticks=yticks)

        # legend
        ax = ax_leg
        xy0 = (-100, -100)
        ax.set_ylim(0, 1)
        ax.set_axis_off()

        # fake data to set up legend
        ax.plot(
            xy0,
            xy0,
            lw=0.0,
            marker=".",
            ms=5,
            mfc="red",
            mec="none",
            mew=0.0,
            label="Well",
        )
        ax.plot(
            xy0,
            xy0,
            lw=0.0,
            marker="s",
            mfc="cyan",
            mec="black",
            mew=0.5,
            alpha=0.5,
            label="Lake",
        )
        ax.plot(
            xy0,
            xy0,
            lw=0.0,
            marker="s",
            mfc="brown",
            mec="black",
            mew=0.5,
            alpha=0.5,
            label="Confining unit",
        )
        ax.axhline(xy0[0], **river_dict, label="River")
        styles.graph_legend(
            ax,
            ncol=2,
            loc="lower center",
            labelspacing=0.1,
            columnspacing=0.6,
            handletextpad=0.3,
        )

        if plot_show:
            plt.show()
        if plot_save:
            sim_folder = os.path.basename(os.path.split(sim_ws)[0])
            fname = f"{sim_folder}-river-discretization.png"
            fig.savefig(figs_path / fname)


def plot_head_results(sims, idx):
    print("Plotting gwf model results...")
    sim_mf6gwf, _, _, _ = sims
    sim_ws = sim_mf6gwf.simulation_data.mfpath.get_sim_path()
    gwf = sim_mf6gwf.flow

    xlims = (extent[0], extent[1])
    ylims = (extent[2], extent[3])

    p2_loc = tuple(welspd[-1][0:2])
    p2_z = gwf.modelgrid.zcellcenters[p2_loc]

    head = gwf.output.head().get_data().squeeze()
    cbc = gwf.output.budget()
    spdis = cbc.get_data(text="DATA-SPDIS")[0]
    qx, qy, qz = flopy.utils.postprocessing.get_specific_discharge(
        spdis, gwf, head=head
    )
    lake_q = cbc.get_data(text="LAK", full3D=True)[0]
    lake_q_dir = np.zeros(lake_q.shape, dtype=int)
    lake_q_dir[lake_q < 0.0] = -1
    lake_q_dir[lake_q > 0.0] = 1

    lake_stage = float(gwf.lak.output.stage().get_data().squeeze())
    lake_stage_vg = np.full(vor.ncpl, 1e30, dtype=float)
    idx = (lake_stage > top_vg) & (lake_cells_vg > 0)
    lake_stage_vg[idx] = lake_stage

    with styles.USGSMap():
        fig = plt.figure(figsize=two_panel_figsize, constrained_layout=True)
        gs = gridspec.GridSpec(ncols=2, nrows=24, figure=fig)
        ax0 = fig.add_subplot(gs[:22, 0])
        ax1 = fig.add_subplot(gs[:22, 1])
        ax2 = fig.add_subplot(gs[22:, :])

        xticks = np.arange(extent[0], extent[1], 1000.0).tolist()
        yticks = np.arange(extent[2], extent[3], 1000.0).tolist()

        for ax in (ax0, ax1):
            ax.set_xlim(xlims)
            ax.set_xticks(xticks)
            ax.set_ylim(ylims)
            ax.set_yticks(yticks)
            ax.set_aspect("equal", "box")

        # topography
        ax = ax0
        styles.heading(ax=ax, idx=0)
        mm = flopy.plot.PlotMapView(
            model=gwf,
            ax=ax,
            extent=voronoi_grid.extent,
        )
        cb = mm.plot_array(top_vg, vmin=top_range[0], vmax=top_range[1])
        mm.plot_grid(**grid_dict)
        plot_wells(ax=ax, ms=3)
        plot_river(ax=ax)
        plot_lake(ax=ax)
        cs = mm.contour_array(
            top_vg,
            **sv_contour_dict,
            levels=top_levels,
        )
        ax.clabel(
            cs,
            **clabel_dict,
        )
        set_ticklabels(ax, fmt="{:.0f}")

        # topography colorbar
        cbar = plt.colorbar(cb, ax=ax, orientation="horizontal", shrink=0.65)
        cbar.ax.tick_params(
            labelsize=5,
            labelcolor="black",
            color="black",
            length=6,
            pad=2,
        )
        cbar.ax.set_title(
            "Elevation (m)",
            pad=2.5,
            loc="left",
            fontdict=font_dict,
        )

        # head
        ax = ax1
        styles.heading(ax=ax, idx=1)
        mm = flopy.plot.PlotMapView(
            model=gwf,
            ax=ax,
            extent=voronoi_grid.extent,
        )
        cb = mm.plot_array(head, vmin=head_range[0], vmax=head_range[1])

        mm.plot_grid(**grid_dict)
        q = mm.plot_vector(qx, qy, normalize=False)
        qk = plt.quiverkey(
            q,
            -0.35,
            -0.31,
            1.0,
            label="Specific discharge vector",
            labelsep=0.05,
            labelpos="E",
            labelcolor="black",
            fontproperties={"size": 9, "weight": "bold"},
        )

        plot_wells(ax=ax, ms=3)
        plot_river(ax=ax)
        plot_lake(ax=ax)
        cs = mm.contour_array(
            head,
            **sv_contour_dict,
            levels=head_levels,
        )
        ax.clabel(
            cs,
            **clabel_dict,
        )
        set_ticklabels(ax, fmt="{:.0f}", xticks=xticks, yticks=yticks)

        # head colorbar
        cbar = plt.colorbar(cb, ax=ax, orientation="horizontal", shrink=0.65)
        cbar.ax.tick_params(
            labelsize=5,
            labelcolor="black",
            color="black",
            length=6,
            pad=2,
        )
        cbar.ax.set_title(
            "Head (m)",
            pad=2.5,
            loc="left",
            fontdict=font_dict,
        )

        # legend
        ax = ax2
        xy0 = (-100, -100)
        ax.set_xlim(0, 1)
        ax.set_ylim(0, 1)
        ax.set_axis_off()

        # fake data to set up legend
        well_patch = ax.plot(
            xy0,
            xy0,
            lw=0.0,
            marker=".",
            ms=5,
            mfc="red",
            mec="none",
            mew=0.0,
            label="Well",
        )
        lake_patch = ax.axhline(xy0[0], color="cyan", lw=0.5, label="Lake")
        river_patch = ax.axhline(xy0[0], **river_dict, label="River")
        contour_patch = ax.axhline(
            xy0[0], **contour_label_dict, label="Contour line (m)"
        )
        styles.graph_legend(
            ax,
            ncol=3,
            loc="lower center",
            labelspacing=0.1,
            columnspacing=0.6,
            handletextpad=0.3,
        )

        if plot_show:
            plt.show()
        if plot_save:
            sim_folder = os.path.basename(os.path.split(sim_ws)[0])
            fname = f"{sim_folder}-head.png"
            fig.savefig(figs_path / fname)


def plot_conc_results(sims):
    print("Plotting gwt model results...")
    _, sim_mf6gwt, _, _ = sims
    sim_ws = sim_mf6gwt.simulation_data.mfpath.get_sim_path()
    gwt = sim_mf6gwt.trans
    with styles.USGSMap():
        fig = plt.figure(figsize=six_panel_figsize, constrained_layout=True)
        gs = gridspec.GridSpec(ncols=3, nrows=24, figure=fig)
        ax0 = fig.add_subplot(gs[:11, 0])
        ax1 = fig.add_subplot(gs[:11, 1])
        ax2 = fig.add_subplot(gs[:11, 2])
        ax3 = fig.add_subplot(gs[11:22, 0])
        ax4 = fig.add_subplot(gs[11:22, 1])
        ax5 = fig.add_subplot(gs[11:22, 2])
        ax6 = fig.add_subplot(gs[22:, :])

        axs = [ax0, ax1, ax2, ax3, ax4, ax5]

        times = gwt.output.concentration().get_times()
        conc = gwt.output.concentration().get_data(totim=times[-1])

        for k in range(6):
            ax = axs[k]
            ax.set_title(f"Layer {k + 1}")
            mm = flopy.plot.PlotMapView(
                model=gwt,
                ax=ax,
                extent=gwt.modelgrid.extent,
                layer=k,
            )

            xticks = np.arange(extent[0], extent[1], 1000.0).tolist()
            yticks = np.arange(extent[2], extent[3], 1000.0).tolist()

            mm.plot_grid(**grid_dict)

            # plot confining unit
            if k == 2:
                mm.plot_array(
                    kclay_loc_vg,
                    masked_values=[
                        0,
                    ],
                    cmap=clay_cmap,
                    alpha=0.5,
                )
            plot_river(ax=ax)
            plot_lake(ax=ax)
            if k == nlay - 1:
                plot_wells(ax=ax, ms=3)
                plot_well_labels(ax)
            cs = mm.contour_array(
                conc,
                masked_values=[0],
                levels=[0.001, 0.01, 0.1, 1],
                **sv_gwt_contour_dict,
            )

            ax.clabel(
                cs,
                inline=True,
                fmt="%1.3g",
                fontsize=6,
                inline_spacing=0.5,
            )

            set_ticklabels(ax, fmt="{:.0f}", xticks=xticks, yticks=yticks)

            styles.heading(ax, idx=k)

        # legend
        ax = ax6
        xy0 = (-100, -100)
        ax.set_xlim(0, 1)
        ax.set_ylim(0, 1)
        ax.set_axis_off()

        # fake data to set up legend
        ax.plot(
            xy0,
            xy0,
            lw=0.0,
            marker=".",
            ms=5,
            mfc="red",
            mec="none",
            mew=0.0,
            label="Well",
        )
        ax.axhline(xy0[0], color="cyan", lw=0.5, label="Lake")
        ax.axhline(xy0[0], **river_dict, label="River")
        ax.axhline(xy0[0], **contour_gwt_label_dict, label="Concentration (mg/L)")
        ax.plot(
            xy0,
            xy0,
            lw=0.0,
            marker="s",
            mfc="brown",
            mec="black",
            mew=0.5,
            alpha=0.5,
            label="Confining unit",
        )
        styles.graph_legend(
            ax,
            ncol=5,
            loc="lower center",
            labelspacing=0.1,
            columnspacing=0.6,
            handletextpad=0.3,
            frameon=False,
        )

        if plot_show:
            plt.show()
        if plot_save:
            sim_folder = os.path.split(sim_ws)[0]
            sim_folder = os.path.basename(sim_folder)
            fname = f"{sim_folder}-conc.png"
            fig.savefig(figs_path / fname)

Running the example

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

[18]:
def scenario(idx, silent=True):
    sim = build_models(sim_name)
    if write:
        write_models(sim, silent=silent)
    if run:
        run_models(sim, silent=silent)
    if plot:
        plot_results(sim, idx)


scenario(0)
Building mf6gwf model...ex-gwt-synthetic-valley
Building mf6gwt model...ex-gwt-synthetic-valley
run_models took 25242.36 ms
Plotting model results...
Plotting river mapping...
../_images/_notebooks_ex-gwt-synthetic-valley_23_3.png
Plotting gwf model results...
../_images/_notebooks_ex-gwt-synthetic-valley_23_5.png
Plotting gwt model results...
../_images/_notebooks_ex-gwt-synthetic-valley_23_7.png