Skip to content

Commit

Permalink
feat(PRT): add MF6 PRT support utilities
Browse files Browse the repository at this point in the history
* to_coords() method for particle data classes
* to_mp7_[pathlines/endpoints] conversion fns
* support PRT pathline data in plot routines
* add PRT tutorial and example notebook
  • Loading branch information
wpbonelli committed Aug 16, 2023
1 parent 0418f85 commit 264bb4f
Show file tree
Hide file tree
Showing 23 changed files with 2,807 additions and 847 deletions.
304 changes: 304 additions & 0 deletions .docs/Notebooks/mf6_prt_tutorial01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
# ---
# jupyter:
# jupytext:
# notebook_metadata_filter: metadata
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.15.0
# kernelspec:
# display_name: Python 3
# language: python
# name: python3
# metadata:
# section: mf6
# ---

# # MODFLOW 6 PRT (particle-tracking) tutorial

# This tutorial runs a GWF model then a PRT model
# in separate simulations via flow model interface.
#
# The grid is a 10x10 square with a single layer,
# the same flow system shown in the FloPy readme.
#
# Particles are released from the top left cell.
#
# ## Initial setup
#
# First, import dependencies, set up a temporary workspace, and define some filenames and model parameters.


import os
from pathlib import Path
from tempfile import TemporaryDirectory

import flopy
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from flopy.utils import PathlineFile
from flopy.utils.binaryfile import HeadFile

# workspace
temp_dir = TemporaryDirectory()
ws = Path(temp_dir.name)

# model names
name = "prtfmi01"
gwfname = f"{name}_gwf"
prtname = f"{name}_prt"
mp7name = f"{name}_mp7"

# output file names
gwf_budget_file = f"{gwfname}.bud"
gwf_head_file = f"{gwfname}.hds"
prt_track_file = f"{prtname}.trk"
prt_track_csv_file = f"{prtname}.trk.csv"
mp7_pathline_file = f"{mp7name}.mppth"

# model info
nlay = 1
nrow = 10
ncol = 10
top = 1.0
botm = [0.0]
nper = 1
perlen = 1.0
nstp = 1
tsmult = 1.0
porosity = 0.1

# Define release points. We will release three particles from the top left cell of the model.

# release points
releasepts = [
# particle index, k, i, j, x, y, z
# (0-based indexing converted to 1-based for mf6 by flopy)
(0, 0, 0, 0, 0.25, 9.25, 0.5),
(1, 0, 0, 0, 0.5, 9.5, 0.5),
(2, 0, 0, 0, 0.75, 9.75, 0.5),
]

# Particle-tracking models require a flow solution to solve for particle motion — PRT models may either run side-by-side with a GWF model with an exchange, or may consume the output of a previously run GWF model via flow model interface.
#
# In this tutorial we do the latter. First we define a GWF model and simulation, then a separate PRT model/simulation.
#
# Define a function to build a MODFLOW 6 GWF model.

def build_gwf_sim(ws, mf6):
# create simulation
sim = flopy.mf6.MFSimulation(
sim_name=name,
exe_name=mf6,
version="mf6",
sim_ws=ws,
)

# create tdis package
flopy.mf6.modflow.mftdis.ModflowTdis(
sim,
pname="tdis",
time_units="DAYS",
nper=nper,
perioddata=[(perlen, nstp, tsmult)],
)

# create gwf model
gwf = flopy.mf6.ModflowGwf(sim, modelname=gwfname, save_flows=True)

# create gwf discretization
flopy.mf6.modflow.mfgwfdis.ModflowGwfdis(
gwf,
pname="dis",
nlay=nlay,
nrow=nrow,
ncol=ncol,
)

# create gwf initial conditions package
flopy.mf6.modflow.mfgwfic.ModflowGwfic(gwf, pname="ic")

# create gwf node property flow package
flopy.mf6.modflow.mfgwfnpf.ModflowGwfnpf(
gwf,
pname="npf",
save_saturation=True,
save_specific_discharge=True,
)

# create gwf chd package
spd = {
0: [[(0, 0, 0), 1.0, 1.0], [(0, 9, 9), 0.0, 0.0]],
1: [[(0, 0, 0), 0.0, 0.0], [(0, 9, 9), 1.0, 2.0]],
}
chd = flopy.mf6.ModflowGwfchd(
gwf,
pname="CHD-1",
stress_period_data=spd,
auxiliary=["concentration"],
)

# create gwf output control package
oc = flopy.mf6.ModflowGwfoc(
gwf,
budget_filerecord=gwf_budget_file,
head_filerecord=gwf_head_file,
saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
)

# create iterative model solution for gwf model
ims = flopy.mf6.ModflowIms(sim)

return sim


# Define the PRT simulation.

def build_prt_sim(ws, mf6):
# create simulation
sim = flopy.mf6.MFSimulation(
sim_name=name,
exe_name=mf6,
version="mf6",
sim_ws=ws,
)

# create tdis package
flopy.mf6.modflow.mftdis.ModflowTdis(
sim,
pname="tdis",
time_units="DAYS",
nper=nper,
perioddata=[(perlen, nstp, tsmult)],
)

# create prt model
prt = flopy.mf6.ModflowPrt(sim, modelname=prtname)

# create prt discretization
flopy.mf6.modflow.mfgwfdis.ModflowGwfdis(
prt,
pname="dis",
nlay=nlay,
nrow=nrow,
ncol=ncol,
)

# create mip package
flopy.mf6.ModflowPrtmip(prt, pname="mip", porosity=porosity)

# create prp package
flopy.mf6.ModflowPrtprp(
prt,
pname="prp1",
filename=f"{prtname}_1.prp",
nreleasepts=len(releasepts),
packagedata=releasepts,
perioddata={0: ["FIRST"]},
)

# create output control package
flopy.mf6.ModflowPrtoc(
prt,
pname="oc",
track_filerecord=[prt_track_file],
trackcsv_filerecord=[prt_track_csv_file],
)

# create the flow model interface
flopy.mf6.ModflowPrtfmi(
prt,
packagedata=[
("GWFHEAD", gwf_head_file),
("GWFBUDGET", gwf_budget_file),
],
)

# add explicit model solution
ems = flopy.mf6.ModflowEms(
sim,
pname="ems",
filename=f"{prtname}.ems",
)
sim.register_solution_package(ems, [prt.name])

return sim


# ## Running models
#
# Next, build and run the models. The flow model must run before the tracking model so the latter can consume the former's cell budget output file (configured above via flow model interface).

# build mf6 models
gwfsim = build_gwf_sim(ws, "mf6")
prtsim = build_prt_sim(ws, "mf6")

# run mf6 models
for sim in [gwfsim, prtsim]:
sim.write_simulation()
success, _ = sim.run_simulation()
assert success

# Extract model and grid objects from the simulations for use with plots.

# extract model objects
gwf = gwfsim.get_model(gwfname)
prt = prtsim.get_model(prtname)

# extract model grid
mg = gwf.modelgrid

# ## Inspecting results
#
# Finally we can load and plot results. First make sure the expected output files were created, then load pathlines, heads, budget, and specific discharge data.

# check mf6 output files exist
assert (ws / gwf_budget_file).is_file()
assert (ws / gwf_head_file).is_file()
assert (ws / prt_track_file).is_file()
assert (ws / prt_track_csv_file).is_file()

# load pathlines
pls = pd.read_csv(ws / prt_track_csv_file)

# extract head, budget, and specific discharge results from GWF model
hds = HeadFile(ws / gwf_head_file).get_data()
bud = gwf.output.budget()
spdis = bud.get_data(text="DATA-SPDIS")[0]
qx, qy, qz = flopy.utils.postprocessing.get_specific_discharge(spdis, gwf)

# Path points/lines can be plotted straightforwardly.

# plot mf6 pathlines in map view
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(13, 13))
ax.set_aspect("equal")
pmv = flopy.plot.PlotMapView(modelgrid=mg, ax=ax)
pmv.plot_grid()
pmv.plot_array(hds[0], alpha=0.1)
pmv.plot_vector(qx, qy, normalize=True, color="white")
pathlines = pls.groupby(["imdl", "iprp", "irpt", "trelease"])
for ipl, ((imdl, iprp, irpt, trelease), pl) in enumerate(pathlines):
pl.plot(
title="Pathlines",
kind="line",
x="x",
y="y",
marker="o",
ax=ax,
legend=False,
color=cm.plasma(ipl / len(pathlines)),
)

# Alternatively, with FloPy's built-in pathline plotting utilities:

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(13, 13))
ax.set_aspect("equal")
pmv = flopy.plot.PlotMapView(modelgrid=mg, ax=ax)
pmv.plot_grid()
pmv.plot_array(hds[0], alpha=0.1)
pmv.plot_vector(qx, qy, normalize=True, color="white")
colors = cm.plasma(np.linspace(0, 1, pls.groupby(["imdl", "iprp", "irpt", "trelease"]).ngroups))
pmv.plot_pathline(pls, layer="all", colors=colors, linewidth=2)
Loading

0 comments on commit 264bb4f

Please sign in to comment.