wolfhece 2.1.98__py3-none-any.whl → 2.1.100__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2399 @@
1
+ import numpy as np
2
+ from matplotlib import pyplot as plt
3
+ from tqdm import tqdm
4
+ from numba import jit
5
+ from scipy.optimize import root_scalar
6
+ from typing import Literal
7
+ from enum import Enum
8
+ import logging
9
+
10
+ """
11
+ Calcul des equations shallow water en 1D section rectangulaire avec frottement selon Manning
12
+
13
+ Continuity : dh/dt + d(q)/dx = 0
14
+ Momentum : dq/dt + d(q^2/h + 1/2 g h^2)/dx + g h dz/dx = -g h J
15
+ Friction Slope : J = n^2 (q/h)^2 / h^(4/3)
16
+
17
+ Discretisation :
18
+
19
+ J = n^2 q_t2 q_t1 / h^(10/3)
20
+ ghJ = q_t2 (g n^2 q_t1 / h^(7/3))
21
+
22
+ h_t2 = h_t1 - dt/dx (q_t,r - q_t,l)
23
+ q_t2 = 1/(1 + dt g n^2 q_t1 / h^(7/3)) * (q_t1 - dt/dx (q_t,r^2/h_t,r - q_t,l^2/h_t,l + 1/2 g (h_t,r^2 - h_t,l^2) + g h_center_mean (z_t,r - z_t,l))
24
+
25
+ """
26
+
27
+ class Scenarios(Enum):
28
+ unsteady_downstream_bc = 0
29
+ hydrograph = 1
30
+ hydrograph_2steps = 2
31
+ Gauss = 3
32
+ water_lines = 4
33
+
34
+ def domain(length:float, dx:float, slope:float) -> np.ndarray:
35
+ """ Create the domain
36
+
37
+ :param length: Length of the domain
38
+ :param dx: Space step
39
+ :param slope: Slope of the domain
40
+ """
41
+
42
+ nb = int(length/dx)
43
+ dom = np.zeros(nb+2)
44
+ x = [-dx/2.] + [(float(i)+.5)*dx for i in range(0,nb)] + [length + dx/2.]
45
+ z = -slope * np.array(x)
46
+ return dom, x, z
47
+
48
+ def _init_conditions(dom:np.ndarray, h0:float, q0:float) -> np.ndarray:
49
+ """ Initial conditions
50
+
51
+ :param dom: Domain
52
+ :param h0: Initial water depth [m]
53
+ :param q0: Initial discharge [m^2/s]
54
+ """
55
+
56
+ h = np.zeros_like(dom)
57
+ h[1:-1] = h0
58
+ q = np.zeros_like(dom)
59
+ q[1:-1] = q0
60
+ return h, q
61
+
62
+ def get_friction_slope_2D_Manning(q:float, h:float, n:float) -> float:
63
+ """ Friction slope based on Manning formula
64
+
65
+ :param q: Discharge [m^2/s]
66
+ :param h: Water depth [m]
67
+ :param n: Manning coefficient [m^(1/3)/s]
68
+ """
69
+ return n**2 * q**2 / h**(10/3)
70
+
71
+ def compute_dt(dx:float, h:np.ndarray, q:np.ndarray, CN:float) -> float:
72
+ """ Compute the time step according to the Courant number anf the maximum velocity
73
+
74
+ :param dx: Space step
75
+ :param h: Water depth
76
+ :param q: Discharge
77
+ :param CN: Courant number
78
+ """
79
+
80
+ h_pos = np.where(h > 0)[0]
81
+ dt = CN * dx / (np.max(np.abs(q[h_pos]/h[h_pos]) + np.sqrt(h[h_pos]*9.81)))
82
+ return dt
83
+
84
+ def all_unk_border(dom:np.ndarray, h0:float, q0:float) -> tuple[np.ndarray]:
85
+ """ Initialize all arrays storing unknowns at center and borders
86
+
87
+ :param dom: Domain
88
+ :param h0: Initial water depth
89
+ :param q0: Initial discharge
90
+ """
91
+
92
+ h, q = _init_conditions(dom, h0, q0)
93
+ h_pred = np.zeros_like(dom)
94
+ h_corr = np.zeros_like(dom)
95
+ q_pred = np.zeros_like(dom)
96
+ q_corr = np.zeros_like(dom)
97
+
98
+ q_border = np.zeros((len(dom), 2))
99
+ h_border = np.zeros((len(dom), 2))
100
+ z_border = np.zeros((len(dom), 2))
101
+
102
+ u_border = np.zeros(len(dom))
103
+ h_center = np.zeros(len(dom)-2)
104
+ u_center = np.zeros(len(dom)-2)
105
+
106
+ return h, q, h_pred, q_pred, h_corr, q_corr, q_border, h_border, z_border, u_border, h_center, u_center
107
+
108
+ def uniform_waterdepth(slope:float, q:float, n:float):
109
+ """ Compute the uniform water depth for a given slope, discharge and Manning coefficient
110
+
111
+ :param slope: Slope
112
+ :param q: Discharge [m^2/s]
113
+ :param n: Manning coefficient
114
+ """
115
+
116
+ if n==0. or slope==0.:
117
+ logging.error("Manning coefficient or slope cannot be null")
118
+ logging.warning("Return 99999.")
119
+ return 99999.
120
+
121
+ return root_scalar(lambda h: slope - get_friction_slope_2D_Manning(q, h, n), bracket=[0.1, 100.]).root
122
+
123
+ # -----------------
124
+ # LOSS Coefficients
125
+ # -----------------
126
+
127
+ def k_abrupt_enlargment(asmall:float, alarge:float) -> float:
128
+ """ Compute the local head loss coefficient of the abrupt enlargment
129
+
130
+ :params asmall: float, area of the section 1 -- smaller section
131
+ :params alarge: float, area of the section 2 -- larger section
132
+ """
133
+
134
+ return (1 - asmall/alarge)**2
135
+
136
+ def k_abrupt_contraction(alarge:float, asmall:float) -> float:
137
+ """ Compute the local head loss coefficient of the abrupt contraction
138
+
139
+ :params alarge: float, area of the section 1 -- larger section
140
+ :params asmall: float, area of the section 2 -- smaller section
141
+ """
142
+
143
+ return .5*(1 - asmall/alarge)
144
+
145
+ def head_loss_enlargment(q:float, asmall:float, alarge:float) -> float:
146
+ """ Compute the head loss of the enlargment.
147
+
148
+ Reference velocity is the velocity in the smaller section.
149
+
150
+ :params q: float, discharge
151
+ :params asmall: float, area of the section 1 -- smaller section
152
+ :params alarge: float, area of the section 2 -- larger section
153
+ """
154
+
155
+ return k_abrupt_enlargment(asmall, alarge) * (q/asmall)**2 / 2 / 9.81
156
+
157
+ def head_loss_contraction(q:float, alarge:float, asmall:float) -> float:
158
+ """ Compute the head loss of the contraction.
159
+
160
+ Reference velocity is the velocity in the smaller section.
161
+
162
+ :params q: float, discharge
163
+ :params alarge: float, area of the section 1 -- larger section
164
+ :params asmall: float, area of the section 2 -- smaller section
165
+ """
166
+
167
+ return k_abrupt_contraction(alarge, asmall) * (q/asmall)**2 / 2 / 9.81
168
+
169
+ def head_loss_contract_enlarge(q:float, a_up:float, asmall:float, a_down:float) -> float:
170
+ """ Compute the head loss of the contraction/enlargment.
171
+
172
+ Reference velocity is the velocity in the smaller section.
173
+
174
+ :params q: float, discharge
175
+ :params a_up: float, area of the section 1 -- larger section
176
+ :params asmall: float, area of the section 2 -- smaller section
177
+ :params a_down: float, area of the section 3 -- larger section
178
+ """
179
+
180
+ return (k_abrupt_enlargment(asmall, a_down) + k_abrupt_contraction(a_up, asmall)) * (q/asmall)**2 / 2 / 9.81
181
+
182
+ # ----------------------------
183
+ # START JIT compiled functions
184
+ # ----------------------------
185
+
186
+ @jit(nopython=True)
187
+ def get_friction_slope_2D_Manning_semi_implicit(u:np.ndarray, h:np.ndarray, n:float) -> np.ndarray:
188
+ """ Friction slope based on Manning formula -- Only semi-implicit formulea for the friction slope
189
+
190
+ :param u: Velocity [m/s]
191
+ :param h: Water depth [m]
192
+ :param n: Manning coefficient [m^(1/3)/s]
193
+ """
194
+
195
+ return n**2 * np.abs(u) / h**(7/3)
196
+
197
+ @jit(nopython=True)
198
+ def Euler_RK(h_t1:np.ndarray, h_t2:np.ndarray,
199
+ q_t1:np.ndarray, q_t2:np.ndarray,
200
+ h:np.ndarray, q:np.ndarray,
201
+ h_border:np.ndarray, q_border:np.ndarray,
202
+ z:np.ndarray, z_border:np.ndarray,
203
+ dt:float, dx:float,
204
+ CL_h:float, CL_q:float,
205
+ n:float, u_border:np.ndarray,
206
+ h_center:np.ndarray, u_center:np.ndarray) -> None:
207
+ """ Solve the mass and momentum equations using a explicit Euler/Runge-Kutta scheme (only 1 step)
208
+
209
+ :param h_t1: Water depth at time t
210
+ :param h_t2: Water depth at time t+dt (or t_star or t_doublestar if RK)
211
+ :param q_t1: Discharge at time t
212
+ :param q_t2: Discharge at time t+dt (or t_star or t_doublestar if RK)
213
+ :param h: Water depth at the mesh center
214
+ :param q: Discharge at the mesh center
215
+ :param h_border: Water depth at the mesh border
216
+ :param q_border: Discharge at the mesh border
217
+ :param z: Bed elevation
218
+ :param z_border: Bed elevation at the mesh border
219
+ :param dt: Time step
220
+ :param dx: Space step
221
+ :param CL_h: Downstream boudary condition for water depth
222
+ :param CL_q: Upstream boundary condition for discharge
223
+ :param n: Manning coefficient
224
+ :param u_border: Velocity at the mesh border
225
+ :param h_center: Water depth at the mesh center
226
+ :param u_center: Velocity at the mesh center
227
+ """
228
+
229
+ g = 9.81
230
+
231
+ slice_mesh = slice(1,-1)
232
+ slice_right_border = slice(2,None)
233
+ slice_left_border = slice(1,-1)
234
+
235
+ up = 0
236
+ do = 1
237
+
238
+ # valeur à gauche du bord
239
+ q_border[slice_right_border, up] = q[1:-1]
240
+ q_border[1,up] = CL_q
241
+
242
+ h_border[slice_right_border, up] = h[1:-1]
243
+ h_border[1,up] = h[1]
244
+
245
+ z_border[slice_right_border, up] = z[1:-1]
246
+ z_border[1,up] = z[1]
247
+
248
+ # valeur à droite du bord
249
+ q_border[slice_left_border, do] = q[1:-1]
250
+ q_border[-1,do] = q[-2]
251
+
252
+ h_border[slice_left_border, do] = h[1:-1]
253
+ h_border[-1,do] = CL_h
254
+
255
+ z_border[slice_left_border, do] = z[1:-1]
256
+ z_border[-1,do] = z[-2]
257
+
258
+ u_border[1:] = q_border[1:,up]/h_border[1:,up]
259
+
260
+ h_center = (h_border[slice_right_border,do] + h_border[slice_left_border,do])/2.
261
+ u_center = q[slice_mesh]/h[slice_mesh]
262
+
263
+ #Continuity
264
+ h_t2[slice_mesh] = h_t1[slice_mesh] - dt/dx * (q_border[slice_right_border,up] - q_border[slice_left_border,up])
265
+
266
+ # Momentum
267
+
268
+ J = get_friction_slope_2D_Manning_semi_implicit(u_center, h[slice_mesh], n)
269
+ qm_right = u_border[slice_right_border]*q_border[slice_right_border,up]
270
+ qm_left = u_border[slice_left_border]*q_border[slice_left_border,up]
271
+
272
+ press_right = 0.5 * g * h_border[slice_right_border,do]**2
273
+ press_left = 0.5 * g * h_border[slice_left_border,do]**2
274
+
275
+ bed_right = g * h_center * z_border[slice_right_border,do]
276
+ bed_left = g * h_center * z_border[slice_left_border,do]
277
+
278
+ q_t2[slice_mesh] = 1./(1. + dt * g *h[slice_mesh] * J) * (q_t1[slice_mesh] - dt/dx * (qm_right - qm_left + press_right - press_left + bed_right - bed_left))
279
+
280
+ limit_h_q(h_t2, q_t2, hmin=1e-3, Froudemax=3.)
281
+
282
+ @jit(nopython=True)
283
+ def Euler_RK_hedge(h_t1:np.ndarray, h_t2:np.ndarray,
284
+ q_t1:np.ndarray, q_t2:np.ndarray,
285
+ h:np.ndarray, q:np.ndarray,
286
+ h_border:np.ndarray, q_border:np.ndarray,
287
+ z:np.ndarray, z_border:np.ndarray,
288
+ dt:float, dx:float,
289
+ CL_h:float, CL_q:float,
290
+ n:float, u_border:np.ndarray,
291
+ h_center:np.ndarray, u_center:np.ndarray) -> None:
292
+ """ Solve the mass and momentum equations using a explicit Euler/Runge-Kutta scheme (only 1 step)
293
+
294
+ :param h_t1: Water depth at time t
295
+ :param h_t2: Water depth at time t+dt (or t_star or t_doublestar if RK)
296
+ :param q_t1: Discharge at time t
297
+ :param q_t2: Discharge at time t+dt (or t_star or t_doublestar if RK)
298
+ :param h: Water depth at the mesh center
299
+ :param q: Discharge at the mesh center
300
+ :param h_border: Water depth at the mesh border
301
+ :param q_border: Discharge at the mesh border
302
+ :param z: Bed elevation
303
+ :param z_border: Bed elevation at the mesh border
304
+ :param dt: Time step
305
+ :param dx: Space step
306
+ :param CL_h: Downstream boudary condition for water depth
307
+ :param CL_q: Upstream boundary condition for discharge
308
+ :param n: Manning coefficient
309
+ :param u_border: Velocity at the mesh border
310
+ :param h_center: Water depth at the mesh center
311
+ :param u_center: Velocity at the mesh center
312
+ """
313
+
314
+ theta = 0.2
315
+ slice_hedge = slice(len(h_t1) // 2-1, len(h_t1) // 2+2)
316
+
317
+ g = 9.81
318
+
319
+ slice_mesh = slice(1,-1)
320
+ slice_right_border = slice(2,None)
321
+ slice_left_border = slice(1,-1)
322
+
323
+ up = 0
324
+ do = 1
325
+
326
+ # valeur à gauche du bord
327
+ q_border[slice_right_border, up] = q[1:-1]
328
+ q_border[1,up] = CL_q
329
+
330
+ h_border[slice_right_border, up] = h[1:-1]
331
+ h_border[1,up] = h[1]
332
+
333
+ z_border[slice_right_border, up] = z[1:-1]
334
+ z_border[1,up] = z[1]
335
+
336
+ # valeur à droite du bord
337
+ q_border[slice_left_border, do] = q[1:-1]
338
+ q_border[-1,do] = q[-2]
339
+
340
+ h_border[slice_left_border, do] = h[1:-1]
341
+ h_border[-1,do] = CL_h
342
+
343
+ z_border[slice_left_border, do] = z[1:-1]
344
+ z_border[-1,do] = z[-2]
345
+
346
+ u_border[1:] = q_border[1:,up]/h_border[1:,up]
347
+
348
+ u_border[slice_hedge] /= theta
349
+
350
+ h_center = (h_border[slice_right_border,do] + h_border[slice_left_border,do])/2.
351
+ u_center = q[slice_mesh]/h[slice_mesh]
352
+
353
+ #Continuity
354
+ h_t2[slice_mesh] = h_t1[slice_mesh] - dt/dx * (q_border[slice_right_border,up] - q_border[slice_left_border,up])
355
+
356
+ # Momentum
357
+
358
+ J = get_friction_slope_2D_Manning_semi_implicit(u_center, h[slice_mesh], n)
359
+ qm_right = u_border[slice_right_border]*q_border[slice_right_border,up]
360
+ qm_left = u_border[slice_left_border]*q_border[slice_left_border,up]
361
+
362
+ press_right = 0.5 * g * h_border[slice_right_border,do]**2
363
+ press_left = 0.5 * g * h_border[slice_left_border,do]**2
364
+
365
+ bed_right = g * h_center * z_border[slice_right_border,do]
366
+ bed_left = g * h_center * z_border[slice_left_border,do]
367
+
368
+ q_t2[slice_mesh] = 1./(1. + dt * g *h[slice_mesh] * J) * (q_t1[slice_mesh] - dt/dx * (qm_right - qm_left + press_right - press_left + bed_right - bed_left))
369
+
370
+ limit_h_q(h_t2, q_t2, hmin=1e-3, Froudemax=3.)
371
+
372
+
373
+ @jit(nopython=True)
374
+ def splitting(q_left:np.float64, q_right:np.float64,
375
+ h_left:np.float64, h_right:np.float64,
376
+ z_left:np.float64, z_right:np.float64,
377
+ z_bridge_left:np.float64, z_bridge_right:np.float64) -> np.ndarray:
378
+ """ Splitting of the unknowns at border between two nodes
379
+ -- Based on the WOLF HECE original scheme
380
+ """
381
+
382
+ prod_q = q_left * q_right
383
+ sum_q = q_left + q_right
384
+
385
+ if prod_q > 0.:
386
+ if q_left > 0.:
387
+ return np.asarray([q_left, min(h_left, z_bridge_left-z_left), h_right, z_right, z_bridge_right], dtype=np.float64)
388
+ else:
389
+ return np.asarray([q_right, min(h_right, z_bridge_right-z_right), h_left, z_left, z_bridge_left], dtype=np.float64)
390
+ elif prod_q < 0.:
391
+ if sum_q > 0.:
392
+ return np.asarray([q_left, min(h_left, z_bridge_left-z_left), h_right, z_right, z_bridge_right], dtype=np.float64)
393
+ elif sum_q < 0.:
394
+ return np.asarray([q_right, min(h_right, z_bridge_right-z_right), h_left, z_left, z_bridge_left], dtype=np.float64)
395
+ else:
396
+ return np.asarray([0., 1., (h_left + h_right) / 2., (z_left + z_right) / 2., (z_bridge_left + z_bridge_right) / 2.], dtype=np.float64)
397
+ else:
398
+ if q_left<0.:
399
+ return np.asarray([np.float64(0.), np.float64(1.), h_left, z_left, z_bridge_left], dtype=np.float64)
400
+ elif q_right<0.:
401
+ return np.asarray([np.float64(0.), np.float64(1.), h_right, z_right, z_bridge_right], dtype=np.float64)
402
+ else:
403
+ return np.asarray([sum_q / 2., # q
404
+ (min(h_left, z_bridge_left-z_left) + min(h_right, z_bridge_right-z_right)) / 2., # h_vel
405
+ (h_left + h_right) / 2., # h
406
+ (z_left + z_right) / 2., # z
407
+ (z_bridge_left + z_bridge_right) / 2.], dtype=np.float64) # z_bridge
408
+
409
+
410
+ @jit(nopython=True)
411
+ def Euler_RK_bridge(h_t1:np.ndarray, h_t2:np.ndarray,
412
+ q_t1:np.ndarray, q_t2:np.ndarray,
413
+ h:np.ndarray, q:np.ndarray,
414
+ h_border:np.ndarray, q_border:np.ndarray,
415
+ z:np.ndarray, z_border:np.ndarray,
416
+ dt:float, dx:float,
417
+ CL_h:float, CL_q:float,
418
+ n:float, u_border:np.ndarray,
419
+ h_center:np.ndarray, u_center:np.ndarray,
420
+ z_bridge:np.ndarray, z_bridge_border:np.ndarray,
421
+ press_mode:int=0,
422
+ infil_exfil=None) -> None:
423
+ """
424
+ Solve the mass and momentum equations using a explicit Euler/Runge-Kutta scheme (only 1 step)
425
+ applying source terms for infiltration/exfiltration and pressure at the roof.
426
+ """
427
+ g = 9.81
428
+
429
+ slice_mesh = slice(1,-1)
430
+ slice_right_border = slice(2,None)
431
+ slice_left_border = slice(1,-1)
432
+
433
+ up = 0
434
+ do = 1
435
+
436
+ # valeur à gauche du bord
437
+ z_border[slice_right_border, up] = z[1:-1]
438
+ z_border[1,up] = z[1]
439
+
440
+ z_bridge_copy = z_bridge.copy()
441
+
442
+ fs_cells = np.where(h <= z_bridge - z)[0]
443
+ press_cells = np.where(h > z_bridge - z)[0]
444
+
445
+ z_bridge_copy[fs_cells] = z[fs_cells] + h[fs_cells]
446
+
447
+ h_border[slice_right_border, up] = h[1:-1]
448
+ h_border[1,up] = h[1]
449
+
450
+ q_border[slice_right_border, up] = q[1:-1]
451
+ q_border[1,up] = CL_q
452
+
453
+ # valeur à droite du bord
454
+ z_border[slice_left_border, do] = z[1:-1]
455
+ z_border[-1,do] = z[-2]
456
+
457
+ z_bridge_border[slice_right_border, up] = z_bridge_copy[1:-1]
458
+ z_bridge_border[1,up] = z_bridge_copy[1]
459
+
460
+ z_bridge_border[slice_left_border, do] = z_bridge_copy[1:-1]
461
+ z_bridge_border[-1,do] = z_bridge_copy[-2]
462
+
463
+ h_border[slice_left_border, do] = h[1:-1]
464
+ h_border[-1,do] = CL_h
465
+
466
+ q_border[slice_left_border, do] = q[1:-1]
467
+ q_border[-1,do] = q[-2]
468
+
469
+
470
+
471
+ for i in range(1, len(h)-1):
472
+
473
+ qc_right, h4u_right, hc_right, zc_right, zbc_right = splitting(q_border[i+1,up], q_border[i+1, do], h_border[i+1,up], h_border[i+1,do], z_border[i+1,up], z_border[i+1,do], z_bridge_border[i+1,up], z_bridge_border[i+1,do])
474
+ qc_left, h4u_left, hc_left, zc_left, zbc_left = splitting(q_border[i,up], q_border[i, do], h_border[i,up], h_border[i,do], z_border[i,up], z_border[i,do], z_bridge_border[i,up], z_bridge_border[i,do])
475
+
476
+ # Continuity
477
+ # ++++++++++
478
+
479
+ h_t2[i] = h_t1[i] - dt/dx * (qc_right - qc_left)
480
+
481
+ # Momentum
482
+ # ++++++++
483
+
484
+ # Limited section at the right border and at the left border -- decentred downstream
485
+ d_right = zbc_right - zc_right
486
+ d_left = zbc_left - zc_left
487
+
488
+ # Pressure on the roof -- 0. if free surface
489
+ press_roof_right = hc_right - d_right
490
+ press_roof_left = hc_left - d_left
491
+
492
+ # Pressure integral at the right border and at the left border
493
+ press_right = 0.5 * g * (hc_right**2. - press_roof_right**2)
494
+ press_left = 0.5 * g * (hc_left**2. - press_roof_left**2)
495
+
496
+ # Friction slope based on center values
497
+ u_center = q[i] / (z_bridge_copy[i] - z[i])
498
+ # Number of surfaces
499
+ nb_frott = 2. if h[i] > z_bridge[i] - z[i] else 1.
500
+ # Integration water depth
501
+ h_frott = min(h[i], z_bridge[i] - z[i])
502
+ # Slope
503
+ J = get_friction_slope_2D_Manning_semi_implicit(u_center, h_frott/nb_frott, n)
504
+
505
+ # Velocity at the right border and at the left border -- decentred upstream
506
+ u_right = qc_right / h4u_right
507
+ u_left = qc_left / h4u_left
508
+
509
+ # Momentum at the right border and at the left border
510
+ qm_right = u_right * qc_right
511
+ qm_left = u_left * qc_left
512
+
513
+ # Mean pressure impacting bed reaction
514
+ h_mean = (hc_right + hc_left)/2.
515
+ bed_right = g * h_mean * zc_right
516
+ bed_left = g * h_mean * zc_left
517
+
518
+ # Mean pressure impacting roof reaction
519
+ h_roof_right = max(hc_right + zc_right - zbc_right, 0.)
520
+ h_roof_left = max(hc_left + zc_left - zbc_left , 0.)
521
+ h_roof_mean = (h_roof_right + h_roof_left) / 2.
522
+
523
+ roof_right = g * h_roof_mean * zbc_right
524
+ roof_left = g * h_roof_mean * zbc_left
525
+
526
+ rhs = (qm_right - qm_left + press_right - press_left + bed_right - bed_left - roof_right + roof_left)
527
+
528
+ if rhs !=0.:
529
+ pass
530
+
531
+ q_t2[i] = 1./(1. + dt * g * h_frott * J) * (q_t1[i] - dt/dx * rhs)
532
+
533
+ if infil_exfil is not None:
534
+ idx_up, idx_do, q_infil_exfil, u_infil_exfil, pond, k = infil_exfil
535
+
536
+ i = idx_up
537
+
538
+ qc_right, h4u_right, hc_right, zc_right, zbc_right = splitting(q_border[i+1,up], q_border[i+1, do], h_border[i+1,up], h_border[i+1,do], z_border[i+1,up], z_border[i+1,do], z_bridge_border[i+1,up], z_bridge_border[i+1,do])
539
+ qc_left, h4u_left, hc_left, zc_left, zbc_left = splitting(q_border[i,up], q_border[i, do], h_border[i,up], h_border[i,do], z_border[i,up], z_border[i,do], z_bridge_border[i,up], z_bridge_border[i,do])
540
+
541
+ # Limited section at the right border and at the left border -- decentred downstream
542
+ d_right = zbc_right - zc_right
543
+ d_left = zbc_left - zc_left
544
+
545
+ # Pressure on the roof -- 0. if free surface
546
+ press_roof_right = hc_right - d_right
547
+ press_roof_left = hc_left - d_left
548
+
549
+ # Pressure integral at the right border and at the left border
550
+ press_right = 0.5 * g * (hc_right**2. - press_roof_right**2)
551
+ press_left = 0.5 * g * (hc_left**2. - press_roof_left**2)
552
+
553
+ # Friction slope based on center values
554
+ u_center = q[i] / (z_bridge_copy[i] - z[i])
555
+ # Number of surfaces
556
+ nb_frott = 2. if h[i] > z_bridge[i] - z[i] else 1.
557
+ # Integration water depth
558
+ h_frott = min(h[i], z_bridge[i] - z[i])
559
+ # Slope
560
+ J = get_friction_slope_2D_Manning_semi_implicit(u_center, h_frott/nb_frott, n)
561
+
562
+ # Velocity at the right border and at the left border -- decentred upstream
563
+ u_right = qc_right / h4u_right
564
+ u_left = qc_left / h4u_left
565
+
566
+ # Momentum at the right border and at the left border
567
+ qm_right = u_right * qc_right
568
+ qm_left = u_left * qc_left
569
+
570
+ # Mean pressure impacting bed reaction
571
+ h_mean = (hc_right + hc_left)/2.
572
+ bed_right = g * h_mean * zc_right
573
+ bed_left = g * h_mean * zc_left
574
+
575
+ # Mean pressure impacting roof reaction
576
+ h_roof_right = max(hc_right + zc_right - zbc_right, 0.)
577
+ h_roof_left = max(hc_left + zc_left - zbc_left , 0.)
578
+ h_roof_mean = (h_roof_right + h_roof_left) / 2.
579
+
580
+ roof_right = g * h_roof_mean * zbc_right
581
+ roof_left = g * h_roof_mean * zbc_left
582
+
583
+ h_t2[i] = h_t1[i] - dt/dx * (qc_right - qc_left + q_infil_exfil)
584
+ q_t2[i] = 1./(1. + dt * g * h_frott * J) * (q_t1[i] - dt/dx * (qm_right - qm_left + \
585
+ press_right - press_left + \
586
+ bed_right - bed_left - \
587
+ roof_right + roof_left + \
588
+ u_infil_exfil * q_infil_exfil))
589
+
590
+ i = idx_do
591
+
592
+ qc_right, h4u_right, hc_right, zc_right, zbc_right = splitting(q_border[i+1,up], q_border[i+1, do], h_border[i+1,up], h_border[i+1,do], z_border[i+1,up], z_border[i+1,do], z_bridge_border[i+1,up], z_bridge_border[i+1,do])
593
+ qc_left, h4u_left, hc_left, zc_left, zbc_left = splitting(q_border[i,up], q_border[i, do], h_border[i,up], h_border[i,do], z_border[i,up], z_border[i,do], z_bridge_border[i,up], z_bridge_border[i,do])
594
+
595
+ # Limited section at the right border and at the left border -- decentred downstream
596
+ d_right = zbc_right - zc_right
597
+ d_left = zbc_left - zc_left
598
+
599
+ # Pressure on the roof -- 0. if free surface
600
+ press_roof_right = hc_right - d_right
601
+ press_roof_left = hc_left - d_left
602
+
603
+ # Pressure integral at the right border and at the left border
604
+ press_right = 0.5 * g * (hc_right**2. - press_roof_right**2)
605
+ press_left = 0.5 * g * (hc_left**2. - press_roof_left**2)
606
+
607
+ # Friction slope based on center values
608
+ u_center = q[i] / (z_bridge_copy[i] - z[i])
609
+ # Number of surfaces
610
+ nb_frott = 2. if h[i] > z_bridge[i] - z[i] else 1.
611
+ # Integration water depth
612
+ h_frott = min(h[i], z_bridge[i] - z[i])
613
+ # Slope
614
+ J = get_friction_slope_2D_Manning_semi_implicit(u_center, h_frott/nb_frott, n)
615
+
616
+ # Velocity at the right border and at the left border -- decentred upstream
617
+ u_right = qc_right / h4u_right
618
+ u_left = qc_left / h4u_left
619
+
620
+ # Momentum at the right border and at the left border
621
+ qm_right = u_right * qc_right
622
+ qm_left = u_left * qc_left
623
+
624
+ # Mean pressure impacting bed reaction
625
+ h_mean = (hc_right + hc_left)/2.
626
+ bed_right = g * h_mean * zc_right
627
+ bed_left = g * h_mean * zc_left
628
+
629
+ # Mean pressure impacting roof reaction
630
+ h_roof_right = max(hc_right + zc_right - zbc_right, 0.)
631
+ h_roof_left = max(hc_left + zc_left - zbc_left , 0.)
632
+ h_roof_mean = (h_roof_right + h_roof_left) / 2.
633
+
634
+ roof_right = g * h_roof_mean * zbc_right
635
+ roof_left = g * h_roof_mean * zbc_left
636
+
637
+ h_t2[i] = h_t1[i] - dt/dx * (qc_right - qc_left - q_infil_exfil)
638
+ q_t2[i] = 1./(1. + dt * g * h_frott * J) * (q_t1[i] - dt/dx * (qm_right - qm_left + \
639
+ press_right - press_left + \
640
+ bed_right - bed_left - \
641
+ roof_right + roof_left -\
642
+ u_infil_exfil * q_infil_exfil))
643
+
644
+ limit_h_q(h_t2, q_t2, hmin=1e-3, Froudemax=3.)
645
+
646
+ @ jit(nopython=True)
647
+ def limit_h_q(h:np.ndarray, q:np.ndarray, hmin:float = 0., Froudemax:float = 3.) -> None:
648
+ """ Limit the water depth and the discharge
649
+
650
+ :param h: Water depth [m]
651
+ :param q: Discharge [m^2/s]
652
+ :param hmin: Minimum water depth [m]
653
+ :param Froudemax: Maximum Froude number [-]
654
+ """
655
+
656
+ # retrieve positive and negative values
657
+ hpos = np.where(h > hmin)
658
+ hneg = np.where(h <= hmin)
659
+
660
+ # limit water depth
661
+ h[hneg] = hmin
662
+ q[hneg] = 0.
663
+
664
+ # limit discharge based on Froude number
665
+ Fr = np.zeros_like(h)
666
+ Fr[hpos] = np.abs(q[hpos]) / h[hpos] / np.sqrt(9.81 * h[hpos])
667
+ q[Fr > Froudemax] = Froudemax * np.sqrt(9.81 * h[Fr > Froudemax]) * h[Fr > Froudemax] * np.sign(q[Fr > Froudemax])
668
+
669
+ # --------------------------
670
+ # END JIT compiled functions
671
+ # --------------------------
672
+
673
+ # ----------------------
674
+ # START Problems section
675
+ # ----------------------
676
+
677
+ def problem(dom:np.ndarray, z:np.ndarray, h0:float, q0:float, dx:float, CN:float, n:float):
678
+ """ Solve the mass and momentum equations using a explicit Runge-Kutta scheme (2 steps - 2nd order)
679
+
680
+ **NO BRIDGE**
681
+ """
682
+
683
+ h, q, h_pred, q_pred, h_corr, q_corr, q_border, h_border, z_border, u_border, h_center, u_center = all_unk_border(dom, h0, q0)
684
+
685
+ totaltime = 4*3600
686
+ eps=1.
687
+
688
+ with tqdm(total=totaltime) as pbar:
689
+ t = 0.
690
+ while eps > 1e-12:
691
+ dt = compute_dt(dx, h, q, CN)
692
+ # Predictor step
693
+ Euler_RK(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center)
694
+ # Corrector step
695
+ Euler_RK(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center)
696
+
697
+ # Update -- Mean for second order in time
698
+ h = (h_pred + h_corr)/2.
699
+ q = (q_pred + q_corr)/2.
700
+ t+=dt
701
+
702
+ eps = np.sum(np.abs(q - q_pred))
703
+
704
+ pbar.update(dt)
705
+
706
+ print("Total time : ", t)
707
+ print("Residual : ", eps)
708
+
709
+ return h, q
710
+
711
+ def problem_hedge(dom:np.ndarray, z:np.ndarray, h0:float, q0:float, dx:float, CN:float, n:float):
712
+ """ Solve the mass and momentum equations using a explicit Runge-Kutta scheme (2 steps - 2nd order)
713
+
714
+ **NO BRIDGE bur HEDGE in the middle**
715
+ """
716
+
717
+ h, q, h_pred, q_pred, h_corr, q_corr, q_border, h_border, z_border, u_border, h_center, u_center = all_unk_border(dom, h0, q0)
718
+
719
+ totaltime = 4*3600
720
+ eps=1.
721
+
722
+ with tqdm(total=totaltime) as pbar:
723
+ t = 0.
724
+ while eps > 1e-12:
725
+ dt = compute_dt(dx, h, q, CN)
726
+ # Predictor step
727
+ Euler_RK_hedge(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center)
728
+ # Corrector step
729
+ Euler_RK_hedge(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center)
730
+
731
+ # Update -- Mean for second order in time
732
+ h = (h_pred + h_corr)/2.
733
+ q = (q_pred + q_corr)/2.
734
+ t+=dt
735
+
736
+ eps = np.sum(np.abs(q - q_pred))
737
+
738
+ pbar.update(dt)
739
+
740
+ print("Total time : ", t)
741
+ print("Residual : ", eps)
742
+
743
+ return h, q
744
+
745
+ def problem_bridge(dom:np.ndarray, z:np.ndarray, z_bridge:np.ndarray,
746
+ h0:float, q0:float,
747
+ dx:float, CN:float, n:float,
748
+ press_mode:int = 4,
749
+ h_ini:np.ndarray = None, q_ini:np.ndarray = None) -> tuple[np.ndarray]:
750
+ """ Solve the mass and momentum equations using a explicit Rung-Kutta scheme (2 steps - 2nd order)
751
+
752
+ **WITH BRIDGE and NO OVERFLOW**
753
+ """
754
+
755
+ h, q, h_pred, q_pred, h_corr, q_corr, q_border, h_border, z_border, u_border, h_center, u_center = all_unk_border(dom, h0, q0)
756
+ z_bridge_border = np.zeros_like(z_border)
757
+
758
+ if h_ini is not None:
759
+ h[:] = h_ini[:]
760
+ if q_ini is not None:
761
+ q[:] = q_ini[:]
762
+
763
+ totaltime = 4*3600
764
+ eps=1.
765
+
766
+ with tqdm(total=totaltime) as pbar:
767
+ t = 0.
768
+ while eps > 1e-7:
769
+ dt = compute_dt(dx, h, q, CN)
770
+ # Predictor step
771
+ Euler_RK_bridge(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center, z_bridge, z_bridge_border, press_mode)
772
+ # Corrector step
773
+ Euler_RK_bridge(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center, z_bridge, z_bridge_border, press_mode)
774
+
775
+ # Update -- Mean for second order in time
776
+ h = (h_pred + h_corr)/2.
777
+ q = (q_pred + q_corr)/2.
778
+ t+=dt
779
+
780
+ eps = np.sum(np.abs(q - q_pred))
781
+
782
+ pbar.update(dt)
783
+
784
+ print("Total time : ", t)
785
+ print("Residual : ", eps)
786
+
787
+ return h, q
788
+
789
+ def problem_bridge_multiple_steadystates(dom:np.ndarray, z:np.ndarray, z_bridge:np.ndarray,
790
+ h0:float, qmin:float, qmax:float,
791
+ dx:float, CN:float, n:float,
792
+ press_mode:int = 4) -> list[tuple[float, np.ndarray, np.ndarray]]:
793
+ """ Solve multiple steady states for a given discharge range """
794
+
795
+ all_q = np.arange(qmin, qmax+.1, (qmax-qmin)/10)
796
+
797
+ ret = []
798
+ h = None
799
+ for curq in all_q:
800
+ h, q = problem_bridge(dom, z, z_bridge, h0, curq, dx, CN, n, press_mode, h_ini=h, q_ini=None)
801
+ ret.append((0., h.copy(), q.copy()))
802
+
803
+ return ret
804
+
805
+ def problem_bridge_unsteady(dom:np.ndarray, z:np.ndarray, z_bridge:np.ndarray,
806
+ h0:float, q0:float,
807
+ dx:float, CN:float, n:float,
808
+ press_mode:int = 4):
809
+ """ Solve the mass and momentum equations using a explicit Runge-Kutta scheme (2 steps - 2nd order).
810
+
811
+ **WITH BRIDGE and NO OVERFLOW**
812
+
813
+ The downstream boundary condition rises temporarily.
814
+
815
+ Firstly, we stabilize the flow with a constant downstream boundary condition.
816
+ Then, we increase the downstream boundary condition.
817
+
818
+ """
819
+
820
+ h, q, h_pred, q_pred, h_corr, q_corr, q_border, h_border, z_border, u_border, h_center, u_center = all_unk_border(dom, h0, q0)
821
+ z_bridge_border = np.zeros_like(z_border)
822
+
823
+ totaltime = 4*3600
824
+ eps=1.
825
+
826
+ # Compute steady state
827
+ with tqdm(total=totaltime) as pbar:
828
+ t = 0.
829
+ while eps > 1e-7:
830
+ dt = compute_dt(dx, h, q, CN)
831
+ # Predictor step
832
+ Euler_RK_bridge(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center, z_bridge, z_bridge_border, press_mode)
833
+ # Corrector step
834
+ Euler_RK_bridge(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center, z_bridge, z_bridge_border, press_mode)
835
+
836
+ # Update -- Mean for second order in time
837
+ h = (h_pred + h_corr)/2.
838
+ q = (q_pred + q_corr)/2.
839
+ t+=dt
840
+
841
+ eps = np.sum(np.abs(q - q_pred))
842
+
843
+ pbar.update(dt)
844
+
845
+ print("Total time : ", t)
846
+ print("Residual : ", eps)
847
+
848
+ res = []
849
+
850
+ totaltime = 2*3600
851
+ k=0
852
+ with tqdm(total=totaltime) as pbar:
853
+ t = 0.
854
+ while t < totaltime:
855
+
856
+ dt = compute_dt(dx, h, q, CN)
857
+
858
+ if t < totaltime/2:
859
+ h_cl = h0 + 7. * t/totaltime*2
860
+ else:
861
+ h_cl = h0 + 7. - 7. * (t - totaltime/2)/totaltime*2
862
+
863
+ # Predictor step
864
+ Euler_RK_bridge(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border, dt, dx, h_cl, q0, n, u_border, h_center, u_center, z_bridge, z_bridge_border, press_mode)
865
+ # Corrector step
866
+ Euler_RK_bridge(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border, dt, dx, h_cl, q0, n, u_border, h_center, u_center, z_bridge, z_bridge_border, press_mode)
867
+
868
+ # Update -- Mean for second order in time
869
+ h = (h_pred + h_corr)/2.
870
+ q = (q_pred + q_corr)/2.
871
+
872
+ if t > 300*k:
873
+ res.append((t, h.copy(), q.copy()))
874
+ k+=1
875
+
876
+ t+=dt
877
+ pbar.update(dt)
878
+
879
+
880
+ return res
881
+
882
+ def problem_bridge_unsteady_topo(dom:np.ndarray, z:np.ndarray,
883
+ z_roof:np.ndarray, z_deck:np.ndarray, z_roof_null:float,
884
+ h0:float, q0:float,
885
+ dx:float, CN:float, n:float,
886
+ press_mode:int = 0,
887
+ motion_duration:float = 300.,
888
+ scenario_bc:Literal['unsteady_downstream_bc',
889
+ 'hydrograph',
890
+ 'hydrograph_2steps',
891
+ 'Gauss'] = 'unsteady_downstream_bc',
892
+ min_overflow:float = 0.05,
893
+ updating_time_interval:float = 0.
894
+ ):
895
+ """ Solve the mass and momentum equations using a explicit Rung-Kutta scheme (2 steps - 2nd order).
896
+
897
+ **WITH BRIDGE and OVERFLOW**
898
+ """
899
+
900
+ h, q, h_pred, q_pred, h_corr, q_corr, q_border, h_border, z_border, u_border, h_center, u_center = all_unk_border(dom, h0, q0)
901
+ z_roof_border = np.zeros_like(z_border)
902
+
903
+ totaltime = 3600 #4*3600
904
+ eps=1.
905
+
906
+ # Compute steady state
907
+ with tqdm(total=totaltime) as pbar:
908
+ t = 0.
909
+ while eps > 1e-7:
910
+ dt = compute_dt(dx, h, q, CN)
911
+ # Predictor step
912
+ Euler_RK_bridge(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center, z_roof, z_roof_border, press_mode)
913
+ # Corrector step
914
+ Euler_RK_bridge(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border, dt, dx, h0, q0, n, u_border, h_center, u_center, z_roof, z_roof_border, press_mode)
915
+
916
+ # Update -- Mean for second order in time
917
+ h = (h_pred + h_corr)/2.
918
+ q = (q_pred + q_corr)/2.
919
+ t+=dt
920
+
921
+ eps = np.sum(np.abs(q - q_pred))
922
+
923
+ pbar.update(dt)
924
+
925
+ print("Total time : ", t)
926
+ print("Residual : ", eps)
927
+
928
+ g = 9.81
929
+
930
+ res = []
931
+
932
+ # Functions to evaluate the head and the local head loss coefficient
933
+ def compute_head(z,h,q):
934
+ return z+h+(q/h)**2/2/g
935
+
936
+ def compute_delta_head(zup,hup,qup,zdo,hdo,qdo):
937
+ return compute_head(zup,hup,qup) - compute_head(zdo,hdo,qdo)
938
+
939
+ def compute_k_mean(zup,hup,qup,zdo,hdo,qdo,A_bridge):
940
+ delta = compute_delta_head(zup,hup,qup,zdo,hdo,qdo)
941
+ q_mean = (qup+qdo)/2
942
+ return delta/((q_mean/A_bridge)**2/2/g)
943
+
944
+ def compute_losses(q, A_bridge, k):
945
+ """ Compute the losses based on the flow rate and the local head loss coefficient
946
+
947
+ :return: k * |Q| * Q / A^2 / 2 / g
948
+ :unit: [m]
949
+ """
950
+ return k * q * np.abs(q) / A_bridge**2. /2. / g
951
+
952
+ def compute_losses_semi_implicit(q, A_bridge, k):
953
+ """ Compute part of the losses based on the flow rate and the local head loss coefficient
954
+ to use in the semi-implicit scheme.
955
+
956
+ :return: k * |Q| / A / 2.
957
+ :unit: [s/m²]
958
+ """
959
+ return k * np.abs(q) / A_bridge /2.
960
+
961
+ def compute_q_wo_inertia(zup,hup,qup,zdo,hdo,qdo,A_bridge,k):
962
+ """ Compute the flow rate based on Bernoulli equation
963
+ without inertia term """
964
+
965
+ delta = compute_delta_head(zup,hup,qup,zdo,hdo,qdo)
966
+ return np.sqrt(2*g*np.abs(delta)/k)*A_bridge*np.sign(delta), delta
967
+
968
+ def compute_q_w_inertia(zup:float, hup:float, qup:float,
969
+ zdo:float, hdo:float, qdo:float,
970
+ A_bridge:float, k:float, q_prev:float,
971
+ dt:float, length:float):
972
+ """ Compute the flow rate based on Bernoulli equation
973
+ with inertia term
974
+
975
+ $ \frac{\partial Q}{\partial t} + \frac {g A}{L} (Head_do - Head_up + Losses) = 0 $
976
+
977
+ $ Losses = k \frac {U^2}{2 g} = k \frac {Q |Q|}{A^2 2 g} $
978
+ """
979
+
980
+ # new_q, delta = compute_q_wo_inertia(zup,hup,qup,zdo,hdo,qdo,A_bridge,k)
981
+
982
+ delta = compute_delta_head(zup,hup,qup,zdo,hdo,qdo)
983
+
984
+ if delta > 1.:
985
+ pass
986
+
987
+ inv_dt = 1./ dt
988
+ return (inv_dt * q_prev + g * A_bridge * delta / length) / (inv_dt + compute_losses_semi_implicit(q_prev, A_bridge, k) / length)
989
+
990
+ def _update_top(z_start:np.ndarray, z_end:np.ndarray, move_time:float, move_totaltime:float, reverse:bool=False):
991
+
992
+ if reverse:
993
+ pond = (move_totaltime - move_time) / move_totaltime
994
+ else:
995
+ pond = move_time / move_totaltime
996
+
997
+ loc_z = z_end * pond + z_start * (1.-pond)
998
+
999
+ return pond, loc_z
1000
+
1001
+ def update_top_q(z:np.ndarray, h:np.ndarray, q:np.ndarray, z_bridge:np.ndarray,
1002
+ idx_up:int, idx_do:int,
1003
+ survey_up:int, survey_do:int,
1004
+ A:float, k:float, q_prev:float, dt:float,
1005
+ move_time:float, move_totaltime:float, move_restore:bool,
1006
+ z_roof_start:np.ndarray, z_roof_end:np.ndarray,
1007
+ z_bath_start:np.ndarray, z_bath_end:np.ndarray,
1008
+ update_top:bool=True, stop_motion:bool = False):
1009
+
1010
+ if stop_motion:
1011
+ move_time = move_totaltime
1012
+
1013
+ zup = z[survey_up]
1014
+ zdo = z[survey_do]
1015
+
1016
+ hup = h[survey_up]
1017
+ hdo = h[survey_do]
1018
+
1019
+ qup = q[survey_up]
1020
+ qdo = q[survey_do]
1021
+
1022
+ # Update the flow rate considering inertia (generalized Bernoulli)
1023
+ qtot_infil_exfil = compute_q_w_inertia(zup, hup, qup,
1024
+ zdo, hdo, qdo,
1025
+ A, k, q_prev, dt,
1026
+ length=dx *(idx_do-idx_up-1))
1027
+
1028
+ if update_top:
1029
+
1030
+ pond, z_bridge[bridge] = _update_top(z_roof_start, z_roof_end, move_time, move_totaltime, move_restore)
1031
+ pond, z[bridge] = _update_top(z_bath_start, z_bath_end, move_time, move_totaltime, move_restore)
1032
+
1033
+ # if move_restore:
1034
+ # zref_up = zup + hup - .15
1035
+ # z[bridge] = np.minimum(z[bridge], zref_up)
1036
+
1037
+ else:
1038
+ pond = 0. if move_restore else 1.
1039
+
1040
+
1041
+ if stop_motion and move_restore:
1042
+ infil_exfil = None
1043
+ else:
1044
+ # Applying the infiltration/exfiltration linearly according to the topo-bathymetry evolution
1045
+ q_infil_exfil = qtot_infil_exfil * pond
1046
+ infil_exfil = (idx_up, idx_do, q_infil_exfil, qup/hup, pond, k)
1047
+
1048
+ return infil_exfil, z_bridge, z, qtot_infil_exfil
1049
+
1050
+ bridge = np.where(z_roof != z_roof_null)[0]
1051
+
1052
+ idx_up = bridge[0]-1
1053
+ idx_do = bridge[-1]+1
1054
+
1055
+ survey_up = idx_up-1
1056
+ survey_do = idx_do+1
1057
+
1058
+ z_overflow = z_deck[bridge].max() + min_overflow
1059
+
1060
+ totaltime = 2*3600
1061
+ total_compute = totaltime + 1800 + 2.
1062
+
1063
+ infil_exfil = None
1064
+
1065
+ filling_bridge = False
1066
+ filled_bridge = False
1067
+ emptying_bridge = False
1068
+ motion_completed = True
1069
+ local_motion_time = 0.
1070
+ total_motion_time = motion_duration
1071
+
1072
+ q_infil_t_current = 0.
1073
+
1074
+ delta_res_time_def = 30.
1075
+
1076
+ if scenario_bc == 'unsteady_downstream_bc':
1077
+ dh_cl = 7.
1078
+ dq_cl = 0.
1079
+
1080
+ elif scenario_bc == 'unsteady_downstream_bc_culvert':
1081
+ dh_cl = 2.
1082
+ dq_cl = 0.5
1083
+
1084
+ elif scenario_bc == 'hydrograph_culvert':
1085
+ dh_cl = 1.
1086
+ dq_cl = 3.
1087
+
1088
+ elif scenario_bc == 'hydrograph':
1089
+ dh_cl = 2.
1090
+ dq_cl = 8.
1091
+
1092
+ elif scenario_bc == 'hydrograph_2steps':
1093
+ dh_cl = 2.
1094
+ dq_cl = 8.
1095
+
1096
+ elif scenario_bc == 'hydrograph_2steps_culvert':
1097
+ dh_cl = 1.
1098
+ dq_cl = 10.
1099
+
1100
+ elif scenario_bc == 'Gauss':
1101
+ dh_cl = 2.
1102
+ dq_cl = 8.
1103
+
1104
+ with tqdm(total=total_compute, desc=scenario_bc) as pbar:
1105
+ t = 0.
1106
+
1107
+ res_time = 0.
1108
+ delta_res_time = delta_res_time_def
1109
+
1110
+ z_roof_start = None
1111
+ z_roof_end = None
1112
+ z_bath_start = None
1113
+ z_bath_end = None
1114
+
1115
+ while t < total_compute:
1116
+
1117
+ dt = compute_dt(dx, h, q, CN)
1118
+
1119
+ if scenario_bc == 'unsteady_downstream_bc' or scenario_bc == 'unsteady_downstream_bc_culvert':
1120
+ # The downstream boundary condition evolves linearly
1121
+ # from h0 to h0 + dh_cl in totaltime/2 seconds
1122
+ # keeps the value h0 + dh_cl during 1000 seconds
1123
+ # and then from h0 + dh_cl to h0 in totaltime/2 seconds
1124
+ #
1125
+ # ____
1126
+ # / \
1127
+ # / \
1128
+ # ------ / \
1129
+ # _/ \____
1130
+
1131
+ if t < totaltime/2:
1132
+ h_cl = h0 + dh_cl * t/totaltime*2
1133
+ q_cl = q0 + dq_cl * t/totaltime*2
1134
+ else:
1135
+ if t < totaltime/2 + 1000.:
1136
+ h_cl = h0 + dh_cl
1137
+ q_cl = q0 + dq_cl
1138
+ else:
1139
+ h_cl = h0 + dh_cl - dh_cl * (t - (totaltime/2+1000.))/(totaltime/2 - 1000.)
1140
+ q_cl = q0 + dq_cl - dq_cl * (t - (totaltime/2+1000.))/(totaltime/2 - 1000.)
1141
+
1142
+ elif scenario_bc == 'hydrograph' or scenario_bc == 'hydrograph_culvert':
1143
+ # The downstream boundary condition evolves linearly
1144
+ # from h0 to h0 + dh_cl in totaltime/2 seconds
1145
+ # keeps the value h0 + dh_cl during 1000 seconds
1146
+ # and then from h0 + dh_cl to h0 - dh_cl/2 in totaltime/2 seconds
1147
+ #
1148
+ # The upstream boundary condition evolves linearly
1149
+ # from q0 to q0 + dq_cl in totaltime/2 seconds
1150
+ # keeps the value q0 + dq_cl during 1000 seconds
1151
+ # and then from q0 + dq_cl to q0 - dq_cl/2 in totaltime/2 seconds
1152
+ #
1153
+ # _____ ____
1154
+ # / \ / \
1155
+ # / \ / \
1156
+ # / \ / \
1157
+ # _/ \ __/ \
1158
+ # \ \
1159
+ # \______ \____
1160
+
1161
+ if t < totaltime/2:
1162
+ h_cl = h0 + dh_cl * t/totaltime*2
1163
+ q_cl = q0 + dq_cl * t/totaltime*2
1164
+ else:
1165
+ if t < totaltime/2 + 1000.:
1166
+ h_cl = h0 + dh_cl
1167
+ q_cl = q0 + dq_cl
1168
+ elif t < totaltime:
1169
+ h_cl = h0 + dh_cl - dh_cl * (t - (totaltime/2+1000.))/(totaltime/2 - 1000.) * 1.5
1170
+ q_cl = q0 + dq_cl - dq_cl * (t - (totaltime/2+1000.))/(totaltime/2 - 1000.) * 1.5
1171
+ else:
1172
+ h_cl = h0 - dh_cl / 2.
1173
+ q_cl = q0 - dq_cl / 2.
1174
+
1175
+ elif scenario_bc == 'hydrograph_2steps' or scenario_bc == 'hydrograph_2steps_culvert':
1176
+ # Same as hydrograph but the downstream boundary condition
1177
+ # evolves linearly a second time during peek flow
1178
+
1179
+ if t < totaltime/2:
1180
+ h_cl = h0 + dh_cl * t/totaltime*2
1181
+ q_cl = q0 + dq_cl * t/totaltime*2
1182
+ else:
1183
+ if t < totaltime/2 + 500.:
1184
+ h_cl = h0 + dh_cl + dh_cl * (t - (totaltime/2))/(500.)
1185
+ q_cl = q0 + dq_cl
1186
+ elif t < totaltime/2 + 1000.:
1187
+ h_cl = h0 + 2* dh_cl - dh_cl * (t - (totaltime/2+500.))/(500.)
1188
+ q_cl = q0 + dq_cl
1189
+ elif t < totaltime:
1190
+ h_cl = h0 + dh_cl - dh_cl * (t - (totaltime/2+1000.))/(totaltime/2 - 1000.) * 1.5
1191
+ q_cl = q0 + dq_cl - dq_cl * (t - (totaltime/2+1000.))/(totaltime/2 - 1000.) * 1.5
1192
+ else:
1193
+ h_cl = h0 - dh_cl / 2.
1194
+ q_cl = q0 - dq_cl / 2.
1195
+
1196
+ elif scenario_bc == 'Gauss':
1197
+ # The downstream and upstream boundary conditions evolve
1198
+ # according to a Gaussian function
1199
+
1200
+ h_cl = h0 + dh_cl * np.exp(-((t-totaltime/2)**.5)/3600)
1201
+ q_cl = q0 + dq_cl * np.exp(-((t-totaltime/2)**.5)/3600)
1202
+
1203
+ # Predictor step
1204
+ Euler_RK_bridge(h, h_pred, q, q_pred, h, q, h_border, q_border, z, z_border,
1205
+ dt, dx, h_cl, q_cl,
1206
+ n, u_border, h_center, u_center, z_roof, z_roof_border,
1207
+ press_mode, infil_exfil)
1208
+
1209
+ # Corrector step
1210
+ Euler_RK_bridge(h, h_corr, q, q_corr, h_pred, q_pred, h_border, q_border, z, z_border,
1211
+ dt, dx, h_cl, q_cl,
1212
+ n, u_border, h_center, u_center, z_roof, z_roof_border,
1213
+ press_mode, infil_exfil)
1214
+
1215
+ # Update -- Mean for second order in time
1216
+ h = (h_pred + h_corr)/2.
1217
+ q = (q_pred + q_corr)/2.
1218
+
1219
+ if t >= res_time:
1220
+ res.append((t, h.copy(), q.copy(), z.copy(), z_roof.copy(), infil_exfil if infil_exfil is not None else (idx_up, idx_do, 0., 0., 0., 0.)))
1221
+ res_time += delta_res_time
1222
+
1223
+ if z[survey_up] + h[survey_up] > z_overflow:
1224
+ # Overflow
1225
+
1226
+ # Convert Bridge into topography
1227
+ # add infiltration/exfiltration
1228
+ if not filling_bridge and motion_completed and not filled_bridge:
1229
+
1230
+ # Movement must be initiated...
1231
+
1232
+ # Decreasing the interval of time for the
1233
+ # saving to be more precise when plotting
1234
+ delta_res_time = delta_res_time_def / 5.
1235
+
1236
+ filling_bridge = True
1237
+ emptying_bridge = False
1238
+ motion_completed = False
1239
+ local_motion_time = 0.
1240
+
1241
+ # Keeping the old values -- Can be useful when restoring
1242
+ old_z_roof = z_roof.copy()
1243
+ old_z = z.copy()
1244
+
1245
+ # Reference section of the bridge...
1246
+ # Keeping the minimum distance between the bridge and the floor
1247
+ A_bridge = np.min(z_roof[bridge] - z[bridge])
1248
+
1249
+
1250
+ # Computing global head loss coefficient associated to the bridge and the reference section
1251
+ k_bridge = compute_k_mean(z[survey_up], h[survey_up], q[survey_up],
1252
+ z[survey_do], h[survey_do], q[survey_do],
1253
+ A_bridge)
1254
+
1255
+ # Starting the motion...
1256
+ # - the bridge's roof is going up
1257
+ # - the bridge's floor is going up
1258
+
1259
+ z_roof_start = old_z_roof[bridge]
1260
+ z_roof_end = z_roof_null
1261
+ z_bath_start = old_z[bridge]
1262
+ z_bath_end = z_deck[bridge]
1263
+
1264
+ # Mean Flow rate under the bridge
1265
+ q_infil_t_current = np.mean(q[bridge])
1266
+
1267
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1268
+ idx_up, idx_do,
1269
+ survey_up, survey_do,
1270
+ A_bridge, k_bridge, q_infil_t_current,
1271
+ dt,
1272
+ local_motion_time, total_motion_time,
1273
+ emptying_bridge,
1274
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end)
1275
+
1276
+ else:
1277
+ if not motion_completed:
1278
+ # Movement is initiated but not finished...
1279
+
1280
+ # Updating the local time
1281
+ local_motion_time += dt
1282
+
1283
+ if local_motion_time > total_motion_time:
1284
+ # Total time is reached...
1285
+ # ... so terminate the movement
1286
+
1287
+ delta_res_time = delta_res_time_def
1288
+
1289
+ filling_bridge = False
1290
+ motion_completed = True
1291
+ filled_bridge = not filled_bridge
1292
+
1293
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1294
+ idx_up, idx_do,
1295
+ survey_up, survey_do,
1296
+ A_bridge, k_bridge, q_infil_t_current, dt,
1297
+ local_motion_time, total_motion_time, emptying_bridge,
1298
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end,
1299
+ stop_motion= True)
1300
+
1301
+ local_motion_time = 0.
1302
+ else:
1303
+ # Total time is not reached...
1304
+ # ... so continue the movement
1305
+
1306
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1307
+ idx_up, idx_do,
1308
+ survey_up, survey_do,
1309
+ A_bridge, k_bridge, q_infil_t_current, dt,
1310
+ local_motion_time, total_motion_time, emptying_bridge,
1311
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end)
1312
+ else:
1313
+ # Movement is done...
1314
+
1315
+ if infil_exfil is not None:
1316
+
1317
+ # Updating the infiltration discharge according to head difference
1318
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1319
+ idx_up, idx_do,
1320
+ survey_up, survey_do,
1321
+ A_bridge, k_bridge, q_infil_t_current, dt,
1322
+ local_motion_time, total_motion_time, emptying_bridge,
1323
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end,
1324
+ update_top=False)
1325
+
1326
+
1327
+ else:
1328
+ # No overflow
1329
+
1330
+ if filling_bridge:
1331
+ # But movement is initiated...
1332
+ local_motion_time += dt
1333
+
1334
+ if local_motion_time > total_motion_time:
1335
+
1336
+ delta_res_time = delta_res_time_def
1337
+
1338
+ # Total time is reached...
1339
+ # ... so terminate the movement
1340
+
1341
+ filling_bridge = False
1342
+ motion_completed = True
1343
+ filled_bridge = not filled_bridge
1344
+
1345
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1346
+ idx_up, idx_do,
1347
+ survey_up, survey_do,
1348
+ A_bridge, k_bridge, q_infil_t_current, dt,
1349
+ local_motion_time, total_motion_time, emptying_bridge,
1350
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end,
1351
+ stop_motion= True)
1352
+
1353
+ local_motion_time = 0.
1354
+
1355
+ else:
1356
+ # Total time is not reached...
1357
+ # ... so continue the movement
1358
+
1359
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1360
+ idx_up, idx_do,
1361
+ survey_up, survey_do,
1362
+ A_bridge, k_bridge, q_infil_t_current, dt,
1363
+ local_motion_time, total_motion_time, emptying_bridge,
1364
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end)
1365
+
1366
+ else:
1367
+
1368
+ if infil_exfil is not None:
1369
+
1370
+ if motion_completed:
1371
+ # The bridge is not moving and the infiltration/exfiltration exists
1372
+
1373
+ # We can start to restore the bridge as it was before the overflow...
1374
+
1375
+ delta_res_time = delta_res_time_def / 5.
1376
+
1377
+ filling_bridge = True
1378
+ local_motion_time = 0.
1379
+ motion_completed = False
1380
+ emptying_bridge = True
1381
+
1382
+ infil_exfil, z_roof, z, q_infil_t_current = update_top_q(z, h, q, z_roof,
1383
+ idx_up, idx_do,
1384
+ survey_up, survey_do,
1385
+ A_bridge, k_bridge, q_infil_t_current, dt,
1386
+ local_motion_time, total_motion_time, emptying_bridge,
1387
+ z_roof_start, z_roof_end, z_bath_start, z_bath_end,
1388
+ )
1389
+
1390
+ t+=dt
1391
+ pbar.update(dt)
1392
+
1393
+
1394
+ return res
1395
+
1396
+
1397
+ # --------------------
1398
+ # END Problems section
1399
+ # --------------------
1400
+
1401
+
1402
+ # ----------------
1403
+ # PLOTTING SECTION
1404
+ # ----------------
1405
+ import matplotlib.animation as animation
1406
+
1407
+ def plot_bridge(ax:plt.Axes, x:np.ndarray,
1408
+ h1:np.ndarray, h2:np.ndarray,
1409
+ q1:np.ndarray, q2:np.ndarray,
1410
+ z:np.ndarray, z_bridge:np.ndarray,
1411
+ hu:float):
1412
+
1413
+ u1=np.zeros_like(q1)
1414
+ u2=np.zeros_like(q1)
1415
+
1416
+ u1[1:-1] = q1[1:-1]/h1[1:-1]
1417
+ u2[1:-1] = q2[1:-1]/np.minimum(h2[1:-1],z_bridge[1:-1]-z[1:-1])
1418
+
1419
+ ax.plot(x[1:-1], z[1:-1], label = 'z')
1420
+ ax.plot(x[1:-1], z_bridge[1:-1], label = 'bridge')
1421
+
1422
+ ax.plot(x[1:-1], z[1:-1] + h1[1:-1], label = 'z + h1')
1423
+ ax.plot(x[1:-1], z[1:-1] + h2[1:-1], label = 'z + h2')
1424
+
1425
+ ax.plot(x[1:-1], q1[1:-1], label = 'q')
1426
+
1427
+ under_bridge = np.where(h2 > z_bridge - z)[0]
1428
+ free_surface = np.where(h2 <= z_bridge - z)[0]
1429
+
1430
+ ax.plot(x[1:-1], z[1:-1] + h1[1:-1] + u1[1:-1]**2/2/9.81, label = 'head1')
1431
+
1432
+ ax.plot(x[1:-1], z[1:-1] + h2[1:-1] + u2[1:-1]**2/2/9.81, label = 'head2')
1433
+ # ax.plot(x[free_surface], (z[free_surface] + h2[free_surface] + u2[free_surface]**2/2/9.81) * h2[free_surface], label = 'head2 free surface')
1434
+
1435
+ if hu != 99999.:
1436
+ ax.plot(x[1:-1], z[1:-1]+hu, linestyle='--', label = 'h uniform')
1437
+ ax.legend()
1438
+
1439
+ def plot_hedge(ax:plt.Axes, x:np.ndarray,
1440
+ h1:np.ndarray, h2:np.ndarray,
1441
+ q1:np.ndarray, q2:np.ndarray,
1442
+ z:np.ndarray, hu:float):
1443
+
1444
+ u1=np.zeros_like(q1)
1445
+ u2=np.zeros_like(q1)
1446
+
1447
+ slice_hedge = slice(len(h1) // 2-1, len(h1) // 2+2)
1448
+ theta = 0.2
1449
+
1450
+ u1[1:-1] = q1[1:-1]/h1[1:-1]
1451
+ u2[1:-1] = q2[1:-1]/h2[1:-1]
1452
+ u2[slice_hedge] /= theta
1453
+
1454
+ ax.plot(x[1:-1], z[1:-1], label = 'z')
1455
+
1456
+ ax.plot(x[1:-1], z[1:-1] + h1[1:-1], label = 'z + h1')
1457
+ ax.plot(x[1:-1], z[1:-1] + h2[1:-1], label = 'z + h2')
1458
+
1459
+ ax.plot(x[1:-1], u1[1:-1], label = 'u1')
1460
+ ax.plot(x[1:-1], u2[1:-1], label = 'u2')
1461
+
1462
+ # ax.plot(x[1:-1], z[1:-1] + h1[1:-1] + u1[1:-1]**2/2/9.81, label = 'head1')
1463
+ # ax.plot(x[1:-1], z[1:-1] + h2[1:-1] + u2[1:-1]**2/2/9.81, label = 'head2')
1464
+
1465
+ ax.plot(x[1:-1], z[1:-1]+hu, linestyle='--', label = 'h uniform')
1466
+ ax.legend()
1467
+
1468
+
1469
+ def animate_bridge_unsteady_topo(dom, poly_bridge_x, poly_bridge_y, res:list[float, np.ndarray, np.ndarray, np.ndarray, np.ndarray, tuple[int,int,float,float,float]],
1470
+ x:np.ndarray, z_ini:np.ndarray, hu:float, z_null:float, length:float, title:str= "Bridge", motion_duration=300.):
1471
+ fig, axes = plt.subplots(2,1)
1472
+
1473
+ all_q_middle = [cur[2][len(dom)//2] for cur in res]
1474
+ all_q_inf_ex = [cur[5][2] for cur in res]
1475
+
1476
+ all_q_up = [cur[2][1] for cur in res]
1477
+ all_q_down = [cur[2][-2] for cur in res]
1478
+
1479
+ all_times = [cur[0] for cur in res]
1480
+ idx_Froude = [int(len(dom)*cur) for cur in np.linspace(0,100,11,True)/100]
1481
+ idx_Froude[0] += 5
1482
+ idx_Froude[-1] -= 5
1483
+ x_Froude = x[idx_Froude]
1484
+ Froude = np.zeros_like(x_Froude)
1485
+
1486
+ def update(frame):
1487
+ ax:plt.Axes
1488
+ ax = axes[0]
1489
+ ax.clear()
1490
+
1491
+ z:np.ndarray
1492
+ t, h, q, z, z_roof, inf_ex = res[frame]
1493
+
1494
+ for i, idx in enumerate(idx_Froude):
1495
+ if h[idx]==0.:
1496
+ Froude[i] = 0.
1497
+ else:
1498
+ Froude[i] = q[idx] / h[idx] /np.sqrt(9.81*h[idx])
1499
+
1500
+ ax.fill_between(x[1:-1], z[1:-1], z[1:-1] + h[1:-1], color='blue', alpha=0.5)
1501
+ ax.plot(x[1:-1], z[1:-1] + h[1:-1], label='water level [m]', color='blue')
1502
+
1503
+ ax.fill_between(x[1:-1], np.ones(z.shape)[1:-1] * z.min(), z[1:-1], color='brown', alpha=0.5)
1504
+ ax.plot(x[1:-1], z[1:-1], label='bottom level [m]', color='brown')
1505
+
1506
+ slice_roof = z_roof != z_null
1507
+ ax.fill_between(x[slice_roof], z_roof[slice_roof], np.ones(z.shape)[slice_roof] * z_null, color='grey', alpha=0.5)
1508
+ ax.plot(x[slice_roof], z_roof[slice_roof], label='roof level [m]', color='grey')
1509
+
1510
+ # ax.plot(x[1:-1], z_ini[1:-1] + hu, linestyle='--', label='h uniform')
1511
+ ax.plot(x[1:-1], q[1:-1], linestyle='-', label='flow rate [$m^2/s$]', color='black', linewidth=1.5)
1512
+ ax.legend(loc='upper right')
1513
+
1514
+ ax.fill(poly_bridge_x, poly_bridge_y, color='black', alpha=0.8)
1515
+
1516
+ q_middle = q[len(dom)//2]
1517
+ q_inf_ex = inf_ex[2]
1518
+
1519
+ txt = f'Total flow rate {q_middle+q_inf_ex:.2f} $m^2/s$'
1520
+ txt += f'\nOverflow = {q_middle:.2f} $m^2/s$ - {q_middle/(q_inf_ex+q_middle)*100:.2f} %'
1521
+ txt += f'\nUnderflow = {q_inf_ex:.2f} $m^2/s$ - {q_inf_ex/(q_inf_ex+q_middle)*100:.2f} %'
1522
+
1523
+ in_txt = '$k_{loss}$ ='
1524
+ txt += f'\n\nMotion time = {motion_duration:.1f} s -- {in_txt} {inf_ex[-1]:.2f}'
1525
+
1526
+ ax.text(x[len(dom)//2+8], 11., txt, fontsize=9)
1527
+
1528
+ for posFroude, curFroude in zip(x_Froude, Froude):
1529
+ ax.text(posFroude, 8., f'Fr = {curFroude:.2f}', fontsize=9, horizontalalignment='center')
1530
+
1531
+ ax.set_xlim(0, 500)
1532
+ ax.set_xticks(np.arange(0, 501, 50))
1533
+
1534
+ ax.grid(axis='x')
1535
+
1536
+ ax.set_ylim(0, 20)
1537
+ ax.set_title(f'{title} - Length = {length:.1f} m - Time = {t:.1f} s')
1538
+
1539
+ ax = axes[1]
1540
+ ax.clear()
1541
+
1542
+ ax.plot(all_times[:frame], all_q_middle[:frame], label='Overflow', color='blue')
1543
+ ax.plot(all_times[:frame], all_q_inf_ex[:frame], label='Underflow', color='red')
1544
+ ax.plot(all_times[:frame], [qinf + qmiddle for qinf, qmiddle in zip(all_q_inf_ex[:frame], all_q_middle[:frame])], label='Total', color='black')
1545
+
1546
+ ax.plot(all_times[:frame], all_q_up[:frame], label='Upstream', color='black', linestyle='--', linewidth=1.)
1547
+ ax.plot(all_times[:frame], all_q_down[:frame], label='Downstream', color='black', linestyle='-.', linewidth=1.)
1548
+
1549
+ ax.legend(loc='upper right')
1550
+ ax.set_title('Flow rate')
1551
+ ax.set_xlabel('Time [s]')
1552
+ ax.set_ylabel('Flow rate [$m^2/s$]')
1553
+
1554
+ ax.set_ylim(0, 20)
1555
+ ax.set_xlim(0, all_times[-1])
1556
+ ax.set_xticks(np.arange(0, all_times[-1]+10, 900))
1557
+ ax.grid()
1558
+
1559
+ fig.set_size_inches(20,8)
1560
+ update(0)
1561
+ fig.tight_layout()
1562
+
1563
+ ani = animation.FuncAnimation(fig, update, frames=len(res), repeat=True)
1564
+
1565
+ return ani
1566
+
1567
+
1568
+ # -------------
1569
+ # REAL PROBLEMS
1570
+ # -------------
1571
+
1572
+ def lake_at_rest():
1573
+ """ Compute Lake at rest problem
1574
+
1575
+ The problem is a simple steady state problem with a bridge in the middle of the domain.
1576
+ The bridge is a simple flat bridge with a height of 2 m.
1577
+
1578
+ No discharge and no water movement is expected.
1579
+ """
1580
+
1581
+ length = 500.
1582
+ dx = 1.
1583
+ CN = 0.4
1584
+ h0 = 4. # Initial water depth
1585
+ q0 = 0. # Initial discharge
1586
+ n = 0.025
1587
+ slope = 0 #1e-4
1588
+ press_mode = 4
1589
+
1590
+ hu = 0
1591
+
1592
+ dom, x, z = domain(length, dx, slope)
1593
+ x = np.array(x)
1594
+
1595
+ # Bridge roof level is 10 m everywhere except in the middle of the domain
1596
+ z_bridge = np.ones_like(z) * 10.
1597
+ z_bridge[len(dom)//2-5:len(dom)//2+5] = z[len(dom)//2-5:len(dom)//2+5] + 2.
1598
+
1599
+ fig, axes = plt.subplots(3,1)
1600
+
1601
+ # free surface flow
1602
+ h1, q1 = problem(dom, z, h0, q0, dx, CN, n)
1603
+ # partially pressurized flow
1604
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0, q0, dx, CN, n, press_mode=press_mode)
1605
+
1606
+ assert np.allclose(h1[1:-1], h0), 'Free surface flow is not steady state'
1607
+ assert np.allclose(q1[1:-1], q0), 'Free surface flow is not steady state'
1608
+ assert np.allclose(h2[1:-1], h0), 'Partially pressurized flow is not steady state'
1609
+ assert np.allclose(q2[1:-1], q0), 'Partially pressurized flow is not steady state'
1610
+
1611
+ plot_bridge(axes[0], x, h1, h2, q1, q2, z, z_bridge, hu)
1612
+
1613
+ # increasing water depth
1614
+ h0 += 1.
1615
+
1616
+ # free surface flow
1617
+ h1, q1 = problem(dom, z, h0+1., q0, dx, CN, n)
1618
+ # partially pressurized flow
1619
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0, q0, dx, CN, n, press_mode=press_mode)
1620
+
1621
+ assert np.allclose(h1[1:-1], h0+1.), 'Free surface flow is not steady state'
1622
+ assert np.allclose(q1[1:-1], q0), 'Free surface flow is not steady state'
1623
+ assert np.allclose(h2[1:-1], h0), 'Partially pressurized flow is not steady state'
1624
+ assert np.allclose(q2[1:-1], q0), 'Partially pressurized flow is not steady state'
1625
+
1626
+ plot_bridge(axes[1], x, h1, h2, q1, q2, z, z_bridge, hu)
1627
+
1628
+ # increasing water depth
1629
+ h0 += 1.
1630
+
1631
+ # free surface flow
1632
+ h1, q1 = problem(dom, z, h0+2., q0, dx, CN, n)
1633
+ # partially pressurized flow
1634
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0, q0, dx, CN, n, press_mode=press_mode)
1635
+
1636
+ assert np.allclose(h1[1:-1], h0+2.), 'Free surface flow is not steady state'
1637
+ assert np.allclose(q1[1:-1], q0), 'Free surface flow is not steady state'
1638
+ assert np.allclose(h2[1:-1], h0), 'Partially pressurized flow is not steady state'
1639
+ assert np.allclose(q2[1:-1], q0), 'Partially pressurized flow is not steady state'
1640
+
1641
+ plot_bridge(axes[2], x, h1, h2, q1, q2, z, z_bridge, hu)
1642
+
1643
+ fig.set_size_inches(15, 10)
1644
+ fig.tight_layout()
1645
+
1646
+ return fig, axes
1647
+
1648
+
1649
+ def water_line():
1650
+ """ Compute Water line problems
1651
+
1652
+ Length = 500 m
1653
+ dx = 1 m
1654
+ CN = 0.4
1655
+ h0 = 4 m
1656
+ q0 = 7 m^2/s
1657
+ n = 0.025
1658
+ slope = 1e-4
1659
+ """
1660
+
1661
+ length = 500.
1662
+ dx = 1.
1663
+ CN = 0.4
1664
+ h0 = 4.
1665
+ q0 = 7.
1666
+ n = 0.025
1667
+ slope = 1e-4
1668
+ press_mode = 4
1669
+
1670
+ dom, x, z = domain(length, dx, slope)
1671
+ x = np.array(x)
1672
+
1673
+ hu = uniform_waterdepth(slope, q0, n)
1674
+
1675
+ # bridge roof level is 10 m everywhere except in the middle of the domain
1676
+ # where the bridge is located.
1677
+ # The bridge is a flat bridge with a height of 2 m.
1678
+ z_bridge = np.ones_like(z) * 10.
1679
+ z_bridge[len(dom)//2-5:len(dom)//2+5] = z[len(dom)//2-5:len(dom)//2+5] + 2.
1680
+
1681
+ fig, axes = plt.subplots(3,1)
1682
+
1683
+ h1, q1 = problem(dom, z, h0, q0, dx, CN, n)
1684
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0, q0, dx, CN, n, press_mode=press_mode)
1685
+
1686
+ plot_bridge(axes[0], x, h1, h2, q1, q2, z, z_bridge, hu)
1687
+
1688
+ h1, q1 = problem(dom, z, h0+1., q0, dx, CN, n)
1689
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0+1., q0, dx, CN, n, press_mode=press_mode)
1690
+
1691
+ plot_bridge(axes[1], x, h1, h2, q1, q2, z, z_bridge, hu)
1692
+
1693
+ h1, q1 = problem(dom, z, h0+2., q0, dx, CN, n)
1694
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0+2., q0, dx, CN, n, press_mode=press_mode)
1695
+
1696
+ plot_bridge(axes[2], x, h1, h2, q1, q2, z, z_bridge, hu)
1697
+
1698
+ fig.set_size_inches(20, 10)
1699
+ fig.tight_layout()
1700
+
1701
+ return fig, axes
1702
+
1703
+ def water_line_noloss_noslope():
1704
+ """ Compute Water line problems
1705
+
1706
+ Length = 500 m
1707
+ dx = 1 m
1708
+ CN = 0.4
1709
+ h0 = 4 m
1710
+ q0 = 7 m^2/s
1711
+ n = 0.0
1712
+ slope = 0.0
1713
+ """
1714
+
1715
+ length = 500.
1716
+ dx = 1.
1717
+ CN = 0.4
1718
+ h0 = 4.
1719
+ q0 = 7.
1720
+ n = 0.
1721
+ slope = 0.
1722
+ press_mode = 4
1723
+
1724
+ dom, x, z = domain(length, dx, slope)
1725
+ x = np.array(x)
1726
+
1727
+ hu = uniform_waterdepth(slope, q0, n)
1728
+
1729
+ # bridge roof level is 10 m everywhere except in the middle of the domain
1730
+ # where the bridge is located.
1731
+ # The bridge is a flat bridge with a height of 2 m.
1732
+ z_bridge = np.ones_like(z) * 10.
1733
+ z_bridge[len(dom)//2-5:len(dom)//2+5] = z[len(dom)//2-5:len(dom)//2+5] + 2.
1734
+
1735
+ fig, axes = plt.subplots(3,1)
1736
+
1737
+ h1, q1 = problem(dom, z, h0, q0, dx, CN, n)
1738
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0, q0, dx, CN, n, press_mode=press_mode)
1739
+
1740
+ plot_bridge(axes[0], x, h1, h2, q1, q2, z, z_bridge, hu)
1741
+
1742
+ h1, q1 = problem(dom, z, h0+1., q0, dx, CN, n)
1743
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0+1., q0, dx, CN, n, press_mode=press_mode)
1744
+
1745
+ plot_bridge(axes[1], x, h1, h2, q1, q2, z, z_bridge, hu)
1746
+
1747
+ h1, q1 = problem(dom, z, h0+2., q0, dx, CN, n)
1748
+ h2, q2 = problem_bridge(dom, z, z_bridge, h0+2., q0, dx, CN, n, press_mode=press_mode)
1749
+
1750
+ plot_bridge(axes[2], x, h1, h2, q1, q2, z, z_bridge, hu)
1751
+
1752
+ fig.set_size_inches(20, 10)
1753
+ fig.tight_layout()
1754
+
1755
+ return fig, axes
1756
+
1757
+ def water_lines():
1758
+ """ Compute multiple water lines problems.
1759
+
1760
+ Evaluate the head loss due to the bridge and compare
1761
+ to theoretical fomula.
1762
+ """
1763
+
1764
+ length = 500.
1765
+ dx = 1.
1766
+ CN = 0.4
1767
+ h0 = 4.
1768
+ q0 = 7.
1769
+ n = 0. #0.025
1770
+ slope = 0 #1e-4
1771
+ press_mode = 4
1772
+
1773
+ dom, x, z = domain(length, dx, slope)
1774
+ x = np.array(x)
1775
+
1776
+ hu = 0. #uniform_waterdepth(slope, q0, n)
1777
+
1778
+ b_bridge = 2.
1779
+
1780
+ z_bridge = np.ones_like(z) * 10.
1781
+ z_bridge[len(dom)//2-5:len(dom)//2+5] = z[len(dom)//2-5:len(dom)//2+5] + b_bridge
1782
+
1783
+ idx_before_bridge = len(dom)//2-5 -2
1784
+ idx_after_bridge = len(dom)//2+5 +2
1785
+ idx_bridge = len(dom)//2
1786
+
1787
+ h0 = 4.5
1788
+ res = problem_bridge_multiple_steadystates(dom, z, z_bridge, h0, 3.5, 20., dx, CN, n, press_mode=press_mode)
1789
+
1790
+ fig, axes = plt.subplots(2,1)
1791
+
1792
+ ax:plt.Axes
1793
+
1794
+ ax = axes[0]
1795
+ for cur in res:
1796
+ ax.plot(x[1:-1], z[1:-1]+cur[1][1:-1], label = f't = {cur[0]:.1f}')
1797
+ ax.plot(x[1:-1], z[1:-1]+hu, linestyle='--', label = 'h uniform')
1798
+ ax.plot(x[1:-1], z_bridge[1:-1], label = 'bridge')
1799
+ ax.plot(x[1:-1], z[1:-1], label = 'z')
1800
+ ax.legend()
1801
+
1802
+
1803
+ # Head losses
1804
+
1805
+ # pressure before, after and at the bridge
1806
+ press_before = [cur[1][idx_before_bridge] for cur in res]
1807
+ press_after = [cur[1][idx_after_bridge] for cur in res]
1808
+ press_bridge = [cur[1][idx_bridge] for cur in res]
1809
+
1810
+ h_bridge = np.minimum(press_bridge, z_bridge[idx_bridge] - z[idx_bridge])
1811
+
1812
+ # flow rate before, after and at the bridge
1813
+ q_before = [cur[2][idx_before_bridge] for cur in res]
1814
+ q_after = [cur[2][idx_after_bridge] for cur in res]
1815
+ q_bridge = [cur[2][idx_bridge] for cur in res]
1816
+
1817
+ # velocity before, after and at the bridge
1818
+ u_before = [q/h for q,h in zip(q_before, press_before)]
1819
+ u_after = [q/h for q,h in zip(q_after, press_after)]
1820
+ u_bridge = [q/h for q,h in zip(q_bridge, h_bridge)]
1821
+
1822
+ # head before, after and at the bridge
1823
+ head_before = [z[idx_before_bridge] + press_before[i] + u_before[i]**2/2/9.81 for i in range(len(res))]
1824
+ head_after = [z[idx_after_bridge] + press_after[i] + u_after[i]**2/2/9.81 for i in range(len(res))]
1825
+ head_bridge = [z[idx_bridge] + press_bridge[i] + u_bridge[i]**2/2/9.81 for i in range(len(res))]
1826
+
1827
+ # head losses
1828
+ delta_head_total = [head_before[i] - head_after[i] for i in range(len(res))]
1829
+ delta_head_up = [head_before[i] - head_bridge[i] for i in range(len(res))]
1830
+ delta_head_do = [head_bridge[i] - head_after[i] for i in range(len(res))]
1831
+
1832
+ ax = axes[1]
1833
+
1834
+ ax.plot(delta_head_up, [head_loss_contraction(q, h1, h2) for q,h1,h2 in zip(q_bridge, press_before, h_bridge)], marker='*', label = 'Contraction Loss')
1835
+ ax.plot(delta_head_do, [head_loss_enlargment(q, h1, h2) for q,h1,h2 in zip(q_bridge, h_bridge, press_after)], marker='o', label = 'Enlargment Loss')
1836
+ ax.plot(delta_head_total, [head_loss_contract_enlarge(q,h1,h2,h3) for q,h1,h2,h3 in zip(q_bridge, press_before, h_bridge, press_after)], marker='x', label = 'Total Loss')
1837
+
1838
+ ax.set_xlabel('Computed $\Delta H$ [m]')
1839
+ ax.set_ylabel('Theoretical $\Delta H$ [m]')
1840
+
1841
+ ax.plot([0,1],[0,1], linestyle='-', linewidth=2, color='black')
1842
+ ax.set_aspect('equal')
1843
+ ax.legend()
1844
+
1845
+ fig.set_size_inches(20, 10)
1846
+ fig.tight_layout()
1847
+
1848
+ return fig, axes
1849
+
1850
+
1851
+ def unsteady_without_bedmotion():
1852
+ """
1853
+ Compute unsteady problem without bed motion.
1854
+
1855
+ The downstream boundary condition rises and decreases.
1856
+ """
1857
+
1858
+ length = 500.
1859
+ dx = 1.
1860
+ CN = 0.4
1861
+ h0 = 4.
1862
+ q0 = 7.
1863
+ n = 0. #0.025
1864
+ slope = 0 #1e-4
1865
+ press_mode = 4
1866
+
1867
+ dom, x, z = domain(length, dx, slope)
1868
+ x = np.array(x)
1869
+
1870
+ hu = 0. #uniform_waterdepth(slope, q0, n)
1871
+
1872
+ b_bridge = 2.
1873
+
1874
+ z_bridge = np.ones_like(z) * 10.
1875
+ z_bridge[len(dom)//2-5:len(dom)//2+5] = z[len(dom)//2-5:len(dom)//2+5] + b_bridge
1876
+
1877
+ idx_before_bridge = len(dom)//2-5 -2
1878
+ idx_after_bridge = len(dom)//2+5 +2
1879
+ idx_bridge = len(dom)//2
1880
+
1881
+ h0 = 1.5
1882
+ res = problem_bridge_unsteady(dom, z, z_bridge, h0, q0, dx, CN, n, press_mode=press_mode)
1883
+
1884
+ fig, axes = plt.subplots(2,1)
1885
+
1886
+ ax:plt.Axes
1887
+
1888
+ ax = axes[0]
1889
+ for cur in res:
1890
+ ax.plot(x[1:-1], z[1:-1]+cur[1][1:-1], label = f't = {cur[0]:.1f}')
1891
+ ax.plot(x[1:-1], z[1:-1]+hu, linestyle='--', label = 'h uniform')
1892
+ ax.plot(x[1:-1], z_bridge[1:-1], label = 'bridge')
1893
+ ax.plot(x[1:-1], z[1:-1], label = 'z')
1894
+ ax.legend()
1895
+
1896
+ press_before = [cur[1][idx_before_bridge] for cur in res]
1897
+ press_after = [cur[1][idx_after_bridge] for cur in res]
1898
+ press_bridge = [cur[1][idx_bridge] for cur in res]
1899
+
1900
+ h_bridge = np.minimum(press_bridge, z_bridge[idx_bridge] - z[idx_bridge])
1901
+
1902
+ q_before = [cur[2][idx_before_bridge] for cur in res]
1903
+ q_after = [cur[2][idx_after_bridge] for cur in res]
1904
+ q_bridge = [cur[2][idx_bridge] for cur in res]
1905
+
1906
+ u_before = [q/h for q,h in zip(q_before, press_before)]
1907
+ u_after = [q/h for q,h in zip(q_after, press_after)]
1908
+ u_bridge = [q/h for q,h in zip(q_bridge, h_bridge)]
1909
+
1910
+ head_before = [z[idx_before_bridge] + press_before[i] + u_before[i]**2/2/9.81 for i in range(len(res))]
1911
+ head_after = [z[idx_after_bridge] + press_after[i] + u_after[i]**2/2/9.81 for i in range(len(res))]
1912
+ head_bridge = [z[idx_bridge] + press_bridge[i] + u_bridge[i]**2/2/9.81 for i in range(len(res))]
1913
+
1914
+ delta_head_total = [head_before[i] - head_after[i] for i in range(len(res))]
1915
+ delta_head_up = [head_before[i] - head_bridge[i] for i in range(len(res))]
1916
+ delta_head_do = [head_bridge[i] - head_after[i] for i in range(len(res))]
1917
+
1918
+ ax = axes[1]
1919
+
1920
+ ax.plot(delta_head_up, [head_loss_contraction(q, h1, h2) for q,h1,h2 in zip(q_bridge, press_before, h_bridge)], marker='*', label = 'Contraction Loss')
1921
+ ax.plot(delta_head_do, [head_loss_enlargment(q, h1, h2) for q,h1,h2 in zip(q_bridge, h_bridge, press_after)], marker='o', label = 'Enlargment Loss')
1922
+ ax.plot(delta_head_total, [head_loss_contract_enlarge(q,h1,h2,h3) for q,h1,h2,h3 in zip(q_bridge, press_before, h_bridge, press_after)], marker='x', label = 'Total Loss')
1923
+
1924
+ ax.set_xlabel('Computed $\Delta H$ [m]')
1925
+ ax.set_ylabel('Theoretical $\Delta H$ [m]')
1926
+
1927
+ ax.plot([0,1],[0,1], linestyle='-', linewidth=2, color='black')
1928
+ ax.set_aspect('equal')
1929
+ ax.legend()
1930
+
1931
+ fig.set_size_inches(20, 10)
1932
+ fig.tight_layout()
1933
+
1934
+ return fig, axes
1935
+
1936
+
1937
+ def unteaady_with_bedmotion(problems:list[int], save_video:bool = False) -> list[animation.FuncAnimation]:
1938
+ """
1939
+ Unsteady problem with bed motion if overflowing occurs.
1940
+
1941
+ :param problems: list of problems to solve
1942
+
1943
+ Problems :
1944
+ 2 - Rectangular bridge - Length = 20 m (will compute 21, 22 and 23)
1945
+ 6 - Rectangular bridge - Length = 60 m (will compute 61, 62 and 63)
1946
+ 7 - V-shape bridge - Length = 20 m (will compute 71, 72 and 73)
1947
+ 8 - U-shape bridge - Length = 20 m (will compute 81, 82 and 83)
1948
+ 9 - Culvert - Length = 100 m (will compute 91, 92 and 93)
1949
+
1950
+ 21 - Rectangular bridge - Length = 20 m - Unsteady downstream bc
1951
+ 22 - Rectangular bridge - Length = 20 m - Hydrograph
1952
+ 23 - Rectangular bridge - Length = 20 m - Hydrograph 2 steps
1953
+
1954
+ 61 - Rectangular bridge - Length = 60 m - Unsteady downstream bc
1955
+ 62 - Rectangular bridge - Length = 60 m - Hydrograph
1956
+ 63 - Rectangular bridge - Length = 60 m - Hydrograph 2 steps
1957
+
1958
+ 71 - V-shape bridge - Length = 20 m - Unsteady downstream bc
1959
+ 72 - V-shape bridge - Length = 20 m - Hydrograph
1960
+ 73 - V-shape bridge - Length = 20 m - Hydrograph 2 steps
1961
+
1962
+ 81 - U-shape bridge - Length = 20 m - Unsteady downstream bc
1963
+ 82 - U-shape bridge - Length = 20 m - Hydrograph
1964
+ 83 - U-shape bridge - Length = 20 m - Hydrograph 2 steps
1965
+
1966
+ 91 - Culvert - Length = 100 m - Unsteady downstream bc
1967
+ 92 - Culvert - Length = 100 m - Hydrograph
1968
+ 93 - Culvert - Length = 100 m - Hydrograph 2 steps
1969
+
1970
+ """
1971
+ length = 500.
1972
+ dx = 1.
1973
+ CN = 0.4
1974
+ h0 = 4.
1975
+ q0 = 7.
1976
+ n = 0.025
1977
+ slope = 0 #1e-4
1978
+ press_mode = 4
1979
+
1980
+ dom, x, z = domain(length, dx, slope)
1981
+ x = np.array(x)
1982
+
1983
+ hu = uniform_waterdepth(slope, q0, n)
1984
+
1985
+ anims=[]
1986
+
1987
+ if 2 in problems or 21 in problems or 22 in problems or 23 in problems or 24 in problems:
1988
+ # Rectangular bridge - Lenght = 20 m
1989
+
1990
+ CN = 0.4
1991
+
1992
+ if 2 in problems:
1993
+ scenarios = ['unsteady_downstream_bc',
1994
+ 'hydrograph',
1995
+ 'hydrograph_2steps',
1996
+ # 'Gauss',
1997
+ ]
1998
+ elif 21 in problems:
1999
+ scenarios = ['unsteady_downstream_bc']
2000
+ elif 22 in problems:
2001
+ scenarios = ['hydrograph']
2002
+ elif 23 in problems:
2003
+ scenarios = ['hydrograph_2steps']
2004
+ elif 24 in problems:
2005
+ scenarios = ['Gauss']
2006
+
2007
+ for scenario in scenarios:
2008
+
2009
+ # UNSTEADY WITH TOPOGRAPHY ADAPTATION
2010
+ motion_duration = 300.
2011
+ len_bridge = 20
2012
+ z_roof_null = 10.
2013
+ min_overflow = 0.25
2014
+
2015
+ h0 = 1.5
2016
+
2017
+ h_under_bridge = 3.5
2018
+ h_deck_bridge = 0.75
2019
+
2020
+ slice_bridge = slice(int(len(dom)//2-len_bridge//2),int(len(dom)//2+len_bridge//2))
2021
+ slice_bridge_up = slice(int(len(dom)//2+(len_bridge//2-1)),int(len(dom)//2-(len_bridge//2+1)),-1)
2022
+
2023
+ z_bridge = np.ones_like(z) * z_roof_null
2024
+ z_deck = np.ones_like(z) * z_roof_null
2025
+
2026
+ for idx in range(len(dom)//2-len_bridge//2,len(dom)//2+len_bridge//2):
2027
+ z_bridge[idx] = z[idx] + h_under_bridge
2028
+ z_deck[idx] = z_bridge[idx] + h_deck_bridge
2029
+
2030
+ poly_bridge_x = np.concatenate((x[slice_bridge], x[slice_bridge_up]))
2031
+ poly_bridge_y = np.concatenate((z_bridge[slice_bridge], z_deck[slice_bridge_up]))
2032
+
2033
+ z_ini = z.copy()
2034
+
2035
+ res = problem_bridge_unsteady_topo(dom, z,
2036
+ z_bridge, z_deck, z_roof_null,
2037
+ h0, q0, dx, CN, n,
2038
+ press_mode= press_mode,
2039
+ motion_duration= motion_duration,
2040
+ scenario_bc= scenario,
2041
+ min_overflow= min_overflow)
2042
+
2043
+ ani = animate_bridge_unsteady_topo(dom, poly_bridge_x, poly_bridge_y, res, x, z_ini, hu, z_roof_null, len_bridge, motion_duration=motion_duration)
2044
+
2045
+ if save_video:
2046
+ update_func = lambda _i, _n: progress_bar.update(1)
2047
+ with tqdm(total=len(res), desc='Saving video') as progress_bar:
2048
+ ani.save(f'bridge_L20_{scenario}.mp4',
2049
+ writer='ffmpeg', fps=5,
2050
+ progress_callback=update_func)
2051
+
2052
+ anims.append(ani)
2053
+
2054
+ if 6 in problems or 61 in problems or 62 in problems or 63 in problems or 64 in problems:
2055
+ # Rectangular bridge - Lenght = 60 m
2056
+
2057
+ CN = 0.2
2058
+
2059
+ if 6 in problems:
2060
+ scenarios = ['unsteady_downstream_bc',
2061
+ 'hydrograph',
2062
+ 'hydrograph_2steps',
2063
+ # 'Gauss',
2064
+ ]
2065
+
2066
+ elif 61 in problems:
2067
+ scenarios = ['unsteady_downstream_bc']
2068
+ elif 62 in problems:
2069
+ scenarios = ['hydrograph']
2070
+ elif 63 in problems:
2071
+ scenarios = ['hydrograph_2steps']
2072
+ elif 64 in problems:
2073
+ scenarios = ['Gauss']
2074
+
2075
+ for scenario in scenarios:
2076
+
2077
+ # UNSTEADY WITH TOPOGRAPHY ADAPTATION
2078
+ motion_duration = 300.
2079
+ z_roof_null = 10.
2080
+ min_overflow = 0.25
2081
+
2082
+ h_under_bridge = 3.5
2083
+ h_deck_bridge = 0.75
2084
+ len_bridge = 60
2085
+ q0 = 6.
2086
+ h0 = 1.5
2087
+
2088
+ slice_bridge = slice(int(len(dom)//2-len_bridge//2),int(len(dom)//2+len_bridge//2))
2089
+ slice_bridge_up = slice(int(len(dom)//2+(len_bridge//2-1)),int(len(dom)//2-(len_bridge//2+1)),-1)
2090
+
2091
+ z_bridge = np.ones_like(z) * z_roof_null
2092
+ z_deck = np.ones_like(z) * z_roof_null
2093
+
2094
+ for idx in range(len(dom)//2-len_bridge//2,len(dom)//2+len_bridge//2):
2095
+ z_bridge[idx] = z[idx] + h_under_bridge
2096
+ z_deck[idx] = z_bridge[idx] + h_deck_bridge
2097
+
2098
+ poly_bridge_x = np.concatenate((x[slice_bridge], x[slice_bridge_up]))
2099
+ poly_bridge_y = np.concatenate((z_bridge[slice_bridge], z_deck[slice_bridge_up]))
2100
+
2101
+ z_ini = z.copy()
2102
+
2103
+ res = problem_bridge_unsteady_topo(dom, z, z_bridge,
2104
+ z_deck, z_roof_null,
2105
+ h0, q0, dx, CN, n,
2106
+ press_mode=press_mode,
2107
+ motion_duration=motion_duration,
2108
+ scenario_bc=scenario,
2109
+ min_overflow=min_overflow)
2110
+
2111
+ ani = animate_bridge_unsteady_topo(dom, res, x, z_ini, hu, z_roof_null, len_bridge, motion_duration=motion_duration)
2112
+
2113
+ if save_video:
2114
+ update_func = lambda _i, _n: progress_bar.update(1)
2115
+ with tqdm(total=len(res), desc='Saving video') as progress_bar:
2116
+ ani.save(f'bridge_L60{scenario}.mp4',
2117
+ writer='ffmpeg', fps=5,
2118
+ progress_callback=update_func)
2119
+
2120
+ anims.append(ani)
2121
+
2122
+ if 9 in problems or 91 in problems or 92 in problems or 93 in problems or 94 in problems:
2123
+ # Culvert
2124
+
2125
+ CN = 0.4
2126
+
2127
+ if 9 in problems:
2128
+ scenarios = ['unsteady_downstream_bc_culvert',
2129
+ 'hydrograph_culvert',
2130
+ 'hydrograph_2steps_culvert',
2131
+ # 'Gauss',
2132
+ ]
2133
+
2134
+ elif 91 in problems:
2135
+ scenarios = ['unsteady_downstream_bc_culvert']
2136
+ elif 92 in problems:
2137
+ scenarios = ['hydrograph_culvert']
2138
+ elif 93 in problems:
2139
+ scenarios = ['hydrograph_2steps_culvert']
2140
+
2141
+ for scenario in scenarios:
2142
+
2143
+ # UNSTEADY WITH TOPOGRAPHY ADAPTATION
2144
+ motion_duration = 300.
2145
+ z_roof_null = 10.
2146
+ min_overflow = 0.25
2147
+
2148
+ h_under_bridge = 1.5
2149
+ h_deck_bridge = 4.0
2150
+ len_bridge = 100
2151
+ h0 = 0.8
2152
+ q0 = 1.
2153
+
2154
+ slice_bridge = slice(int(len(dom)//2-len_bridge//2),int(len(dom)//2+len_bridge//2))
2155
+ slice_bridge_up = slice(int(len(dom)//2+(len_bridge//2-1)),int(len(dom)//2-(len_bridge//2+1)),-1)
2156
+
2157
+ z_bridge = np.ones_like(z) * z_roof_null
2158
+ z_deck = np.ones_like(z) * z_roof_null
2159
+
2160
+ for idx in range(len(dom)//2-len_bridge//2,len(dom)//2+len_bridge//2):
2161
+ z_bridge[idx] = z[idx] + h_under_bridge
2162
+ z_deck[idx] = z_bridge[idx] + h_deck_bridge
2163
+
2164
+ poly_bridge_x = np.concatenate((x[slice_bridge], x[slice_bridge_up]))
2165
+ poly_bridge_y = np.concatenate((z_bridge[slice_bridge], z_deck[slice_bridge_up]))
2166
+
2167
+ z_ini = z.copy()
2168
+
2169
+ res = problem_bridge_unsteady_topo(dom, z, z_bridge,
2170
+ z_deck, z_roof_null,
2171
+ h0, q0, dx, CN, n,
2172
+ press_mode=press_mode,
2173
+ motion_duration=motion_duration,
2174
+ scenario_bc=scenario,
2175
+ min_overflow=min_overflow)
2176
+
2177
+ ani = animate_bridge_unsteady_topo(dom, poly_bridge_x, poly_bridge_y, res, x, z_ini, hu, z_roof_null, len_bridge, title='Culvert', motion_duration=motion_duration)
2178
+
2179
+ if save_video:
2180
+ update_func = lambda _i, _n: progress_bar.update(1)
2181
+ with tqdm(total=len(res), desc='Saving video') as progress_bar:
2182
+ ani.save(f'culvert_{scenario}.mp4',
2183
+ writer='ffmpeg', fps=5,
2184
+ progress_callback=update_func)
2185
+
2186
+ anims.append(ani)
2187
+
2188
+ if 7 in problems or 71 in problems or 72 in problems or 73 in problems or 74 in problems:
2189
+ # V-shape Bridge
2190
+
2191
+ CN = 0.4
2192
+
2193
+ if 7 in problems:
2194
+ scenarios = ['unsteady_downstream_bc',
2195
+ 'hydrograph',
2196
+ 'hydrograph_2steps',
2197
+ # 'Gauss',
2198
+ ]
2199
+ elif 71 in problems:
2200
+ scenarios = ['unsteady_downstream_bc']
2201
+ elif 72 in problems:
2202
+ scenarios = ['hydrograph']
2203
+ elif 73 in problems:
2204
+ scenarios = ['hydrograph_2steps']
2205
+ elif 74 in problems:
2206
+ scenarios = ['Gauss']
2207
+
2208
+ for scenario in scenarios:
2209
+
2210
+ # UNSTEADY WITH TOPOGRAPHY ADAPTATION
2211
+ motion_duration = 300.
2212
+ z_roof_null = 10.
2213
+ min_overflow = 0.25
2214
+
2215
+ h_under_bridge = 3.5
2216
+ h_deck_bridge = 0.75
2217
+ len_bridge = 20
2218
+
2219
+ h0 = 1.5
2220
+
2221
+ z_bridge = np.ones_like(z) * z_roof_null
2222
+ z_deck = np.ones_like(z) * z_roof_null
2223
+
2224
+ slice_bridge = slice(len(dom)//2-len_bridge//2,len(dom)//2+len_bridge//2)
2225
+ slice_bridge_up = slice(len(dom)//2+(len_bridge//2-1),len(dom)//2-(len_bridge//2+1),-1)
2226
+
2227
+ for idx in range(len(dom)//2-len_bridge//2,len(dom)//2+len_bridge//2):
2228
+ decal = abs(idx - (len(dom)//2))
2229
+ z_bridge[idx] = z[idx] + h_under_bridge + 0.05 * decal
2230
+ z_deck[idx] = h_under_bridge + h_deck_bridge
2231
+
2232
+ poly_bridge_x = np.concatenate((x[slice_bridge], x[slice_bridge_up]))
2233
+ poly_bridge_y = np.concatenate((z_bridge[slice_bridge], z_deck[slice_bridge_up]))
2234
+
2235
+ z_ini = z.copy()
2236
+
2237
+ res = problem_bridge_unsteady_topo(dom, z,
2238
+ z_bridge, z_deck, z_roof_null,
2239
+ h0, q0, dx, CN, n,
2240
+ press_mode=press_mode,
2241
+ motion_duration=motion_duration,
2242
+ scenario_bc=scenario,
2243
+ min_overflow= min_overflow)
2244
+
2245
+ ani = animate_bridge_unsteady_topo(dom, poly_bridge_x, poly_bridge_y, res, x, z_ini, hu, z_roof_null, len_bridge, motion_duration=motion_duration)
2246
+
2247
+ if save_video:
2248
+ update_func = lambda _i, _n: progress_bar.update(1)
2249
+ with tqdm(total=len(res), desc='Saving video') as progress_bar:
2250
+ ani.save(f'bridge_Vshape{scenario}.mp4',
2251
+ writer='ffmpeg', fps=5,
2252
+ progress_callback=update_func)
2253
+
2254
+ anims.append(ani)
2255
+
2256
+ if 8 in problems or 81 in problems or 82 in problems or 83 in problems or 84 in problems:
2257
+ # U-shape Bridge
2258
+
2259
+ CN = 0.4
2260
+
2261
+ if 8 in problems:
2262
+ scenarios = ['unsteady_downstream_bc',
2263
+ 'hydrograph',
2264
+ 'hydrograph_2steps',
2265
+ # 'Gauss',
2266
+ ]
2267
+ elif 81 in problems:
2268
+ scenarios = ['unsteady_downstream_bc']
2269
+ elif 82 in problems:
2270
+ scenarios = ['hydrograph']
2271
+ elif 83 in problems:
2272
+ scenarios = ['hydrograph_2steps']
2273
+ elif 84 in problems:
2274
+ scenarios = ['Gauss']
2275
+
2276
+ for scenario in scenarios:
2277
+
2278
+ # UNSTEADY WITH TOPOGRAPHY ADAPTATION
2279
+ motion_duration = 300.
2280
+ z_roof_null = 10.
2281
+ min_overflow = 0.25
2282
+
2283
+ h_under_bridge = 3.5
2284
+ h_deck_bridge = 0.4
2285
+ len_bridge = 20
2286
+
2287
+ h0 = 1.5
2288
+
2289
+ z_bridge = np.ones_like(z) * z_roof_null
2290
+ z_deck = np.ones_like(z) * z_roof_null
2291
+
2292
+ slice_bridge = slice(len(dom)//2-len_bridge//2,len(dom)//2+len_bridge//2)
2293
+ slice_bridge_up = slice(len(dom)//2+(len_bridge//2-1),len(dom)//2-(len_bridge//2+1),-1)
2294
+
2295
+ z_bridge[slice_bridge] = z[slice_bridge] + h_under_bridge
2296
+ z_deck[slice_bridge] = z_bridge[slice_bridge] + h_deck_bridge
2297
+
2298
+ idx_up = len(dom)//2-len_bridge//2
2299
+ idx_down = len(dom)//2+len_bridge//2-1
2300
+
2301
+ z_bridge[idx_up] -= .4
2302
+ z_bridge[idx_up+1] -= .4
2303
+
2304
+ z_bridge[idx_down] -= .4
2305
+ z_bridge[idx_down-1] -= .4
2306
+
2307
+ poly_bridge_x = np.concatenate((x[slice_bridge], x[slice_bridge_up]))
2308
+ poly_bridge_y = np.concatenate((z_bridge[slice_bridge], z_deck[slice_bridge_up]))
2309
+
2310
+ z_ini = z.copy()
2311
+
2312
+ res = problem_bridge_unsteady_topo(dom, z,
2313
+ z_bridge, z_deck, z_roof_null,
2314
+ h0, q0, dx, CN, n,
2315
+ press_mode=press_mode,
2316
+ motion_duration=motion_duration,
2317
+ scenario_bc=scenario,
2318
+ min_overflow= min_overflow)
2319
+
2320
+ ani = animate_bridge_unsteady_topo(dom, poly_bridge_x, poly_bridge_y, res, x, z_ini, hu, z_roof_null, len_bridge, motion_duration=motion_duration)
2321
+
2322
+ if save_video:
2323
+ update_func = lambda _i, _n: progress_bar.update(1)
2324
+ with tqdm(total=len(res), desc='Saving video') as progress_bar:
2325
+ ani.save(f'bridge_Ushape{scenario}.mp4',
2326
+ writer='ffmpeg', fps=5,
2327
+ progress_callback=update_func)
2328
+
2329
+ anims.append(ani)
2330
+
2331
+ return anims
2332
+
2333
+
2334
+ def hedge():
2335
+ """ Compute Water line problems with hedge
2336
+
2337
+ Length = 500 m
2338
+ dx = 1 m
2339
+ CN = 0.4
2340
+ h0 = 4 m
2341
+ q0 = 7 m^2/s
2342
+ n = 0.025
2343
+ slope = 1e-4
2344
+ """
2345
+
2346
+ length = 500.
2347
+ dx = 1.
2348
+ CN = 0.4
2349
+ h0 = 4.
2350
+ q0 = 7.
2351
+ n = 0.025
2352
+ slope = 1e-4
2353
+
2354
+ dom, x, z = domain(length, dx, slope)
2355
+ x = np.array(x)
2356
+
2357
+ hu = uniform_waterdepth(slope, q0, n)
2358
+
2359
+ # bridge roof level is 10 m everywhere except in the middle of the domain
2360
+ # where the bridge is located.
2361
+ # The bridge is a flat bridge with a height of 2 m.
2362
+ z_bridge = np.ones_like(z) * 10.
2363
+ z_bridge[len(dom)//2-5:len(dom)//2+5] = z[len(dom)//2-5:len(dom)//2+5] + 2.
2364
+
2365
+ fig, axes = plt.subplots(3,1)
2366
+
2367
+ h1, q1 = problem(dom, z, h0, q0, dx, CN, n)
2368
+ h2, q2 = problem_hedge(dom, z, h0, q0, dx, CN, n)
2369
+
2370
+ plot_hedge(axes[0], x, h1, h2, q1, q2, z, hu)
2371
+
2372
+ h1, q1 = problem(dom, z, h0+1., q0, dx, CN, n)
2373
+ h2, q2 = problem_hedge(dom, z, h0+1., q0, dx, CN, n)
2374
+
2375
+ plot_hedge(axes[1], x, h1, h2, q1, q2, z, hu)
2376
+
2377
+ h1, q1 = problem(dom, z, h0+2., q0, dx, CN, n)
2378
+ h2, q2 = problem_hedge(dom, z, h0+2., q0, dx, CN, n)
2379
+
2380
+ plot_hedge(axes[2], x, h1, h2, q1, q2, z, hu)
2381
+
2382
+ fig.set_size_inches(20, 10)
2383
+ fig.tight_layout()
2384
+
2385
+ return fig, axes
2386
+
2387
+ if __name__ == '__main__':
2388
+
2389
+ # fig, axes = steady()
2390
+ fig, axes = water_line()
2391
+ # fig, axes = water_lines()
2392
+ # fig, axes = unsteady_without_bedmotion()
2393
+ # fig, axes = unteaady_with_bedmotion([2, 6, 7, 8, 9])
2394
+ # fig, axes = hedge()
2395
+ fig, axes = water_line_noloss_noslope()
2396
+
2397
+ plt.show()
2398
+
2399
+ pass