tilupy 2.0.0__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.
- tilupy/__init__.py +23 -0
- tilupy/analytic_sol.py +2403 -0
- tilupy/benchmark.py +1563 -0
- tilupy/calibration.py +134 -0
- tilupy/cmd.py +177 -0
- tilupy/compare.py +195 -0
- tilupy/download_data.py +68 -0
- tilupy/initdata.py +207 -0
- tilupy/initsimus.py +50 -0
- tilupy/make_mass.py +111 -0
- tilupy/make_topo.py +468 -0
- tilupy/models/__init__.py +0 -0
- tilupy/models/lave2D/__init__.py +0 -0
- tilupy/models/lave2D/initsimus.py +665 -0
- tilupy/models/lave2D/read.py +264 -0
- tilupy/models/ravaflow/__init__.py +0 -0
- tilupy/models/ravaflow/initsimus.py +192 -0
- tilupy/models/ravaflow/read.py +273 -0
- tilupy/models/saval2D/__init__.py +0 -0
- tilupy/models/saval2D/read.py +298 -0
- tilupy/models/shaltop/__init__.py +0 -0
- tilupy/models/shaltop/initsimus.py +375 -0
- tilupy/models/shaltop/read.py +613 -0
- tilupy/notations.py +866 -0
- tilupy/plot.py +234 -0
- tilupy/raster.py +199 -0
- tilupy/read.py +2588 -0
- tilupy/utils.py +656 -0
- tilupy-2.0.0.dist-info/METADATA +876 -0
- tilupy-2.0.0.dist-info/RECORD +34 -0
- tilupy-2.0.0.dist-info/WHEEL +5 -0
- tilupy-2.0.0.dist-info/entry_points.txt +3 -0
- tilupy-2.0.0.dist-info/licenses/LICENSE +516 -0
- tilupy-2.0.0.dist-info/top_level.txt +1 -0
tilupy/analytic_sol.py
ADDED
|
@@ -0,0 +1,2403 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.cm as cm
|
|
6
|
+
import matplotlib
|
|
7
|
+
|
|
8
|
+
from scipy.optimize import fsolve
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Depth_result(ABC):
|
|
14
|
+
"""Abstract base class representing simulation results for flow depth and velocity.
|
|
15
|
+
|
|
16
|
+
This class defines a common interface for analytical solution that compute flow height
|
|
17
|
+
h(x,t) and flow velocity u(x,t).
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
theta : float, optional
|
|
22
|
+
Angle of the surface, in radian, by default 0.
|
|
23
|
+
|
|
24
|
+
Attributes
|
|
25
|
+
----------
|
|
26
|
+
_g = 9.81 : float
|
|
27
|
+
Gravitational constant.
|
|
28
|
+
_theta : float
|
|
29
|
+
Angle of the surface, in radian.
|
|
30
|
+
_x : float or np.ndarray
|
|
31
|
+
Spatial coordinates.
|
|
32
|
+
_t : float or np.ndarray
|
|
33
|
+
Time instant.
|
|
34
|
+
_h : np.ndarray
|
|
35
|
+
Flow height depending on space at a moment.
|
|
36
|
+
_u : np.ndarray
|
|
37
|
+
Flow velocity depending on space at a moment.
|
|
38
|
+
"""
|
|
39
|
+
def __init__(self,
|
|
40
|
+
theta: float=None
|
|
41
|
+
):
|
|
42
|
+
self._g = 9.81
|
|
43
|
+
self._theta = theta
|
|
44
|
+
self._x = None
|
|
45
|
+
self._t = None
|
|
46
|
+
self._h = None
|
|
47
|
+
self._u = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def compute_h(self,
|
|
52
|
+
x: float | np.ndarray,
|
|
53
|
+
t: float | np.ndarray
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Virtual function that compute the flow height :attr:`_h` at given space and time.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
x : float or np.ndarray
|
|
60
|
+
Spatial coordinates.
|
|
61
|
+
t : float or np.ndarray
|
|
62
|
+
Time instant.
|
|
63
|
+
"""
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def compute_u(self,
|
|
69
|
+
x: float | np.ndarray,
|
|
70
|
+
t: float | np.ndarray
|
|
71
|
+
) -> None:
|
|
72
|
+
"""Virtual function that compute the flow velocity :attr:`_u` at given space and time.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
x : float or np.ndarray
|
|
77
|
+
Spatial coordinates.
|
|
78
|
+
t : float or np.ndarray
|
|
79
|
+
Time instant.
|
|
80
|
+
"""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def h(self):
|
|
86
|
+
"""Accessor of h(x,t) solution.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
numpy.ndarray
|
|
91
|
+
Attribute :attr:`_h`. If None, no solution computed.
|
|
92
|
+
"""
|
|
93
|
+
return self._h
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def u(self):
|
|
98
|
+
"""Accessor of u(x,t) solution.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
numpy.ndarray
|
|
103
|
+
Attribute :attr:`_u`. If None, no solution computed.
|
|
104
|
+
"""
|
|
105
|
+
return self._u
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def x(self):
|
|
110
|
+
"""Accessor of the spatial distribution of the computed solution.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
numpy.ndarray
|
|
115
|
+
Attribute :attr:`_x`. If None, no solution computed.
|
|
116
|
+
"""
|
|
117
|
+
return self._x
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def t(self):
|
|
122
|
+
"""Accessor of the time instant of the computed solution.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
float or numpy.ndarray
|
|
127
|
+
Attribut :attr:`_t`. If None, no solution computed.
|
|
128
|
+
"""
|
|
129
|
+
return self._t
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def plot(self,
|
|
133
|
+
show_h: bool=False,
|
|
134
|
+
show_u: bool=False,
|
|
135
|
+
show_surface: bool=False,
|
|
136
|
+
linestyles: list[str]=None,
|
|
137
|
+
x_unit:str = "m",
|
|
138
|
+
h_unit:str = "m",
|
|
139
|
+
u_unit:str = "m/s",
|
|
140
|
+
show_plot:bool = True,
|
|
141
|
+
figsize:tuple = None,
|
|
142
|
+
) -> matplotlib.axes._axes.Axes:
|
|
143
|
+
"""Plot the simulation results.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
show_h : bool, optional
|
|
148
|
+
If True, plot the flow height (:attr:`_h`) curve.
|
|
149
|
+
show_u : bool, optional
|
|
150
|
+
If True, plot the flow velocity (:attr:`_u`) curve.
|
|
151
|
+
show_surface : bool, optional
|
|
152
|
+
If True, plot the slop of the surface.
|
|
153
|
+
linestyles : list[str], optional
|
|
154
|
+
List of linestyle to applie to the graph, must have the same since as the numbre of curve to plot or it
|
|
155
|
+
will not be taken into account (-1), by default None. If None, copper colormap will be applied.
|
|
156
|
+
x_unit: str
|
|
157
|
+
Space unit.
|
|
158
|
+
h_unit: str
|
|
159
|
+
Height unit.
|
|
160
|
+
u_unit: str
|
|
161
|
+
Velocity unit.
|
|
162
|
+
show_plot: bool, optional
|
|
163
|
+
If True, show the resulting plot. By default True.
|
|
164
|
+
figsize: tuple, optional
|
|
165
|
+
Size of the wanted plot, by default None.
|
|
166
|
+
|
|
167
|
+
Return
|
|
168
|
+
------
|
|
169
|
+
matplotlib.axes._axes.Axes
|
|
170
|
+
Resulting plot.
|
|
171
|
+
|
|
172
|
+
Raises
|
|
173
|
+
------
|
|
174
|
+
ValueError
|
|
175
|
+
If no solution computed (:attr:`_h` and :attr:`_u` are None).
|
|
176
|
+
"""
|
|
177
|
+
z_surf = [0, 0]
|
|
178
|
+
|
|
179
|
+
if self._h is None and self._u is None:
|
|
180
|
+
raise ValueError("No solution computed.")
|
|
181
|
+
|
|
182
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
183
|
+
|
|
184
|
+
if show_h and self._h is not None:
|
|
185
|
+
if self._h.ndim == 1:
|
|
186
|
+
ax.plot(self._x, self._h, color='black', linewidth=1)
|
|
187
|
+
else:
|
|
188
|
+
if linestyles is None or len(linestyles)!=(len(self._t)):
|
|
189
|
+
norm = plt.Normalize(vmin=min(self._t), vmax=max(self._t))
|
|
190
|
+
cmap = plt.cm.copper
|
|
191
|
+
|
|
192
|
+
for h_idx, h_val in enumerate(self._h):
|
|
193
|
+
t_val = self._t[h_idx]
|
|
194
|
+
if linestyles is None or len(linestyles)!=(len(self._t)):
|
|
195
|
+
color = cmap(norm(t_val)) if t_val != 0 else "red"
|
|
196
|
+
l_style = "-" if t_val != 0 else (0, (1, 4))
|
|
197
|
+
else:
|
|
198
|
+
color = "black" if t_val != 0 else "red"
|
|
199
|
+
l_style = linestyles[h_idx] if t_val != 0 else (0, (1, 4))
|
|
200
|
+
ax.plot(self._x, h_val, color=color, linestyle=l_style, label=f"t={t_val}s")
|
|
201
|
+
|
|
202
|
+
if show_surface:
|
|
203
|
+
ax.plot([self._x[0], self._x[-1]], z_surf, color='black', linewidth=2)
|
|
204
|
+
|
|
205
|
+
ax.grid(which='major')
|
|
206
|
+
ax.grid(which='minor', alpha=0.5)
|
|
207
|
+
ax.set_xlim(left=min(self._x), right=max(self._x))
|
|
208
|
+
|
|
209
|
+
ax.set_title(f"Flow height for t={self._t}")
|
|
210
|
+
ax.set_xlabel(f"x [{x_unit}]")
|
|
211
|
+
ax.set_ylabel(f"h [{h_unit}]")
|
|
212
|
+
ax.legend(loc='upper right')
|
|
213
|
+
if show_plot:
|
|
214
|
+
plt.show()
|
|
215
|
+
|
|
216
|
+
return ax
|
|
217
|
+
|
|
218
|
+
if show_u and self._u is not None:
|
|
219
|
+
if self._u.ndim == 1:
|
|
220
|
+
ax.plot(self._x, self._u, color='black', linewidth=1)
|
|
221
|
+
|
|
222
|
+
else:
|
|
223
|
+
if linestyles is None or len(linestyles)!=(len(self._t)):
|
|
224
|
+
norm = plt.Normalize(vmin=min(self._t), vmax=max(self._t))
|
|
225
|
+
cmap = plt.cm.copper
|
|
226
|
+
|
|
227
|
+
for u_idx, u_val in enumerate(self._u):
|
|
228
|
+
t_val = self._t[u_idx]
|
|
229
|
+
if t_val == 0:
|
|
230
|
+
continue
|
|
231
|
+
if linestyles is None or len(linestyles)!=(len(self._t)):
|
|
232
|
+
color = cmap(norm(t_val))
|
|
233
|
+
l_style = "-"
|
|
234
|
+
else:
|
|
235
|
+
color = "black"
|
|
236
|
+
l_style = linestyles[u_idx]
|
|
237
|
+
ax.plot(self._x, u_val, color=color, linestyle=l_style, label=f"t={t_val}s")
|
|
238
|
+
|
|
239
|
+
ax.grid(which='major')
|
|
240
|
+
ax.grid(which='minor', alpha=0.5)
|
|
241
|
+
ax.set_xlim(left=min(self._x), right=max(self._x))
|
|
242
|
+
|
|
243
|
+
ax.set_title(f"Flow velocity for t={self._t}")
|
|
244
|
+
ax.set_xlabel(f"x [{x_unit}]")
|
|
245
|
+
ax.set_ylabel(f"u [{u_unit}]")
|
|
246
|
+
ax.legend(loc='best')
|
|
247
|
+
if show_plot:
|
|
248
|
+
plt.show()
|
|
249
|
+
|
|
250
|
+
return ax
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class Ritter_dry(Depth_result):
|
|
254
|
+
r"""Dam-break solution on a dry domain using shallow water theory.
|
|
255
|
+
|
|
256
|
+
This class implements the 1D analytical Ritter's solution of an ideal dam break on a dry domain.
|
|
257
|
+
The dam break is instantaneous, over an horizontal and flat surface with no friction.
|
|
258
|
+
It computes the flow height (took verticaly) and velocity over space and time, based on the equation implemanted
|
|
259
|
+
in SWASHES, based on Ritter's equation.
|
|
260
|
+
|
|
261
|
+
Ritter, A., 1892, Die Fortpflanzung der Wasserwellen, Zeitschrift des Vereines Deutscher Ingenieure, vol. 36(33), p. 947-954.
|
|
262
|
+
|
|
263
|
+
Parameters
|
|
264
|
+
----------
|
|
265
|
+
x_0 : float, optional
|
|
266
|
+
Initial dam location (position along x-axis), by default 0.
|
|
267
|
+
h_0 : float
|
|
268
|
+
Initial water depth to the left of the dam.
|
|
269
|
+
|
|
270
|
+
Attributes
|
|
271
|
+
----------
|
|
272
|
+
_x0 : float
|
|
273
|
+
Initial dam location (position along x-axis).
|
|
274
|
+
_h0 : float
|
|
275
|
+
Initial water depth to the left of the dam.
|
|
276
|
+
"""
|
|
277
|
+
def __init__(self,
|
|
278
|
+
h_0: float,
|
|
279
|
+
x_0: float=0,
|
|
280
|
+
):
|
|
281
|
+
super().__init__()
|
|
282
|
+
self._x0 = x_0
|
|
283
|
+
self._h0 = h_0
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def xa(self, t: float) -> float:
|
|
287
|
+
r"""
|
|
288
|
+
Position of the rarefaction wave front (left-most edge) :
|
|
289
|
+
|
|
290
|
+
.. math::
|
|
291
|
+
x_A(t) = x_0 - t \sqrt{g h_0}
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
t : float
|
|
296
|
+
Time instant.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
float
|
|
301
|
+
Position of the front edge of the rarefaction wave.
|
|
302
|
+
"""
|
|
303
|
+
return self._x0 - (t * np.sqrt(self._g*self._h0))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def xb(self, t: float) -> float:
|
|
307
|
+
r"""
|
|
308
|
+
Position of the contact discontinuity:
|
|
309
|
+
|
|
310
|
+
.. math::
|
|
311
|
+
x_B(t) = x_0 + 2 t \sqrt{g h_0}
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
t : float
|
|
316
|
+
Time instant.
|
|
317
|
+
|
|
318
|
+
Returns
|
|
319
|
+
-------
|
|
320
|
+
float
|
|
321
|
+
Position of the contact wave (end of rarefaction).
|
|
322
|
+
"""
|
|
323
|
+
return self._x0 + (2 * t * np.sqrt(self._g*self._h0))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def compute_h(self,
|
|
327
|
+
x: float | np.ndarray,
|
|
328
|
+
T: float | np.ndarray
|
|
329
|
+
) -> None:
|
|
330
|
+
r"""Compute the flow height h(x, t) at given time and positions.
|
|
331
|
+
|
|
332
|
+
.. math::
|
|
333
|
+
h(x, t) =
|
|
334
|
+
\begin{cases}
|
|
335
|
+
h_0 & \text{if } x \leq x_A(t), \\\\
|
|
336
|
+
\frac{4}{9g} \left( \sqrt{g h_0} - \frac{x - x_0}{2t} \right)^2 & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
337
|
+
0 & \text{if } x_B(t) < x,
|
|
338
|
+
\end{cases}
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
x : float or np.ndarray
|
|
343
|
+
Spatial positions.
|
|
344
|
+
T : float or nd.ndarray
|
|
345
|
+
Time instant.
|
|
346
|
+
|
|
347
|
+
Notes
|
|
348
|
+
-----
|
|
349
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._h`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
350
|
+
"""
|
|
351
|
+
if isinstance(x, float):
|
|
352
|
+
x = [x]
|
|
353
|
+
if isinstance(T, float):
|
|
354
|
+
T = [T]
|
|
355
|
+
|
|
356
|
+
self._x = x
|
|
357
|
+
self._t = T
|
|
358
|
+
|
|
359
|
+
h = []
|
|
360
|
+
for t in T:
|
|
361
|
+
sub_h = []
|
|
362
|
+
for i in x:
|
|
363
|
+
if i <= self.xa(t):
|
|
364
|
+
sub_h.append(self._h0)
|
|
365
|
+
elif self.xa(t) < i <= self.xb(t):
|
|
366
|
+
sub_h.append((4/(9*self._g)) *
|
|
367
|
+
(np.sqrt(self._g*self._h0)-((i-self._x0)/(2*t)))**2)
|
|
368
|
+
else:
|
|
369
|
+
sub_h.append(0)
|
|
370
|
+
h.append(sub_h)
|
|
371
|
+
self._h = np.array(h)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def compute_u(self,
|
|
375
|
+
x: float | np.ndarray,
|
|
376
|
+
T: float | np.ndarray
|
|
377
|
+
) -> None:
|
|
378
|
+
r"""Compute the flow velocity u(x, t) at given time and positions.
|
|
379
|
+
|
|
380
|
+
.. math::
|
|
381
|
+
u(x,t) =
|
|
382
|
+
\begin{cases}
|
|
383
|
+
0 & \text{if } x \leq x_A(t), \\\\
|
|
384
|
+
\frac{2}{3} \left( \frac{x - x_0}{t} + \sqrt{g h_0} \right) & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
385
|
+
0 & \text{if } x_B(t) < x,
|
|
386
|
+
\end{cases}
|
|
387
|
+
|
|
388
|
+
Parameters
|
|
389
|
+
----------
|
|
390
|
+
x : float or np.ndarray
|
|
391
|
+
Spatial positions.
|
|
392
|
+
T : float or np.ndarray
|
|
393
|
+
Time instant.
|
|
394
|
+
|
|
395
|
+
Notes
|
|
396
|
+
-----
|
|
397
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._u`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
398
|
+
"""
|
|
399
|
+
if isinstance(x, float):
|
|
400
|
+
x = [x]
|
|
401
|
+
if isinstance(T, float):
|
|
402
|
+
T = [T]
|
|
403
|
+
|
|
404
|
+
self._x = x
|
|
405
|
+
self._t = T
|
|
406
|
+
|
|
407
|
+
u = []
|
|
408
|
+
for t in T:
|
|
409
|
+
sub_u = []
|
|
410
|
+
for i in x:
|
|
411
|
+
if i <= self.xa(t):
|
|
412
|
+
sub_u.append(np.nan)
|
|
413
|
+
elif i > self.xa(t) and i <= self.xb(t):
|
|
414
|
+
sub_u.append((2/3)*(((i-self._x0)/t) + np.sqrt(self._g*self._h0)))
|
|
415
|
+
else:
|
|
416
|
+
sub_u.append(np.nan)
|
|
417
|
+
u.append(sub_u)
|
|
418
|
+
self._u = np.array(u)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class Stoker_SWASHES_wet(Depth_result):
|
|
422
|
+
r"""Dam-break solution on a wet domain using shallow water theory.
|
|
423
|
+
|
|
424
|
+
This class implements the 1D analytical Stoker's solution of an ideal dam break on a wet domain.
|
|
425
|
+
The dam break is instantaneous, over an horizontal and flat surface with no friction.
|
|
426
|
+
It computes the flow height (took verticaly) and velocity over space and time, based on the equation implemanted
|
|
427
|
+
in SWASHES, based on Stoker's equation.
|
|
428
|
+
|
|
429
|
+
Delestre, O., Lucas, C., Ksinant, P.-A., Darboux, F., Laguerre, C., Vo, T.-N.-T., James, F. & Cordier, S., 2013, SWASHES: a compilation of shallow water
|
|
430
|
+
analytic solutions for hydraulic and environmental studies, International Journal for Numerical Methods in Fluids, v. 72(3), p. 269-300, doi:10.1002/fld.3741.
|
|
431
|
+
|
|
432
|
+
Stoker, J.J., 1957, Water Waves: The Mathematical Theory with Applications, Pure and Applied Mathematics, vol. 4, Interscience Publishers, New York, USA.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
x_0 : float
|
|
437
|
+
Initial dam location (position along x-axis).
|
|
438
|
+
h_0 : float
|
|
439
|
+
Water depth to the left of the dam.
|
|
440
|
+
h_r : float
|
|
441
|
+
Water depth to the right of the dam.
|
|
442
|
+
h_m : float, optional
|
|
443
|
+
Intermediate height used to compute the critical speed cm. If not provided,
|
|
444
|
+
it will be computed numerically via the 'compute_cm()' method.
|
|
445
|
+
|
|
446
|
+
Attributes
|
|
447
|
+
----------
|
|
448
|
+
_x0 : float
|
|
449
|
+
Initial dam location (position along x-axis).
|
|
450
|
+
_h0 : float
|
|
451
|
+
Water depth to the left of the dam.
|
|
452
|
+
_hr : float
|
|
453
|
+
Water depth to the right of the dam.
|
|
454
|
+
_cm : float
|
|
455
|
+
Critical velocity.
|
|
456
|
+
"""
|
|
457
|
+
def __init__(self,
|
|
458
|
+
x_0: float,
|
|
459
|
+
h_0: float,
|
|
460
|
+
h_r: float,
|
|
461
|
+
h_m: float=None
|
|
462
|
+
):
|
|
463
|
+
super().__init__()
|
|
464
|
+
self._x0 = x_0
|
|
465
|
+
self._h0 = h_0
|
|
466
|
+
self._hr = h_r
|
|
467
|
+
self._cm = None
|
|
468
|
+
self.compute_cm()
|
|
469
|
+
|
|
470
|
+
if h_m is not None:
|
|
471
|
+
self._cm = np.sqrt(self._g * h_m)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def xa(self, t: float) -> float:
|
|
475
|
+
r"""
|
|
476
|
+
Position of the rarefaction wave front (left-most edge) :
|
|
477
|
+
|
|
478
|
+
.. math::
|
|
479
|
+
x_A(t) = x_0 - t \sqrt{g h_0}
|
|
480
|
+
|
|
481
|
+
Parameters
|
|
482
|
+
----------
|
|
483
|
+
t : float
|
|
484
|
+
Time instant.
|
|
485
|
+
|
|
486
|
+
Returns
|
|
487
|
+
-------
|
|
488
|
+
float
|
|
489
|
+
Position of the front edge of the rarefaction wave.
|
|
490
|
+
"""
|
|
491
|
+
return self._x0 - (t * np.sqrt(self._g*self._h0))
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def xb(self, t: float) -> float:
|
|
495
|
+
r"""
|
|
496
|
+
Position of the contact discontinuity:
|
|
497
|
+
|
|
498
|
+
.. math::
|
|
499
|
+
x_B(t) = x_0 + t \left( 2 \sqrt{g h_0} - 3 c_m \right)
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
t : float
|
|
504
|
+
Time instant.
|
|
505
|
+
|
|
506
|
+
Returns
|
|
507
|
+
-------
|
|
508
|
+
float
|
|
509
|
+
Position of the contact wave (end of rarefaction).
|
|
510
|
+
"""
|
|
511
|
+
return self._x0 + (t * ((2 * np.sqrt(self._g*self._h0)) - (3*self._cm)))
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def xc(self, t: float) -> float:
|
|
515
|
+
r"""
|
|
516
|
+
Position of the shock wave front (right-most wave):
|
|
517
|
+
|
|
518
|
+
.. math::
|
|
519
|
+
x_C(t) = x_0 + t \cdot \frac{2 c_m^2 \left( \sqrt{g h_0} - c_m \right)}{c_m^2 - g h_r}
|
|
520
|
+
|
|
521
|
+
Parameters
|
|
522
|
+
----------
|
|
523
|
+
t : float
|
|
524
|
+
Time instant.
|
|
525
|
+
|
|
526
|
+
Returns
|
|
527
|
+
-------
|
|
528
|
+
float
|
|
529
|
+
Position of the shock front.
|
|
530
|
+
"""
|
|
531
|
+
return self._x0 + (t * (((2*self._cm**2)*(np.sqrt(self._g*self._h0)-self._cm)) / ((self._cm**2) - (self._g*self._hr))))
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def equation_cm(self, cm) -> float:
|
|
535
|
+
r"""Equation of the critical velocity cm:
|
|
536
|
+
|
|
537
|
+
.. math::
|
|
538
|
+
-8.g.hr.cm^{2}.(g.h0 - cm^{2})^{2} + (cm^{2} - g.hr)^{2} . (cm^{2} + g.hr) = 0
|
|
539
|
+
|
|
540
|
+
Parameters
|
|
541
|
+
----------
|
|
542
|
+
cm : float
|
|
543
|
+
Trial value for :attr:`_cm`.
|
|
544
|
+
|
|
545
|
+
Returns
|
|
546
|
+
-------
|
|
547
|
+
float
|
|
548
|
+
Residual of the equation. Zero when :attr:`_cm` satisfies the system.
|
|
549
|
+
"""
|
|
550
|
+
return -8 * self._g * self._hr * cm**2 * (self._g * self._h0 - cm**2)**2 + (cm**2 - self._g * self._hr)**2 * (cm**2 + self._g * self._hr)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def compute_cm(self) -> None:
|
|
554
|
+
r"""Solves the non-linear equation to compute the critical velocity :attr:`_cm`.
|
|
555
|
+
|
|
556
|
+
Uses numerical root-finding to find a valid value of cm that separates
|
|
557
|
+
the flow regimes. Sets :attr:`_cm` if a valid solution is found.
|
|
558
|
+
"""
|
|
559
|
+
guesses = np.linspace(0.01, 1000, 1000)
|
|
560
|
+
solutions = []
|
|
561
|
+
|
|
562
|
+
for guess in guesses:
|
|
563
|
+
sol = fsolve(self.equation_cm, guess)[0]
|
|
564
|
+
|
|
565
|
+
if abs(self.equation_cm(sol)) < 1e-6 and not any(np.isclose(sol, s, atol=1e-6) for s in solutions):
|
|
566
|
+
solutions.append(sol)
|
|
567
|
+
|
|
568
|
+
for sol in solutions:
|
|
569
|
+
hm = sol**2 / self._g
|
|
570
|
+
if hm < self._h0 and hm > self._hr:
|
|
571
|
+
find = True
|
|
572
|
+
self._cm = sol
|
|
573
|
+
break
|
|
574
|
+
else:
|
|
575
|
+
find = False
|
|
576
|
+
|
|
577
|
+
if find:
|
|
578
|
+
print(f"Find cm: {self._cm}\nhm:{self._cm**2 / self._g}")
|
|
579
|
+
else:
|
|
580
|
+
print(
|
|
581
|
+
f"Didn't find cm, try with greater range of value. Default value: {None}")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def compute_h(self,
|
|
585
|
+
x: float | np.ndarray,
|
|
586
|
+
T: float | np.ndarray
|
|
587
|
+
) -> None:
|
|
588
|
+
r"""Compute the flow height h(x, t) at given time and positions.
|
|
589
|
+
|
|
590
|
+
.. math::
|
|
591
|
+
h(x, t) =
|
|
592
|
+
\begin{cases}
|
|
593
|
+
h_0 & \text{if } x \leq x_A(t), \\\\
|
|
594
|
+
\frac{4}{9g} \left( \sqrt{g h_0} - \frac{x - x_0}{2t} \right)^2 & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
595
|
+
\frac{c_m^2}{g} & \text{if } x_B(t) < x \leq x_C(t), \\\\
|
|
596
|
+
h_r & \text{if } x_C(t) < x,
|
|
597
|
+
\end{cases}
|
|
598
|
+
|
|
599
|
+
Parameters
|
|
600
|
+
----------
|
|
601
|
+
x : float or np.ndarray
|
|
602
|
+
Spatial positions.
|
|
603
|
+
T : float or np.ndarray
|
|
604
|
+
Time instant.
|
|
605
|
+
|
|
606
|
+
Notes
|
|
607
|
+
-----
|
|
608
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._h`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
609
|
+
"""
|
|
610
|
+
if self._cm is not None:
|
|
611
|
+
if isinstance(x, float):
|
|
612
|
+
x = [x]
|
|
613
|
+
self._x = x
|
|
614
|
+
self._t = T
|
|
615
|
+
|
|
616
|
+
if isinstance(T, float):
|
|
617
|
+
h = []
|
|
618
|
+
for i in x:
|
|
619
|
+
if i <= self.xa(T):
|
|
620
|
+
h.append(self._h0)
|
|
621
|
+
# elif i > self.xa(t) and i <= self.xb(t):
|
|
622
|
+
elif self.xa(T) < i <= self.xb(T):
|
|
623
|
+
h.append((4/(9*self._g))*(np.sqrt(self._g *
|
|
624
|
+
self._h0)-((i-self._x0)/(2*T)))**2) # i-x0 and not i to recenter the breach of the dam at x=0.
|
|
625
|
+
elif self.xb(T) < i <= self.xc(T):
|
|
626
|
+
h.append((self._cm**2)/self._g)
|
|
627
|
+
else:
|
|
628
|
+
h.append(self._hr)
|
|
629
|
+
self._h = np.array(h)
|
|
630
|
+
else:
|
|
631
|
+
h = []
|
|
632
|
+
for t in T:
|
|
633
|
+
sub_h = []
|
|
634
|
+
for i in x:
|
|
635
|
+
if i <= self.xa(t):
|
|
636
|
+
sub_h.append(self._h0)
|
|
637
|
+
elif self.xa(t) < i <= self.xb(t):
|
|
638
|
+
sub_h.append((4/(9*self._g))*(np.sqrt(self._g *
|
|
639
|
+
self._h0)-((i-self._x0)/(2*t)))**2)
|
|
640
|
+
elif self.xb(t) < i <= self.xc(t):
|
|
641
|
+
sub_h.append((self._cm**2)/self._g)
|
|
642
|
+
else:
|
|
643
|
+
sub_h.append(self._hr)
|
|
644
|
+
h.append(sub_h)
|
|
645
|
+
self._h = np.array(h)
|
|
646
|
+
|
|
647
|
+
else:
|
|
648
|
+
print("No critical velocity found")
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def compute_u(self,
|
|
652
|
+
x: float | np.ndarray,
|
|
653
|
+
T: float | np.ndarray
|
|
654
|
+
) -> None:
|
|
655
|
+
r"""Compute the flow velocity u(x, t) at given time and positions.
|
|
656
|
+
|
|
657
|
+
.. math::
|
|
658
|
+
u(x,t) =
|
|
659
|
+
\begin{cases}
|
|
660
|
+
0 & \text{if } x \leq x_A(t), \\\\
|
|
661
|
+
\frac{2}{3} \left( \frac{x - x_0}{t} + \sqrt{g h_0} \right) & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
662
|
+
2 \left( \sqrt{g h_0} - c_m \right) & \text{if } x_B(t) < x \leq x_C(t), \\\\
|
|
663
|
+
0 & \text{if } x_C(t) < x,
|
|
664
|
+
\end{cases}
|
|
665
|
+
|
|
666
|
+
Parameters
|
|
667
|
+
----------
|
|
668
|
+
x : float or np.ndarray
|
|
669
|
+
Spatial positions.
|
|
670
|
+
T : float or np.ndarray
|
|
671
|
+
Time instant.
|
|
672
|
+
|
|
673
|
+
Notes
|
|
674
|
+
-----
|
|
675
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._u`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
676
|
+
"""
|
|
677
|
+
if self._cm is not None:
|
|
678
|
+
if isinstance(x, float):
|
|
679
|
+
x = [x]
|
|
680
|
+
self._x = x
|
|
681
|
+
self._t = T
|
|
682
|
+
|
|
683
|
+
if isinstance(T, float):
|
|
684
|
+
u = []
|
|
685
|
+
for i in x:
|
|
686
|
+
if i <= self.xa(T):
|
|
687
|
+
u.append(0)
|
|
688
|
+
elif i > self.xa(T) and i <= self.xb(T):
|
|
689
|
+
u.append((2/3)*(((i-self._x0)/T) +
|
|
690
|
+
np.sqrt(self._g*self._h0)))
|
|
691
|
+
elif i > self.xb(T) and i <= self.xc(T):
|
|
692
|
+
u.append(2*(np.sqrt(self._g*self._h0) - self._cm))
|
|
693
|
+
else:
|
|
694
|
+
u.append(0)
|
|
695
|
+
self._u = np.array(u)
|
|
696
|
+
|
|
697
|
+
else:
|
|
698
|
+
u = []
|
|
699
|
+
for t in T:
|
|
700
|
+
sub_u = []
|
|
701
|
+
for i in x:
|
|
702
|
+
if i <= self.xa(t):
|
|
703
|
+
sub_u.append(0)
|
|
704
|
+
elif i > self.xa(t) and i <= self.xb(t):
|
|
705
|
+
sub_u.append((2/3)*(((i-self._x0)/t) +
|
|
706
|
+
np.sqrt(self._g*self._h0)))
|
|
707
|
+
elif i > self.xb(t) and i <= self.xc(t):
|
|
708
|
+
sub_u.append(2*(np.sqrt(self._g*self._h0) - self._cm))
|
|
709
|
+
else:
|
|
710
|
+
sub_u.append(0)
|
|
711
|
+
u.append(sub_u)
|
|
712
|
+
self._u = np.array(u)
|
|
713
|
+
|
|
714
|
+
else:
|
|
715
|
+
print("First define cm")
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
class Stoker_SARKHOSH_wet(Depth_result):
|
|
719
|
+
r"""Dam-break solution on a wet domain using shallow water theory.
|
|
720
|
+
|
|
721
|
+
This class implements the 1D analytical Stoker's solution of an ideal dam break on a wet domain.
|
|
722
|
+
The dam break is instantaneous, over an horizontal and flat surface with no friction.
|
|
723
|
+
It computes the flow height (took verticaly) and velocity over space and time, based on the equation implemanted
|
|
724
|
+
in SWASHES, based on Stoker's equation.
|
|
725
|
+
|
|
726
|
+
Sarkhosh, P., 2021, Stoker solution package, version 1.0.0, Zenodo. https://doi.org/10.5281/zenodo.5598374
|
|
727
|
+
|
|
728
|
+
Stoker, J.J., 1957, Water Waves: The Mathematical Theory with Applications, Pure and Applied Mathematics, vol. 4, Interscience Publishers, New York, USA.
|
|
729
|
+
|
|
730
|
+
Parameters
|
|
731
|
+
----------
|
|
732
|
+
h_0 : float
|
|
733
|
+
Initial water depth to the left of the dam.
|
|
734
|
+
h_r : float
|
|
735
|
+
Initial water depth to the right of the dam.
|
|
736
|
+
|
|
737
|
+
Attributes
|
|
738
|
+
----------
|
|
739
|
+
_h0 : float
|
|
740
|
+
Water depth to the left of the dam.
|
|
741
|
+
_hr : float
|
|
742
|
+
Water depth to the right of the dam.
|
|
743
|
+
_cm : float
|
|
744
|
+
Shock front speed.
|
|
745
|
+
_hm : float
|
|
746
|
+
Height of the shock front.
|
|
747
|
+
"""
|
|
748
|
+
def __init__(self,
|
|
749
|
+
h_0: float,
|
|
750
|
+
h_r: float,
|
|
751
|
+
):
|
|
752
|
+
super().__init__()
|
|
753
|
+
self._h0 = h_0
|
|
754
|
+
self._hr = h_r
|
|
755
|
+
|
|
756
|
+
if self._hr == 0:
|
|
757
|
+
self._cm = 0
|
|
758
|
+
self._hm = 0
|
|
759
|
+
else:
|
|
760
|
+
self._cm = self.compute_cm()
|
|
761
|
+
self._hm = 0.5 * self._hr * (np.sqrt(1 + 8 * self._cm**2 / np.sqrt(self._g * self._hr)**2) - 1)
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def xa(self, t: float) -> float:
|
|
765
|
+
r"""
|
|
766
|
+
Position of the rarefaction wave front (left-most edge) :
|
|
767
|
+
|
|
768
|
+
.. math::
|
|
769
|
+
x_A(t) = x_0 - t \sqrt{g h_0}
|
|
770
|
+
|
|
771
|
+
Parameters
|
|
772
|
+
----------
|
|
773
|
+
t : float
|
|
774
|
+
Time instant.
|
|
775
|
+
|
|
776
|
+
Returns
|
|
777
|
+
-------
|
|
778
|
+
float
|
|
779
|
+
Position of the front edge of the rarefaction wave.
|
|
780
|
+
"""
|
|
781
|
+
return -(t * np.sqrt(self._g*self._h0))
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def xb(self, hm: float, t: float) -> float:
|
|
785
|
+
r"""
|
|
786
|
+
Position of the contact discontinuity:
|
|
787
|
+
|
|
788
|
+
.. math::
|
|
789
|
+
x_B(t) = t \left( 2 \sqrt{g h_0} - 3 \sqrt{g h_m} \right)
|
|
790
|
+
|
|
791
|
+
Parameters
|
|
792
|
+
----------
|
|
793
|
+
hm : float
|
|
794
|
+
Height of the shock front.
|
|
795
|
+
t : float
|
|
796
|
+
Time instant.
|
|
797
|
+
|
|
798
|
+
Returns
|
|
799
|
+
-------
|
|
800
|
+
float
|
|
801
|
+
Position of the contact wave (end of rarefaction).
|
|
802
|
+
"""
|
|
803
|
+
return (2 * np.sqrt(self._g * self._h0) - 3 * np.sqrt(self._g * hm)) * t
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def xc(self, cm: float, t: float) -> float:
|
|
807
|
+
r"""
|
|
808
|
+
Position of the shock wave front (right-most wave):
|
|
809
|
+
|
|
810
|
+
.. math::
|
|
811
|
+
x_C(t) = c_m t
|
|
812
|
+
|
|
813
|
+
Parameters
|
|
814
|
+
----------
|
|
815
|
+
cm : float
|
|
816
|
+
Shock front speed.
|
|
817
|
+
t : float
|
|
818
|
+
Time instant.
|
|
819
|
+
|
|
820
|
+
Returns
|
|
821
|
+
-------
|
|
822
|
+
float
|
|
823
|
+
Position of the shock front.
|
|
824
|
+
"""
|
|
825
|
+
return cm * t
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def compute_cm(self) -> float:
|
|
829
|
+
r"""Compute the shock front speed using Newton-Raphson's method to find the solution of:
|
|
830
|
+
|
|
831
|
+
.. math::
|
|
832
|
+
c_m h_r - h_r \left( \sqrt{1 + \frac{8 c_m^2}{g h_r}} - 1 \right) \left( \frac{c_m}{2} - \sqrt{g h_0} + \sqrt{\frac{g h_r}{2} \left( \sqrt{1 + \frac{8 c_m^2}{g h_r}} - 1 \right)} \right) = 0
|
|
833
|
+
|
|
834
|
+
Returns
|
|
835
|
+
-------
|
|
836
|
+
float
|
|
837
|
+
Speed of the shock front.
|
|
838
|
+
"""
|
|
839
|
+
f_cm = 1
|
|
840
|
+
df_cm = 1
|
|
841
|
+
cm = 10 * self._h0
|
|
842
|
+
|
|
843
|
+
while abs(f_cm / cm) > 1e-10:
|
|
844
|
+
root_term = np.sqrt(8 * cm**2 / np.sqrt(self._g * self._hr)**2 + 1)
|
|
845
|
+
inner_sqrt = np.sqrt(self._g * self._hr * (root_term - 1) / 2)
|
|
846
|
+
|
|
847
|
+
f_cm = cm * self._hr - self._hr * (root_term - 1) * (cm / 2 - np.sqrt(self._g * self._h0) + inner_sqrt)
|
|
848
|
+
df_cm = (self._hr
|
|
849
|
+
- self._hr * ((2 * cm * self._g * self._hr) / (np.sqrt(self._g * self._hr)**2 * root_term * inner_sqrt) + 0.5) * (root_term - 1)
|
|
850
|
+
- (8 * cm * self._hr * (cm / 2 - np.sqrt(self._g * self._h0) + inner_sqrt)) / (np.sqrt(self._g * self._hr)**2 * root_term))
|
|
851
|
+
|
|
852
|
+
cm -= f_cm / df_cm
|
|
853
|
+
|
|
854
|
+
return cm
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def compute_h(self,
|
|
858
|
+
x: float | np.ndarray,
|
|
859
|
+
T: float | np.ndarray
|
|
860
|
+
) -> None:
|
|
861
|
+
r"""Compute the flow height h(x, t) at given time and positions.
|
|
862
|
+
|
|
863
|
+
.. math::
|
|
864
|
+
h(x, t) =
|
|
865
|
+
\begin{cases}
|
|
866
|
+
h_0 & \text{if } x \leq x_A(t), \\\\
|
|
867
|
+
\frac{\left( 2 \sqrt{g h_0} - \frac{x}{t} \right)^2}{9 g} & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
868
|
+
h_m = \frac{1}{2} h_r \left( \sqrt{1 + \frac{8 c_m^2}{g h_r}} - 1 \right) & \text{if } x_B(t) < x \leq x_C(t), \\\\
|
|
869
|
+
h_r & \text{if } x_C(t) < x
|
|
870
|
+
\end{cases}
|
|
871
|
+
|
|
872
|
+
Parameters
|
|
873
|
+
----------
|
|
874
|
+
x : float or np.ndarray
|
|
875
|
+
Spatial positions.
|
|
876
|
+
T : float or np.ndarray
|
|
877
|
+
Time instant.
|
|
878
|
+
|
|
879
|
+
Notes
|
|
880
|
+
-----
|
|
881
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._h`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
882
|
+
"""
|
|
883
|
+
if isinstance(x, float):
|
|
884
|
+
x = [x]
|
|
885
|
+
if isinstance(T, float):
|
|
886
|
+
T = [T]
|
|
887
|
+
|
|
888
|
+
self._x = x
|
|
889
|
+
self._t = T
|
|
890
|
+
|
|
891
|
+
h = []
|
|
892
|
+
for t in T:
|
|
893
|
+
sub_h = []
|
|
894
|
+
for i in x:
|
|
895
|
+
if t == 0:
|
|
896
|
+
h_val = (2 * np.sqrt(self._g * self._h0) - 1e18) ** 2 / (9 * self._g)
|
|
897
|
+
else:
|
|
898
|
+
h_val = (2 * np.sqrt(self._g * self._h0) - (i/t)) ** 2 / (9 * self._g)
|
|
899
|
+
|
|
900
|
+
if i < self.xa(t):
|
|
901
|
+
# if h_val >= self._h0:
|
|
902
|
+
h_val = self._h0
|
|
903
|
+
|
|
904
|
+
if self._hm == 0 and h_val > sub_h[-1]:
|
|
905
|
+
h_val = 0
|
|
906
|
+
else:
|
|
907
|
+
if (self.xb(self._hm, t) < i <= self.xc(self._cm, t)) and h_val <= self._hm:
|
|
908
|
+
h_val = self._hm
|
|
909
|
+
elif i > self.xc(self._cm, t):
|
|
910
|
+
h_val = self._hr
|
|
911
|
+
|
|
912
|
+
sub_h.append(h_val)
|
|
913
|
+
h.append(sub_h)
|
|
914
|
+
self._h = np.array(h)
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def compute_u(self,
|
|
918
|
+
x: float | np.ndarray,
|
|
919
|
+
T: float | np.ndarray
|
|
920
|
+
) -> None:
|
|
921
|
+
r"""Compute the flow velocity u(x, t) at given time and positions.
|
|
922
|
+
|
|
923
|
+
.. math::
|
|
924
|
+
u(x,t) =
|
|
925
|
+
\begin{cases}
|
|
926
|
+
0 & \text{if } x \leq x_A(t), \\\\
|
|
927
|
+
\frac{2}{3} \left( \frac{x}{t} + \sqrt{g h_0} \right) & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
928
|
+
2 \sqrt{g h_0} - 2 \sqrt{g h_m} & \text{if } x_B(t) < x \leq x_C(t), \\\\
|
|
929
|
+
0 & \text{if } x_C(t) < x,
|
|
930
|
+
\end{cases}
|
|
931
|
+
|
|
932
|
+
Parameters
|
|
933
|
+
----------
|
|
934
|
+
x : float or np.ndarray
|
|
935
|
+
Spatial positions.
|
|
936
|
+
T : float or np.ndarray
|
|
937
|
+
Time instant.
|
|
938
|
+
|
|
939
|
+
Notes
|
|
940
|
+
-----
|
|
941
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._u`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
942
|
+
"""
|
|
943
|
+
if isinstance(x, float):
|
|
944
|
+
x = [x]
|
|
945
|
+
if isinstance(T, float):
|
|
946
|
+
T = [T]
|
|
947
|
+
|
|
948
|
+
self._x = x
|
|
949
|
+
self._t = T
|
|
950
|
+
|
|
951
|
+
um = 2 * np.sqrt(self._g * self._h0) - 2 * np.sqrt(self._g * self._hm)
|
|
952
|
+
|
|
953
|
+
u = []
|
|
954
|
+
for t in T:
|
|
955
|
+
sub_u = []
|
|
956
|
+
for i in x:
|
|
957
|
+
if t == 0:
|
|
958
|
+
u_val = 2 * (1e18 + np.sqrt(self._g * self._h0)) / 3
|
|
959
|
+
else:
|
|
960
|
+
u_val = 2 * ((i/t) + np.sqrt(self._g * self._h0)) / 3
|
|
961
|
+
|
|
962
|
+
if i < self.xa(t):
|
|
963
|
+
# if h_val >= self._h0:
|
|
964
|
+
u_val = np.nan
|
|
965
|
+
|
|
966
|
+
if self._hm == 0 and u_val > sub_u[-1]:
|
|
967
|
+
u_val = np.nan
|
|
968
|
+
else:
|
|
969
|
+
if (self.xb(self._hm, t) < i <= self.xc(self._cm, t)):
|
|
970
|
+
u_val = um
|
|
971
|
+
elif i > self.xc(self._cm, t):
|
|
972
|
+
u_val = np.nan
|
|
973
|
+
|
|
974
|
+
sub_u.append(u_val)
|
|
975
|
+
u.append(sub_u)
|
|
976
|
+
self._u = np.array(u)
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
class Mangeney_dry(Depth_result):
|
|
980
|
+
r"""Dam-break solution on an inclined dry domain with friction using shallow water theory.
|
|
981
|
+
|
|
982
|
+
This class implements the 1D analytical Stoker's solution of an ideal dam break on a dry domain.
|
|
983
|
+
The dam break is instantaneous, over an inclined and flat surface with friction.
|
|
984
|
+
It computes the flow height (took normal to the surface) and velocity over space and time with an
|
|
985
|
+
infinitely-long fluid mass on an infinite surface.
|
|
986
|
+
|
|
987
|
+
Mangeney, A., Heinrich, P., & Roche, R., 2000, Analytical solution for testing debris avalanche numerical models,
|
|
988
|
+
Pure and Applied Geophysics, vol. 157, p. 1081-1096.
|
|
989
|
+
|
|
990
|
+
Parameters
|
|
991
|
+
----------
|
|
992
|
+
x_0 : float
|
|
993
|
+
Initial dam location (position along x-axis), by default 0.
|
|
994
|
+
h_0 : float
|
|
995
|
+
Initial water depth.
|
|
996
|
+
theta : float
|
|
997
|
+
Angle of the surface, in degree.
|
|
998
|
+
delta : float
|
|
999
|
+
Dynamic friction angle (20°-40° for debris avalanche), in degree.
|
|
1000
|
+
|
|
1001
|
+
Attributes
|
|
1002
|
+
----------
|
|
1003
|
+
_x0 : float
|
|
1004
|
+
Initial dam location (position along x-axis).
|
|
1005
|
+
_h0 : float
|
|
1006
|
+
Initial water depth.
|
|
1007
|
+
_delta : float
|
|
1008
|
+
Dynamic friction angle, in radian.
|
|
1009
|
+
_c0 : float
|
|
1010
|
+
Initial wave propagation speed.
|
|
1011
|
+
_m : float
|
|
1012
|
+
Constant horizontal acceleration of the front.
|
|
1013
|
+
"""
|
|
1014
|
+
def __init__(self,
|
|
1015
|
+
x_0: float,
|
|
1016
|
+
h_0: float,
|
|
1017
|
+
theta: float,
|
|
1018
|
+
delta: float,
|
|
1019
|
+
):
|
|
1020
|
+
super().__init__(theta=np.radians(theta))
|
|
1021
|
+
self._delta = np.radians(delta)
|
|
1022
|
+
self._h0 = h_0
|
|
1023
|
+
self._x0 = x_0
|
|
1024
|
+
self._c0 = self.compute_c0()
|
|
1025
|
+
self._m = self.compute_m()
|
|
1026
|
+
|
|
1027
|
+
# print(f"delta: {self._delta}, theta: {self._theta}, m: {self._m}, c0: {self._c0}")
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def xa(self, t: float) -> float:
|
|
1031
|
+
r"""
|
|
1032
|
+
Edge of the quiet area:
|
|
1033
|
+
|
|
1034
|
+
.. math::
|
|
1035
|
+
x_A(t) = x_0 + \frac{1}{2}mt^2 - c_0 t
|
|
1036
|
+
|
|
1037
|
+
Parameters
|
|
1038
|
+
----------
|
|
1039
|
+
t : float
|
|
1040
|
+
Time instant.
|
|
1041
|
+
|
|
1042
|
+
Returns
|
|
1043
|
+
-------
|
|
1044
|
+
float
|
|
1045
|
+
Position of the edge of the quiet region.
|
|
1046
|
+
"""
|
|
1047
|
+
return self._x0 + 0.5*self._m*t**2 - (self._c0*t)
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
def xb(self, t: float) -> float:
|
|
1051
|
+
r"""
|
|
1052
|
+
Front of the flow:
|
|
1053
|
+
|
|
1054
|
+
.. math::
|
|
1055
|
+
x_B(t) = x_0 + \frac{1}{2}mt^2 + 2 c_0 t
|
|
1056
|
+
|
|
1057
|
+
Parameters
|
|
1058
|
+
----------
|
|
1059
|
+
t : float
|
|
1060
|
+
Time instant.
|
|
1061
|
+
|
|
1062
|
+
Returns
|
|
1063
|
+
-------
|
|
1064
|
+
float
|
|
1065
|
+
Position of the front edge of the fluid.
|
|
1066
|
+
"""
|
|
1067
|
+
return self._x0 + 0.5*self._m*t**2 + (2*self._c0*t)
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
def compute_c0(self) -> float:
|
|
1071
|
+
r"""Compute the initial wave propagation speed defined by:
|
|
1072
|
+
|
|
1073
|
+
.. math::
|
|
1074
|
+
c_0 = \sqrt{g h_0 \cos{\theta}}
|
|
1075
|
+
|
|
1076
|
+
Returns
|
|
1077
|
+
-------
|
|
1078
|
+
float
|
|
1079
|
+
Value of the initial wave propagation speed.
|
|
1080
|
+
"""
|
|
1081
|
+
return np.sqrt(self._g * self._h0 * np.cos(self._theta))
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def compute_m(self) -> float:
|
|
1085
|
+
r"""Compute the constant horizontal acceleration of the front defined by:
|
|
1086
|
+
|
|
1087
|
+
.. math::
|
|
1088
|
+
m = g \sin{\theta} - g \cos{\theta} \tan{\delta}
|
|
1089
|
+
|
|
1090
|
+
Returns
|
|
1091
|
+
-------
|
|
1092
|
+
float
|
|
1093
|
+
Value of the constant horizontal acceleration of the front.
|
|
1094
|
+
"""
|
|
1095
|
+
return (self._g * np.sin(self._theta)) - (self._g * np.cos(self._theta) * np.tan(self._delta))
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def compute_h(self,
|
|
1099
|
+
x: float | np.ndarray,
|
|
1100
|
+
T: float | np.ndarray) -> None:
|
|
1101
|
+
r"""Compute the flow height h(x, t) at given time and positions.
|
|
1102
|
+
|
|
1103
|
+
.. math::
|
|
1104
|
+
h(x, t) =
|
|
1105
|
+
\begin{cases}
|
|
1106
|
+
h_0 & \text{if } x \leq x_A(t), \\\\
|
|
1107
|
+
\frac{1}{9g cos(\theta)} \left(2 c_0 - \frac{x-x_0}{t} + \frac{1}{2} m t \right)^2 & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
1108
|
+
0 & \text{if } x_B(t) < x,
|
|
1109
|
+
\end{cases}
|
|
1110
|
+
|
|
1111
|
+
Parameters
|
|
1112
|
+
----------
|
|
1113
|
+
x : float or np.ndarray
|
|
1114
|
+
Spatial positions.
|
|
1115
|
+
T : float or np.ndarray
|
|
1116
|
+
Time instant.
|
|
1117
|
+
|
|
1118
|
+
Notes
|
|
1119
|
+
-----
|
|
1120
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._h`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
1121
|
+
"""
|
|
1122
|
+
if isinstance(x, float):
|
|
1123
|
+
x = [x]
|
|
1124
|
+
if isinstance(T, float):
|
|
1125
|
+
T = [T]
|
|
1126
|
+
|
|
1127
|
+
self._x = x
|
|
1128
|
+
self._t = T
|
|
1129
|
+
|
|
1130
|
+
h = []
|
|
1131
|
+
for t in T:
|
|
1132
|
+
sub_h = []
|
|
1133
|
+
|
|
1134
|
+
for i in x:
|
|
1135
|
+
if i <= self.xa(t):
|
|
1136
|
+
sub_h.append(self._h0)
|
|
1137
|
+
|
|
1138
|
+
elif self.xa(t) < i < self.xb(t):
|
|
1139
|
+
sub_h.append( (1/(9*self._g*np.cos(self._theta))) * ( (-(i-self._x0)/t) + (2 * self._c0) + (0.5*t*self._m))**2 )
|
|
1140
|
+
|
|
1141
|
+
else:
|
|
1142
|
+
sub_h.append(0)
|
|
1143
|
+
h.append(sub_h)
|
|
1144
|
+
|
|
1145
|
+
self._h = np.array(h)
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
def compute_u(self,
|
|
1149
|
+
x: float | np.ndarray,
|
|
1150
|
+
T: float | np.ndarray) -> None:
|
|
1151
|
+
r"""Compute the flow velocity u(x, t) at given time and positions.
|
|
1152
|
+
|
|
1153
|
+
.. math::
|
|
1154
|
+
u(x,t) =
|
|
1155
|
+
\begin{cases}
|
|
1156
|
+
0 & \text{if } x \leq x_A(t), \\\\
|
|
1157
|
+
\frac{2}{3} \left( \frac{x-x_0}{t} + c_0 + mt \right) & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
1158
|
+
0 & \text{if } x_B(t) < x,
|
|
1159
|
+
\end{cases}
|
|
1160
|
+
|
|
1161
|
+
Parameters
|
|
1162
|
+
----------
|
|
1163
|
+
x : float or np.ndarray
|
|
1164
|
+
Spatial positions.
|
|
1165
|
+
T : float or np.ndarray
|
|
1166
|
+
Time instant.
|
|
1167
|
+
|
|
1168
|
+
Notes
|
|
1169
|
+
-----
|
|
1170
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._u`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
1171
|
+
"""
|
|
1172
|
+
if isinstance(x, float):
|
|
1173
|
+
x = [x]
|
|
1174
|
+
if isinstance(T, float):
|
|
1175
|
+
T = [T]
|
|
1176
|
+
|
|
1177
|
+
self._x = x
|
|
1178
|
+
self._t = T
|
|
1179
|
+
|
|
1180
|
+
# x = [i - max(x) for i in x]
|
|
1181
|
+
|
|
1182
|
+
u = []
|
|
1183
|
+
for t in T:
|
|
1184
|
+
sub_u = []
|
|
1185
|
+
for i in x:
|
|
1186
|
+
if i <= self.xa(t):
|
|
1187
|
+
sub_u.append(np.nan)
|
|
1188
|
+
elif self.xa(t) < i <= self.xb(t):
|
|
1189
|
+
u_val = (2/3) * ( ((i-self._x0)/t) + self._c0 + self._m * t )
|
|
1190
|
+
sub_u.append(u_val)
|
|
1191
|
+
else:
|
|
1192
|
+
sub_u.append(np.nan)
|
|
1193
|
+
u.append(sub_u)
|
|
1194
|
+
self._u = np.array(u)
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
class Dressler_dry(Depth_result):
|
|
1198
|
+
r"""Dam-break solution on a dry domain with friction using shallow water theory.
|
|
1199
|
+
|
|
1200
|
+
This class implements the 1D analytical Dressler's solution of an ideal dam break on a dry domain with friction.
|
|
1201
|
+
The dam break is instantaneous, over an horizontal and flat surface with friction.
|
|
1202
|
+
It computes the flow height (took verticaly) and velocity over space and time, based on the equation implemanted
|
|
1203
|
+
in SWASHES, based on Dressler's equation.
|
|
1204
|
+
|
|
1205
|
+
Dressler, R.F., 1952, Hydraulic resistance effect upon the dam‑break functions, Journal of Research of the National Bureau
|
|
1206
|
+
of Standards, vol. 49(3), p. 217-225.
|
|
1207
|
+
|
|
1208
|
+
Parameters
|
|
1209
|
+
----------
|
|
1210
|
+
x_0 : float
|
|
1211
|
+
Initial dam location (position along x-axis).
|
|
1212
|
+
h_0 : float
|
|
1213
|
+
Water depth to the left of the dam.
|
|
1214
|
+
C : float, optional
|
|
1215
|
+
Chézy coefficient, by default 40.
|
|
1216
|
+
|
|
1217
|
+
Attributes
|
|
1218
|
+
----------
|
|
1219
|
+
_x0 : float
|
|
1220
|
+
Initial dam location (position along x-axis).
|
|
1221
|
+
_h0 : float
|
|
1222
|
+
Water depth to the left of the dam.
|
|
1223
|
+
_c : float, optional
|
|
1224
|
+
Chézy coefficient, by default 40.
|
|
1225
|
+
_xt : float
|
|
1226
|
+
Position of the tip area, by default None.
|
|
1227
|
+
_ht : float, optional
|
|
1228
|
+
Depth of the tip area, by default None.
|
|
1229
|
+
_ut : float, optional
|
|
1230
|
+
Velocity of the tip area, by default None.
|
|
1231
|
+
"""
|
|
1232
|
+
def __init__(self,
|
|
1233
|
+
x_0: float,
|
|
1234
|
+
h_0: float,
|
|
1235
|
+
C: float=40
|
|
1236
|
+
):
|
|
1237
|
+
super().__init__()
|
|
1238
|
+
self._x0 = x_0
|
|
1239
|
+
self._h0 = h_0
|
|
1240
|
+
self._c = C
|
|
1241
|
+
self._xt = []
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def xa(self, t: float) -> float:
|
|
1245
|
+
r"""
|
|
1246
|
+
Position of the rarefaction wave front (left-most edge) :
|
|
1247
|
+
|
|
1248
|
+
.. math::
|
|
1249
|
+
x_A(t) = x_0 - t \sqrt{g h_0}
|
|
1250
|
+
|
|
1251
|
+
Parameters
|
|
1252
|
+
----------
|
|
1253
|
+
t : float
|
|
1254
|
+
Time instant.
|
|
1255
|
+
|
|
1256
|
+
Returns
|
|
1257
|
+
-------
|
|
1258
|
+
float
|
|
1259
|
+
Position of the front edge of the rarefaction wave.
|
|
1260
|
+
"""
|
|
1261
|
+
return self._x0 - (t * np.sqrt(self._g*self._h0))
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
def xb(self, t: float) -> float:
|
|
1265
|
+
r"""
|
|
1266
|
+
Position of the contact discontinuity:
|
|
1267
|
+
|
|
1268
|
+
.. math::
|
|
1269
|
+
x_B(t) = x_0 + 2 t \sqrt{g h_0}
|
|
1270
|
+
|
|
1271
|
+
Parameters
|
|
1272
|
+
----------
|
|
1273
|
+
t : float
|
|
1274
|
+
Time instant.
|
|
1275
|
+
|
|
1276
|
+
Returns
|
|
1277
|
+
-------
|
|
1278
|
+
float
|
|
1279
|
+
Position of the contact wave (end of rarefaction).
|
|
1280
|
+
"""
|
|
1281
|
+
return self._x0 + (2 * t * np.sqrt(self._g*self._h0))
|
|
1282
|
+
|
|
1283
|
+
|
|
1284
|
+
def alpha1(self, x: float, t: float) -> float:
|
|
1285
|
+
r"""
|
|
1286
|
+
Correction coefficient for the height:
|
|
1287
|
+
|
|
1288
|
+
.. math::
|
|
1289
|
+
\alpha_1(\xi) = \frac{6}{5(2-\xi)} - \frac{2}{3} + \frac{4 \sqrt{3}}{135} (2-\xi)^{3/2}), \\\\
|
|
1290
|
+
|
|
1291
|
+
with :math:`\xi = \frac{x-x_0}{t\sqrt{g h_0}}`
|
|
1292
|
+
|
|
1293
|
+
Parameters
|
|
1294
|
+
----------
|
|
1295
|
+
x : float
|
|
1296
|
+
Spatial position.
|
|
1297
|
+
t : float
|
|
1298
|
+
Time instant.
|
|
1299
|
+
|
|
1300
|
+
Returns
|
|
1301
|
+
-------
|
|
1302
|
+
float
|
|
1303
|
+
Correction coefficient.
|
|
1304
|
+
"""
|
|
1305
|
+
xi = (x-self._x0)/(t*np.sqrt(self._g*self._h0))
|
|
1306
|
+
# return (6 / (5*(2 - (x/(t*np.sqrt(self._g*self._h0)))))) - (2/3) + (4*np.sqrt(3)/135)*((2 - (x/(t*np.sqrt(self._g*self._h0))))**(3/2))
|
|
1307
|
+
# return (6 / (5 * (2 - xi))) - (2 / 3) + (4 * np.sqrt(3) / 135) * (2 - xi) ** (3 / 2)
|
|
1308
|
+
if xi < 2:
|
|
1309
|
+
return (6 / (5 * (2 - xi))) - (2 / 3) + (4 * np.sqrt(3) / 135) * (2 - xi) ** (3 / 2)
|
|
1310
|
+
else:
|
|
1311
|
+
return 0
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
def alpha2(self, x: float, t: float) -> float:
|
|
1315
|
+
r"""
|
|
1316
|
+
Correction coefficient for the velocity:
|
|
1317
|
+
|
|
1318
|
+
.. math::
|
|
1319
|
+
\alpha_2(\xi) = \frac{12}{2-(2-\xi)} - \frac{8}{3} + \frac{8 \sqrt{3}}{189} (2-\xi)^{3/2}) - \frac{108}{7(2 - \xi)}, \\\\
|
|
1320
|
+
|
|
1321
|
+
with :math:`\xi = \frac{x-x_0}{t\sqrt{g h_0}}`
|
|
1322
|
+
|
|
1323
|
+
Parameters
|
|
1324
|
+
----------
|
|
1325
|
+
x : float
|
|
1326
|
+
Spatial position.
|
|
1327
|
+
t : float
|
|
1328
|
+
Time instant.
|
|
1329
|
+
|
|
1330
|
+
Returns
|
|
1331
|
+
-------
|
|
1332
|
+
float
|
|
1333
|
+
Correction coefficient.
|
|
1334
|
+
"""
|
|
1335
|
+
xi = (x-self._x0)/(t*np.sqrt(self._g*self._h0))
|
|
1336
|
+
|
|
1337
|
+
if xi < 2:
|
|
1338
|
+
return 12./(2 - xi)- 8/3 + 8*np.sqrt(3)/189 * ((2 - xi)**(3/2)) - 108/(7*(2 - xi)**2)
|
|
1339
|
+
else:
|
|
1340
|
+
return 0
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
def compute_u(self,
|
|
1344
|
+
x: float | np.ndarray,
|
|
1345
|
+
T: float | np.ndarray
|
|
1346
|
+
) -> None:
|
|
1347
|
+
"""Call :meth:`compute_h`."""
|
|
1348
|
+
if self._u is None:
|
|
1349
|
+
self.compute_h(x, T)
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
def compute_h(self,
|
|
1353
|
+
x: float | np.ndarray,
|
|
1354
|
+
T: float | np.ndarray
|
|
1355
|
+
) -> None:
|
|
1356
|
+
r"""Compute the flow height h(x, t) and velocity u(x, t) at given time and positions.
|
|
1357
|
+
|
|
1358
|
+
.. math::
|
|
1359
|
+
h(x, t) =
|
|
1360
|
+
\begin{cases}
|
|
1361
|
+
h_0 & \text{if } x \leq x_A(t), \\\\
|
|
1362
|
+
\frac{1}{g} \left( \frac{2}{3} \sqrt{g h_0} - \frac{x - x_0}{3t} + \frac{g^{2}}{C^2} \alpha_1 t \right)^2 & \text{if } x_A(t) < x \leq x_t(t), \\\\
|
|
1363
|
+
\frac{-b-\sqrt{b^2 - 4 a (c-x(t))}}{2 a} & \text{if } x_t(t) < x \leq x_B(t), \\\\
|
|
1364
|
+
0 & \text{if } x_B(t) < x,
|
|
1365
|
+
\end{cases}
|
|
1366
|
+
|
|
1367
|
+
with :math:`r = \left. \frac{dx}{dh} \right|_{h = h_t}`, :math:`c = x_B(t)`, :math:`a = \frac{r h_t + c - x_t}{h_t^2}`, :math:`b = r - 2 a h_t`. :math:`x_t` and :math:`h_t` being the position
|
|
1368
|
+
and the flow depth at the beginning of the tip area.
|
|
1369
|
+
|
|
1370
|
+
.. math::
|
|
1371
|
+
u(x,t) =
|
|
1372
|
+
\begin{cases}
|
|
1373
|
+
0 & \text{if } x \leq x_A(t), \\\\
|
|
1374
|
+
u_{co} = \frac{2\sqrt{g h_0}}{3} + \frac{2(x - x_0)}{3t} + \frac{g^2}{C^2} \alpha_2 t & \text{if } x_A(t) < x \leq x_t(t), \\\\
|
|
1375
|
+
\max_{x \in [x_A(t), x_t(t)]} u_{co}(x, t) & \text{if } x_t(t) < x \leq x_B(t), \\\\
|
|
1376
|
+
0 & \text{if } x_B(t) < x,
|
|
1377
|
+
\end{cases}
|
|
1378
|
+
|
|
1379
|
+
Parameters
|
|
1380
|
+
----------
|
|
1381
|
+
x : float | np.ndarray
|
|
1382
|
+
Spatial positions.
|
|
1383
|
+
T : float | np.ndarray
|
|
1384
|
+
Time instant.
|
|
1385
|
+
|
|
1386
|
+
Notes
|
|
1387
|
+
-----
|
|
1388
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._h`, :attr:`tilupy.analytic_sol.Depth_result._u`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
1389
|
+
"""
|
|
1390
|
+
if isinstance(x, float):
|
|
1391
|
+
x = [x]
|
|
1392
|
+
if isinstance(T, float):
|
|
1393
|
+
T = [T]
|
|
1394
|
+
|
|
1395
|
+
self._x = x
|
|
1396
|
+
self._t = T
|
|
1397
|
+
|
|
1398
|
+
h = []
|
|
1399
|
+
u = []
|
|
1400
|
+
|
|
1401
|
+
xt = None
|
|
1402
|
+
ht = None
|
|
1403
|
+
ut = None
|
|
1404
|
+
|
|
1405
|
+
for t in T:
|
|
1406
|
+
sub_h = []
|
|
1407
|
+
sub_u = []
|
|
1408
|
+
for i in x:
|
|
1409
|
+
if i <= self.xa(t):
|
|
1410
|
+
sub_h.append(self._h0)
|
|
1411
|
+
sub_u.append(np.nan)
|
|
1412
|
+
|
|
1413
|
+
elif self.xa(t) < i <= self.xb(t):
|
|
1414
|
+
if t == 0:
|
|
1415
|
+
t = 1e-18
|
|
1416
|
+
term = ((2/3)*np.sqrt(self._g*self._h0)) - ((i - self._x0) / (3*t)) + ((self._g**2)/(self._c**2)) * self.alpha1(i, t) * t
|
|
1417
|
+
h_val = (1/self._g) * term**2
|
|
1418
|
+
|
|
1419
|
+
if sub_u[-1] is np.nan:
|
|
1420
|
+
sub_u[-1] = 0
|
|
1421
|
+
u_val = (2/3)*np.sqrt(self._g*self._h0)*(1+(i-self._x0)/(np.sqrt(self._g*self._h0)*t)) + ((self._g**2)/(self._c**2))*self.alpha2(i, t)*t
|
|
1422
|
+
|
|
1423
|
+
if u_val < sub_u[-1] and xt is None:
|
|
1424
|
+
xt = i
|
|
1425
|
+
ht = sub_h[-1]
|
|
1426
|
+
ut = sub_u[-1]
|
|
1427
|
+
|
|
1428
|
+
dx = x[1] - x[0]
|
|
1429
|
+
dh = sub_h[-1] - sub_h[-2]
|
|
1430
|
+
|
|
1431
|
+
r = dx / dh
|
|
1432
|
+
c = self.xb(t)
|
|
1433
|
+
a = (r * ht + c - xt) / (ht ** 2)
|
|
1434
|
+
b = r - 2 * a * ht
|
|
1435
|
+
|
|
1436
|
+
if xt is not None:
|
|
1437
|
+
u_val = ut
|
|
1438
|
+
h_val = (-b-np.sqrt((b**2) - 4.*a*(c-i))) / (2*a)
|
|
1439
|
+
|
|
1440
|
+
sub_h.append(h_val)
|
|
1441
|
+
sub_u.append(u_val)
|
|
1442
|
+
|
|
1443
|
+
else:
|
|
1444
|
+
sub_h.append(0)
|
|
1445
|
+
sub_u.append(np.nan)
|
|
1446
|
+
h.append(sub_h)
|
|
1447
|
+
u.append(sub_u)
|
|
1448
|
+
|
|
1449
|
+
self._xt.append(xt)
|
|
1450
|
+
xt = None
|
|
1451
|
+
ht = None
|
|
1452
|
+
ut = None
|
|
1453
|
+
|
|
1454
|
+
self._h = np.array(h)
|
|
1455
|
+
self._u = np.array(u)
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
class Chanson_dry(Depth_result):
|
|
1459
|
+
r"""Dam-break solution on a dry domain with friction using shallow water theory.
|
|
1460
|
+
|
|
1461
|
+
This class implements the 1D analytical Chanson's solution of an ideal dam break on a dry domain with friction.
|
|
1462
|
+
The dam break is instantaneous, over an horizontal and flat surface with friction.
|
|
1463
|
+
It computes the flow height (took verticaly) and velocity over space and time, based on the equation implemanted
|
|
1464
|
+
in SWASHES, based on Chanson's equation.
|
|
1465
|
+
|
|
1466
|
+
Chanson, H., 2005, Applications of the Saint-Venant Equations and Method of Characteristics to the Dam Break Wave Problem. https://espace.library.uq.edu.au/view/UQ:9438
|
|
1467
|
+
|
|
1468
|
+
Parameters
|
|
1469
|
+
----------
|
|
1470
|
+
x_0 : float
|
|
1471
|
+
Initial dam location (position along x-axis).
|
|
1472
|
+
h_0 : float
|
|
1473
|
+
Water depth to the left of the dam.
|
|
1474
|
+
f : float
|
|
1475
|
+
Darcy friction factor.
|
|
1476
|
+
|
|
1477
|
+
Attributes
|
|
1478
|
+
----------
|
|
1479
|
+
_x0 : float
|
|
1480
|
+
Initial dam location (position along x-axis).
|
|
1481
|
+
_h0 : float
|
|
1482
|
+
Water depth to the left of the dam.
|
|
1483
|
+
_f : float, optional
|
|
1484
|
+
Darcy friction factor.
|
|
1485
|
+
"""
|
|
1486
|
+
def __init__(self,
|
|
1487
|
+
h_0: float,
|
|
1488
|
+
x_0: float,
|
|
1489
|
+
f: float
|
|
1490
|
+
):
|
|
1491
|
+
super().__init__()
|
|
1492
|
+
self._h0 = h_0
|
|
1493
|
+
self._x0 = x_0
|
|
1494
|
+
self._f = f
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
def xa(self, t: float) -> float:
|
|
1498
|
+
r"""
|
|
1499
|
+
Position of the rarefaction wave front (left-most edge) :
|
|
1500
|
+
|
|
1501
|
+
.. math::
|
|
1502
|
+
x_A(t) = x_0 - t \sqrt{g h_0}
|
|
1503
|
+
|
|
1504
|
+
Parameters
|
|
1505
|
+
----------
|
|
1506
|
+
t : float
|
|
1507
|
+
Time instant.
|
|
1508
|
+
|
|
1509
|
+
Returns
|
|
1510
|
+
-------
|
|
1511
|
+
float
|
|
1512
|
+
Position of the front edge of the rarefaction wave.
|
|
1513
|
+
"""
|
|
1514
|
+
return self._x0 - (t * np.sqrt(self._g*self._h0))
|
|
1515
|
+
|
|
1516
|
+
|
|
1517
|
+
def xb(self, t: float) -> float:
|
|
1518
|
+
r"""
|
|
1519
|
+
Position of the tip of the flow:
|
|
1520
|
+
|
|
1521
|
+
.. math::
|
|
1522
|
+
x_B(t) = x_0 + \left( \frac{3}{2} \frac{U(t)}{\sqrt{g h_0}} - 1 \right) t \sqrt{g h_0}
|
|
1523
|
+
|
|
1524
|
+
Parameters
|
|
1525
|
+
----------
|
|
1526
|
+
t : float
|
|
1527
|
+
Time instant.
|
|
1528
|
+
|
|
1529
|
+
Returns
|
|
1530
|
+
-------
|
|
1531
|
+
float
|
|
1532
|
+
Position of the flow tip.
|
|
1533
|
+
"""
|
|
1534
|
+
cf = self.compute_cf(t)
|
|
1535
|
+
return self._x0 + ((3*cf)/(2*np.sqrt(self._g*self._h0))-1) * (t*np.sqrt(self._g*self._h0))
|
|
1536
|
+
# return ((3/2) * cf - np.sqrt(self._g * self._h0)) * t
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
def xc(self, t: float) -> float:
|
|
1540
|
+
r"""
|
|
1541
|
+
Position of the contact discontinuity:
|
|
1542
|
+
|
|
1543
|
+
.. math::
|
|
1544
|
+
x_C(t) = x_0 + \left( \frac{3}{2} \frac{U(t)}{\sqrt{g h_0}} - 1 \right) t \sqrt{\frac{g}{h_0}} + \frac{4}{f\frac{U(t)^2}{g h_0}} \left( 1 - \frac{U(t)}{2 \sqrt{g h_0}} \right)^4
|
|
1545
|
+
|
|
1546
|
+
Parameters
|
|
1547
|
+
----------
|
|
1548
|
+
t : float
|
|
1549
|
+
Time instant.
|
|
1550
|
+
|
|
1551
|
+
Returns
|
|
1552
|
+
-------
|
|
1553
|
+
float
|
|
1554
|
+
Position of the contact wave (wave front).
|
|
1555
|
+
"""
|
|
1556
|
+
cf = self.compute_cf(t)
|
|
1557
|
+
|
|
1558
|
+
term1 = ((1.5 * (cf / np.sqrt(self._g * self._h0))) - 1) * np.sqrt(self._g / self._h0) * t
|
|
1559
|
+
term2 = (4 / (self._f * ((cf**2) / (self._g * self._h0)))) * (1 - 0.5 * (cf / np.sqrt(self._g * self._h0)))**4
|
|
1560
|
+
|
|
1561
|
+
x_s = self._x0 + self._h0 * (term1 + term2)
|
|
1562
|
+
return x_s
|
|
1563
|
+
|
|
1564
|
+
|
|
1565
|
+
def compute_cf(self, t: float) -> float:
|
|
1566
|
+
r"""Compute the celerity of the wave front by resolving:
|
|
1567
|
+
|
|
1568
|
+
.. math::
|
|
1569
|
+
\left( \frac{U}{\sqrt{g h_0}} \right)^3 - 8 \left( 0.75 - \frac{3 f t \sqrt{g}}{8 \sqrt{h_0}} \right) \left( \frac{U}{\sqrt{g h_0}} \right)^2 + 12 \left( \frac{U}{\sqrt{g h_0}} \right) - 8 = 0
|
|
1570
|
+
|
|
1571
|
+
Parameters
|
|
1572
|
+
----------
|
|
1573
|
+
t : float
|
|
1574
|
+
Time instant
|
|
1575
|
+
|
|
1576
|
+
Returns
|
|
1577
|
+
-------
|
|
1578
|
+
float
|
|
1579
|
+
Value of the front wave velocity.
|
|
1580
|
+
"""
|
|
1581
|
+
coeffs = [1, (-8*(0.75 - ((3 * self._f * t * np.sqrt(self._g)) / (8 * np.sqrt(self._h0))))), 12, -8]
|
|
1582
|
+
roots = np.roots(coeffs)
|
|
1583
|
+
|
|
1584
|
+
real_root = roots[-1].real
|
|
1585
|
+
return real_root * np.sqrt(self._g * self._h0)
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
def compute_h(self,
|
|
1589
|
+
x: float | np.ndarray,
|
|
1590
|
+
T: float | np.ndarray
|
|
1591
|
+
) -> None:
|
|
1592
|
+
r"""Compute the flow height h(x, t) at given time and positions.
|
|
1593
|
+
|
|
1594
|
+
.. math::
|
|
1595
|
+
h(x, t) =
|
|
1596
|
+
\begin{cases}
|
|
1597
|
+
h_0 & \text{if } x \leq x_A(t), \\\\
|
|
1598
|
+
\frac{4}{9g} \left( \sqrt{g h_0} - \frac{x - x_0}{2t} \right)^2 & \text{if } x_A(t) < x \leq x_B(t), \\\\
|
|
1599
|
+
\sqrt{\frac{f}{4} \frac{U(t)^2}{g h_0} \frac{x_C(t)-x}{h_0}} & \text{if } x_B(t) < x \leq x_C(t), \\\\
|
|
1600
|
+
0 & \text{if } x_C(t) < x
|
|
1601
|
+
\end{cases}
|
|
1602
|
+
|
|
1603
|
+
Parameters
|
|
1604
|
+
----------
|
|
1605
|
+
x : float or np.ndarray
|
|
1606
|
+
Spatial positions.
|
|
1607
|
+
T : float or np.ndarray
|
|
1608
|
+
Time instant.
|
|
1609
|
+
|
|
1610
|
+
Notes
|
|
1611
|
+
-----
|
|
1612
|
+
Updates the internal :attr:`tilupy.analytic_sol.Depth_result._h`, :attr:`tilupy.analytic_sol.Depth_result._x`, :attr:`tilupy.analytic_sol.Depth_result._t` attributes with the computed result.
|
|
1613
|
+
"""
|
|
1614
|
+
if isinstance(x, float):
|
|
1615
|
+
x = [x]
|
|
1616
|
+
if isinstance(T, float):
|
|
1617
|
+
T = [T]
|
|
1618
|
+
|
|
1619
|
+
self._x = x
|
|
1620
|
+
self._t = T
|
|
1621
|
+
|
|
1622
|
+
h = []
|
|
1623
|
+
for t in T:
|
|
1624
|
+
sub_h = []
|
|
1625
|
+
cf = self.compute_cf(t)
|
|
1626
|
+
|
|
1627
|
+
for i in x:
|
|
1628
|
+
if i <= self.xa(t):
|
|
1629
|
+
sub_h.append(self._h0)
|
|
1630
|
+
|
|
1631
|
+
elif self.xa(t) < i <= self.xb(t):
|
|
1632
|
+
sub_h.append((4/(9*self._g)) * (np.sqrt(self._g*self._h0)-((i-self._x0)/(2*t)))**2)
|
|
1633
|
+
|
|
1634
|
+
elif self.xb(t) <= i <= self.xc(t):
|
|
1635
|
+
# h_left = (4/(9*self._g)) * (np.sqrt(self._g*self._h0) - (self.xb(t)/(2*t)))**2
|
|
1636
|
+
# K = (h_left / self._h0)**2 / ((self.xc(t) - self.xb(t)) / self._h0)
|
|
1637
|
+
# term = K * ((self.xc(t) - i) / self._h0)
|
|
1638
|
+
# val = np.sqrt(term) * self._h0
|
|
1639
|
+
|
|
1640
|
+
# h_left = (4/(9*self._g)) * (np.sqrt(self._g*self._h0) - (self.xb(t)/(2*t)))**2
|
|
1641
|
+
# term_denominator = (self._f / 4) * ((cf**2) / (self._g * self._h0)) * ((self.xc(t)-self.xb(t)) / self._h0)
|
|
1642
|
+
# val_term_at_xb = np.sqrt(term_denominator) * self._h0
|
|
1643
|
+
# C = h_left / val_term_at_xb
|
|
1644
|
+
# term = (self._f / 4) * ((cf**2) / (self._g * self._h0)) * ((self.xc(t)-i) / self._h0)
|
|
1645
|
+
# val = C * np.sqrt(term) * self._h0
|
|
1646
|
+
|
|
1647
|
+
term = (self._f / 4) * ((cf**2) / (self._g * self._h0)) * ((self.xc(t)-(i)) / self._h0)
|
|
1648
|
+
val = np.sqrt(term) * self._h0
|
|
1649
|
+
sub_h.append(val)
|
|
1650
|
+
|
|
1651
|
+
else:
|
|
1652
|
+
sub_h.append(0)
|
|
1653
|
+
h.append(sub_h)
|
|
1654
|
+
self._h = np.array(h)
|
|
1655
|
+
|
|
1656
|
+
|
|
1657
|
+
def compute_u(self,
|
|
1658
|
+
x: float | np.ndarray,
|
|
1659
|
+
T: float | np.ndarray
|
|
1660
|
+
) -> None:
|
|
1661
|
+
r"""No solution"""
|
|
1662
|
+
self._u = None
|
|
1663
|
+
|
|
1664
|
+
|
|
1665
|
+
class Shape_result(ABC):
|
|
1666
|
+
"""Abstract base class representing shape results of a simulated flow.
|
|
1667
|
+
|
|
1668
|
+
This class defines a common interface for flow simulation that compute
|
|
1669
|
+
the geometry of the final shape of a flow simulation.
|
|
1670
|
+
|
|
1671
|
+
Parameters
|
|
1672
|
+
----------
|
|
1673
|
+
theta : float, optional
|
|
1674
|
+
Angle of the surface, in radian, by default 0.
|
|
1675
|
+
|
|
1676
|
+
Attributes
|
|
1677
|
+
----------
|
|
1678
|
+
_g = 9.81 : float
|
|
1679
|
+
Gravitational constant.
|
|
1680
|
+
_theta : float
|
|
1681
|
+
Angle of the surface, in radian.
|
|
1682
|
+
_x : float or np.ndarray
|
|
1683
|
+
Spatial coordinates.
|
|
1684
|
+
_h : np.ndarray
|
|
1685
|
+
Flow height depending on space.
|
|
1686
|
+
"""
|
|
1687
|
+
def __init__(self,
|
|
1688
|
+
theta: float=0):
|
|
1689
|
+
self._g = 9.81
|
|
1690
|
+
self._theta = theta
|
|
1691
|
+
|
|
1692
|
+
self._x = None
|
|
1693
|
+
self._y = None
|
|
1694
|
+
self._h = None
|
|
1695
|
+
|
|
1696
|
+
|
|
1697
|
+
@property
|
|
1698
|
+
def h(self):
|
|
1699
|
+
"""Accessor of the shape h of the flow.
|
|
1700
|
+
|
|
1701
|
+
Returns
|
|
1702
|
+
-------
|
|
1703
|
+
numpy.ndarray
|
|
1704
|
+
Attribute :attr:`_h`. If None, no solution computed.
|
|
1705
|
+
"""
|
|
1706
|
+
return self._h
|
|
1707
|
+
|
|
1708
|
+
|
|
1709
|
+
@property
|
|
1710
|
+
def x(self):
|
|
1711
|
+
"""Accessor of the spatial distribution of the computed solution.
|
|
1712
|
+
|
|
1713
|
+
Returns
|
|
1714
|
+
-------
|
|
1715
|
+
numpy.ndarray
|
|
1716
|
+
Attribute :attr:`_x`. If None, no solution computed.
|
|
1717
|
+
"""
|
|
1718
|
+
return self._x
|
|
1719
|
+
|
|
1720
|
+
|
|
1721
|
+
@property
|
|
1722
|
+
def y(self):
|
|
1723
|
+
"""Accessor of the lateral spatial distribution of the computed solution.
|
|
1724
|
+
|
|
1725
|
+
Returns
|
|
1726
|
+
-------
|
|
1727
|
+
numpy.ndarray
|
|
1728
|
+
Attribute :attr:`_y`. If None, no solution computed.
|
|
1729
|
+
"""
|
|
1730
|
+
return self._y
|
|
1731
|
+
|
|
1732
|
+
|
|
1733
|
+
class Coussot_shape(Shape_result):
|
|
1734
|
+
r"""Shape solution on an inclined dry domain without friction.
|
|
1735
|
+
|
|
1736
|
+
This class implements the final shape of a simulated flow.
|
|
1737
|
+
The flow is over an inclined and flat surface without friction with a finite volume of fluid.
|
|
1738
|
+
It computes the spatial coordinates from the flow lenght and height.
|
|
1739
|
+
|
|
1740
|
+
Coussot, P., Proust, S., & Ancey, C., 1996, Rheological interpretation of deposits of yield stress fluids,
|
|
1741
|
+
Journal of Non-Newtonian Fluid Mechanics, v. 66(1), p. 55-70, doi:10.1016/0377-0257(96)01474-7.
|
|
1742
|
+
|
|
1743
|
+
Parameters
|
|
1744
|
+
----------
|
|
1745
|
+
rho : float
|
|
1746
|
+
Fluid density.
|
|
1747
|
+
tau : float
|
|
1748
|
+
Threshold constraint.
|
|
1749
|
+
theta : float, optional
|
|
1750
|
+
Angle of the surface, in degree, by default 0.
|
|
1751
|
+
h_final : float, optional
|
|
1752
|
+
The final flow depth, by default 1.
|
|
1753
|
+
H_size : int, optional
|
|
1754
|
+
Number of value wanted in the H array, by default 100.
|
|
1755
|
+
|
|
1756
|
+
Attributes
|
|
1757
|
+
----------
|
|
1758
|
+
_rho : float
|
|
1759
|
+
Fluid density.
|
|
1760
|
+
_tau : float
|
|
1761
|
+
Threshold constraint.
|
|
1762
|
+
_D : float or numpy.ndarray
|
|
1763
|
+
Normalized distance of the front from the origin.
|
|
1764
|
+
_H : float or numpy.ndarray
|
|
1765
|
+
Normalized fluid depth.
|
|
1766
|
+
_d : float or numpy.ndarray
|
|
1767
|
+
Distance of the front from the origin.
|
|
1768
|
+
_h : float or numpy.ndarray
|
|
1769
|
+
Fluid depth.
|
|
1770
|
+
_H_size : int
|
|
1771
|
+
Number of point in H-axis.
|
|
1772
|
+
|
|
1773
|
+
"""
|
|
1774
|
+
def __init__(self,
|
|
1775
|
+
rho: float,
|
|
1776
|
+
tau: float,
|
|
1777
|
+
theta: float=0,
|
|
1778
|
+
h_final: float=1,
|
|
1779
|
+
H_size: int=100
|
|
1780
|
+
):
|
|
1781
|
+
super().__init__(np.radians(theta))
|
|
1782
|
+
self._rho = rho
|
|
1783
|
+
self._tau = tau
|
|
1784
|
+
|
|
1785
|
+
self._H_size = H_size
|
|
1786
|
+
self._D = None
|
|
1787
|
+
self._d = None
|
|
1788
|
+
if theta>0 and self.h_to_H(h_final) >=1:
|
|
1789
|
+
self._H = np.linspace(0, 0.99999999, H_size)
|
|
1790
|
+
else:
|
|
1791
|
+
self._H = np.linspace(0, self.h_to_H(h_final), H_size)
|
|
1792
|
+
self._h = np.array([self.H_to_h(H) for H in self._H])
|
|
1793
|
+
|
|
1794
|
+
|
|
1795
|
+
def h_to_H(self,
|
|
1796
|
+
h: float
|
|
1797
|
+
) -> float:
|
|
1798
|
+
r"""Normalize the fluid depth by following:
|
|
1799
|
+
|
|
1800
|
+
.. math::
|
|
1801
|
+
H = \frac{\rho g h \sin(\theta)}{\tau_c}
|
|
1802
|
+
|
|
1803
|
+
If :math:`\theta = 0`, the expression is:
|
|
1804
|
+
|
|
1805
|
+
.. math::
|
|
1806
|
+
H = \frac{\rho g h}{\tau_c}
|
|
1807
|
+
|
|
1808
|
+
Parameters
|
|
1809
|
+
----------
|
|
1810
|
+
h : float
|
|
1811
|
+
Initial fluid depth.
|
|
1812
|
+
|
|
1813
|
+
Returns
|
|
1814
|
+
-------
|
|
1815
|
+
float
|
|
1816
|
+
Normalized fluid depth.
|
|
1817
|
+
"""
|
|
1818
|
+
if self._theta == 0:
|
|
1819
|
+
return (self._rho*self._g*h)/self._tau
|
|
1820
|
+
else:
|
|
1821
|
+
return (self._rho*self._g*h*np.sin(self._theta))/self._tau
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
def H_to_h(self,
|
|
1825
|
+
H: float
|
|
1826
|
+
) -> float:
|
|
1827
|
+
r"""Find the original value of the fluid depth from the normalized one
|
|
1828
|
+
by following:
|
|
1829
|
+
|
|
1830
|
+
.. math::
|
|
1831
|
+
h = \frac{H \tau_c}{\rho g \sin(\theta)}
|
|
1832
|
+
|
|
1833
|
+
If :math:`\theta = 0`, the expression is:
|
|
1834
|
+
|
|
1835
|
+
.. math::
|
|
1836
|
+
h = \frac{H \tau_c}{\rho g}
|
|
1837
|
+
|
|
1838
|
+
Parameters
|
|
1839
|
+
----------
|
|
1840
|
+
H : float
|
|
1841
|
+
Normalized value of the fluid depth.
|
|
1842
|
+
|
|
1843
|
+
Returns
|
|
1844
|
+
-------
|
|
1845
|
+
float
|
|
1846
|
+
True value of the fluid depth.
|
|
1847
|
+
"""
|
|
1848
|
+
if self._theta == 0:
|
|
1849
|
+
return ((H*self._tau)/(self._rho*self._g))
|
|
1850
|
+
else:
|
|
1851
|
+
return ((H*self._tau)/(self._rho*self._g*np.sin(self._theta)))
|
|
1852
|
+
|
|
1853
|
+
|
|
1854
|
+
def x_to_X(self,
|
|
1855
|
+
x: float
|
|
1856
|
+
) -> float:
|
|
1857
|
+
r"""Normalize the spatial coordinates by following:
|
|
1858
|
+
|
|
1859
|
+
.. math::
|
|
1860
|
+
X = \frac{\rho g x (\sin(\theta))^2}{\tau_c \cos(\theta)}
|
|
1861
|
+
|
|
1862
|
+
If :math:`\theta = 0`, the expression is:
|
|
1863
|
+
|
|
1864
|
+
.. math::
|
|
1865
|
+
X = \frac{\rho g x}{\tau_c}
|
|
1866
|
+
|
|
1867
|
+
Parameters
|
|
1868
|
+
----------
|
|
1869
|
+
x : float
|
|
1870
|
+
Initial spatial coordinate.
|
|
1871
|
+
|
|
1872
|
+
Returns
|
|
1873
|
+
-------
|
|
1874
|
+
float
|
|
1875
|
+
Normalized spatial coordinate.
|
|
1876
|
+
"""
|
|
1877
|
+
if self._theta == 0:
|
|
1878
|
+
return (self._rho*self._g*x)/self._tau
|
|
1879
|
+
else:
|
|
1880
|
+
return (self._rho*self._g*x*np.sin(self._theta)*np.sin(self._theta)) / (self._tau*np.cos(self._theta))
|
|
1881
|
+
|
|
1882
|
+
|
|
1883
|
+
def X_to_x(self,
|
|
1884
|
+
X: float
|
|
1885
|
+
) -> float:
|
|
1886
|
+
r"""Find the original value of the spatial coordinates from the normalized one
|
|
1887
|
+
by following:
|
|
1888
|
+
|
|
1889
|
+
.. math::
|
|
1890
|
+
x = \frac{X \tau_c \cos(\theta)}{\rho g (\sin(\theta))^2}
|
|
1891
|
+
|
|
1892
|
+
If :math:`\theta = 0`, the expression is:
|
|
1893
|
+
|
|
1894
|
+
.. math::
|
|
1895
|
+
x = \frac{X \tau_c}{\rho g}
|
|
1896
|
+
|
|
1897
|
+
Parameters
|
|
1898
|
+
----------
|
|
1899
|
+
X : float
|
|
1900
|
+
Normalized values of the spatial coordinates.
|
|
1901
|
+
|
|
1902
|
+
Returns
|
|
1903
|
+
-------
|
|
1904
|
+
float
|
|
1905
|
+
True value of the spatial coordinate.
|
|
1906
|
+
"""
|
|
1907
|
+
if self._theta == 0:
|
|
1908
|
+
return (X*self._tau)/(self._rho*self._g)
|
|
1909
|
+
else:
|
|
1910
|
+
return (X*self._tau*np.cos(self._theta))/(self._rho*self._g*np.sin(self._theta)*np.sin(self._theta))
|
|
1911
|
+
|
|
1912
|
+
|
|
1913
|
+
def compute_rheological_test_front_morpho(self) -> None:
|
|
1914
|
+
r"""Compute the shape of the frontal lobe from the normalized fluid depth for a rheological test on an inclined
|
|
1915
|
+
surface by following :
|
|
1916
|
+
|
|
1917
|
+
.. math::
|
|
1918
|
+
D = - H - \ln(1 - H)
|
|
1919
|
+
|
|
1920
|
+
If :math:`\theta = 0`, the expression is:
|
|
1921
|
+
|
|
1922
|
+
.. math::
|
|
1923
|
+
D = \frac{H^2}{2}
|
|
1924
|
+
"""
|
|
1925
|
+
if self._theta == 0:
|
|
1926
|
+
D = []
|
|
1927
|
+
d = []
|
|
1928
|
+
for H_val in self._H:
|
|
1929
|
+
D.append((H_val*H_val)/2)
|
|
1930
|
+
d.append(self.X_to_x(D[-1]))
|
|
1931
|
+
|
|
1932
|
+
else:
|
|
1933
|
+
D = []
|
|
1934
|
+
d = []
|
|
1935
|
+
for H_val in self._H:
|
|
1936
|
+
D.append(- H_val - np.log(1 - H_val))
|
|
1937
|
+
d.append(self.X_to_x(D[-1]))
|
|
1938
|
+
|
|
1939
|
+
self._D = np.array(D)
|
|
1940
|
+
self._d = np.array(d)
|
|
1941
|
+
|
|
1942
|
+
|
|
1943
|
+
def compute_rheological_test_lateral_morpho(self) -> None:
|
|
1944
|
+
r"""Compute the shape of the lateral lobe from the normalized fluid depth for a rheological test on an inclined
|
|
1945
|
+
surface by following :
|
|
1946
|
+
|
|
1947
|
+
.. math::
|
|
1948
|
+
D = 1 - \sqrt{1 - H^2}
|
|
1949
|
+
"""
|
|
1950
|
+
D = []
|
|
1951
|
+
d = []
|
|
1952
|
+
for H_val in self._H:
|
|
1953
|
+
D.append(1 - np.sqrt(1 - (H_val**2)))
|
|
1954
|
+
d.append(self.X_to_x(D[-1]))
|
|
1955
|
+
|
|
1956
|
+
self._D = np.array(D)
|
|
1957
|
+
self._d = np.array(d)
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
def compute_slump_test_hf(self, h_init: float) -> float:
|
|
1961
|
+
r"""Compute the final fluid depth for a cylindrical slump test following :
|
|
1962
|
+
|
|
1963
|
+
.. math::
|
|
1964
|
+
\frac{h_f}{h_i} = 1 - \frac{2 \tau_c}{\rho g h_i} \left( 1 - \ln{\frac{2 \tau_c}{\rho g h_i}} \right)
|
|
1965
|
+
|
|
1966
|
+
N. Pashias, D. V. Boger, J. Summers, D. J. Glenister; A fifty cent rheometer for yield stress measurement. J. Rheol.
|
|
1967
|
+
1 November 1996; 40 (6): 1179-1189. https://doi.org/10.1122/1.550780
|
|
1968
|
+
|
|
1969
|
+
Parameters
|
|
1970
|
+
----------
|
|
1971
|
+
h_init : float
|
|
1972
|
+
Initial fluid depth.
|
|
1973
|
+
|
|
1974
|
+
Returns
|
|
1975
|
+
-------
|
|
1976
|
+
float
|
|
1977
|
+
Final fluid depth
|
|
1978
|
+
"""
|
|
1979
|
+
H_init = self.h_to_H(h_init)
|
|
1980
|
+
val = 1 - ((2/H_init)*(1-np.log(2/H_init)))
|
|
1981
|
+
return self.H_to_h(val*H_init)
|
|
1982
|
+
|
|
1983
|
+
|
|
1984
|
+
def translate_front(self, d_final: float) -> None:
|
|
1985
|
+
"""Translate the shape of the frontal (or transversal) lobe to the wanted x (or y) coordinate.
|
|
1986
|
+
|
|
1987
|
+
Parameters
|
|
1988
|
+
----------
|
|
1989
|
+
d_final : float
|
|
1990
|
+
Final wanted coordinate.
|
|
1991
|
+
"""
|
|
1992
|
+
self._d += d_final
|
|
1993
|
+
|
|
1994
|
+
|
|
1995
|
+
def change_orientation_flow(self) -> None:
|
|
1996
|
+
"""Swap the direction of the result.
|
|
1997
|
+
|
|
1998
|
+
Notes
|
|
1999
|
+
------
|
|
2000
|
+
There must not have been any prior translation to use this method.
|
|
2001
|
+
"""
|
|
2002
|
+
self._h = self._h[::-1]
|
|
2003
|
+
new_d = [-1*v for v in self._d]
|
|
2004
|
+
self._d = np.array(new_d[::-1])
|
|
2005
|
+
|
|
2006
|
+
|
|
2007
|
+
def interpolate_on_d(self) -> None:
|
|
2008
|
+
"""Interpolate the profile on d-axis.
|
|
2009
|
+
"""
|
|
2010
|
+
from scipy.interpolate import interp1d
|
|
2011
|
+
|
|
2012
|
+
d_min, d_max = self._d.min(), self._d.max()
|
|
2013
|
+
d_curve = np.linspace(d_min, d_max, self._H_size)
|
|
2014
|
+
|
|
2015
|
+
f = interp1d(self._d, self._h, kind='cubic')
|
|
2016
|
+
h_curve = f(d_curve)
|
|
2017
|
+
|
|
2018
|
+
self._d = d_curve
|
|
2019
|
+
self._h = h_curve
|
|
2020
|
+
|
|
2021
|
+
|
|
2022
|
+
@property
|
|
2023
|
+
def d(self):
|
|
2024
|
+
"""Accessor of the spatial distribution of the computed solution.
|
|
2025
|
+
|
|
2026
|
+
Returns
|
|
2027
|
+
-------
|
|
2028
|
+
numpy.ndarray
|
|
2029
|
+
Attribute :attr:`_d`. If None, no solution computed.
|
|
2030
|
+
"""
|
|
2031
|
+
return self._d
|
|
2032
|
+
|
|
2033
|
+
|
|
2034
|
+
class Front_result:
|
|
2035
|
+
"""Class computing front position of a simulated flow.
|
|
2036
|
+
|
|
2037
|
+
This class defines multiple methods for flow simulation that compute
|
|
2038
|
+
the position of the front flow at the specified moment.
|
|
2039
|
+
|
|
2040
|
+
Parameters
|
|
2041
|
+
----------
|
|
2042
|
+
h0 : float
|
|
2043
|
+
Initial fluid depth.
|
|
2044
|
+
|
|
2045
|
+
Attributes
|
|
2046
|
+
----------
|
|
2047
|
+
_g = 9.81 : float
|
|
2048
|
+
Gravitational constant.
|
|
2049
|
+
_h0 : float
|
|
2050
|
+
Initial fluid depth.
|
|
2051
|
+
_xf : dictionnary
|
|
2052
|
+
Dictionnary of spatial coordinates of the front flow for each time step (keys).
|
|
2053
|
+
_labels : dictionnary
|
|
2054
|
+
Dictionnary of spatial coordinates computation's method for each time step (keys).
|
|
2055
|
+
"""
|
|
2056
|
+
def __init__(self,
|
|
2057
|
+
h0: float,
|
|
2058
|
+
):
|
|
2059
|
+
self._g = 9.81
|
|
2060
|
+
|
|
2061
|
+
self._h0 = h0
|
|
2062
|
+
|
|
2063
|
+
self._xf = {}
|
|
2064
|
+
self._labels = {}
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
def xf_mangeney(self,
|
|
2068
|
+
t: float,
|
|
2069
|
+
delta: float,
|
|
2070
|
+
theta: float=0
|
|
2071
|
+
) -> float:
|
|
2072
|
+
r"""
|
|
2073
|
+
Mangeney's equation for a dam-break solution over an infinite inclined dry domain with friction
|
|
2074
|
+
and an infinitely-long fluid mass:
|
|
2075
|
+
|
|
2076
|
+
.. math::
|
|
2077
|
+
x_f(t) = \frac{1}{2}mt^2 + 2 c_0 t
|
|
2078
|
+
|
|
2079
|
+
with :math:`c_0` the initial wave propagation speed defined by:
|
|
2080
|
+
|
|
2081
|
+
.. math::
|
|
2082
|
+
c_0 = \sqrt{g h_0 \cos{\theta}}
|
|
2083
|
+
|
|
2084
|
+
and :math:`m` the constant horizontal acceleration of the front defined by:
|
|
2085
|
+
|
|
2086
|
+
.. math::
|
|
2087
|
+
m = g \sin{\theta} - g \cos{\theta} \tan{\delta}
|
|
2088
|
+
|
|
2089
|
+
Mangeney, A., Heinrich, P., & Roche, R., 2000, Analytical solution for testing debris avalanche numerical models,
|
|
2090
|
+
Pure and Applied Geophysics, vol. 157, p. 1081-1096.
|
|
2091
|
+
|
|
2092
|
+
Parameters
|
|
2093
|
+
----------
|
|
2094
|
+
t : float
|
|
2095
|
+
Time instant.
|
|
2096
|
+
delta : float
|
|
2097
|
+
Dynamic friction angle, in degree.
|
|
2098
|
+
theta : float
|
|
2099
|
+
Slope angle, in degree.
|
|
2100
|
+
|
|
2101
|
+
Returns
|
|
2102
|
+
-------
|
|
2103
|
+
float
|
|
2104
|
+
Position of the front edge of the fluid.
|
|
2105
|
+
"""
|
|
2106
|
+
theta_rad = np.radians(theta)
|
|
2107
|
+
delta_rad = np.radians(delta)
|
|
2108
|
+
|
|
2109
|
+
m = self._g * np.sin(theta_rad) - (self._g * np.cos(theta_rad) * np.tan(delta_rad))
|
|
2110
|
+
c0 = np.sqrt(self._g * self._h0 * np.cos(theta_rad))
|
|
2111
|
+
xf = 0.5*m*(t**2) + (2*c0*t)
|
|
2112
|
+
|
|
2113
|
+
if t in self._labels:
|
|
2114
|
+
if f"Mangeney d{delta}" not in self._labels[t] :
|
|
2115
|
+
self._labels[t].append(f"Mangeney d{delta}")
|
|
2116
|
+
self._xf[t].append(xf)
|
|
2117
|
+
else:
|
|
2118
|
+
self._labels[t] = [f"Mangeney d{delta}"]
|
|
2119
|
+
self._xf[t] = [xf]
|
|
2120
|
+
|
|
2121
|
+
return xf
|
|
2122
|
+
|
|
2123
|
+
|
|
2124
|
+
def xf_dressler(self,
|
|
2125
|
+
t: float,
|
|
2126
|
+
) -> float:
|
|
2127
|
+
r"""
|
|
2128
|
+
Dressler's equation for a dam-break solution over an infinite inclined dry domain with friction:
|
|
2129
|
+
|
|
2130
|
+
.. math::
|
|
2131
|
+
x_f(t) = 2 t \sqrt{g h_0}
|
|
2132
|
+
|
|
2133
|
+
Parameters
|
|
2134
|
+
----------
|
|
2135
|
+
t : float
|
|
2136
|
+
Time instant.
|
|
2137
|
+
|
|
2138
|
+
Returns
|
|
2139
|
+
-------
|
|
2140
|
+
float
|
|
2141
|
+
Position of the front edge of the fluid.
|
|
2142
|
+
"""
|
|
2143
|
+
xf = 2 * t * np.sqrt(self._g*self._h0)
|
|
2144
|
+
|
|
2145
|
+
if t in self._labels:
|
|
2146
|
+
if "Dressler" not in self._labels[t] :
|
|
2147
|
+
self._labels[t].append("Dressler")
|
|
2148
|
+
self._xf[t].append(xf)
|
|
2149
|
+
else:
|
|
2150
|
+
self._labels[t] = ["Dressler"]
|
|
2151
|
+
self._xf[t] = [xf]
|
|
2152
|
+
|
|
2153
|
+
return xf
|
|
2154
|
+
|
|
2155
|
+
|
|
2156
|
+
def xf_ritter(self,
|
|
2157
|
+
t: float
|
|
2158
|
+
) -> float:
|
|
2159
|
+
r"""
|
|
2160
|
+
Ritter's equation for a dam-break solution over an infinite inclined dry domain without friction:
|
|
2161
|
+
|
|
2162
|
+
.. math::
|
|
2163
|
+
x_f(t) = 2 t \sqrt{g h_0}
|
|
2164
|
+
|
|
2165
|
+
Ritter A. Die Fortpflanzung der Wasserwellen. Zeitschrift des Vereines Deuscher Ingenieure
|
|
2166
|
+
August 1892; 36(33): 947-954.
|
|
2167
|
+
|
|
2168
|
+
Parameters
|
|
2169
|
+
----------
|
|
2170
|
+
t : float
|
|
2171
|
+
Time instant.
|
|
2172
|
+
|
|
2173
|
+
Returns
|
|
2174
|
+
-------
|
|
2175
|
+
float
|
|
2176
|
+
Position of the front edge of the fluid.
|
|
2177
|
+
"""
|
|
2178
|
+
xf = 2 * t * np.sqrt(self._g*self._h0)
|
|
2179
|
+
|
|
2180
|
+
if t in self._labels:
|
|
2181
|
+
if "Ritter" not in self._labels[t] :
|
|
2182
|
+
self._labels[t].append("Ritter")
|
|
2183
|
+
self._xf[t].append(xf)
|
|
2184
|
+
else:
|
|
2185
|
+
self._labels[t] = ["Ritter"]
|
|
2186
|
+
self._xf[t] = [xf]
|
|
2187
|
+
|
|
2188
|
+
return xf
|
|
2189
|
+
|
|
2190
|
+
|
|
2191
|
+
def xf_stoker(self,
|
|
2192
|
+
t: float,
|
|
2193
|
+
hr: float
|
|
2194
|
+
) -> float:
|
|
2195
|
+
r"""
|
|
2196
|
+
Stoker's equation for a dam-break solution over an infinite inclined wet domain without friction:
|
|
2197
|
+
|
|
2198
|
+
.. math::
|
|
2199
|
+
x_f(t) =t c_m
|
|
2200
|
+
|
|
2201
|
+
with :math:`c_m` the front wave velocity solution of:
|
|
2202
|
+
|
|
2203
|
+
.. math::
|
|
2204
|
+
c_m h_r - h_r \left( \sqrt{1 + \frac{8 c_m^2}{g h_r}} - 1 \right) \left( \frac{c_m}{2} - \sqrt{g h_0} + \sqrt{\frac{g h_r}{2} \left( \sqrt{1 + \frac{8 c_m^2}{g h_r}} - 1 \right)} \right) = 0
|
|
2205
|
+
|
|
2206
|
+
Stoker JJ. Water Waves: The Mathematical Theory with Applications, Pure and Applied Mathematics,
|
|
2207
|
+
Vol. 4. Interscience Publishers: New York, USA, 1957.
|
|
2208
|
+
|
|
2209
|
+
Sarkhosh, P. (2021). Stoker solution package (1.0.0). Zenodo. https://doi.org/10.5281/zenodo.5598374
|
|
2210
|
+
|
|
2211
|
+
Parameters
|
|
2212
|
+
----------
|
|
2213
|
+
t : float
|
|
2214
|
+
Time instant.
|
|
2215
|
+
hr : float
|
|
2216
|
+
Fluid depth at the right of the dam.
|
|
2217
|
+
|
|
2218
|
+
Returns
|
|
2219
|
+
-------
|
|
2220
|
+
float
|
|
2221
|
+
Position of the front edge of the fluid.
|
|
2222
|
+
"""
|
|
2223
|
+
f_cm = 1
|
|
2224
|
+
df_cm = 1
|
|
2225
|
+
cm = 10 * self._h0
|
|
2226
|
+
|
|
2227
|
+
while abs(f_cm / cm) > 1e-10:
|
|
2228
|
+
root_term = np.sqrt(8 * cm**2 / np.sqrt(self._g * hr)**2 + 1)
|
|
2229
|
+
inner_sqrt = np.sqrt(self._g * hr * (root_term - 1) / 2)
|
|
2230
|
+
|
|
2231
|
+
f_cm = cm * hr - hr * (root_term - 1) * (cm / 2 - np.sqrt(self._g * self._h0) + inner_sqrt)
|
|
2232
|
+
df_cm = (hr
|
|
2233
|
+
- hr * ((2 * cm * self._g * hr) / (np.sqrt(self._g * hr)**2 * root_term * inner_sqrt) + 0.5) * (root_term - 1)
|
|
2234
|
+
- (8 * cm * hr * (cm / 2 - np.sqrt(self._g * self._h0) + inner_sqrt)) / (np.sqrt(self._g * hr)**2 * root_term))
|
|
2235
|
+
|
|
2236
|
+
cm -= f_cm / df_cm
|
|
2237
|
+
|
|
2238
|
+
xf = cm * t
|
|
2239
|
+
|
|
2240
|
+
if t in self._labels:
|
|
2241
|
+
if "Stoker" not in self._labels[t] :
|
|
2242
|
+
self._labels[t].append("Stoker")
|
|
2243
|
+
self._xf[t].append(xf)
|
|
2244
|
+
else:
|
|
2245
|
+
self._labels[t] = ["Stoker"]
|
|
2246
|
+
self._xf[t] = [xf]
|
|
2247
|
+
|
|
2248
|
+
return xf
|
|
2249
|
+
|
|
2250
|
+
|
|
2251
|
+
def xf_chanson(self,
|
|
2252
|
+
t: float,
|
|
2253
|
+
f: float
|
|
2254
|
+
) -> float:
|
|
2255
|
+
r"""
|
|
2256
|
+
Chanson's equation for a dam-break solution over an infinite inclined dry domain with friction:
|
|
2257
|
+
|
|
2258
|
+
.. math::
|
|
2259
|
+
x_f(t) = \left( \frac{3}{2} \frac{U(t)}{\sqrt{g h_0}} - 1 \right) t \sqrt{\frac{g}{h_0}} + \frac{4}{f\frac{U(t)^2}{g h_0}} \left( 1 - \frac{U(t)}{2 \sqrt{g h_0}} \right)^4
|
|
2260
|
+
|
|
2261
|
+
with :math:`U(t)` the front wave velocity solution of:
|
|
2262
|
+
|
|
2263
|
+
.. math::
|
|
2264
|
+
\left( \frac{U}{\sqrt{g h_0}} \right)^3 - 8 \left( 0.75 - \frac{3 f t \sqrt{g}}{8 \sqrt{h_0}} \right) \left( \frac{U}{\sqrt{g h_0}} \right)^2 + 12 \left( \frac{U}{\sqrt{g h_0}} \right) - 8 = 0
|
|
2265
|
+
|
|
2266
|
+
Chanson, Hubert. (2005). Analytical Solution of Dam Break Wave with Flow Resistance: Application to Tsunami Surges. 137.
|
|
2267
|
+
|
|
2268
|
+
Parameters
|
|
2269
|
+
----------
|
|
2270
|
+
t : float
|
|
2271
|
+
Time instant.
|
|
2272
|
+
f : float
|
|
2273
|
+
Darcy friction coefficient.
|
|
2274
|
+
|
|
2275
|
+
Returns
|
|
2276
|
+
-------
|
|
2277
|
+
float
|
|
2278
|
+
Position of the front edge of the fluid.
|
|
2279
|
+
"""
|
|
2280
|
+
coeffs = [1, (-8*(0.75 - ((3 * f * t * np.sqrt(self._g)) / (8 * np.sqrt(self._h0))))), 12, -8]
|
|
2281
|
+
roots = np.roots(coeffs)
|
|
2282
|
+
|
|
2283
|
+
real_root = roots[-1].real
|
|
2284
|
+
cf = real_root * np.sqrt(self._g * self._h0)
|
|
2285
|
+
|
|
2286
|
+
term1 = ((1.5 * (cf / np.sqrt(self._g * self._h0))) - 1) * np.sqrt(self._g / self._h0) * t
|
|
2287
|
+
term2 = (4 / (f * ((cf**2) / (self._g * self._h0)))) * (1 - 0.5 * (cf / np.sqrt(self._g * self._h0)))**4
|
|
2288
|
+
|
|
2289
|
+
xf = self._h0 * (term1 + term2)
|
|
2290
|
+
|
|
2291
|
+
if t in self._labels:
|
|
2292
|
+
if "Chanson" not in self._labels[t] :
|
|
2293
|
+
self._labels[t].append("Chanson")
|
|
2294
|
+
self._xf[t].append(xf)
|
|
2295
|
+
else:
|
|
2296
|
+
self._labels[t] = ["Chanson"]
|
|
2297
|
+
self._xf[t] = [xf]
|
|
2298
|
+
|
|
2299
|
+
return xf
|
|
2300
|
+
|
|
2301
|
+
|
|
2302
|
+
def compute_cf(self, t: float) -> float:
|
|
2303
|
+
r"""Compute the celerity of the wave front by resolving:
|
|
2304
|
+
|
|
2305
|
+
|
|
2306
|
+
Parameters
|
|
2307
|
+
----------
|
|
2308
|
+
t : float
|
|
2309
|
+
Time instant
|
|
2310
|
+
|
|
2311
|
+
Returns
|
|
2312
|
+
-------
|
|
2313
|
+
float
|
|
2314
|
+
Value of the front wave velocity.
|
|
2315
|
+
"""
|
|
2316
|
+
|
|
2317
|
+
|
|
2318
|
+
def show_fronts_over_methods(self, x_unit: str="m") -> None:
|
|
2319
|
+
"""Plot the front distance from the initial position for each method.
|
|
2320
|
+
|
|
2321
|
+
Parameters
|
|
2322
|
+
----------
|
|
2323
|
+
x_unit : str, optional
|
|
2324
|
+
X-axis unit, by default "m"
|
|
2325
|
+
"""
|
|
2326
|
+
fig, ax = plt.subplots(figsize=(10, 5))
|
|
2327
|
+
|
|
2328
|
+
label_order = []
|
|
2329
|
+
for t in sorted(self._labels.keys()):
|
|
2330
|
+
for label in self._labels[t]:
|
|
2331
|
+
if label not in label_order:
|
|
2332
|
+
label_order.append(label)
|
|
2333
|
+
|
|
2334
|
+
y_levels = {label: i for i, label in enumerate(reversed(label_order))}
|
|
2335
|
+
yticks = list(y_levels.values())
|
|
2336
|
+
yticklabels = list(reversed(label_order))
|
|
2337
|
+
|
|
2338
|
+
sorted_times = sorted(self._xf.keys())
|
|
2339
|
+
colors = cm.copper(np.linspace(0, 1, len(sorted_times)))
|
|
2340
|
+
|
|
2341
|
+
for color, t in zip(colors, sorted_times):
|
|
2342
|
+
x_list = self._xf[t]
|
|
2343
|
+
label_list = self._labels[t]
|
|
2344
|
+
|
|
2345
|
+
for x, label in zip(x_list, label_list):
|
|
2346
|
+
y = y_levels[label]
|
|
2347
|
+
ax.vlines(x, y - 0.3, y + 0.3, color=color, linewidth=2)
|
|
2348
|
+
ax.text(x + 1, y, f"{x:.2f}", rotation=90, va='center', fontsize=8, color=color)
|
|
2349
|
+
|
|
2350
|
+
ax.set_yticks(yticks)
|
|
2351
|
+
ax.set_yticklabels(yticklabels)
|
|
2352
|
+
ax.invert_yaxis()
|
|
2353
|
+
|
|
2354
|
+
ax.set_xlim(left=0)
|
|
2355
|
+
ax.set_xlim(right=max(x for sublist in self._xf.values() for x in sublist) + 5)
|
|
2356
|
+
|
|
2357
|
+
ax.set_xlabel(f"x [{x_unit}]")
|
|
2358
|
+
ax.set_title("Flow front positions over time")
|
|
2359
|
+
|
|
2360
|
+
ax.grid(True, axis='x')
|
|
2361
|
+
|
|
2362
|
+
from matplotlib.lines import Line2D
|
|
2363
|
+
legend_elements = [
|
|
2364
|
+
Line2D([0], [0], color=color, lw=2, label=f"t = {t}s")
|
|
2365
|
+
for color, t in zip(colors, sorted_times)
|
|
2366
|
+
]
|
|
2367
|
+
ax.legend(handles=legend_elements, title="Time steps", loc="best")
|
|
2368
|
+
|
|
2369
|
+
plt.tight_layout()
|
|
2370
|
+
plt.show()
|
|
2371
|
+
|
|
2372
|
+
|
|
2373
|
+
def show_fronts_over_time(self, x_unit: str="m") -> None:
|
|
2374
|
+
"""Plot the front distance from the initial position over time for each method.
|
|
2375
|
+
|
|
2376
|
+
Parameters
|
|
2377
|
+
----------
|
|
2378
|
+
x_unit : str, optional
|
|
2379
|
+
X-axis unit, by default "m"
|
|
2380
|
+
"""
|
|
2381
|
+
T = sorted(self._labels.keys())
|
|
2382
|
+
|
|
2383
|
+
dico_xf = {}
|
|
2384
|
+
dico_time = {}
|
|
2385
|
+
for t in T:
|
|
2386
|
+
for i in range(len(self._labels[t])):
|
|
2387
|
+
if self._labels[t][i] not in dico_xf:
|
|
2388
|
+
dico_xf[self._labels[t][i]] = [self._xf[t][i]]
|
|
2389
|
+
dico_time[self._labels[t][i]] = [t]
|
|
2390
|
+
else:
|
|
2391
|
+
dico_xf[self._labels[t][i]].append(self._xf[t][i])
|
|
2392
|
+
dico_time[self._labels[t][i]].append(t)
|
|
2393
|
+
|
|
2394
|
+
for label in dico_xf.keys():
|
|
2395
|
+
plt.scatter(dico_xf[label], dico_time[label], marker='x', label=label)
|
|
2396
|
+
|
|
2397
|
+
plt.xlabel(f'Distance to the dam break [{x_unit}]')
|
|
2398
|
+
plt.ylabel('Time [s]')
|
|
2399
|
+
|
|
2400
|
+
|
|
2401
|
+
plt.grid(which="major")
|
|
2402
|
+
plt.legend(loc='best')
|
|
2403
|
+
plt.show()
|