06: FloPy class project: Structured grid version¶
[1]:
import numpy as np
import matplotlib.pyplot as plt
import geopandas as gpd
import shapely
import shapefile
import flopy
from flopy.utils.gridgen import Gridgen
from flopy.discretization import StructuredGrid, VertexGrid
from flopy.utils.triangle import Triangle as Triangle
from flopy.utils.voronoi import VoronoiGrid
from flopy.utils.gridintersect import GridIntersect
[2]:
model_ws = "../temp/structured/"
Load a few raster files
[3]:
bottom = flopy.utils.Raster.load("../data_project/aquifer_bottom.asc")
top = flopy.utils.Raster.load("../data_project/aquifer_top.asc")
kaq = flopy.utils.Raster.load("../data_project/aquifer_k.asc")
Load a few shapefiles with geopandas
[4]:
river = gpd.read_file("../data_project/Flowline_river.shp")
inactive = gpd.read_file("../data_project/inactive_area.shp")
active = gpd.read_file("../data_project/active_area.shp")
wells = gpd.read_file("../data_project/pumping_well_locations.shp")
Plot the shapefiles with geopandas
[5]:
ax = river.plot(color="cyan")
active.plot(ax=ax, color="blue")
inactive.plot(ax=ax, color="white")
wells.plot(ax=ax, color="red", markersize=8);
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_8_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_8_0.png)
Make a structured model grid¶
[6]:
nlay, nrow, ncol = 3, 160, 80
shape2d, shape3d = (nrow, ncol), (nlay, nrow, ncol)
xlen, ylen = 5000.0, 10000.0
delr = np.full(ncol, xlen / ncol, dtype=float)
delc = np.full(nrow, ylen / nrow, dtype=float)
ttop = np.full(shape2d, 1.0, dtype=float)
tbotm = np.full(shape3d, 0.0, dtype=float)
[7]:
base_grid = StructuredGrid(
delc=delc, delr=delr, nlay=1, nrow=nrow, ncol=ncol,
top=ttop, botm=tbotm
)
Intersect the modelgrid with the shapefiles¶
Create an intersection object¶
[8]:
ix = GridIntersect(base_grid, method="vertex", rtree=True)
Intersect inactive and active shapefiles with the modelgrid¶
After all of the intersection operations, take a look at the data contained in the returned objects
[9]:
bedrock = ix.intersect(inactive.geometry[0])
active_cells = ix.intersect(active.geometry[0])
[10]:
active_cells[:4], active_cells.dtype
[10]:
(rec.array([((0, 0), <POLYGON ((62.5 10000, 62.5 9937.5, 0 9937.5, 0 10000, 62.5 10000))>, 3906.25),
((0, 1), <POLYGON ((125 10000, 125 9937.5, 62.5 9937.5, 62.5 10000, 125 10000))>, 3906.25),
((0, 2), <POLYGON ((187.5 10000, 187.5 9937.5, 125 9937.5, 125 10000, 187.5 10000))>, 3906.25),
((0, 3), <POLYGON ((250 10000, 250 9937.5, 187.5 9937.5, 187.5 10000, 250 10000))>, 3906.25)],
dtype=[('cellids', 'O'), ('ixshapes', 'O'), ('areas', '<f8')]),
dtype((numpy.record, [('cellids', 'O'), ('ixshapes', 'O'), ('areas', '<f8')])))
Intersect well shapefile with the modelgrid¶
[11]:
well_cells = []
for g in wells.geometry:
v = ix.intersect(g)
well_cells += v["cellids"].tolist()
[12]:
well_cells
[12]:
[(33, 61), (41, 49), (77, 53), (101, 37), (113, 21), (133, 45)]
Intersect river shapefile with the modelgrid¶
[13]:
river_cells = ix.intersect(river.geometry[0])
[14]:
river_cells[:4], river_cells.dtype
[14]:
(rec.array([((0, 53), <LINESTRING (3347.204 10000, 3351.371 9937.5)>, 62.63873491),
((1, 53), <LINESTRING (3351.371 9937.5, 3355.537 9875)>, 62.63873491),
((2, 53), <LINESTRING (3355.537 9875, 3359.704 9812.5)>, 62.63873491),
((3, 53), <LINESTRING (3359.704 9812.5, 3363.599 9754.072, 3364.797 9750)>, 62.80215445)],
dtype=[('cellids', 'O'), ('ixshapes', 'O'), ('lengths', '<f8')]),
dtype((numpy.record, [('cellids', 'O'), ('ixshapes', 'O'), ('lengths', '<f8')])))
Intersect constant head line with the modelgrid¶
Use a line with two points to defined the location of the constant head cells. The line verticase are [(1250, 0.1), (4250, 0.1)]
.
[15]:
constant_cells = ix.intersect(
[(1250, 0.1), (4250, 0.1)], shapetype="linestring"
)
[16]:
constant_cells[:4], constant_cells.dtype
[16]:
(rec.array([((159, 20), <LINESTRING (1250 0.1, 1312.5 0.1)>, 62.5),
((159, 21), <LINESTRING (1312.5 0.1, 1375 0.1)>, 62.5),
((159, 22), <LINESTRING (1375 0.1, 1437.5 0.1)>, 62.5),
((159, 23), <LINESTRING (1437.5 0.1, 1500 0.1)>, 62.5)],
dtype=[('cellids', 'O'), ('ixshapes', 'O'), ('lengths', '<f8')]),
dtype((numpy.record, [('cellids', 'O'), ('ixshapes', 'O'), ('lengths', '<f8')])))
Resample the raster data to the modelgrid¶
Use the resample_to_grid()
method on each raster object.
[17]:
rtop = top.resample_to_grid(
base_grid,
band=top.bands[0],
method="linear",
extrapolate_edges=True,
)
rbot = bottom.resample_to_grid(
base_grid,
band=bottom.bands[0],
method="linear",
extrapolate_edges=True,
)
rkaq = (
kaq.resample_to_grid(
base_grid, band=kaq.bands[0], method="linear",
extrapolate_edges=True
)
* 86400.0
)
Plot the resampled data¶
Plot the aquifer top, bottom, and hydraulic conductivity. Also plot the aquifer thickness.
[18]:
mm = flopy.plot.PlotMapView(modelgrid=base_grid)
mm.plot_array(rtop)
mm.plot_grid(lw=0.5, color="0.5");
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_29_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_29_0.png)
[19]:
mm = flopy.plot.PlotMapView(modelgrid=base_grid)
mm.plot_array(rbot)
mm.plot_grid(lw=0.5, color="0.5");
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_30_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_30_0.png)
[20]:
mm = flopy.plot.PlotMapView(modelgrid=base_grid)
cb = mm.plot_array(rtop - rbot)
mm.plot_grid(lw=0.5, color="0.5")
plt.colorbar(cb);
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_31_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_31_0.png)
[21]:
mm = flopy.plot.PlotMapView(modelgrid=base_grid)
cb = mm.plot_array(rkaq, cmap="jet", vmin=1, vmax=10)
mm.plot_grid(lw=0.5, color="0.5")
plt.colorbar(cb);
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_32_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_32_0.png)
Build the model data¶
Create the bottom of each model layer
Assume that the thickness of each layer at a row, column location is equal.
[22]:
botm = np.zeros(shape3d, dtype=float)
botm[-1, :, :] = rbot[:, :]
layer_thickness = (rtop - rbot) / nlay
for k in reversed(range(nlay - 1)):
botm[k] = botm[k + 1] + layer_thickness
Create the idomain array
Use the intersection data from the active and inactive shapefiles to create the idomain array
[23]:
idomain = np.zeros((nlay, nrow, ncol), dtype=float)
for i, j in active_cells["cellids"]:
idomain[:, i, j] = 1
for i, j in bedrock["cellids"]:
idomain[:, i, j] = 0
A more pythonic approach
[24]:
idomain_bool = np.zeros((nrow, ncol), dtype=bool)
[25]:
idomain_bool[tuple(zip(*active_cells["cellids"].tolist()))] = True
[26]:
idomain_bool[tuple(zip(*bedrock["cellids"].tolist()))] = False
[27]:
plt.imshow(idomain_bool)
[27]:
<matplotlib.image.AxesImage at 0x14dd33b50>
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_41_1.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_41_1.png)
[28]:
idomain_bool
[28]:
array([[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
[ True, True, True, ..., True, True, True],
...,
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False],
[False, False, False, ..., False, False, False]])
[29]:
idomain = np.zeros((nrow, ncol), dtype=float)
idomain[idomain_bool] = 1
v = plt.imshow(idomain)
plt.colorbar(v)
[29]:
<matplotlib.colorbar.Colorbar at 0x14dae3d90>
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_43_1.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_43_1.png)
[30]:
idomain = np.array([idomain, idomain, idomain])
idomain.shape
[30]:
(3, 160, 80)
Build the well package stress period data
The pumping rates are in the
wells
geopandas dataframePumping rates are in m\(^3\)/sec
The wells are located in model layer 3
[31]:
wells
[31]:
FID | Q | geometry | |
---|---|---|---|
0 | 0 | -0.00820 | POINT (3875.000 7875.000) |
1 | 1 | -0.00410 | POINT (3125.000 7375.000) |
2 | 2 | -0.00390 | POINT (3375.000 5125.000) |
3 | 3 | -0.00083 | POINT (2375.000 3625.000) |
4 | 4 | -0.00072 | POINT (1375.000 2875.000) |
5 | 5 | -0.00430 | POINT (2875.000 1625.000) |
[32]:
well_spd = []
for (i, j), q in zip(well_cells, wells["Q"]):
well_spd.append([2, i, j, q * 86400.0])
well_spd
[32]:
[[2, 33, 61, -708.48],
[2, 41, 49, -354.24],
[2, 77, 53, -336.96],
[2, 101, 37, -71.712],
[2, 113, 21, -62.208000000000006],
[2, 133, 45, -371.52]]
Build the river package stress period data
Calculate the length of the river using the
"lengths"
key.The vertical hydraulic conductivity of the river bed sediments is 3.5 m/d.
The thickness of river bottom sediments at the upstream (North) and downstream (South) end of the river is 0.5 and 1.5 meters, respectively.
The river bottom at the upstream and downstream end of the river is 16.5 and 14.5 meters, respectively. The river width at the upstream and downstream end of the river is 5.0 and 10.0 meters, respectively.
The river stage at the upstream and downstream end of the river is 16.6 and 15.5 meters, respectively.
Use the boundname
upstream
for river cells where the upstream end of the river cell is less than 5000 m from the North end of the model. Use the boundnamedownstream
for all other river cells.Make sure the river cell is in the upper most layer where the river bottom exceeds the bottom of the cell.
Use the upstream and downstream values to interpolate the river sediment thickness, bottom, width, and stage for each river cell.
The river cells will be connected to model layer 1. The river bottom, width, and stage values should be calculated at the center of the river reach.
[33]:
river_kv = 3.5
river_thickness_up, river_thickness_down = 0.5, 1.5
river_bot_up, river_bot_down = 16.5, 14.5
river_width_up, river_width_down = 5.0, 10.0
stage_up, stage_down = 16.6, 15.5
[34]:
river_length = river_cells["lengths"].sum()
river_length
[34]:
12419.290359618695
[35]:
river_thickness_slope = (
river_thickness_down - river_thickness_up
) / river_length
[36]:
river_bot_slope = (river_bot_down - river_bot_up) / river_length
[37]:
river_width_slope = (river_width_down - river_width_up) / river_length
[38]:
stage_slope = (stage_down - stage_up) / river_length
[39]:
boundname = "upstream"
total_length = 0.0
river_spd = []
for idx, (cellid, length) in enumerate(
zip(river_cells["cellids"], river_cells["lengths"])
):
if total_length >= 5000.0 and boundname == "upstream":
boundname = "downstream"
dx = 0.5 * length
total_length += dx
river_thickness = river_thickness_up + river_thickness_slope * total_length
river_bot = river_bot_up + river_bot_slope * total_length
river_width = river_width_up + river_width_slope * total_length
river_stage = stage_up + stage_slope * total_length
conductance = river_kv * length * river_width / river_thickness
i, j = cellid
for k in range(nlay):
if river_bot - river_thickness > botm[k, i, j]:
riv_layer = k
break
river_spd.append(
[riv_layer, i, j, river_stage, conductance, river_bot, boundname]
)
total_length += dx
river_spd[:2], river_spd[-2:]
[39]:
([[0,
0,
53,
16.597225984480367,
2186.8547136345433,
16.494956335418845,
'upstream'],
[0,
1,
53,
16.591677953441096,
2176.0166873198878,
16.484869006256535,
'upstream']],
[[0,
158,
50,
15.508347242017402,
1480.5598623536812,
14.515176803668005,
'downstream'],
[0,
159,
50,
15.502770376374766,
1460.266817949857,
14.50503704795412,
'downstream']])
A more pythonic approach
[40]:
xmid = river_cells["lengths"].cumsum() - 0.5 * river_cells["lengths"]
xp = [0, river_cells["lengths"].cumsum()[-1]]
xp
[40]:
[0, 12419.290359618704]
[41]:
riv_stage = np.interp(xmid, xp, [stage_up, stage_down])
riv_thick = np.interp(xmid, xp, [river_thickness_up, river_thickness_down])
riv_bot = np.interp(xmid, xp, [river_bot_up, river_bot_down])
riv_wid = np.interp(xmid, xp, [river_width_up, river_width_down])
riv_cond = river_kv * river_cells["lengths"] * riv_wid / riv_thick
[42]:
riv_bot_elev = riv_bot - riv_thick
riv_layer = [0 if riv_bot_elev[idx] > botm[0, i, j] else 1 for idx, (i, j) in enumerate(river_cells["cellids"])]
[43]:
riv_bndname = np.full(riv_cond.shape, "upstream", dtype="|S10")
riv_bndname[xmid > 5000.] = "downstream"
[44]:
river_spd = [(riv_layer[idx], i, j, riv_stage[idx], riv_cond[idx], riv_bot[idx], riv_bndname[idx].decode('UTF-8'))
for idx, (i,j) in enumerate(river_cells["cellids"])]
river_spd[:2], river_spd[-2:]
[44]:
([(0,
0,
53,
16.597225984480367,
2186.8547136345433,
16.494956335418845,
'upstream'),
(0,
1,
53,
16.591677953441096,
2176.0166873198878,
16.484869006256535,
'upstream')],
[(0,
158,
50,
15.508347242017402,
1480.5598623536819,
14.515176803668004,
'downstream'),
(0,
159,
50,
15.502770376374766,
1460.2668179498573,
14.50503704795412,
'downstream')])
Define river observations
[45]:
riv_obs = {
"riv_obs.csv": [
("UPSTREAM", "RIV", "UPSTREAM"),
("DOWNSTREAM", "RIV", "DOWNSTREAM"),
]
}
Define the constant head cells
Assume the constant head cells are located in all three layers and have values equal to the downstream river stage (stage_down
). Make sure the constant head stage is greater that the bottom of the layer.
[46]:
chd_spd = []
for i, j in constant_cells["cellids"]:
for k in range(nlay):
if stage_down > botm[k, i, j] and idomain[k, i, j] > 0:
chd_spd.append((k, i, j, stage_down))
chd_spd[:2], chd_spd[-2:]
[46]:
([(1, 159, 21, 15.5), (2, 159, 21, 15.5)],
[(1, 159, 59, 15.5), (2, 159, 59, 15.5)])
Define recharge rates
The recharge rate is 0.16000000E-08 m/sec
[47]:
recharge_rate = 0.16000000e-08 * 86400.0
recharge_rate
[47]:
0.00013824
Build the model¶
Build a steady-state model using the data that you have created. Packages to create:
Simulation
TDIS (1 stress period,
TIME_UNITS='days'
)IMS (default parameters)
GWF model (
SAVE_FLOWS=True
)DIS (
LENGTH_UNITS='meters'
)IC (
STRT=40.
)NPF (Unconfined, same K for all layers,
SAVE_SPECIFIC_DISCHARGE=True
,icelltype=1
)RCH (array based)
RIV (
BOUNDNAMES=True
,addRIV
observations for defined boundnames)CHD
WEL
OC (Save
HEAD ALL
andBUDGET ALL
)
[48]:
name = "project"
[49]:
sim = flopy.mf6.MFSimulation(sim_name=name, sim_ws=model_ws, exe_name="mf6")
tdis = flopy.mf6.ModflowTdis(sim, time_units="days")
ims = flopy.mf6.ModflowIms(
sim, outer_maximum=200, inner_maximum=100, linear_acceleration="bicgstab"
)
[50]:
gwf = flopy.mf6.ModflowGwf(
sim,
modelname=name,
save_flows=True,
newtonoptions="NETWON UNDER_RELAXATION",
)
[51]:
riv_obs
[51]:
{'riv_obs.csv': [('UPSTREAM', 'RIV', 'UPSTREAM'),
('DOWNSTREAM', 'RIV', 'DOWNSTREAM')]}
[52]:
dis = flopy.mf6.ModflowGwfdis(
gwf,
length_units="meters",
nlay=nlay,
nrow=nrow,
ncol=ncol,
delr=delr,
delc=delc,
top=rtop,
botm=botm,
idomain=idomain,
)
ic = flopy.mf6.ModflowGwfic(gwf, strt=40.0)
npf = flopy.mf6.ModflowGwfnpf(
gwf, save_specific_discharge=True, k=[rkaq, rkaq, rkaq], icelltype=1
)
rch = flopy.mf6.ModflowGwfrcha(gwf, recharge=recharge_rate)
riv = flopy.mf6.ModflowGwfriv(
gwf, boundnames=True, stress_period_data=river_spd
)
riv.obs.initialize(
filename=f"{name}.riv.obs",
digits=9,
print_input=True,
continuous=riv_obs,
)
wel = flopy.mf6.ModflowGwfwel(gwf, stress_period_data=well_spd)
chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=chd_spd)
oc = flopy.mf6.ModflowGwfoc(
gwf,
head_filerecord=f"{name}.hds",
budget_filerecord=f"{name}.cbc",
saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")],
)
Write the model files¶
[53]:
sim.write_simulation()
writing simulation...
writing simulation name file...
writing simulation tdis package...
writing solution package ims_-1...
writing model project...
writing model name file...
writing package dis...
writing package ic...
writing package npf...
writing package rcha_0...
writing package riv_0...
INFORMATION: maxbound in ('gwf6', 'riv', 'dimensions') changed to 249 based on size of stress_period_data
writing package obs_0...
writing package wel_0...
INFORMATION: maxbound in ('gwf6', 'wel', 'dimensions') changed to 6 based on size of stress_period_data
writing package chd_0...
INFORMATION: maxbound in ('gwf6', 'chd', 'dimensions') changed to 102 based on size of stress_period_data
writing package oc...
Run the model¶
[54]:
sim.run_simulation()
FloPy is using the following executable to run the model: ../../../../../../../.local/share/flopy/bin/mf6
MODFLOW 6
U.S. GEOLOGICAL SURVEY MODULAR HYDROLOGIC MODEL
VERSION 6.4.2 06/28/2023
MODFLOW 6 compiled Jul 05 2023 20:29:14 with Intel(R) Fortran Intel(R) 64
Compiler Classic for applications running on Intel(R) 64, Version 2021.7.0
Build 20220726_000000
This software has been approved for release by the U.S. Geological
Survey (USGS). Although the software has been subjected to rigorous
review, the USGS reserves the right to update the software as needed
pursuant to further analysis and review. No warranty, expressed or
implied, is made by the USGS or the U.S. Government as to the
functionality of the software and related material nor shall the
fact of release constitute any such warranty. Furthermore, the
software is released on condition that neither the USGS nor the U.S.
Government shall be held liable for any damages resulting from its
authorized or unauthorized use. Also refer to the USGS Water
Resources Software User Rights Notice for complete use, copyright,
and distribution information.
Run start date and time (yyyy/mm/dd hh:mm:ss): 2024/02/08 12:36:32
Writing simulation list file: mfsim.lst
Using Simulation name file: mfsim.nam
Solving: Stress period: 1 Time step: 1
Run end date and time (yyyy/mm/dd hh:mm:ss): 2024/02/08 12:36:33
Elapsed run time: 0.821 Seconds
Normal termination of simulation.
[54]:
(True, [])
Post-process the results¶
Use gwf.output.
method to get the observations.
[55]:
myobs = gwf.riv.output.obs().get_data()
myobs.dtype
[55]:
dtype([('totim', '<f8'), ('UPSTREAM', '<f8'), ('DOWNSTREAM', '<f8')])
[56]:
myobs["UPSTREAM"], myobs["DOWNSTREAM"]
[56]:
(array([-1300.79659]), array([-2300.13083]))
Use gwf.output.
method to get the heads and specific discharge. Make a map and cross-sections of the data using flopy.plot
methods. Plot specific discharge vectors on the map and cross-sections.
[57]:
head = gwf.output.head().get_data()
fpth = “/Users/jdhughes/Documents/Training/python-for-hydrology/notebooks/part1_flopy/data_project/aquifer_top.asc” np.savetxt(fpth, head[0] + 2.)
[58]:
gwf.output.budget().get_unique_record_names()
[58]:
[b' FLOW-JA-FACE',
b' DATA-SPDIS',
b' WEL',
b' RIV',
b' RCHA',
b' CHD']
[59]:
spdis = gwf.output.budget().get_data(text="DATA-SPDIS")[0]
qx, qy, qz = flopy.utils.postprocessing.get_specific_discharge(
spdis, gwf, head=head
)
[60]:
fig, ax = plt.subplots(
ncols=1,
nrows=1,
figsize=(5, 8),
constrained_layout=True,
subplot_kw=dict(aspect="equal"),
)
mm = flopy.plot.PlotMapView(model=gwf, ax=ax, layer=0)
cb = mm.plot_array(head, masked_values=[1e30], vmin=10, vmax=30)
river.plot(color="cyan", ax=mm.ax)
mm.plot_grid(lw=0.5, color="0.5")
mm.plot_vector(qx, qy, normalize=True)
cs = mm.contour_array(
head,
masked_values=[1e30],
colors="red",
levels=np.arange(10, 28, 1),
linestyles=":",
linewidths=1.0,
)
ax.clabel(
cs,
inline=True,
fmt="%1.0f",
fontsize=10,
inline_spacing=0.5,
)
# mm.plot_inactive(ibound=idomain)
plt.colorbar(cb, ax=mm.ax, shrink=0.5)
fig.savefig("../figures/freyberg-structured.png", dpi=300);
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_86_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_86_0.png)
[61]:
fig, ax = plt.subplots(
ncols=1,
nrows=1,
figsize=(5, 8),
constrained_layout=True,
subplot_kw=dict(aspect="equal"),
)
mm = flopy.plot.PlotMapView(model=gwf, ax=ax, layer=0)
cb = mm.plot_array(gwf.dis.top.array, masked_values=[1e30], vmin=15, vmax=30)
river.plot(color="cyan", ax=mm.ax)
mm.plot_grid(lw=0.5, color="0.5", zorder=11)
mm.plot_inactive(zorder=10)
cs = mm.contour_array(
gwf.dis.top.array,
colors="red",
levels=np.arange(15, 31, 1),
linestyles=":",
linewidths=1.0,
)
ax.clabel(
cs,
inline=True,
fmt="%1.0f",
fontsize=10,
inline_spacing=0.5,
)
plt.colorbar(cb, ax=mm.ax, shrink=0.5)
fig.savefig("../figures/freyberg-structured-grid.png", dpi=300);
![../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_87_0.png](../../../_images/notebooks_part1_flopy_solutions_06-Project-structured_completed_87_0.png)
[ ]: