Skip to content

Commit 3405517

Browse files
authored
Feat(hfb_util): add method for creating HFBs (#2745)
* New feats: updates to Grid, GridIntersect, and VoronoiGrid Updates support development of generalized HFB creation methods * Grid: add cell_area property via the shoelace algorithm * GridIntersect: add "experimental" support for UnstructuredGrid * VoronoiGrid: add support for get_disu6_gridprops() that can build MF6 DISU packages * Feat(hfb_util): add hfb builder method * add make_hfb_array and supporting methods to build HFB recarrays from LineString / Grid intersections * Linting * update hfb_util: add perturbation routine for edge cases * catches "mid cell split" and "colinear with cell boundary" edge cases * Added tests for hfb_util * fix spelling * Add notebook documentation for `make_hfb_array` utility
1 parent 66b4423 commit 3405517

7 files changed

Lines changed: 1246 additions & 4 deletions

File tree

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
# # Creating HFB input data from shapefile linework
5+
#
6+
# This notebook shows examples of how to create a horizontal flow barrier package (HFB) from shapefile information in FloPy. FloPy has support for creating HFB inputs from LineString like vector data for all discretization types (`DIS`, `DISV`, `DISU`).
7+
#
8+
# This notebook starts off by generating two simple models:
9+
# - a structured (DIS) model
10+
# - a vertex (DISV) model
11+
#
12+
# And then shows how to generate HFB input for each of these models.
13+
14+
# In[1]:
15+
16+
17+
from tempfile import TemporaryDirectory
18+
19+
import geopandas as gpd
20+
import matplotlib.pyplot as plt
21+
import numpy as np
22+
from shapely.geometry import LineString
23+
24+
import flopy
25+
from flopy.utils import make_hfb_array
26+
27+
temp_dir = TemporaryDirectory()
28+
workspace = temp_dir.name
29+
30+
31+
# ### Generate a Structured (DIS) model
32+
33+
# In[2]:
34+
35+
36+
def simple_structured_model():
37+
"""
38+
Method to generate a 10x10 structured grid model
39+
"""
40+
lx = 100
41+
ly = 100
42+
nlay = 1
43+
nrow = 10
44+
ncol = 10
45+
delc = np.full((nrow,), ly / nrow)
46+
delr = np.full((ncol,), ly / ncol)
47+
top = np.full((nrow, ncol), 10)
48+
botm = np.zeros((nlay, nrow, ncol))
49+
idomain = np.ones(botm.shape, dtype=int)
50+
51+
sim = flopy.mf6.MFSimulation(sim_ws="tmp_struct")
52+
ims = flopy.mf6.ModflowIms(sim, complexity="SIMPLE")
53+
tdis = flopy.mf6.ModflowTdis(sim)
54+
55+
gwf = flopy.mf6.ModflowGwf(sim, modelname="hfb_model")
56+
dis = flopy.mf6.ModflowGwfdis(
57+
gwf,
58+
nlay=nlay,
59+
nrow=nrow,
60+
ncol=ncol,
61+
delc=delc,
62+
delr=delr,
63+
top=top,
64+
botm=botm,
65+
idomain=idomain,
66+
)
67+
npf = flopy.mf6.ModflowGwfnpf(gwf)
68+
sto = flopy.mf6.ModflowGwfsto(gwf)
69+
ic = flopy.mf6.ModflowGwfic(gwf, strt=np.full((nlay, nrow, ncol), 9))
70+
71+
return sim
72+
73+
74+
# ### Generate a voronoi (DISV) model
75+
76+
# In[3]:
77+
78+
79+
def simple_vertex_model(workspace):
80+
"""
81+
Method to generate a voroni vertex grid model
82+
"""
83+
from flopy.utils.triangle import Triangle
84+
from flopy.utils.voronoi import VoronoiGrid
85+
86+
geom = [(0, 0), (100, 0), (100, 100), (0, 100), (0, 0)]
87+
tri = Triangle(angle=30, model_ws=workspace)
88+
tri.add_polygon(geom)
89+
tri.add_region((5, 5), 0, maximum_area=40)
90+
tri.build()
91+
92+
vor = VoronoiGrid(tri)
93+
pkg_props = vor.get_disv_gridprops()
94+
ncpl = pkg_props["ncpl"]
95+
nlay = 1
96+
top = np.full((ncpl,), 10)
97+
botm = np.zeros((nlay, ncpl))
98+
idomain = np.ones(botm.shape, dtype=int)
99+
100+
sim = flopy.mf6.MFSimulation(sim_ws="tmp_struct")
101+
ims = flopy.mf6.ModflowIms(sim, complexity="SIMPLE")
102+
tdis = flopy.mf6.ModflowTdis(sim)
103+
104+
gwf = flopy.mf6.ModflowGwf(sim, modelname="hfb_model")
105+
disv = flopy.mf6.ModflowGwfdisv(
106+
gwf, nlay=1, top=top, botm=botm, idomain=idomain, **pkg_props
107+
)
108+
npf = flopy.mf6.ModflowGwfnpf(gwf)
109+
sto = flopy.mf6.ModflowGwfsto(gwf)
110+
ic = flopy.mf6.ModflowGwfic(gwf, strt=np.full((nlay, ncpl), 9))
111+
112+
return sim
113+
114+
115+
# ### Create HFB inputs for a Structured (DIS) model
116+
117+
# The `make_hfb_array` utility is used to intersect `LineString` like data with `modelgrid` instances to produce HFB data. The `make_hfb_array` utility accepts two parameters:
118+
# - `modelgrid` : a flopy Grid instance
119+
# - `geom` : a geospatial LineString like object that can be: an iterable of points, GeoJSON, Shapely.geometry.LineString, or a shapefile.Shape object.
120+
121+
# First define a fault line and visualize it with the model
122+
123+
# In[4]:
124+
125+
126+
# define a fault line
127+
fault = [(0, 4), (50, 55), (100, 55)]
128+
129+
# build simulation
130+
sim = simple_structured_model()
131+
gwf = sim.get_model()
132+
modelgrid = gwf.modelgrid
133+
134+
fig, ax = plt.subplots(figsize=(5, 5))
135+
pmv = flopy.plot.PlotMapView(modelgrid=modelgrid, ax=ax)
136+
pmv.plot_grid()
137+
plt.plot(np.array(fault).T[0], np.array(fault).T[1], "r--")
138+
# now create hfb data with the `make_hfb_array` utility
139+
140+
# In[5]:
141+
142+
143+
hfbs = flopy.utils.make_hfb_array(modelgrid, fault)
144+
hfbs[0:5]
145+
146+
147+
# notice that the `hydchr` parameter is not filled out; this is left up to the user
148+
149+
# In[6]:
150+
151+
152+
hfbs["hydchr"] = 1e-05
153+
154+
155+
# Now we can build an HFB package and compare it to the fault line that was defined above
156+
157+
# In[7]:
158+
159+
160+
hfb = flopy.mf6.ModflowGwfhfb(gwf, stress_period_data={0: hfbs})
161+
162+
163+
# In[8]:
164+
165+
166+
# plot up the results
167+
fig, ax = plt.subplots(figsize=(5, 5))
168+
pmv = flopy.plot.PlotMapView(modelgrid=modelgrid)
169+
pmv.plot_grid()
170+
pmv.plot_bc(package=hfb, color="b", lw=2)
171+
plt.plot(np.array(fault).T[0], np.array(fault).T[1], "r--")
172+
# ### Create HFB inputs for a Vertex (DISV) model
173+
174+
# The `make_hfb_array` utility is used to intersect `LineString` like data with `modelgrid` instances to produce HFB data. The `make_hfb_array` utility accepts two parameters:
175+
# - `modelgrid` : a flopy Grid instance
176+
# - `geom` : a geospatial LineString like object that can be: an iterable of points, GeoJSON, Shapely.geometry.LineString, or a shapefile.Shape object.
177+
178+
# First define a fault line and visualize it with the model
179+
180+
# In[9]:
181+
182+
183+
# define a fault line
184+
fault = [(0, 4), (50, 55), (100, 55)]
185+
186+
# build simulation
187+
sim = simple_vertex_model(workspace)
188+
gwf = sim.get_model()
189+
modelgrid = gwf.modelgrid
190+
191+
fig, ax = plt.subplots(figsize=(5, 5))
192+
pmv = flopy.plot.PlotMapView(modelgrid=modelgrid, ax=ax)
193+
pmv.plot_grid()
194+
plt.plot(np.array(fault).T[0], np.array(fault).T[1], "r--")
195+
# now create hfb data with the `make_hfb_array` utility
196+
197+
# In[10]:
198+
199+
200+
hfbs = flopy.utils.make_hfb_array(modelgrid, fault)
201+
hfbs[0:5]
202+
203+
204+
# notice that the `hydchr` parameter is not filled out; this is left up to the user
205+
206+
# In[11]:
207+
208+
209+
hfbs["hydchr"] = 3e-06
210+
211+
212+
# Now we can build an HFB package and compare it to the fault line that was defined above
213+
214+
# In[12]:
215+
216+
217+
hfb = flopy.mf6.ModflowGwfhfb(gwf, stress_period_data={0: hfbs})
218+
219+
220+
# In[13]:
221+
222+
223+
# plot up the results
224+
fig, ax = plt.subplots(figsize=(5, 5))
225+
pmv = flopy.plot.PlotMapView(modelgrid=modelgrid)
226+
pmv.plot_grid()
227+
pmv.plot_bc(package=hfb, color="b", lw=2)
228+
plt.plot(np.array(fault).T[0], np.array(fault).T[1], "r--")
229+
# ### Working with multiple fault geometries
230+
#
231+
# The `make_hfb_array` only accepts a single LineString geometry at a time. This was done by design for a couple reasons.
232+
# 1) This gives the user an opportunity to set the `hydchr` parameter to a unique value for each fault line
233+
# 2) This also gives the user an opportunity to save the boundary condition data (cellid1, cellid2) to an external file for each fault line that can be useful during model calibration with automated software (e.g., PEST++)
234+
#
235+
# As a result of these design descisions recarray data will need to be concatenated when working with multiple LineString geometries.
236+
237+
# For this example, a geodataframe of two fault lines is created and then used to build a HFB package
238+
239+
# In[14]:
240+
241+
242+
geom1 = LineString([(0, 4), (50, 44)])
243+
geom2 = LineString([(50, 44), (88, 100)])
244+
d = {"name": ["fault1", "fault2"], "geometry": [geom1, geom2]}
245+
gdf = gpd.GeoDataFrame(d)
246+
gdf
247+
248+
249+
# Visualize the faults on the modelgrid
250+
251+
# In[15]:
252+
253+
254+
# build simulation
255+
sim = simple_structured_model()
256+
gwf = sim.get_model()
257+
modelgrid = gwf.modelgrid
258+
259+
fig, ax = plt.subplots(figsize=(5, 5))
260+
pmv = flopy.plot.PlotMapView(modelgrid=modelgrid, ax=ax)
261+
pmv.plot_grid()
262+
gdf.geometry.plot(color="r", linestyle="--", ax=ax)
263+
# now create hfb data with the `make_hfb_array` utility and concatenate it
264+
265+
# In[16]:
266+
267+
268+
hydchrs = {"fault1": 1e-05, "fault2": 1e-06}
269+
270+
hfb_ras = []
271+
for name, geom in zip(gdf.name, gdf.geometry):
272+
hfbs = make_hfb_array(modelgrid, geom)
273+
hfbs["hydchr"] = hydchrs[name]
274+
hfb_ras.append(hfbs)
275+
276+
hfbs = np.concatenate(hfb_ras)
277+
hfbs = hfbs.view(np.recarray)
278+
hfbs
279+
280+
281+
# Now create the hfb package from this data and visualize it for comparison with the fault lines
282+
283+
# In[17]:
284+
285+
286+
hfb = flopy.mf6.ModflowGwfhfb(gwf, stress_period_data={0: hfbs})
287+
288+
289+
# In[18]:
290+
291+
292+
# plot up the results
293+
fig, ax = plt.subplots(figsize=(5, 5))
294+
pmv = flopy.plot.PlotMapView(modelgrid=modelgrid)
295+
pmv.plot_grid()
296+
pmv.plot_bc(package=hfb, color="b", lw=2)
297+
gdf.geometry.plot(color="r", linestyle="--", ax=ax)
298+
# In[19]:
299+
300+
301+
try:
302+
temp_dir.cleanup()
303+
except PermissionError:
304+
# can occur on windows: https://docs.python.org/3/library/tempfile.html#tempfile.TemporaryDirectory
305+
pass

0 commit comments

Comments
 (0)