This page was generated from ex-gwf-bcf2ss.py. It's also available as a notebook.

BCF2SS Model

This problem is described in McDonald and Harbaugh (1988) and duplicated in Harbaugh and McDonald (1996). This problem is also is distributed with MODFLOW-2005 (Harbaugh, 2005) and MODFLOW 6 (Langevin and others, 2017).

Two scenarios are included, first solved with the standard method, then with the Newton-Raphson formulation.

Initial setup

Initial setup

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

[1]:
import os
import pathlib as pl

import flopy
import git
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pooch
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.
sim_name = "ex-gwf-bcf2ss"
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 = root / "data" / sim_name 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]:
# Model units
length_units = "feet"
time_units = "days"

# Load the wetdry array for layer 1
fname = "wetdry01.txt"
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:3a4b357b7d2cd5175a205f3347ab973d",
)
wetdry_layer0 = np.loadtxt(fpath)

# Scenario-specific parameters
parameters = {
    "ex-gwf-bcf2ss-p01a": {
        "rewet": True,
        "wetfct": 1.0,
        "iwetit": 1,
        "ihdwet": 0,
        "linear_acceleration": "cg",
        "newton": None,
    },
    "ex-gwf-bcf2ss-p02a": {
        "rewet": False,
        "wetfct": None,
        "iwetit": None,
        "ihdwet": None,
        "linear_acceleration": "bicgstab",
        "newton": "NEWTON",
    },
}

# Model parameters
nper = 2  # Number of periods
nlay = 2  # Number of layers
nrow = 10  # Number of rows
ncol = 15  # Number of columns
delr = 500.0  # Column width ($ft$)
delc = 500.0  # Row width ($ft$)
top = 150.0  # Top of the model ($ft$)
botm_str = "50.0, -50."  # Layer bottom elevations ($ft$)
icelltype_str = "1, 0"  # Cell conversion type
k11_str = "10.0, 5.0"  # Horizontal hydraulic conductivity ($ft/d$)
k33 = 0.1  # Vertical hydraulic conductivity ($ft/d$)
strt = 0.0  # Starting head ($ft$)
recharge = 0.004  # Recharge rate ($ft/d$)

# Time discretization
tdis_ds = (
    (1.0, 1.0, 1),
    (1.0, 1.0, 1),
)

# Parse parameter strings into tuples
botm = [float(value) for value in botm_str.split(",")]
icelltype = [int(value) for value in icelltype_str.split(",")]
k11 = [float(value) for value in k11_str.split(",")]

# Well boundary conditions
wel_spd = {
    1: [
        [1, 2, 3, -35000.0],
        [1, 7, 3, -35000.0],
    ]
}

# River boundary conditions
riv_spd = {0: [[1, i, 14, 0.0, 10000.0, -5] for i in range(nrow)]}

# Solver parameters
nouter = 500
ninner = 100
hclose = 1e-6
rclose = 1e-3
relax = 0.97

Model setup

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

[3]:
def build_models(name, rewet, wetfct, iwetit, ihdwet, linear_acceleration, newton):
    sim_ws = os.path.join(workspace, name)
    sim = flopy.mf6.MFSimulation(sim_name=sim_name, sim_ws=sim_ws, exe_name="mf6")
    flopy.mf6.ModflowTdis(sim, nper=nper, perioddata=tdis_ds, time_units=time_units)
    flopy.mf6.ModflowIms(
        sim,
        linear_acceleration=linear_acceleration,
        outer_maximum=nouter,
        outer_dvclose=hclose,
        inner_maximum=ninner,
        inner_dvclose=hclose,
        rcloserecord=f"{rclose} strict",
        relaxation_factor=relax,
    )
    gwf = flopy.mf6.ModflowGwf(
        sim, modelname=sim_name, save_flows=True, newtonoptions=newton
    )
    flopy.mf6.ModflowGwfdis(
        gwf,
        length_units=length_units,
        nlay=nlay,
        nrow=nrow,
        ncol=ncol,
        delr=delr,
        delc=delc,
        top=top,
        botm=botm,
    )
    if rewet:
        rewet_record = [
            "wetfct",
            wetfct,
            "iwetit",
            iwetit,
            "ihdwet",
            ihdwet,
        ]
        wetdry = [wetdry_layer0, 0]
    else:
        rewet_record = None
        wetdry = None

    flopy.mf6.ModflowGwfnpf(
        gwf,
        rewet_record=rewet_record,
        wetdry=wetdry,
        icelltype=icelltype,
        k=k11,
        k33=k33,
        save_specific_discharge=True,
    )
    flopy.mf6.ModflowGwfic(gwf, strt=strt)
    flopy.mf6.ModflowGwfriv(gwf, stress_period_data=riv_spd)
    flopy.mf6.ModflowGwfwel(gwf, stress_period_data=wel_spd)
    flopy.mf6.ModflowGwfrcha(gwf, recharge=recharge)
    head_filerecord = f"{sim_name}.hds"
    budget_filerecord = f"{sim_name}.cbc"
    flopy.mf6.ModflowGwfoc(
        gwf,
        head_filerecord=head_filerecord,
        budget_filerecord=budget_filerecord,
        saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
    )
    return sim


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


@timed
def run_models(sim, silent=True):
    success, buff = sim.run_simulation(silent=silent)
    assert success, buff

Plotting results

Define functions to plot model results.

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


def plot_simulated_results(num, gwf, ho, co, silent=True):
    with styles.USGSMap():
        botm_arr = gwf.dis.botm.array

        fig = plt.figure(figsize=(6.8, 6), constrained_layout=False)
        gs = mpl.gridspec.GridSpec(ncols=10, nrows=7, figure=fig, wspace=5)
        plt.axis("off")

        ax1 = fig.add_subplot(gs[:3, :5])
        ax2 = fig.add_subplot(gs[:3, 5:], sharey=ax1)
        ax3 = fig.add_subplot(gs[3:6, :5], sharex=ax1)
        ax4 = fig.add_subplot(gs[3:6, 5:], sharex=ax1, sharey=ax1)
        ax5 = fig.add_subplot(gs[6, :])
        axes = [ax1, ax2, ax3, ax4, ax5]

        labels = ("A", "B", "C", "D")
        aquifer = ("Upper aquifer", "Lower aquifer")
        cond = ("natural conditions", "pumping conditions")
        vmin, vmax = -10, 140
        masked_values = [1e30, -1e30]
        levels = [
            np.arange(0, 130, 10),
            (10, 20, 30, 40, 50, 55, 60),
        ]
        plot_number = 0
        for idx, totim in enumerate(
            (
                1,
                2,
            )
        ):
            head = ho.get_data(totim=totim)
            head[head < botm_arr] = -1e30
            qx, qy, qz = flopy.utils.postprocessing.get_specific_discharge(
                co.get_data(text="DATA-SPDIS", kstpkper=(0, totim - 1))[0],
                gwf,
            )

            for k in range(nlay):
                ax = axes[plot_number]
                ax.set_aspect("equal")
                mm = flopy.plot.PlotMapView(model=gwf, ax=ax, layer=k)
                mm.plot_grid(lw=0.5, color="0.5")
                cm = mm.plot_array(
                    head, masked_values=masked_values, vmin=vmin, vmax=vmax
                )
                mm.plot_bc(ftype="WEL", kper=totim - 1)
                mm.plot_bc(ftype="RIV", color="green", kper=0)
                mm.plot_vector(qx, qy, normalize=True, color="0.75")
                cn = mm.contour_array(
                    head,
                    masked_values=masked_values,
                    levels=levels[idx],
                    colors="black",
                    linewidths=0.5,
                )
                plt.clabel(cn, fmt="%3.0f")
                heading = f"{aquifer[k]} under\n{cond[totim - 1]}"
                styles.heading(ax, letter=labels[plot_number], heading=heading)
                styles.remove_edge_ticks(ax)

                plot_number += 1

        # set axis labels
        ax1.set_ylabel("y-coordinate, in feet")
        ax3.set_ylabel("y-coordinate, in feet")
        ax3.set_xlabel("x-coordinate, in feet")
        ax4.set_xlabel("x-coordinate, in feet")

        # legend
        ax = axes[-1]
        ax.set_ylim(1, 0)
        ax.set_xlim(-5, 5)
        ax.set_xticks([])
        ax.set_yticks([])
        ax.spines["top"].set_color("none")
        ax.spines["bottom"].set_color("none")
        ax.spines["left"].set_color("none")
        ax.spines["right"].set_color("none")
        ax.patch.set_alpha(0.0)

        # items for legend
        ax.plot(
            -1000,
            -1000,
            "s",
            ms=5,
            color="green",
            mec="black",
            mew=0.5,
            label="River",
        )
        ax.plot(
            -1000,
            -1000,
            "s",
            ms=5,
            color="red",
            mec="black",
            mew=0.5,
            label="Well",
        )
        ax.plot(
            -1000,
            -1000,
            "s",
            ms=5,
            color="none",
            mec="black",
            mew=0.5,
            label="Dry cell",
        )
        ax.plot(
            -10000,
            -10000,
            lw=0,
            marker="$\u2192$",
            ms=10,
            mfc="0.75",
            mec="0.75",
            label="Normalized specific discharge",
        )
        # ax.plot(
        #     -1000,
        #     -1000,
        #     lw=0.5,
        #     color="black",
        #     label="Head, in feet",
        # )
        styles.graph_legend(
            ax,
            ncol=5,
            frameon=False,
            loc="upper center",
        )

        cbar = plt.colorbar(
            cm, ax=ax, shrink=0.5, orientation="horizontal", location="bottom"
        )
        cbar.ax.set_xlabel("Head, in feet")

        if plot_show:
            plt.show()
        if plot_save:
            fig.savefig(figs_path / f"{sim_name}-{num:02d}.png")


def plot_results(silent=True):
    if not plot:
        return

    if silent:
        verbosity_level = 0
    else:
        verbosity_level = 1

    with styles.USGSMap():
        name = list(parameters.keys())[0]
        sim_ws = os.path.join(workspace, name)
        sim = flopy.mf6.MFSimulation.load(
            sim_name=sim_name, sim_ws=sim_ws, verbosity_level=verbosity_level
        )
        gwf = sim.get_model(sim_name)

        # create MODFLOW 6 head object
        hobj = gwf.output.head()

        # create MODFLOW 6 cell-by-cell budget object
        cobj = gwf.output.budget()

        # extract heads
        head = hobj.get_data(totim=1)

        # plot grid
        fig = plt.figure(figsize=(6.8, 3.5), constrained_layout=True)
        gs = mpl.gridspec.GridSpec(nrows=8, ncols=10, figure=fig, hspace=40, wspace=10)
        plt.axis("off")

        ax = fig.add_subplot(gs[:7, 0:7])
        ax.set_aspect("equal")
        mm = flopy.plot.PlotMapView(model=gwf, ax=ax)
        mm.plot_bc(ftype="WEL", kper=1, plotAll=True)
        mm.plot_bc(ftype="RIV", color="green", plotAll=True)
        mm.plot_grid(lw=0.5, color="0.5")
        ax.set_ylabel("y-coordinate, in feet")
        ax.set_xlabel("x-coordinate, in feet")
        styles.heading(ax, letter="A", heading="Map view")
        styles.remove_edge_ticks(ax)

        ax = fig.add_subplot(gs[:5, 7:])
        mm = flopy.plot.PlotCrossSection(model=gwf, ax=ax, line={"row": 7})
        mm.plot_array(np.ones((nlay, nrow, ncol)), head=head, cmap="jet")
        mm.plot_bc(ftype="WEL", kper=1)
        mm.plot_bc(ftype="RIV", color="green", head=head)
        mm.plot_grid(lw=0.5, color="0.5")
        ax.set_ylabel("Elevation, in feet")
        ax.set_xlabel("x-coordinate along \nrow 8, in feet")
        styles.heading(ax, letter="B", heading="Cross-section view")
        styles.remove_edge_ticks(ax)

        # items for legend
        ax = fig.add_subplot(gs[7, :])
        ax.set_xlim(0, 1)
        ax.set_ylim(0, 1)
        ax.set_xticks([])
        ax.set_yticks([])
        ax.spines["top"].set_color("none")
        ax.spines["bottom"].set_color("none")
        ax.spines["left"].set_color("none")
        ax.spines["right"].set_color("none")
        ax.patch.set_alpha(0.0)
        ax.plot(
            -1100,
            -1100,
            "s",
            ms=5,
            color="green",
            mec="black",
            mew=0.5,
            label="River",
        )
        ax.plot(
            -1100,
            -1100,
            "s",
            ms=5,
            color="red",
            mec="black",
            mew=0.5,
            label="Well",
        )
        ax.plot(
            -1100,
            -1100,
            "s",
            ms=5,
            color="blue",
            mec="black",
            mew=0.5,
            label="Steady-state water table",
        )
        styles.graph_legend(
            ax,
            ncol=3,
            frameon=False,
            loc="upper center",
        )

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

        # figure with wetdry array
        fig = plt.figure(figsize=(4.76, 3), constrained_layout=True)
        ax = fig.add_subplot(1, 1, 1)
        ax.set_aspect("equal")
        mm = flopy.plot.PlotMapView(model=gwf, ax=ax)
        wd = mm.plot_array(wetdry_layer0)
        mm.plot_grid(lw=0.5, color="0.5")
        cbar = plt.colorbar(wd, shrink=0.5)
        cbar.ax.set_ylabel("WETDRY parameter")
        ax.set_ylabel("y-coordinate, in feet")
        ax.set_xlabel("x-coordinate, in feet")
        styles.remove_edge_ticks(ax)

        if plot_show:
            plt.show()
        if plot_save:
            fig.savefig(figs_path / f"{sim_name}-01.png")

        # plot simulated rewetting results
        plot_simulated_results(2, gwf, hobj, cobj)

        # plot simulated newton results
        name = list(parameters.keys())[1]
        sim_ws = os.path.join(workspace, name)
        sim = flopy.mf6.MFSimulation.load(
            sim_name=sim_name, sim_ws=sim_ws, verbosity_level=verbosity_level
        )
        gwf = sim.get_model(sim_name)

        # create MODFLOW 6 head object
        hobj = gwf.output.head()

        # create MODFLOW 6 cell-by-cell budget object
        cobj = gwf.output.budget()

        # plot the newton results
        plot_simulated_results(3, gwf, hobj, cobj)

Running the example

Define a function to run the example scenarios.

[5]:
def scenario(idx, silent=True):
    key = list(parameters.keys())[idx]
    params = parameters[key].copy()
    sim = build_models(key, **params)
    if write:
        write_models(sim, silent=silent)
    if run:
        run_models(sim, silent=silent)

Solve the model by the default means.

[6]:
scenario(0)
<flopy.mf6.data.mfstructure.MFDataItemStructure object at 0x7f5c29a38e20>
run_models took 26.13 ms

Solve the model with the Newton-Raphson formulation.

[7]:
scenario(1)
<flopy.mf6.data.mfstructure.MFDataItemStructure object at 0x7f5c29a38e20>
run_models took 24.12 ms

Plot results.

[8]:
if plot:
    # Simulated water levels and normalized specific discharge vectors in the
    # upper and lower aquifers under natural and pumping conditions using (1) the
    # rewetting option in the Node Property Flow (NPF) Package with the
    # Standard Conductance Formulation and (2) the Newton-Raphson formulation.
    # A. Upper aquifer results under natural conditions. B. Lower aquifer results
    # under natural conditions C. Upper aquifer results under pumping conditions.
    # D. Lower aquifer results under pumping conditions
    plot_results()
/opt/hostedtoolcache/Python/3.9.19/x64/lib/python3.9/site-packages/IPython/core/pylabtools.py:152: UserWarning: constrained_layout not applied because axes sizes collapsed to zero.  Try making figure larger or Axes decorations smaller.
  fig.canvas.print_figure(bytes_io, **kw)
../_images/_notebooks_ex-gwf-bcf2ss_16_1.png
/tmp/ipykernel_8590/3899375827.py:257: UserWarning: constrained_layout not applied because axes sizes collapsed to zero.  Try making figure larger or Axes decorations smaller.
  fig.savefig(fpth)
../_images/_notebooks_ex-gwf-bcf2ss_16_3.png
../_images/_notebooks_ex-gwf-bcf2ss_16_4.png
../_images/_notebooks_ex-gwf-bcf2ss_16_5.png