Skip to content

Commit b0d9733

Browse files
committed
feat(utils): move n-point cross-section fns from modflow6 repo
1 parent 1114c93 commit b0d9733

File tree

2 files changed

+312
-0
lines changed

2 files changed

+312
-0
lines changed

autotest/test_crossection_util.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
3+
4+
def test_calculate_rectchan_mannings_discharge():
5+
pass
6+
7+
8+
def test_get_wetted_station():
9+
pass
10+
11+
12+
13+
def test_get_wetted_perimeter():
14+
pass

flopy/utils/crosssection_util.py

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
import numpy as np
2+
3+
# power for Manning's hydraulic radius term
4+
_mpow = 2.0 / 3.0
5+
6+
7+
def calculate_rectchan_mannings_discharge(
8+
conversion_factor, roughness, slope, width, depth
9+
):
10+
"""
11+
Calculate Manning's discharge for a rectangular channel.
12+
13+
"""
14+
area = width * depth
15+
return conversion_factor * area * depth**_mpow * slope**0.5 / roughness
16+
17+
18+
# n-point cross-section functions
19+
def get_wetted_station(
20+
x0,
21+
x1,
22+
h0,
23+
h1,
24+
depth,
25+
):
26+
"""Get the wetted length in the x-direction"""
27+
# -- calculate the minimum and maximum depth
28+
hmin = min(h0, h1)
29+
hmax = max(h0, h1)
30+
31+
# -- if depth is less than or equal to the minimum value the
32+
# station length (xlen) is zero
33+
if depth <= hmin:
34+
x1 = x0
35+
# -- if depth is between hmin and hmax, station length is less
36+
# than h1 - h0
37+
elif depth < hmax:
38+
xlen = x1 - x0
39+
dlen = h1 - h0
40+
if abs(dlen) > 0.0:
41+
slope = xlen / dlen
42+
else:
43+
slope = 0.0
44+
if h0 > h1:
45+
dx = (depth - h1) * slope
46+
xt = x1 + dx
47+
xt0 = xt
48+
xt1 = x1
49+
else:
50+
dx = (depth - h0) * slope
51+
xt = x0 + dx
52+
xt0 = x0
53+
xt1 = xt
54+
x0 = xt0
55+
x1 = xt1
56+
return x0, x1
57+
58+
59+
def get_wetted_perimeter(
60+
x0,
61+
x1,
62+
h0,
63+
h1,
64+
depth,
65+
):
66+
"""
67+
Computes the length of the intersection between a channel and a segment of a cross-section
68+
plane normal to the direction of flow. See https://en.wikipedia.org/wiki/Wetted_perimeter.
69+
70+
Parameters
71+
----------
72+
x0:
73+
x1
74+
h0
75+
h1
76+
depth: the height of the water
77+
78+
Returns
79+
-------
80+
The length of the wetted perimeter of the channel's cross-sectional area
81+
"""
82+
83+
# -- calculate the minimum and maximum depth
84+
hmin = min(h0, h1)
85+
hmax = max(h0, h1)
86+
87+
# -- calculate the wetted perimeter for the segment
88+
xlen = x1 - x0
89+
if xlen > 0.0:
90+
if depth > hmax:
91+
dlen = hmax - hmin
92+
else:
93+
dlen = depth - hmin
94+
else:
95+
if depth > hmin:
96+
dlen = min(depth, hmax) - hmin
97+
else:
98+
dlen = 0.0
99+
100+
return np.sqrt(xlen**2.0 + dlen**2.0)
101+
102+
103+
def get_wetted_area(x0, x1, h0, h1, depth):
104+
# -- calculate the minimum and maximum depth
105+
hmin = min(h0, h1)
106+
hmax = max(h0, h1)
107+
108+
# -- calculate the wetted area for the segment
109+
xlen = x1 - x0
110+
area = 0.0
111+
if xlen > 0.0:
112+
# -- add the area above hmax
113+
if depth > hmax:
114+
area = xlen * (depth - hmax)
115+
# -- add the area below zmax
116+
if hmax != hmin and depth > hmin:
117+
area += 0.5 * (depth - hmin)
118+
return area
119+
120+
121+
def wetted_area(
122+
x,
123+
h,
124+
depth,
125+
verbose=False,
126+
):
127+
area = 0.0
128+
if x.shape[0] == 1:
129+
area = x[0] * depth
130+
else:
131+
for idx in range(0, x.shape[0] - 1):
132+
x0, x1 = x[idx], x[idx + 1]
133+
h0, h1 = h[idx], h[idx + 1]
134+
135+
# get station data
136+
x0, x1 = get_wetted_station(x0, x1, h0, h1, depth)
137+
138+
# get wetted area
139+
a = get_wetted_area(x0, x1, h0, h1, depth)
140+
area += a
141+
142+
# write to screen
143+
if verbose:
144+
print(
145+
f"{idx}->{idx + 1} ({x0},{x1}) - "
146+
f"perimeter={x1 - x0} - area={a}"
147+
)
148+
149+
return area
150+
151+
152+
def wetted_perimeter(
153+
x,
154+
h,
155+
depth,
156+
verbose=False,
157+
):
158+
perimeter = 0.0
159+
if x.shape[0] == 1:
160+
perimeter = x[0]
161+
else:
162+
for idx in range(0, x.shape[0] - 1):
163+
x0, x1 = x[idx], x[idx + 1]
164+
h0, h1 = h[idx], h[idx + 1]
165+
166+
# get station data
167+
x0, x1 = get_wetted_station(x0, x1, h0, h1, depth)
168+
169+
# get wetted perimeter
170+
perimeter += get_wetted_perimeter(x0, x1, h0, h1, depth)
171+
172+
# write to screen
173+
if verbose:
174+
print(f"{idx}->{idx + 1} ({x0},{x1}) - perimeter={x1 - x0}")
175+
176+
return perimeter
177+
178+
179+
def manningsq(
180+
x,
181+
h,
182+
depth,
183+
roughness=0.01,
184+
slope=0.001,
185+
conv=1.0,
186+
):
187+
if isinstance(roughness, float):
188+
roughness = np.ones(x.shape, dtype=float) * roughness
189+
if x.shape[0] > 1:
190+
q = 0.0
191+
for i0 in range(x.shape[0] - 1):
192+
i1 = i0 + 1
193+
perimeter = get_wetted_perimeter(x[i0], x[i1], h[i0], h[i1], depth)
194+
area = get_wetted_area(x[i0], x[i1], h[i0], h[i1], depth)
195+
if perimeter > 0.0:
196+
radius = area / perimeter
197+
q += (
198+
conv
199+
* area
200+
* radius**_mpow
201+
* slope**0.5
202+
/ roughness[i0]
203+
)
204+
else:
205+
perimeter = wetted_perimeter(x, h, depth)
206+
area = wetted_area(x, h, depth)
207+
radius = 0.0
208+
if perimeter > 0.0:
209+
radius = area / perimeter
210+
q = conv * area * radius**_mpow * slope**0.5 / roughness[0]
211+
return q
212+
213+
214+
def get_depths(
215+
flows,
216+
x,
217+
h,
218+
roughness=0.01,
219+
slope=0.001,
220+
conv=1.0,
221+
dd=1e-4,
222+
verbose=False,
223+
):
224+
if isinstance(flows, float):
225+
flows = np.array([flows], dtype=float)
226+
if isinstance(roughness, float):
227+
roughness = np.ones(x.shape, dtype=float) * roughness
228+
depths = np.zeros(flows.shape, dtype=float)
229+
for idx, q in enumerate(flows):
230+
depths[idx] = qtodepth(
231+
x,
232+
h,
233+
q,
234+
roughness=roughness,
235+
slope=slope,
236+
conv=conv,
237+
dd=dd,
238+
verbose=False,
239+
)
240+
241+
return depths
242+
243+
244+
def qtodepth(
245+
x,
246+
h,
247+
q,
248+
roughness=0.01,
249+
slope=0.001,
250+
conv=1.0,
251+
dd=1e-4,
252+
verbose=False,
253+
):
254+
h0 = 0.0
255+
q0 = manningsq(
256+
x,
257+
h,
258+
h0,
259+
roughness=roughness,
260+
slope=slope,
261+
conv=conv,
262+
)
263+
r = q0 - q
264+
265+
iter = 0
266+
if verbose:
267+
print(f"iteration {iter:>2d} - residual={r}")
268+
while abs(r) > 1e-12:
269+
q1 = manningsq(
270+
x,
271+
h,
272+
h0 + dd,
273+
roughness=roughness,
274+
slope=slope,
275+
conv=conv,
276+
)
277+
dq = q1 - q0
278+
if dq != 0.0:
279+
derv = dd / (q1 - q0)
280+
else:
281+
derv = 0.0
282+
h0 -= derv * r
283+
q0 = manningsq(
284+
x,
285+
h,
286+
h0,
287+
roughness=roughness,
288+
slope=slope,
289+
conv=conv,
290+
)
291+
r = q0 - q
292+
293+
iter += 1
294+
if verbose:
295+
print(f"iteration {iter:>2d} - residual={r}")
296+
if iter > 100:
297+
break
298+
return h0

0 commit comments

Comments
 (0)