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
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
import numpy as np
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
from scipy.interpolate import RegularGridInterpolator
|
|
9
|
+
|
|
10
|
+
import tilupy.raster
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_edges_matrices(nx: int, ny: int) -> list[np.ndarray, np.ndarray]:
|
|
14
|
+
"""Numbering edges for a regular rectangular grid.
|
|
15
|
+
|
|
16
|
+
Considering a matrix M with whape (ny, nx),the convention is that
|
|
17
|
+
M[0, 0] corresponds to the lower left corner of the matrix, M[0, -1]
|
|
18
|
+
to the lower right corner, M[-1, 0] to the upper left and M[-1, -1] to
|
|
19
|
+
the upper right. Edges are numbered cell by cell, counter clockwise :
|
|
20
|
+
bottom, right, up, left. The first cell is M[0, 0], then cells are
|
|
21
|
+
processed line by line.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
nx : int
|
|
26
|
+
Number of faces in the X direction
|
|
27
|
+
ny : int
|
|
28
|
+
Number of faces in the Y direction
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
h_edges : numpy.ndarray
|
|
33
|
+
Matrix containing the global edge indices of all horizontal edges
|
|
34
|
+
(bottom and top edges of the cells). Rows correspond to y-levels,
|
|
35
|
+
columns to x-positions.
|
|
36
|
+
v_edges : numpy.ndarray
|
|
37
|
+
Matrix containing the global edge indices of all vertical edges
|
|
38
|
+
(left and right edges of the cells). Rows correspond to y-levels,
|
|
39
|
+
columns to x-positions.
|
|
40
|
+
"""
|
|
41
|
+
# Numbers of horizontal edges
|
|
42
|
+
h_edges = np.zeros((ny + 1, nx))
|
|
43
|
+
# Numbers of vertical edges
|
|
44
|
+
v_edges = np.zeros((ny, nx + 1))
|
|
45
|
+
|
|
46
|
+
# Number of edges on first completed line
|
|
47
|
+
n_edges_l1 = 4 + 3 * (nx - 1)
|
|
48
|
+
# Number of edges on folowwing lines
|
|
49
|
+
n_edges_l = 3 + 2 * (nx - 1)
|
|
50
|
+
|
|
51
|
+
# Fill first line of h_edges
|
|
52
|
+
h_edges[0, 0] = 1
|
|
53
|
+
h_edges[0, 1:] = np.arange(nx - 1) * 3 + 5
|
|
54
|
+
# Fill second line of h_edges
|
|
55
|
+
h_edges[1, :] = h_edges[0, :] + 2
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Fill first column of h_edges
|
|
59
|
+
h_edges[2:, 0] = np.arange(ny - 1) * n_edges_l + n_edges_l1 + 2
|
|
60
|
+
# Fill the rest of the table
|
|
61
|
+
tmp = np.concatenate(([3], np.arange(nx - 2) * 2 + 5))
|
|
62
|
+
h_edges[2:, 1:] = h_edges[2:, 0][:, np.newaxis] + tmp[np.newaxis, :]
|
|
63
|
+
except IndexError:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
# Fill first 2 columns of v_edges
|
|
67
|
+
v_edges[0, 0] = 4
|
|
68
|
+
try:
|
|
69
|
+
v_edges[1:, 0] = np.arange(ny - 1) * n_edges_l + n_edges_l1 + 3
|
|
70
|
+
except IndexError:
|
|
71
|
+
pass
|
|
72
|
+
v_edges[:, 1] = v_edges[:, 0] - 2
|
|
73
|
+
# Fill the rest of v_edges
|
|
74
|
+
try:
|
|
75
|
+
v_edges[0, 2:] = 2 + np.concatenate(([4], np.arange(nx - 2) * 3 + 7))
|
|
76
|
+
tmp = np.concatenate(([3], np.arange(nx - 2) * 2 + 5))
|
|
77
|
+
v_edges[1:, 2:] = v_edges[1:, 1][:, np.newaxis] + tmp[np.newaxis, :]
|
|
78
|
+
except IndexError:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
h_edges = np.flip(h_edges, axis=0).astype(int)
|
|
82
|
+
v_edges = np.flip(v_edges, axis=0).astype(int)
|
|
83
|
+
return h_edges, v_edges
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ModellingDomain:
|
|
87
|
+
"""Mesh of simulation topography
|
|
88
|
+
|
|
89
|
+
This class represents a structured 2D mesh built either from a raster
|
|
90
|
+
or from user-specified dimensions and spacing.
|
|
91
|
+
It stores the topographic data (z), the grid geometry (x, y, dx, dy, nx, ny),
|
|
92
|
+
and the numbering of edges used for discretization of the domain.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
raster : numpy.ndarray, str, or None, optional
|
|
97
|
+
- If numpy.ndarray: used directly as the elevation grid (z).
|
|
98
|
+
- If str: path to a raster file readable by :func:`tilupy.raster.read_raster`.
|
|
99
|
+
- If None: grid is generated from :data:`nx`, :data:`ny`, :data:`dx`, :data:`dy`.
|
|
100
|
+
xmin : float, optional
|
|
101
|
+
Minimum X coordinate of the grid, used if :data:`raster` is None,
|
|
102
|
+
by default 0.0.
|
|
103
|
+
ymin : float, optional
|
|
104
|
+
Minimum Y coordinate of the grid, used if :data:`raster` is None,
|
|
105
|
+
by default 0.0.
|
|
106
|
+
nx : int, optional
|
|
107
|
+
Number of grid points in the X direction, used if :data:`raster` is None,
|
|
108
|
+
by default None.
|
|
109
|
+
ny : int, optional
|
|
110
|
+
Number of grid points in the Y direction, used if :data:`raster` is None,
|
|
111
|
+
by default None.
|
|
112
|
+
dx : float, optional
|
|
113
|
+
Grid spacing along X, by default 1.0.
|
|
114
|
+
dy : float, optional
|
|
115
|
+
Grid spacing along Y, by default 1.0.
|
|
116
|
+
|
|
117
|
+
Attributes
|
|
118
|
+
----------
|
|
119
|
+
_z : numpy.ndarray
|
|
120
|
+
Elevation values of the mesh nodes ([ny, nx]).
|
|
121
|
+
_nx : int
|
|
122
|
+
Number of grid points along the X direction.
|
|
123
|
+
_ny : int
|
|
124
|
+
Number of grid points along the Y direction.
|
|
125
|
+
_dx : float
|
|
126
|
+
Grid resolution in the X direction.
|
|
127
|
+
_dy : float
|
|
128
|
+
Grid resolution in the Y direction.
|
|
129
|
+
_x : numpy.ndarray
|
|
130
|
+
Array of X coordinates.
|
|
131
|
+
_y : numpy.ndarray
|
|
132
|
+
Array of Y coordinates.
|
|
133
|
+
_h_edges : numpy.ndarray
|
|
134
|
+
Matrix of horizontal edge numbers ([ny, nx-1]).
|
|
135
|
+
_v_edges : numpy.ndarray
|
|
136
|
+
Matrix of vertical edge numbers ([ny-1, nx]).
|
|
137
|
+
"""
|
|
138
|
+
def __init__(self,
|
|
139
|
+
raster: np.ndarray = None,
|
|
140
|
+
xmin: float = 0,
|
|
141
|
+
ymin: float = 0,
|
|
142
|
+
nx: int = None,
|
|
143
|
+
ny: int = None,
|
|
144
|
+
dx: float = 1,
|
|
145
|
+
dy: float = 1,
|
|
146
|
+
):
|
|
147
|
+
if raster is not None:
|
|
148
|
+
if isinstance(raster, np.ndarray):
|
|
149
|
+
self._z = raster
|
|
150
|
+
self._nx = raster.shape[1]
|
|
151
|
+
self._ny = raster.shape[0]
|
|
152
|
+
self._dx = dx
|
|
153
|
+
self._dy = dy
|
|
154
|
+
self._x = np.arange(xmin,
|
|
155
|
+
xmin + (self._nx - 1) * self._dx + self._dx / 2,
|
|
156
|
+
self._dx)
|
|
157
|
+
self._y = np.arange(ymin,
|
|
158
|
+
ymin + (self._ny - 1) * self._dy + self._dy / 2,
|
|
159
|
+
self._dy)
|
|
160
|
+
|
|
161
|
+
if isinstance(raster, str):
|
|
162
|
+
self._x, self._y, self._z = tilupy.raster.read_raster(raster)
|
|
163
|
+
self._nx = len(self._x)
|
|
164
|
+
self._ny = len(self._y)
|
|
165
|
+
self._dx = self._x[1] - self._x[0]
|
|
166
|
+
self._dy = self._y[1] - self._y[0]
|
|
167
|
+
else:
|
|
168
|
+
self._z = raster
|
|
169
|
+
self._nx = nx
|
|
170
|
+
self._ny = ny
|
|
171
|
+
self._dx = dx
|
|
172
|
+
self._dy = dy
|
|
173
|
+
if (xmin is not None
|
|
174
|
+
and ymin is not None
|
|
175
|
+
and nx is not None
|
|
176
|
+
and ny is not None
|
|
177
|
+
and dx is not None
|
|
178
|
+
and dy is not None
|
|
179
|
+
):
|
|
180
|
+
self._x = np.arange(xmin, xmin + dx * nx + dx / 2, dx)
|
|
181
|
+
self._y = np.arange(ymin, ymin + dy * ny + dy / 2, dy)
|
|
182
|
+
|
|
183
|
+
self._h_edges = None
|
|
184
|
+
self._v_edges = None
|
|
185
|
+
|
|
186
|
+
if self._nx is not None and self._ny is not None:
|
|
187
|
+
self.set_edges()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def set_edges(self) -> None:
|
|
191
|
+
"""Compute and assign horizontal and vertical edge number.
|
|
192
|
+
|
|
193
|
+
Calls :func:`tilupy.initsimus.make_edges_matrices` to build the edge numbering
|
|
194
|
+
for the grid defined by nx and ny.
|
|
195
|
+
Results are stored in attributes :attr:`_h_edges` and :attr:`_v_edges`.
|
|
196
|
+
"""
|
|
197
|
+
self._h_edges, self._v_edges = make_edges_matrices(self._nx - 1, self._ny - 1)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def get_edge(self, xcoord: float, ycoord: float, cardinal: str) -> int:
|
|
201
|
+
"""Get edge number for given coordinates and cardinal direction
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
xcoord : float
|
|
206
|
+
X coordinate of the point.
|
|
207
|
+
ycoord : float
|
|
208
|
+
Y coordinate of the point.
|
|
209
|
+
cardinal : str
|
|
210
|
+
Cardinal direction of the edge ('N', 'S', 'E', 'W'):
|
|
211
|
+
|
|
212
|
+
- 'N': top edge of the cell
|
|
213
|
+
- 'S': bottom edge of the cell
|
|
214
|
+
- 'E': right edge of the cell
|
|
215
|
+
- 'W': left edge of the cell
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
int
|
|
220
|
+
Edge number corresponding to the requested location and direction.
|
|
221
|
+
|
|
222
|
+
Raises
|
|
223
|
+
------
|
|
224
|
+
AssertionError
|
|
225
|
+
If :meth:`set_edges` has not been called before (i.e. :attr:`_h_edges` and :attr:`_v_edges` are None).
|
|
226
|
+
"""
|
|
227
|
+
assert (self._h_edges is not None) and (self._v_edges is not None), "h_edges and v_edges must be computed to get edge number"
|
|
228
|
+
ix = np.argmin(np.abs(self._x[:-1] + self._dx / 2 - xcoord))
|
|
229
|
+
iy = np.argmin(np.abs(self._y[:-1] + self._dx / 2 - ycoord))
|
|
230
|
+
if cardinal == "S":
|
|
231
|
+
return self._h_edges[-iy - 1, ix]
|
|
232
|
+
elif cardinal == "N":
|
|
233
|
+
return self._h_edges[-iy - 2, ix]
|
|
234
|
+
elif cardinal == "W":
|
|
235
|
+
return self._v_edges[-iy - 1, ix]
|
|
236
|
+
elif cardinal == "E":
|
|
237
|
+
return self._v_edges[-iy - 1, ix + 1]
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_edges(self, xcoords: np.ndarray, ycoords: np.ndarray, cardinal: str, from_extremities: bool=True) -> dict[str: list[int]]:
|
|
241
|
+
"""Get edges numbers for a set of coordinates and cardinals direction.
|
|
242
|
+
|
|
243
|
+
If :data:`from_extremities` is True, xcoords and ycoords are treated as the extremities of a segment.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
xcoords : np.ndarray or list
|
|
248
|
+
X coordinates of points. If :data:`from_extremities` is True, indicated (xmin, xmax).
|
|
249
|
+
ycoords : np.ndarray or list
|
|
250
|
+
Y coordinates of points. If :data:`from_extremities` is True, indicated (ymin, ymax).
|
|
251
|
+
cardinal : str
|
|
252
|
+
Cardinal directions to extract, any combination of {'N', 'S', 'E', 'W'}.
|
|
253
|
+
Example: "NS" will return both north and south edges.
|
|
254
|
+
from_extremities : bool, optional
|
|
255
|
+
If True, :data:`xcoords` and :data:`ycoords` are considered as the endpoints
|
|
256
|
+
of a line, by default True
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
dict[str: list[int]]
|
|
261
|
+
Dictionary where keys are the requested cardinals and values are
|
|
262
|
+
sorted lists of unique edge numbers.
|
|
263
|
+
"""
|
|
264
|
+
if from_extremities:
|
|
265
|
+
d = np.sqrt((xcoords[1] - xcoords[0]) ** 2 + (ycoords[1] - ycoords[0]) ** 2)
|
|
266
|
+
npts = int(np.ceil(d / min(self._dx, self._dy)))
|
|
267
|
+
xcoords = np.linspace(xcoords[0], xcoords[1], npts + 1)
|
|
268
|
+
ycoords = np.linspace(ycoords[0], ycoords[1], npts + 1)
|
|
269
|
+
|
|
270
|
+
if self._h_edges is None or self._v_edges is None:
|
|
271
|
+
self.set_edges
|
|
272
|
+
|
|
273
|
+
res = dict()
|
|
274
|
+
|
|
275
|
+
# cardinal is a string with any combination of cardinal directions
|
|
276
|
+
for card in cardinal:
|
|
277
|
+
edges_num = []
|
|
278
|
+
for xcoord, ycoord in zip(xcoords, ycoords):
|
|
279
|
+
edges_num.append(self.get_edge(xcoord, ycoord, card))
|
|
280
|
+
res[card] = list(set(edges_num)) # Duplicate edges are removed
|
|
281
|
+
res[card].sort()
|
|
282
|
+
|
|
283
|
+
return res
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class Simu:
|
|
287
|
+
"""Simulation configuration and input file generator.
|
|
288
|
+
|
|
289
|
+
This class manages the setup of lave2D. It creates the necessary input files (topography, numeric
|
|
290
|
+
parameters, rheology, boundary conditions, initial mass) that will be read by lave2D.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
folder : str
|
|
295
|
+
Directory where simulation files are written. Created if it does not exist.
|
|
296
|
+
name : str
|
|
297
|
+
Base name of the simulation (used as file prefix).
|
|
298
|
+
|
|
299
|
+
Attributes
|
|
300
|
+
----------
|
|
301
|
+
_folder : str
|
|
302
|
+
Path to the directory where simulation input and output files are stored.
|
|
303
|
+
_name : str
|
|
304
|
+
Base name of the simulation, used as a prefix for generated files. Must have 8 characters.
|
|
305
|
+
_x : numpy.ndarray
|
|
306
|
+
Array of X coordinates of the topography grid.
|
|
307
|
+
_y : numpy.ndarray
|
|
308
|
+
Array of Y coordinates of the topography grid.
|
|
309
|
+
_z : numpy.ndarray
|
|
310
|
+
2D array of topographic elevations.
|
|
311
|
+
_tmax : float
|
|
312
|
+
Maximum simulation time.
|
|
313
|
+
_dtsorties : float
|
|
314
|
+
Time step for output results.
|
|
315
|
+
"""
|
|
316
|
+
def __init__(self, folder: str, name: str):
|
|
317
|
+
os.makedirs(folder, exist_ok=True)
|
|
318
|
+
self._folder = folder
|
|
319
|
+
self._name = name
|
|
320
|
+
self._x = None
|
|
321
|
+
self._y = None
|
|
322
|
+
self._z = None
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def set_topography(self, z: np.ndarray | str,
|
|
326
|
+
x: np.ndarray=None,
|
|
327
|
+
y: np.ndarray=None,
|
|
328
|
+
file_out: str=None
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Set simulation topography and write it as an ASCII grid.
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
z : ndarray or str
|
|
335
|
+
Topography values as a 2D NumPy array, or path to a raster file.
|
|
336
|
+
x : ndarray, optional
|
|
337
|
+
X coordinates of the grid, ignored if :data:`z` is a file path.
|
|
338
|
+
y : ndarray, optional
|
|
339
|
+
Y coordinates of the grid, ignored if :data:`z` is a file path.
|
|
340
|
+
file_out : str, optional
|
|
341
|
+
Output filename (add ".asc"), by defaults "toposimu.asc".
|
|
342
|
+
Filenames are truncated or padded to 8 characters.
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
None
|
|
347
|
+
"""
|
|
348
|
+
if isinstance(z, str):
|
|
349
|
+
self._x, self._y, self._z = tilupy.raster.read_raster(z)
|
|
350
|
+
else:
|
|
351
|
+
self._x, self._y, self._z = x, y, z
|
|
352
|
+
|
|
353
|
+
if file_out is None:
|
|
354
|
+
file_out = "toposimu.asc"
|
|
355
|
+
else:
|
|
356
|
+
fname = file_out.split(".")[0]
|
|
357
|
+
if len(fname) > 8:
|
|
358
|
+
warnings.warns("""Topography file name is too long,
|
|
359
|
+
only 8 first characters are retained"""
|
|
360
|
+
)
|
|
361
|
+
fname = fname[:8]
|
|
362
|
+
elif len(fname) < 8:
|
|
363
|
+
warnings.warns("""Topography file name is too short and is adapted,
|
|
364
|
+
exactly 8 characters needed"""
|
|
365
|
+
)
|
|
366
|
+
fname = fname.ljust(8, "0")
|
|
367
|
+
file_out = fname + ".asc"
|
|
368
|
+
|
|
369
|
+
tilupy.raster.write_ascii(self._x,
|
|
370
|
+
self._y,
|
|
371
|
+
self._z,
|
|
372
|
+
os.path.join(self._folder, file_out))
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def set_numeric_params(self,
|
|
376
|
+
tmax: float,
|
|
377
|
+
dtsorties: float,
|
|
378
|
+
paray: float=0.00099,
|
|
379
|
+
dtinit: float=0.01,
|
|
380
|
+
cfl_const: int=1,
|
|
381
|
+
CFL: float=0.2,
|
|
382
|
+
alpha_cor_pente: float=1.0,
|
|
383
|
+
) -> None:
|
|
384
|
+
"""Set numerical simulation parameters and write them to file.
|
|
385
|
+
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
tmax : float
|
|
389
|
+
Maximum simulation time.
|
|
390
|
+
dtsorties : float
|
|
391
|
+
Time step for output results.
|
|
392
|
+
paray : float, optional
|
|
393
|
+
Hydraulic roughness parameter, by default 0.00099.
|
|
394
|
+
dtinit : float, optional
|
|
395
|
+
Initial time step, by default 0.01.
|
|
396
|
+
cfl_const : int, optional
|
|
397
|
+
If 1, CFL condition is constant, by default 1.
|
|
398
|
+
CFL : float, optional
|
|
399
|
+
Courant-Friedrichs-Lewy number, by default 0.2.
|
|
400
|
+
alpha_cor_pente : float, optional
|
|
401
|
+
Slope correction coefficient, by default 1.0.
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
None
|
|
406
|
+
"""
|
|
407
|
+
self._tmax = tmax
|
|
408
|
+
self._dtsorties = dtsorties
|
|
409
|
+
|
|
410
|
+
with open(os.path.join(self._folder, "DONLEDD1.DON"), "w") as fid:
|
|
411
|
+
fid.write("paray".ljust(34, " ") + "{:.12f}\n".format(paray).lstrip("0"))
|
|
412
|
+
fid.write("tmax".ljust(34, " ") + "{:.12f}\n".format(tmax))
|
|
413
|
+
fid.write("dtinit".ljust(34, " ") + "{:.12f}\n".format(dtinit))
|
|
414
|
+
fid.write("cfl constante ?".ljust(34, " ") + "{:.0f}\n".format(cfl_const))
|
|
415
|
+
fid.write("CFL".ljust(34, " ") + "{:.12f}\n".format(CFL))
|
|
416
|
+
fid.write("Go,VaLe1,VaLe2".ljust(34, " ") + "{:.0f}\n".format(3))
|
|
417
|
+
fid.write("secmbr0/1".ljust(34, " ") + "{:.0f}\n".format(1))
|
|
418
|
+
fid.write("CL variable (si=0) ?".ljust(34, " ") + "{:d}\n".format(0))
|
|
419
|
+
fid.write("dtsorties".ljust(34, " ") + "{:.12f}\n".format(dtsorties))
|
|
420
|
+
fid.write("alpha cor pentes".ljust(34, " ") + "{:.12f}".format(alpha_cor_pente))
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def set_rheology(self, tau_rho: float, K_tau: float=0.3) -> None:
|
|
424
|
+
r"""Set Herschel-Bulkley rheology parameters
|
|
425
|
+
|
|
426
|
+
Rheological parameters are tau/rho and K/tau. See :
|
|
427
|
+
|
|
428
|
+
- Coussot, P., 1994. Steady, laminar, flow of concentrated mud suspensions in open channel. Journal of Hydraulic Research 32, 535-559. doi.org/10.1080/00221686.1994.9728354
|
|
429
|
+
--> Eq 8 and text after eq 22 for the default value of K/tau
|
|
430
|
+
- Rickenmann, D. et al., 2006. Comparison of 2D debris-flow simulation models with field events. Computational Geosciences 10, 241-264. doi.org/10.1007/s10596-005-9021-3
|
|
431
|
+
--> Eq 9
|
|
432
|
+
|
|
433
|
+
tau_rho : float
|
|
434
|
+
Yield stress divided by density (:math:`\tau/\rho`).
|
|
435
|
+
K_tau : float, optional
|
|
436
|
+
Consistency index divided by yield stress (:math:`K/\tau`).
|
|
437
|
+
By default 0.3, following Rickenmann (2006).
|
|
438
|
+
|
|
439
|
+
Returns
|
|
440
|
+
-------
|
|
441
|
+
None
|
|
442
|
+
"""
|
|
443
|
+
with open(os.path.join(self._folder, self._name + ".rhe"), "w") as fid:
|
|
444
|
+
fid.write("{:.3f}\n".format(tau_rho))
|
|
445
|
+
fid.write("{:.3f}".format(K_tau))
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def set_boundary_conditions(self,
|
|
449
|
+
xcoords: list,
|
|
450
|
+
ycoords: list,
|
|
451
|
+
cardinal: str,
|
|
452
|
+
discharges: list | float,
|
|
453
|
+
times: list=None,
|
|
454
|
+
thicknesses: list=None,
|
|
455
|
+
tmax: float=9999,
|
|
456
|
+
discharge_from_volume: bool=False,
|
|
457
|
+
) -> None:
|
|
458
|
+
r"""Define and write inflow hydrograph boundary conditions.
|
|
459
|
+
|
|
460
|
+
Parameters
|
|
461
|
+
----------
|
|
462
|
+
xcoords : list
|
|
463
|
+
X coordinates of boundary location (two values for segment ends).
|
|
464
|
+
ycoords : list
|
|
465
|
+
Y coordinates of boundary location (two values for segment ends).
|
|
466
|
+
cardinal : str
|
|
467
|
+
Boundary orientation ('N', 'S', 'E', 'W').
|
|
468
|
+
discharges : list or float
|
|
469
|
+
If :data:`times` is None: interpreted as inflow volume (:math:`m^3`).
|
|
470
|
+
Else: Interpreted as discharge values (:math:`m^3/s`) at each time step.
|
|
471
|
+
times : list, optional
|
|
472
|
+
Time vector associated with discharges. If None, a synthetic
|
|
473
|
+
hydrograph is built from volume. By default None.
|
|
474
|
+
thicknesses : list, optional
|
|
475
|
+
Flow thicknesses for each time step. If None, put 1m everywhere. By default None.
|
|
476
|
+
tmax : float, optional
|
|
477
|
+
Maximum time for synthetic hydrograph generation, by default 9999.
|
|
478
|
+
discharge_from_volume : bool, optional
|
|
479
|
+
If True, discharges are computed from inflow volume, by default False. Not used.
|
|
480
|
+
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
None
|
|
484
|
+
|
|
485
|
+
Raises
|
|
486
|
+
------
|
|
487
|
+
AttributeError
|
|
488
|
+
If no topography not been set.
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
modelling_domain = ModellingDomain(self._z,
|
|
492
|
+
self._x[0],
|
|
493
|
+
self._y[0],
|
|
494
|
+
dx=self._x[1] - self._x[0],
|
|
495
|
+
dy=self._y[1] - self._y[0])
|
|
496
|
+
except AttributeError:
|
|
497
|
+
print("Simulation topography has not been set")
|
|
498
|
+
raise
|
|
499
|
+
|
|
500
|
+
edges = modelling_domain.get_edges(xcoords,
|
|
501
|
+
ycoords,
|
|
502
|
+
cardinal,
|
|
503
|
+
from_extremities=True)
|
|
504
|
+
edges = edges[cardinal]
|
|
505
|
+
|
|
506
|
+
if times is None:
|
|
507
|
+
# If no time vector is given, discharges is interpreted as a volume
|
|
508
|
+
# from which an empirical peak discharge is computed
|
|
509
|
+
peak_discharge = 0.0188 * discharges**0.79
|
|
510
|
+
times = [0, 2 * discharges / peak_discharge, tmax]
|
|
511
|
+
discharges = [peak_discharge, 0, 0]
|
|
512
|
+
|
|
513
|
+
n_times = len(times)
|
|
514
|
+
n_edges = len(edges)
|
|
515
|
+
|
|
516
|
+
assert n_times == len(discharges), "discharges and time vectors must be the same length"
|
|
517
|
+
|
|
518
|
+
if thicknesses is None:
|
|
519
|
+
thicknesses = [1 for i in range(n_times)]
|
|
520
|
+
|
|
521
|
+
if cardinal in ["W", "E"]:
|
|
522
|
+
dd = self._y[1] - self._y[0]
|
|
523
|
+
else:
|
|
524
|
+
dd = self._x[1] - self._x[0]
|
|
525
|
+
|
|
526
|
+
with open(os.path.join(self._folder, self._name + ".cli"), "w") as fid:
|
|
527
|
+
fid.write("{:d}\n".format(n_times)) # Number of time steps in the hydrogram
|
|
528
|
+
for i, time in enumerate(times):
|
|
529
|
+
fid.write("{:.4f}\n".format(time)) # Write time
|
|
530
|
+
fid.write("{:d}\n".format(n_edges)) # Write n_edges
|
|
531
|
+
qn = discharges[i] / (n_edges * dd)
|
|
532
|
+
qt = 0
|
|
533
|
+
for i_edge, edge in enumerate(edges):
|
|
534
|
+
fid.write("\t{:d} {:.7E} {:.7E} {:.7E}\n".format(edge,
|
|
535
|
+
thicknesses[i],
|
|
536
|
+
qn,
|
|
537
|
+
qt))
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def set_init_mass(self, mass_raster: str, h_min: float=0.01) -> None:
|
|
541
|
+
"""Set initial debris-flow mass from a raster file.
|
|
542
|
+
|
|
543
|
+
Reads an ASCII raster of initial thickness and interpolates it onto
|
|
544
|
+
the simulation grid cell centers.
|
|
545
|
+
|
|
546
|
+
Parameters
|
|
547
|
+
----------
|
|
548
|
+
mass_raster : str
|
|
549
|
+
Path to raster file containing initial mass thickness.
|
|
550
|
+
h_min : float, optional
|
|
551
|
+
Minimum non-zero thickness assigned to the grid. Default is 0.01.
|
|
552
|
+
|
|
553
|
+
Returns
|
|
554
|
+
-------
|
|
555
|
+
None
|
|
556
|
+
"""
|
|
557
|
+
x, y, m = tilupy.raster.read_ascii(mass_raster)
|
|
558
|
+
# The input raster is given as the topography for the grid corners,
|
|
559
|
+
# but results are then written on grid cell centers
|
|
560
|
+
x2 = x[1:] - (x[1] - x[0]) / 2
|
|
561
|
+
y2 = y[1:] - (y[1] - y[0]) / 2
|
|
562
|
+
|
|
563
|
+
y2 = y2[::-1]
|
|
564
|
+
|
|
565
|
+
fm = RegularGridInterpolator((y, x),
|
|
566
|
+
m,
|
|
567
|
+
method="linear",
|
|
568
|
+
bounds_error=False,
|
|
569
|
+
fill_value=None)
|
|
570
|
+
x_mesh, y_mesh = np.meshgrid(x2, y2)
|
|
571
|
+
m2 = fm((y_mesh, x_mesh))
|
|
572
|
+
m2[m2 < h_min] = h_min
|
|
573
|
+
np.savetxt(os.path.join(self._folder, self._name + ".cin"),
|
|
574
|
+
m2.flatten(),
|
|
575
|
+
header="0.000000E+00",
|
|
576
|
+
comments="",
|
|
577
|
+
fmt="%.10E")
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def write_simu(raster_topo: str,
|
|
581
|
+
raster_mass: str,
|
|
582
|
+
tmax: float,
|
|
583
|
+
dt_im: float,
|
|
584
|
+
simulation_name: str,
|
|
585
|
+
lave2D_exe_folder: str,
|
|
586
|
+
rheology_type: str,
|
|
587
|
+
rheology_params: dict,
|
|
588
|
+
folder_out: str = None,
|
|
589
|
+
) -> None:
|
|
590
|
+
"""
|
|
591
|
+
Prepares all input files required for a Lave2D simulation and saves them in a dedicated folder.
|
|
592
|
+
|
|
593
|
+
Parameters
|
|
594
|
+
----------
|
|
595
|
+
raster_topo : str
|
|
596
|
+
Name of the ASCII topography file.
|
|
597
|
+
raster_mass : str
|
|
598
|
+
Name of the ASCII initial mass file.
|
|
599
|
+
tmax : float
|
|
600
|
+
Maximum simulation time.
|
|
601
|
+
dt_im : float
|
|
602
|
+
Output image interval (in time steps).
|
|
603
|
+
simulation_name : str
|
|
604
|
+
Simulation/project name.
|
|
605
|
+
lave2D_exe_folder : str
|
|
606
|
+
Path to the folder containing "Lave2_Arc.exe" and "vf2marc.exe".
|
|
607
|
+
rheology_type : str
|
|
608
|
+
Rheology to use for the simulation.
|
|
609
|
+
rheology_params : dict
|
|
610
|
+
Necessary parameters for the rheology. For this case:
|
|
611
|
+
- tau_rho
|
|
612
|
+
- K_tau
|
|
613
|
+
folder_out : str, optional
|
|
614
|
+
Output folder where simulation inputs will be stored.
|
|
615
|
+
|
|
616
|
+
Returns
|
|
617
|
+
-------
|
|
618
|
+
None
|
|
619
|
+
|
|
620
|
+
Raises
|
|
621
|
+
------
|
|
622
|
+
ValueError
|
|
623
|
+
If the rheology isn't Herschel_Bulkley.
|
|
624
|
+
"""
|
|
625
|
+
if folder_out is None:
|
|
626
|
+
folder_out = "."
|
|
627
|
+
|
|
628
|
+
if rheology_type != "Herschel_Bulkley":
|
|
629
|
+
raise ValueError("Rheology type must be 'Herschel_Bulkley'.")
|
|
630
|
+
|
|
631
|
+
# output_file = os.path.join(folder_out, "lave2D")
|
|
632
|
+
|
|
633
|
+
os.makedirs(folder_out, exist_ok=True)
|
|
634
|
+
|
|
635
|
+
shutil.copy2(os.path.join(lave2D_exe_folder, "Lave2_Arc.exe"),
|
|
636
|
+
folder_out)
|
|
637
|
+
shutil.copy2(os.path.join(lave2D_exe_folder, "vf2marc.exe"),
|
|
638
|
+
folder_out)
|
|
639
|
+
|
|
640
|
+
simu_lave2D = Simu(folder_out, simulation_name)
|
|
641
|
+
simu_lave2D.set_topography(raster_topo)
|
|
642
|
+
simu_lave2D.set_init_mass(raster_mass)
|
|
643
|
+
simu_lave2D.set_rheology(rheology_params["tau_rho"], rheology_params["K_tau"])
|
|
644
|
+
simu_lave2D.set_numeric_params(tmax, dt_im)
|
|
645
|
+
|
|
646
|
+
## Not used in simulation, but the .cli file is needed
|
|
647
|
+
simu_lave2D.set_boundary_conditions([0, 0], # X min and max coords for input flux
|
|
648
|
+
[1, 2], # Y min and max coord for input flux
|
|
649
|
+
"W", # Cardinal direction (Flow from East to West)
|
|
650
|
+
[0, 0], # Discharge hydrogram
|
|
651
|
+
times=[0, tmax + 1], # Corresponding times
|
|
652
|
+
thicknesses=[0, 0], # Thickness hydrogramm
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
"""
|
|
657
|
+
if __name__ == "__main__":
|
|
658
|
+
h_edges, v_edges = make_edges_matrices(1, 1)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
if __name__ == "__main__":
|
|
662
|
+
domain = ModellingDomain(xmin=0, ymin=0, nx=4, ny=3, dx=1, dy=1)
|
|
663
|
+
domain.set_edges()
|
|
664
|
+
res = domain.get_edges([1.5, 2.5], [0, 0], "S")
|
|
665
|
+
"""
|