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/make_topo.py ADDED
@@ -0,0 +1,468 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import numpy as np
4
+ import scipy
5
+
6
+ import pytopomap.plot
7
+
8
+
9
+ def gray99(nx: int = None,
10
+ ny: int = None,
11
+ dx: float = 0.01,
12
+ dy: float = 0.01,
13
+ xmin: float = -0.4,
14
+ x1: float = 1.75,
15
+ x2: float = 2.15,
16
+ xmax: float = 3.2,
17
+ ymax: float = 0.6,
18
+ R: float = 1.1,
19
+ theta1: float = 40,
20
+ theta2: float = 0,
21
+ maxz: float = None,
22
+ plot: bool = False,
23
+ ) -> list[np.ndarray]:
24
+ """Construct channel as in Gray et all 99.
25
+
26
+ Input coordinates are in curvilinear coordinates along the reference
27
+ topography following the channel bottom. Output coordinates are in the
28
+ fixed cartesian frame.
29
+
30
+ Parameters
31
+ ----------
32
+ nx : int
33
+ Size of the grid in x direction.
34
+ ny : int
35
+ Size of the grid in y direction.
36
+ dx : float, optional
37
+ Cell size of the x axis. if specified, nx is recomputed. By default 0.01.
38
+ dy : float, optional
39
+ Cell size of the y axis. if specified, ny is recomputed. By default 0.01.
40
+ xmin : float, optional
41
+ Minimum x coordinate, by default -0.4.
42
+ x1 : float, optional
43
+ Min coordinate of the channel outlet (transition zone), by default 1.75.
44
+ x2 : float, optional
45
+ Max coordinate of the channel outlet (transition zone), by default 2.15.
46
+ xmax : float, optional
47
+ Maximum x coordinate, by default 3.2.
48
+ ymax : float, optional
49
+ Maximum y coordinate, the final y axis spans from -ymax to ymax, by default 0.5.
50
+ R : float, optional
51
+ Radius of curvature of the channel, by default 1.1.
52
+ theta1 : float, optional
53
+ Slope of the channel in degree, by default 40.
54
+ theta2 : float, optional
55
+ Slope after the channel, by default 0.
56
+ maxz : float, optional
57
+ Maximum z coordinate, by default None.
58
+ plot : bool, optional
59
+ Plot result, by default False.
60
+
61
+ Returns
62
+ -------
63
+ list[numpy.ndarray, numpy.ndarray, numpy.ndarray]
64
+ Xout : numpy.ndarray
65
+ Mesh of X coordinates in the cartesian frame (nx*ny).
66
+ Yout : numpy.ndarray
67
+ Mesh of Y coordinates in the cartesian frame (nx*ny).
68
+ Zout : numpy.ndarray
69
+ Mesh of Z coordinates in the cartesian frame (nx*ny).
70
+ """
71
+ theta1 = np.deg2rad(theta1)
72
+ theta2 = np.deg2rad(theta2)
73
+
74
+ if nx is None:
75
+ x = np.arange(xmin, xmax + dx / 2, dx)
76
+ nx = len(x)
77
+ else:
78
+ x = np.linspace(xmin, xmax, nx)
79
+
80
+ if ny is None:
81
+ y = np.arange(-ymax, ymax + dy / 2, dy)
82
+ ny = len(y)
83
+ else:
84
+ y = np.linspace(-ymax, ymax, ny)
85
+
86
+ ycurv = np.tile(y.reshape((1, ny)), (nx, 1))
87
+
88
+ # Superficial topography : channel
89
+ # alpha=1/(2*R)*np.sin(0.5*np.pi*(x-x2)/(x1-x2))**2
90
+ # alpha=1/(2*R)*np.abs(x-x2)**1/np.abs(x1-x2)**1
91
+ alpha = (1
92
+ / (2 * R)
93
+ * (3 * ((x - x2) / (x1 - x2)) ** 2 - 2 * ((x - x2) / (x1 - x2)) ** 3)
94
+ )
95
+ alpha[x > x2] = 0
96
+ alpha[x < x1] = 1 / (2 * R)
97
+ alpha = np.tile(alpha.reshape((nx, 1)), (1, ny))
98
+
99
+ zchannel = alpha * np.abs(ycurv) ** 2
100
+ # plt.figure()
101
+ # plt.imshow(zchannel)
102
+
103
+ # del alpha
104
+
105
+ if not maxz:
106
+ maxz = R / 2
107
+
108
+ zchannel[zchannel > maxz] = maxz
109
+
110
+ # Base topography in curvilinear system.
111
+ # The transition zone between x1 and x2 is a cylindre
112
+ zbase = -np.sin(theta2) * (x - xmax)
113
+
114
+ ind = (x <= x2) & (x >= x1)
115
+ angle = (x[ind] - x1) / (x2 - x1) * (theta2 - theta1) + theta1
116
+ R2 = (x2 - x1) / (theta1 - theta2)
117
+ z2 = -np.sin(theta2) * (x2 - xmax)
118
+ zbase[ind] = R2 * (1 - np.cos(angle)) - R2 * (1 - np.cos(theta2)) + z2
119
+
120
+ ind = x <= x1
121
+ z1 = R2 * (1 - np.cos(theta1)) - R2 * (1 - np.cos(theta2)) + z2
122
+ zbase[ind] = -np.sin(theta1) * (x[ind] - x1) + z1
123
+ zbase = np.tile(zbase.reshape((nx, 1)), (1, ny))
124
+
125
+ # Conversion in fixed cartesian frame
126
+ zd = np.gradient(zbase, x[1] - x[0], edge_order=2, axis=0)
127
+ Xd = np.sqrt(1 - zd**2)
128
+ X = scipy.integrate.cumulative_trapezoid(Xd, x, axis=0, initial=0)
129
+ X = X + xmin * np.cos(theta1)
130
+
131
+ # plt.figure()
132
+ # plt.plot(X[:,0])
133
+
134
+ del Xd
135
+
136
+ # Topography conversion in fixed cartesian frame
137
+ [Fx, Fy] = np.gradient(zbase, X[:, 0], ycurv[0, :], edge_order=2)
138
+ Fz = np.ones(zbase.shape)
139
+ costh = 1 / np.sqrt(Fx**2 + Fy**2 + 1) # Slope angle
140
+ Fx = -Fx * costh
141
+ Fy = -Fy * costh
142
+ Fz = Fz * costh
143
+ Z = zbase + zchannel * Fz
144
+ Xmesh = X + zchannel * Fx
145
+ Ymesh = ycurv + zchannel * Fy
146
+
147
+ # Reconstruction of regular cartesian mesh
148
+ Xout = np.linspace(Xmesh.min(), Xmesh.max(), nx)
149
+ Xout = np.tile(Xout.reshape((nx, 1)), (1, ny))
150
+ Yout = np.linspace(Ymesh.min(), Ymesh.max(), ny)
151
+ Yout = np.tile(Yout.reshape((1, ny)), (nx, 1))
152
+ Zout = scipy.interpolate.griddata((Xmesh.reshape(nx * ny), Ymesh.reshape(nx * ny)),
153
+ Z.reshape(nx * ny),
154
+ (Xout, Yout),
155
+ method="cubic")
156
+ Ztmp = scipy.interpolate.griddata((Xmesh.reshape(nx * ny), Ymesh.reshape(nx * ny)),
157
+ Z.reshape(nx * ny),
158
+ (Xout, Yout),
159
+ method="nearest")
160
+ ind = np.isnan(Zout)
161
+ Zout[ind] = Ztmp[ind]
162
+
163
+ del Ztmp
164
+ # fz=scipy.interpolate.Rbf(Xmesh,Ymesh,Z)
165
+ # Zout=fz(Xout,Yout)
166
+
167
+ if plot:
168
+ if theta2 == 0:
169
+ blod, thin = pytopomap.plot.get_contour_intervals(np.nanmin(Zout),
170
+ np.nanmax(Zout))
171
+ level_min = thin
172
+ else:
173
+ level_min = None
174
+ pytopomap.plot.plot_topo(Zout.T,
175
+ Xout[:, 1],
176
+ Yout[1, :],
177
+ level_min=level_min)
178
+
179
+ return Xout[:, 1], Yout[1, :], Zout.T
180
+
181
+
182
+ def channel(nx: int = None,
183
+ ny: int = None,
184
+ dx: float = None,
185
+ dy: float = None,
186
+ xmin: float = -0.4,
187
+ xmax: float = 3.6,
188
+ ymax: float = 0.5,
189
+ xstart_channel: float = 0.65,
190
+ xend_channel: float = 2.3,
191
+ xstart_trans: float = 0.4,
192
+ xend_trans: float = 2.75,
193
+ R: float = 1.1,
194
+ bend: float = 0.2,
195
+ nbends: int = 1,
196
+ theta_start: float = 40,
197
+ theta_channel: float = 40,
198
+ theta_end: float = 0,
199
+ plot: bool = False,
200
+ maxh: float = None,
201
+ interp_method: str = "linear",
202
+ ) -> list[np.ndarray]:
203
+ """Generate channel with potential multiple bends.
204
+
205
+ Input coordinates are curvilinear along the flattened topography.
206
+
207
+ Parameters
208
+ ----------
209
+ nx : int, optinal
210
+ Size of the grid in x direction, by default None.
211
+ ny : int, optinal
212
+ Size of the grid in y direction, by default None.
213
+ dx : float, optional
214
+ Cell size of the x axis. if specified, nx is recomputed, by default 0.01.
215
+ dy : float, optional
216
+ Cell size of the y axis. if specified, ny is recomputed, by default 0.01.
217
+ xmin : float, optional
218
+ Minimum x coordinate, by default -0.4.
219
+ xmax : float, optional
220
+ Maximum x coordinate, by default 3.2.
221
+ ymax : float, optional
222
+ Maximum y coordinate, the final y axis spans from -ymax to ymax, by default 0.5.
223
+ xstart_channel : float, optional
224
+ Start of the channel, by default 0.65.
225
+ xend_channel : float, optional
226
+ End of the channel, by default 2.3.
227
+ xstart_trans : float, optional
228
+ Start of the transition zone before the channel start, by default 0.4.
229
+ xend_trans : float, optional
230
+ End of the transition zone after the channel end, by default 2.75.
231
+ R : float, optional
232
+ Radius of curvature of the channel, by default 1.1.
233
+ bend : float, optional
234
+ Width of the channel bend, by default 0.2.
235
+ nbends : ind, optional
236
+ Number of bends, by default 1.
237
+ theta_start : float, optional
238
+ Slope before the channel, by default 40.
239
+ theta_channel : float, optional
240
+ Slope of the channel, by default 40.
241
+ theta_end : float, optional
242
+ Slope after the channel, by default 0.
243
+ plot : bool, optional
244
+ Plot generated topography, by default False.
245
+ maxh : float, optional
246
+ Depth of the channel, by default None.
247
+ interp_method : string, optional
248
+ Interpolation method for converting the topography from curvilinear
249
+ coordinates to cartesian coordinates, by default 'linear'.
250
+
251
+ Returns
252
+ -------
253
+ list[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]
254
+ Xout : numpy.ndarray
255
+ Mesh of X coordinates in the cartesian frame (nx*ny).
256
+ Yout : numpy.ndarray
257
+ Mesh of Y coordinates in the cartesian frame (nx*ny).
258
+ Zout : numpy.ndarray
259
+ Mesh of Z coordinates in the cartesian frame (nx*ny).
260
+ thalweg : numpy.ndarray
261
+ """
262
+
263
+ theta_start = np.deg2rad(theta_start)
264
+ theta_channel = np.deg2rad(theta_channel)
265
+ theta_end = np.deg2rad(theta_end)
266
+
267
+ if ny is None and dy is None:
268
+ dy = ymax / 100
269
+
270
+ if nx is None and dx is None:
271
+ if dy is not None:
272
+ dx = dy
273
+ else:
274
+ raise ValueError("nx or dx must be specified as input")
275
+
276
+ # x and y coordinates in the flattened topography
277
+ if nx is None:
278
+ xtopo = np.arange(xmin, xmax + dx / 2, dx)
279
+ nx = len(xtopo)
280
+ else:
281
+ xtopo = np.linspace(xmin, xmax, nx)
282
+
283
+ if ny is None:
284
+ ytopo = np.arange(-ymax, ymax + dy / 2, dy)
285
+ ny = len(ytopo)
286
+ else:
287
+ ytopo = np.linspace(-ymax, ymax, ny)
288
+
289
+ xtopo = np.tile(xtopo[:, np.newaxis], (1, ny))
290
+ ytopo = np.tile(ytopo[np.newaxis, :], (nx, 1))
291
+
292
+ # Height above flattened topography is a channel
293
+ # in alpha(x)*(y-thalweg(x))**2,
294
+
295
+ # alpha(x) is 1/2R in the channel, and depends on a transition
296
+ # function in the transition zones
297
+ def trans_function(x, x1, x2):
298
+ xx = 3 * ((x - x2) / (x1 - x2)) ** 2 - 2 * ((x - x2) / (x1 - x2)) ** 3
299
+ return xx
300
+
301
+ alpha = np.zeros((nx, ny))
302
+
303
+ ind = (xtopo > xstart_channel) & (xtopo < xend_channel)
304
+ alpha[ind] = 1 / (2 * R)
305
+
306
+ ind = (xtopo > xstart_trans) & (xtopo <= xstart_channel)
307
+ alpha[ind] = (1 / (2 * R) * trans_function(xtopo[ind], xstart_channel, xstart_trans))
308
+
309
+ ind = (xtopo >= xend_channel) & (xtopo < xend_trans)
310
+ alpha[ind] = (1 / (2 * R) * trans_function(xtopo[ind], xend_channel, xend_trans))
311
+
312
+ # the thalweg is centered on y=0 outside [xstart_channel,xend_channel]. Inbetween,
313
+ # it is given by a cos**2
314
+ def end_bend(x, x1, x2):
315
+ yy = (bend / 2) * (1 + np.cos(np.pi * (x - x2) / (x1 - x2)))
316
+ return yy
317
+
318
+ def mid_bend(x, x1, x2):
319
+ yy = bend * np.cos(np.pi * (x - x1) / (x2 - x1))
320
+ return yy
321
+
322
+ thalweg = np.zeros((nx, ny))
323
+
324
+ if nbends > 0:
325
+ step = (xend_channel - xstart_channel) / nbends
326
+
327
+ ind = (xtopo > xstart_channel) & (xtopo < xstart_channel + step / 2)
328
+ thalweg[ind] = end_bend(xtopo[ind],
329
+ xstart_channel,
330
+ xstart_channel + step / 2)
331
+
332
+ ind = (xtopo >= xend_channel - step / 2) & (xtopo < xend_channel)
333
+ thalweg[ind] = (-1) ** (nbends + 1) * end_bend(xtopo[ind],
334
+ xend_channel,
335
+ xend_channel - step / 2)
336
+
337
+ if nbends > 1:
338
+ ind = (xtopo >= xstart_channel + step / 2) & (
339
+ xtopo < xend_channel - step / 2
340
+ )
341
+ thalweg[ind] = mid_bend(xtopo[ind],
342
+ xstart_channel + step / 2,
343
+ xstart_channel + (3 / 2) * step)
344
+
345
+ htopo = alpha * (ytopo - thalweg) ** 2
346
+
347
+ if not maxh:
348
+ maxh = R / 2
349
+
350
+ htopo[htopo > maxh] = maxh
351
+
352
+ # Reconstruction of bz the basal topography. The real topo is given by
353
+ # bz+\vec{n}*htopo. Slopes of bz are given by theta_* outside the transition
354
+ # zones. We use a cylinder shape inbetween. This is done by computing the slope
355
+ # angle of bz, and using then -sin(slope_angle)=d(bz)/d(xtopo)
356
+
357
+ slope_angle = np.zeros((nx, ny))
358
+
359
+ ind = xtopo < xstart_trans
360
+ slope_angle[ind] = theta_start
361
+
362
+ ind = xtopo >= xend_trans
363
+ slope_angle[ind] = theta_end
364
+
365
+ ind = (xtopo >= xstart_channel) & (xtopo < xend_channel)
366
+ slope_angle[ind] = theta_channel
367
+
368
+ ind = (xtopo >= xstart_trans) & (xtopo < xstart_channel)
369
+ slope_angle[ind] = (xtopo[ind] - xstart_trans) / (xstart_channel - xstart_trans)
370
+ slope_angle[ind] = (slope_angle[ind] * (theta_channel - theta_start) + theta_start)
371
+
372
+ ind = (xtopo >= xend_channel) & (xtopo < xend_trans)
373
+ slope_angle[ind] = (xtopo[ind] - xend_trans) / (xend_channel - xend_trans)
374
+ slope_angle[ind] = (slope_angle[ind] * (theta_channel - theta_end) + theta_end)
375
+
376
+ bz = scipy.integrate.cumulative_trapezoid(-np.sin(slope_angle),
377
+ xtopo,
378
+ axis=0,
379
+ initial=0)
380
+ bz = bz - np.min(bz)
381
+
382
+ # Get the coordinates of (xtopo,ytopo) in the cartesian reference frame
383
+ # by=ytopo
384
+ bx = scipy.integrate.cumulative_trapezoid(np.cos(slope_angle),
385
+ xtopo,
386
+ axis=0,
387
+ initial=0)
388
+ bx = bx + xmin * np.cos(theta_start)
389
+
390
+ # Vector normal to topography in cartesian coordinates
391
+ # (nx,ny,nz)=(-sin(theta),0,cos(theta))
392
+ # as the topography does vary in the y direction
393
+ # The real topography is thus given in cartesian coordinates by
394
+ # (xcart,ycart,zcart)=(bx,by,bz)+htopo(nx,ny,nz)
395
+ xcart = bx + htopo * np.sin(slope_angle)
396
+ zcart = bz + htopo * np.cos(slope_angle)
397
+
398
+ # Reconstruct regular mesh for interpolation
399
+ Xout = np.linspace(xcart[0, 0], xcart[-1, 0], nx)
400
+ Yout = ytopo
401
+ Xout = np.tile(Xout[:, np.newaxis], (1, ny))
402
+ Zout = scipy.interpolate.griddata((xcart.reshape(nx * ny), ytopo.reshape(nx * ny)),
403
+ zcart.reshape(nx * ny),
404
+ (Xout, Yout),
405
+ method=interp_method)
406
+ Ztmp = scipy.interpolate.griddata((xcart.reshape(nx * ny), ytopo.reshape(nx * ny)),
407
+ zcart.reshape(nx * ny),
408
+ (Xout, Yout),
409
+ method="nearest")
410
+ ind = np.isnan(Zout)
411
+ Zout[ind] = Ztmp[ind]
412
+
413
+ if plot:
414
+ if theta_end == 0:
415
+ blod, thin = pytopomap.plot.get_contour_intervals(np.nanmin(Zout),
416
+ np.nanmax(Zout))
417
+ level_min = thin
418
+ else:
419
+ level_min = None
420
+ pytopomap.plot.plot_topo(Zout.T,
421
+ Xout[:, 1],
422
+ Yout[1, :],
423
+ level_min=level_min)
424
+
425
+ return Xout[:, 1], Yout[1, :], Zout.T, thalweg
426
+
427
+
428
+ """
429
+ if __name__ == "__main__":
430
+ # %% Test gray99
431
+ X, Y, Z = gray99(plot=True)
432
+
433
+ # %% Test synthetic channel
434
+ bend = 0.25
435
+ R = 0.2
436
+
437
+ nx = 600
438
+ ny = 300
439
+
440
+ xmin = 0.1
441
+ xmax = 4.5
442
+ xstart_trans = -0.3
443
+ xstart_channel = 0.2
444
+ xend_channel = 2.3
445
+ xend_trans = 2.75
446
+ ymax = 1
447
+
448
+ theta_start = 10
449
+ theta_channel = 10
450
+ theta_end = 0
451
+ x, y, z, t = channel(
452
+ nx,
453
+ ny,
454
+ xmin=xmin,
455
+ xmax=xmax,
456
+ ymax=ymax,
457
+ xstart_channel=xstart_channel,
458
+ xend_channel=xend_channel,
459
+ xstart_trans=xstart_trans,
460
+ theta_start=theta_start,
461
+ theta_end=theta_end,
462
+ theta_channel=theta_channel,
463
+ R=R,
464
+ bend=bend,
465
+ maxh=R,
466
+ plot=True,
467
+ )
468
+ """
File without changes
File without changes