tilupy 0.1.4__py3-none-any.whl → 1.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.

Potentially problematic release.


This version of tilupy might be problematic. Click here for more details.

tilupy/read.py CHANGED
@@ -11,225 +11,725 @@ import numpy as np
11
11
 
12
12
  import os
13
13
  import importlib
14
+ import warnings
14
15
 
15
16
  import tilupy.notations as notations
16
17
  import tilupy.plot as plt_fn
17
18
  import tilupy.raster
18
19
 
19
- RAW_STATES = ['hvert', 'h', 'ux', 'uy']
20
+ RAW_STATES = ["hvert", "h", "ux", "uy"]
20
21
 
21
- TEMPORAL_DATA_0D = ['hu2int', 'vol']
22
- TEMPORAL_DATA_1D = ['']
23
- TEMPORAL_DATA_2D = ['hvert', 'h', 'u', 'ux', 'uy', 'hu', 'hu2']
22
+ TEMPORAL_DATA_0D = ["ek", "vol"]
23
+ TEMPORAL_DATA_1D = [""]
24
+ TEMPORAL_DATA_2D = ["hvert", "h", "u", "ux", "uy", "hu", "hu2"]
24
25
  STATIC_DATA_0D = []
25
26
  STATIC_DATA_1D = []
26
27
  STATIC_DATA_2D = []
27
28
 
28
- TOPO_DATA_2D = ['z', 'zinit', 'costh']
29
+ TOPO_DATA_2D = ["z", "zinit", "costh"]
29
30
 
30
- NP_OPERATORS = ['max', 'mean', 'std', 'sum', 'min']
31
- OTHER_OPERATORS = ['final', 'initial']
31
+ NP_OPERATORS = ["max", "mean", "std", "sum", "min"]
32
+ OTHER_OPERATORS = ["final", "init", "int"]
33
+ TIME_OPERATORS = ["final", "init", "int"]
32
34
 
33
35
  COMPUTED_STATIC_DATA_2D = []
34
-
35
36
  for stat in NP_OPERATORS + OTHER_OPERATORS:
36
37
  for name in TEMPORAL_DATA_2D:
37
- COMPUTED_STATIC_DATA_2D.append(name + '_' + stat)
38
-
38
+ COMPUTED_STATIC_DATA_2D.append(name + "_" + stat)
39
39
  STATIC_DATA_2D += COMPUTED_STATIC_DATA_2D
40
40
 
41
+ COMPUTED_SPAT_1D_DATA = []
42
+ for stat in NP_OPERATORS + OTHER_OPERATORS:
43
+ for name in TEMPORAL_DATA_2D:
44
+ for axis in ["x", "y"]:
45
+ COMPUTED_SPAT_1D_DATA.append(name + "_" + stat + "_" + axis)
46
+ TEMPORAL_DATA_1D += COMPUTED_SPAT_1D_DATA
47
+
48
+ COMPUTED_SPAT_0D_DATA = []
49
+ for stat in NP_OPERATORS + OTHER_OPERATORS:
50
+ for name in TEMPORAL_DATA_2D:
51
+ COMPUTED_SPAT_0D_DATA.append(name + "_" + stat + "_xy")
52
+ TEMPORAL_DATA_0D += COMPUTED_SPAT_0D_DATA
53
+
54
+ TEMPORAL_DATA = TEMPORAL_DATA_0D + TEMPORAL_DATA_1D + TEMPORAL_DATA_2D
55
+ STATIC_DATA = (
56
+ STATIC_DATA_0D + STATIC_DATA_1D + STATIC_DATA_2D + COMPUTED_STATIC_DATA_2D
57
+ )
58
+
41
59
  DATA_NAMES = TEMPORAL_DATA_0D + TEMPORAL_DATA_1D + TEMPORAL_DATA_2D
42
60
  DATA_NAMES += STATIC_DATA_0D + STATIC_DATA_1D + STATIC_DATA_2D
43
61
 
44
- class TemporalResults:
45
- """ Time dependent result of simulation """
46
62
 
47
- def __init__(self, name, d, t, x=None, y=None, z=None):
48
- # 0d, 1d or 2d result, plus one dimension for time.
63
+ class AbstractResults:
64
+ """Abstract class for TemporalResults and StaticResults"""
65
+
66
+ def __init__(self, name, d, notation=None, **kwargs):
67
+ self.name = name
49
68
  self.d = d
69
+ if isinstance(notation, dict):
70
+ self.notation = notations.Notation(**notation)
71
+ elif notation is None:
72
+ self.notation = notations.get_notation(name)
73
+ else:
74
+ self.notation = notation
75
+ self.__dict__.update(kwargs)
76
+
77
+
78
+ class TemporalResults(AbstractResults):
79
+ """Time dependent result of simulation"""
80
+
81
+ def __init__(
82
+ self,
83
+ name,
84
+ d,
85
+ t,
86
+ notation=None,
87
+ ):
88
+ super().__init__(name, d, notation=notation)
50
89
  # 1d array with times, matching last dimension of self.d
51
90
  self.t = t
52
- # Name of data (e.g. h, u, hu, ...)
53
- self.name = name
54
- # x and y arrays
55
- self.x = x
56
- self.y = y
57
- # topography
58
- self.z = z
59
91
 
60
92
  def get_temporal_stat(self, stat):
61
- """ Statistical analysis along temporal dimension """
93
+ """Statistical analysis along temporal dimension"""
62
94
  if stat in NP_OPERATORS:
63
95
  dnew = getattr(np, stat)(self.d, axis=-1)
64
- elif stat == 'final':
96
+ elif stat == "final":
65
97
  dnew = self.d[..., -1]
66
- elif stat == 'initial':
98
+ elif stat == "init":
67
99
  dnew = self.d[..., 0]
68
- return StaticResults(self.name + '_' + stat, dnew)
100
+ elif stat == "int":
101
+ dnew = np.trapz(self.d, x=self.t)
102
+
103
+ notation = notations.add_operator(self.notation, stat, axis="t")
104
+
105
+ if dnew.ndim == 2:
106
+ return StaticResults2D(
107
+ self.name + "_" + stat,
108
+ dnew,
109
+ notation=notation,
110
+ x=self.x,
111
+ y=self.y,
112
+ z=self.z,
113
+ )
114
+ elif dnew.ndim == 1:
115
+ return StaticResults1D(
116
+ self.name + "_" + stat,
117
+ dnew,
118
+ notation=notation,
119
+ coords=self.coords,
120
+ coords_name=self.coords_name,
121
+ )
122
+
123
+ def get_spatial_stat(self, stat, axis):
124
+ raise NotImplementedError()
125
+
126
+ def plot(*arg, **kwargs):
127
+ """Plot results as time dependent"""
128
+ raise NotImplementedError()
129
+
130
+ def save(*arg, **kwargs):
131
+ raise NotImplementedError()
132
+
133
+
134
+ class TemporalResults0D(TemporalResults):
135
+ """
136
+ Class inheretied from TemporalResults where the data is one or multiple
137
+ scalar functions of time
138
+ of time.
139
+ """
140
+
141
+ def __init__(
142
+ self,
143
+ name,
144
+ d,
145
+ t,
146
+ scalar_names=None,
147
+ notation=None,
148
+ ):
149
+ """
150
+ initiates TemporalResults0D instance
151
+
152
+ Parameters
153
+ ----------
154
+ name : string
155
+ Name of the data type.
156
+ d : array
157
+ array like data, with last dimension corresponding to time. It can
158
+ be a one dimensionnal Nt array, or a two dimensionnal NdxNt array,
159
+ where Nt is the legnth of t, and Nd correspond to the number of
160
+ scalar values of interest (e.g. X and Y coordinates of the center
161
+ of mass / front)
162
+ t : array
163
+ Array of time of length Nt.
164
+ scalar_names : list of strings, optional
165
+ List of length Nd containing the names of the scalar fields (one
166
+ name per row of d)
167
+
168
+ Returns
169
+ -------
170
+ None.
171
+
172
+ """
173
+ super().__init__(name, d, t, notation=notation)
174
+ self.scalar_names = scalar_names
175
+
176
+ def plot(self, axe=None, figsize=None, **kwargs):
177
+ """Plot results.
178
+
179
+ :param axe: DESCRIPTION, defaults to None
180
+ :type axe: TYPE, optional
181
+ :param figsize: DESCRIPTION, defaults to None
182
+ :type figsize: TYPE, optional
183
+ :param **kwargs: DESCRIPTION
184
+ :type **kwargs: TYPE
185
+ :return: DESCRIPTION
186
+ :rtype: TYPE
187
+
188
+ """
189
+
190
+ if axe is None:
191
+ fig, axe = plt.subplots(
192
+ 1, 1, figsize=figsize, layout="constrained"
193
+ )
194
+
195
+ if isinstance(self.d, np.ndarray):
196
+ data = self.d.T
197
+ else:
198
+ data = self.d
199
+ axe.plot(self.t, data, label=self.scalar_names)
200
+ axe.set_xlabel(notations.get_label("t"))
201
+ axe.set_ylabel(notations.get_label(self.notation))
202
+
203
+ return axe
204
+
205
+ def save(self):
206
+ raise NotImplementedError(
207
+ "Saving method for TemporalResults0D not implemented yet"
208
+ )
209
+
210
+ def get_spatial_stat(self, *arg, **kwargs):
211
+ raise NotImplementedError(
212
+ (
213
+ "Spatial integration of Spatialresults0D"
214
+ + " is not implemented because non relevant"
215
+ )
216
+ )
217
+
218
+
219
+ class TemporalResults1D(TemporalResults):
220
+ """Class for simulation results described by one dimension for space
221
+ and one dimension for time.
222
+
223
+ :param name: Name of the data
224
+ :type name: str
225
+ :param d: data
226
+ :type d: numpy.ndarray
227
+ :param t: time array
228
+ :type t: array like
229
+ :param coords: 1D coordinate for spatial dimension
230
+ :type coords: array like
231
+ :param coords_name: name of the 1D coordinate (typically "X" or "Y")
232
+ :type coords_name: str
233
+
234
+ """
235
+
236
+ def __init__(
237
+ self, name, d, t, coords=None, coords_name=None, notation=None
238
+ ):
239
+ """Constructor method."""
240
+ super().__init__(name, d, t, notation=notation)
241
+ # x and y arrays
242
+ self.coords = coords
243
+ self.coords_name = coords_name
244
+
245
+ def plot(self, coords=None, **kwargs):
246
+ """Plot results.
69
247
 
70
- def spatial_integration(self, axis=(0, 1), cellsize=None):
71
- """ Spatial integration along one or two axes """
72
- if cellsize is None:
248
+ :param axe: DESCRIPTION, defaults to None
249
+ :type axe: TYPE, optional
250
+ :param figsize: DESCRIPTION, defaults to None
251
+ :type figsize: TYPE, optional
252
+ :param **kwargs: DESCRIPTION
253
+ :type **kwargs: TYPE
254
+ :return: DESCRIPTION
255
+ :rtype: TYPE
256
+
257
+ """
258
+
259
+ if coords is None:
260
+ coords = self.coords
261
+
262
+ if coords is None:
263
+ raise TypeError("coords data missing")
264
+
265
+ xlabel = notations.get_label(self.coords_name, with_unit=True)
266
+ if "colorbar_kwargs" not in kwargs:
267
+ kwargs["colorbar_kwargs"] = dict()
268
+ if "label" not in kwargs["colorbar_kwargs"]:
269
+ clabel = notations.get_label(self.notation)
270
+ kwargs["colorbar_kwargs"]["label"] = clabel
271
+
272
+ axe = plt_fn.plot_shotgather(
273
+ self.coords,
274
+ self.t,
275
+ self.d,
276
+ xlabel=xlabel,
277
+ ylabel=notations.get_label("t"),
278
+ **kwargs
279
+ )
280
+
281
+ return axe
282
+
283
+ def save(self):
284
+ raise NotImplementedError(
285
+ "Saving method for TemporalResults1D not implemented yet"
286
+ )
287
+
288
+ def get_spatial_stat(self, stat, **kwargs):
289
+ """
290
+
291
+ :param stat: DESCRIPTION
292
+ :type stat: TYPE
293
+ :return: DESCRIPTION
294
+ :rtype: TYPE
295
+
296
+ """
297
+
298
+ if stat in NP_OPERATORS:
299
+ dnew = getattr(np, stat)(self.d, axis=0)
300
+ elif stat == "int":
301
+ dd = self.coords[1] - self.coords[0]
302
+ dnew = np.sum(self.d, axis=0) * dd
303
+ notation = notations.add_operator(
304
+ self.notation, stat, axis=self.coords_name
305
+ )
306
+ return TemporalResults0D(
307
+ self.name + "_" + stat, dnew, self.t, notation=notation
308
+ )
309
+
310
+
311
+ class TemporalResults2D(TemporalResults):
312
+ def __init__(self, name, d, t, x=None, y=None, z=None, notation=None):
313
+ """Initiate instance of TemporalResults2D.
314
+
315
+ :param name: DESCRIPTION
316
+ :type name: TYPE
317
+ :param d: DESCRIPTION
318
+ :type d: TYPE
319
+ :param t: DESCRIPTION
320
+ :type t: TYPE
321
+ :param x: DESCRIPTION, defaults to None
322
+ :type x: TYPE, optional
323
+ :param y: DESCRIPTION, defaults to None
324
+ :type y: TYPE, optional
325
+ :param z: DESCRIPTION, defaults to None
326
+ :type z: TYPE, optional
327
+ :return: DESCRIPTION
328
+ :rtype: TYPE
329
+
330
+ """
331
+
332
+ super().__init__(name, d, t, notation=notation)
333
+ # x and y arrays
334
+ self.x = x
335
+ self.y = y
336
+ # topography
337
+ self.z = z
338
+
339
+ def plot(
340
+ self,
341
+ x=None,
342
+ y=None,
343
+ z=None,
344
+ file_name=None,
345
+ folder_out=None,
346
+ figsize=None,
347
+ dpi=None,
348
+ fmt="png",
349
+ sup_plt_fn=None,
350
+ sup_plt_fn_args=None,
351
+ **kwargs
352
+ ):
353
+ """Plot results.
354
+
355
+ :param **kwargs: DESCRIPTION
356
+ :type **kwargs: TYPE
357
+ :return: DESCRIPTION
358
+ :rtype: TYPE
359
+
360
+ """
361
+ if file_name is None:
362
+ file_name = self.name
363
+
364
+ if x is None:
365
+ x = self.x
366
+ if y is None:
367
+ y = self.y
368
+ if z is None:
369
+ z = self.z
370
+
371
+ if x is None or y is None:
372
+ raise TypeError("x, y or z data missing")
373
+
374
+ if z is None:
375
+ warnings.warn("No topography given.")
376
+
377
+ if "colorbar_kwargs" not in kwargs:
378
+ kwargs["colorbar_kwargs"] = dict()
379
+ if "label" not in kwargs["colorbar_kwargs"]:
380
+ clabel = notations.get_label(self.notation)
381
+ kwargs["colorbar_kwargs"]["label"] = clabel
382
+
383
+ plt_fn.plot_maps(
384
+ x,
385
+ y,
386
+ z,
387
+ self.d,
388
+ self.t,
389
+ file_name=file_name,
390
+ folder_out=folder_out,
391
+ figsize=figsize,
392
+ dpi=dpi,
393
+ fmt=fmt,
394
+ sup_plt_fn=sup_plt_fn,
395
+ sup_plt_fn_args=sup_plt_fn_args,
396
+ **kwargs
397
+ )
398
+
399
+ return None
400
+
401
+ def save(
402
+ self,
403
+ folder=None,
404
+ file_name=None,
405
+ fmt="asc",
406
+ time=None,
407
+ x=None,
408
+ y=None,
409
+ **kwargs
410
+ ):
411
+ if x is None:
412
+ x = self.x
413
+ if y is None:
414
+ y = self.y
415
+ if x is None or y is None:
416
+ raise ValueError("x et y arrays must not be None")
417
+
418
+ if file_name is None:
419
+ file_name = self.name
420
+
421
+ if folder is not None:
422
+ file_name = os.path.join(folder, file_name)
423
+
424
+ if time is not None:
425
+ if isinstance(time, str):
426
+ if time == "final":
427
+ inds = [self.d.shape[2] - 1]
428
+ elif time == "initial":
429
+ inds = [0]
430
+ else:
431
+ inds = [np.argmin(time - np.abs(np.array(self.t) - time))]
432
+
433
+ for i in range(inds):
434
+ file_out = file_name + "_{:04d}.".format(i) + fmt
435
+ tilupy.raster.write_raster(
436
+ x, y, self.d[:, :, i], file_out, fmt=fmt, **kwargs
437
+ )
438
+
439
+ def get_spatial_stat(self, stat, axis=None):
440
+ """
441
+
442
+ :param stat: DESCRIPTION
443
+ :type stat: TYPE
444
+ :param axis: DESCRIPTION, defaults to None
445
+ :type axis: TYPE, optional
446
+ :return: DESCRIPTION
447
+ :rtype: TYPE
448
+
449
+ """
450
+ if axis is None:
451
+ axis = (0, 1)
452
+
453
+ if isinstance(axis, str):
454
+ axis_str = axis
455
+ if axis == "x":
456
+ axis = 1
457
+ elif axis == "y":
458
+ axis = 0
459
+ elif axis == "xy":
460
+ axis = (0, 1)
461
+ else:
462
+ if axis == 1:
463
+ axis_str = "x"
464
+ elif axis == 0:
465
+ axis_str = "y"
466
+ elif axis == (0, 1):
467
+ axis_str = "xy"
468
+
469
+ if stat in NP_OPERATORS:
470
+ dnew = getattr(np, stat)(self.d, axis=axis)
471
+ elif stat == "int":
73
472
  dnew = np.sum(self.d, axis=axis)
473
+ if axis == 1:
474
+ dd = self.x[1] - self.x[0]
475
+ elif axis == 0:
476
+ dd = self.y[1] - self.y[0]
477
+ elif axis == (0, 1):
478
+ dd = (self.x[1] - self.x[0]) * (self.y[1] - self.y[0])
479
+ dnew = dnew * dd
480
+
481
+ if axis == 1:
482
+ # Needed to get correct orinetation as d[0, 0] is the upper corner
483
+ # of the data, with coordinates x[0], y[-1]
484
+ dnew = np.flip(dnew, axis=0)
485
+
486
+ new_name = self.name + "_" + stat + "_" + axis_str
487
+ notation = notations.add_operator(self.notation, stat, axis=axis_str)
488
+
489
+ if axis == (0, 1):
490
+ return TemporalResults0D(new_name, dnew, self.t, notation=notation)
74
491
  else:
75
- dnew = np.sum(self.d*cellsize, axis=axis)
76
- self.d = dnew
77
-
78
- def plot(self, axe=None, figsize=None, folder_out=None, fmt='png', dpi=150,
79
- x=None, y=None, z=None, sup_plt_fn=None, sup_plt_fn_args=None,
80
- **kwargs):
81
- """ Plot results as time dependent"""
82
-
83
- if axe is None:
84
- fig, axe = plt.subplots(1, 1, figsize=figsize)
85
-
86
- if self.d.ndim == 1:
87
- axe.plot(self.t, self.d, **kwargs)
88
- axe.set_xlabel('Time (s)')
89
- axe.set_ylabel(notations.LABELS[self.name])
90
-
91
- elif self.d.ndim == 2:
92
- raise NotImplementedError(('Plot of 1D data as time',
93
- ' functions not implemented yet'))
94
-
95
- elif self.d.ndim == 3:
96
- if x is None or y is None or z is None:
97
- raise TypeError('x, y or z data missing')
98
- plt_fn.plot_maps(x, y, z, self.d, self.t,
99
- self.name, folder_out=folder_out,
100
- figsize=figsize, fmt=fmt, dpi=dpi,
101
- sup_plt_fn=sup_plt_fn,
102
- sup_plt_fn_args=sup_plt_fn_args,
103
- **kwargs)
104
- axe, fig = None, None
105
-
106
- if self.d.ndim != 3 and sup_plt_fn is not None:
107
- if sup_plt_fn_args is None:
108
- sup_plt_fn_args = dict()
109
- sup_plt_fn(axe, **sup_plt_fn_args)
110
-
111
- return axe, fig
112
-
113
- def save(self, folder=None, file_name=None, fmt='asc', time=None,
114
- x=None, y=None, **kwargs):
115
-
116
- if self.d.ndim == 1:
117
- raise NotImplementedError()
118
-
119
- elif self.d.ndim == 2:
120
- raise NotImplementedError()
121
-
122
- elif self.d.ndim == 3:
123
- if x is None:
124
- x = self.x
125
- if y is None:
126
- y = self.y
127
- if x is None or y is None:
128
- raise ValueError('x et y arrays must not be None')
129
-
130
- if file_name is None:
131
- file_name = self.name
132
-
133
- if folder is not None:
134
- file_name = os.path.join(folder, file_name)
135
-
136
- if time is not None:
137
- if isinstance(time, str):
138
- if time == 'final':
139
- inds = [self.d.shape[2]-1]
140
- elif time == 'initial':
141
- inds = [0]
142
- else:
143
- inds = [np.argmin(time - np.abs(np.array(self.t)-time))]
144
-
145
- for i in range(inds):
146
- file_out = file_name + '_{:04d}.'.format(i) + fmt
147
- tilupy.raster.write_raster(x, y, self.d[:, :, i], file_out,
148
- fmt=fmt, **kwargs)
149
-
492
+ if axis == 0:
493
+ coords = self.x
494
+ coords_name = "x"
495
+ else:
496
+ coords = self.x
497
+ coords_name = "y"
498
+ return TemporalResults1D(
499
+ new_name,
500
+ dnew,
501
+ self.t,
502
+ coords,
503
+ coords_name=coords_name,
504
+ notation=notation,
505
+ )
150
506
 
151
507
 
152
- class StaticResults:
153
- """ Result of simulation without time dependence"""
508
+ class StaticResults(AbstractResults):
509
+ """Result of simulation without time dependence"""
154
510
 
155
- def __init__(self, name, d, x=None, y=None, z=None):
156
- # 1d or 2d array
157
- self.d = d
158
- # Name of data
159
- self.name = name
511
+ def __init__(self, name, d, notation=None):
512
+ super().__init__(name, d, notation=notation)
513
+ # x and y arrays
514
+
515
+ def plot(self):
516
+ raise NotImplementedError()
517
+
518
+ def save(self):
519
+ raise NotImplementedError()
520
+
521
+
522
+ class StaticResults2D(StaticResults):
523
+ def __init__(self, name, d, x=None, y=None, z=None, notation=None):
524
+ super().__init__(name, d, notation=notation)
160
525
  # x and y arrays
161
526
  self.x = x
162
527
  self.y = y
163
528
  # topography
164
529
  self.z = z
165
-
166
- def plot(self, axe=None, figsize=None,
167
- folder_out=None, suffix=None, prefix=None,
168
- fmt='png', dpi=150,
169
- x=None, y=None, z=None,
170
- sup_plt_fn=None, sup_plt_fn_args=None, **kwargs):
530
+
531
+ def plot(
532
+ self,
533
+ axe=None,
534
+ figsize=None,
535
+ folder_out=None,
536
+ suffix=None,
537
+ prefix=None,
538
+ fmt="png",
539
+ dpi=150,
540
+ x=None,
541
+ y=None,
542
+ z=None,
543
+ sup_plt_fn=None,
544
+ sup_plt_fn_args=None,
545
+ **kwargs
546
+ ):
171
547
  """Plot results as map"""
172
-
548
+
173
549
  if axe is None:
174
- fig, axe = plt.subplots(1, 1, figsize=figsize)
175
- else:
176
- fig = axe.figure
177
-
550
+ fig, axe = plt.subplots(
551
+ 1, 1, figsize=figsize, layout="constrained"
552
+ )
553
+
178
554
  if x is None:
179
555
  x = self.x
180
556
  if y is None:
181
557
  y = self.y
182
558
  if z is None:
183
559
  z = self.z
184
-
560
+
185
561
  if x is None or y is None or z is None:
186
- raise TypeError('x, y or z data missing')
187
-
188
- axe = plt_fn.plot_data_on_topo(x, y, z, self.d, axe=axe,
189
- figsize=figsize,
190
- **kwargs)
562
+ raise TypeError("x, y or z data missing")
563
+
564
+ if "colorbar_kwargs" not in kwargs:
565
+ kwargs["colorbar_kwargs"] = dict()
566
+ if "label" not in kwargs["colorbar_kwargs"]:
567
+ clabel = notations.get_label(self.notation)
568
+ kwargs["colorbar_kwargs"]["label"] = clabel
569
+
570
+ print(self.name)
571
+
572
+ axe = plt_fn.plot_data_on_topo(
573
+ x, y, z, self.d, axe=axe, figsize=figsize, **kwargs
574
+ )
191
575
  if sup_plt_fn is not None:
192
576
  if sup_plt_fn_args is None:
193
577
  sup_plt_fn_args = dict()
194
578
  sup_plt_fn(axe, **sup_plt_fn_args)
195
-
579
+
196
580
  if folder_out is not None:
197
581
  file_name = self.name
198
582
  if suffix is not None:
199
- file_name = file_name + '_' + suffix
583
+ file_name = file_name + "_" + suffix
200
584
  if prefix is not None:
201
- file_name = prefix + '_' + file_name
202
- file_out = os.path.join(folder_out,file_name + '.' + fmt)
585
+ file_name = prefix + "_" + file_name
586
+ file_out = os.path.join(folder_out, file_name + "." + fmt)
203
587
  axe.figure.tight_layout(pad=0.1)
204
- axe.figure.savefig(file_out, dpi=dpi, bbox_inches='tight',
205
- pad_inches=0.05)
206
-
207
- return axe, fig
208
-
209
- def save(self, folder=None, file_name=None, fmt='txt', time=None,
210
- x=None, y=None, **kwargs):
211
-
588
+ axe.figure.savefig(
589
+ file_out, dpi=dpi, bbox_inches="tight", pad_inches=0.05
590
+ )
591
+
592
+ return axe
593
+
594
+ def save(
595
+ self,
596
+ folder=None,
597
+ file_name=None,
598
+ fmt="txt",
599
+ time=None,
600
+ x=None,
601
+ y=None,
602
+ **kwargs
603
+ ):
212
604
  if x is None:
213
605
  x = self.x
214
606
  if y is None:
215
607
  y = self.y
216
-
608
+
217
609
  if x is None or y is None:
218
- raise ValueError('x et y arrays must not be None')
219
-
610
+ raise ValueError("x et y arrays must not be None")
611
+
220
612
  if file_name is None:
221
- file_name = self.name + '.' + fmt
222
-
613
+ file_name = self.name + "." + fmt
614
+
223
615
  if folder is not None:
224
616
  file_name = os.path.join(folder, file_name)
225
-
226
- tilupy.raster.write_raster(x, y, self.d, file_name,
227
- fmt=fmt, **kwargs)
228
-
229
-
617
+
618
+ tilupy.raster.write_raster(x, y, self.d, file_name, fmt=fmt, **kwargs)
619
+
620
+ def get_spatial_stat(self, stat, axis=None):
621
+ """
622
+
623
+ :param stat: DESCRIPTION
624
+ :type stat: TYPE
625
+ :param axis: DESCRIPTION, defaults to None
626
+ :type axis: TYPE, optional
627
+ :return: DESCRIPTION
628
+ :rtype: TYPE
629
+
630
+ """
631
+ if axis is None:
632
+ axis = (0, 1)
633
+
634
+ if isinstance(axis, str):
635
+ axis_str = axis
636
+ if axis == "x":
637
+ axis = 1
638
+ elif axis == "y":
639
+ axis = 0
640
+ elif axis == "xy":
641
+ axis = (0, 1)
642
+ else:
643
+ if axis == 1:
644
+ axis_str = "x"
645
+ elif axis == 0:
646
+ axis_str = "y"
647
+ elif axis == (0, 1):
648
+ axis_str = "xy"
649
+
650
+ if stat in NP_OPERATORS:
651
+ dnew = getattr(np, stat)(self.d, axis=axis)
652
+ elif stat == "int":
653
+ dnew = np.sum(self.d, axis=axis)
654
+ if axis == 1:
655
+ dd = self.x[1] - self.x[0]
656
+ elif axis == 0:
657
+ dd = self.y[1] - self.y[0]
658
+ elif axis == (0, 1):
659
+ dd = (self.x[1] - self.x[0]) * (self.y[1] - self.y[0])
660
+ dnew = dnew * dd
661
+
662
+ if axis == 1:
663
+ # Needed to get correct orinetation as d[0, 0] is the upper corner
664
+ # of the data, with coordinates x[0], y[-1]
665
+ dnew = np.flip(dnew, axis=0)
666
+
667
+ new_name = self.name + "_" + stat + "_" + axis_str
668
+ notation = notations.add_operator(self.notation, stat, axis=axis_str)
669
+
670
+ if axis == (0, 1):
671
+ return StaticResults0D(new_name, dnew, notation=notation)
672
+ else:
673
+ if axis == 0:
674
+ coords = self.x
675
+ coords_name = "x"
676
+ else:
677
+ coords = self.y
678
+ coords_name = "y"
679
+ return StaticResults1D(
680
+ new_name,
681
+ dnew,
682
+ coords,
683
+ coords_name=coords_name,
684
+ notation=notation,
685
+ )
686
+
687
+
688
+ class StaticResults1D(StaticResults):
689
+ def __init__(self, name, d, coords=None, coords_name=None, notation=None):
690
+ """Constructor method."""
691
+ super().__init__(name, d, notation=notation)
692
+ # x and y arrays
693
+ self.coords = coords
694
+ self.coords_name = coords_name
695
+
696
+ def plot(self, axe=None, figsize=None, **kwargs):
697
+ """Plot results.
698
+
699
+ :param axe: DESCRIPTION, defaults to None
700
+ :type axe: TYPE, optional
701
+ :param figsize: DESCRIPTION, defaults to None
702
+ :type figsize: TYPE, optional
703
+ :param **kwargs: DESCRIPTION
704
+ :type **kwargs: TYPE
705
+ :return: DESCRIPTION
706
+ :rtype: TYPE
707
+
708
+ """
709
+
710
+ if axe is None:
711
+ fig, axe = plt.subplots(
712
+ 1, 1, figsize=figsize, layout="constrained"
713
+ )
714
+
715
+ if isinstance(self.d, np.ndarray):
716
+ data = self.d.T
717
+ else:
718
+ data = self.d
719
+ axe.plot(self.coords, data, label=self.scalar_names)
720
+ axe.set_xlabel(notations.get_label(self.coords_name))
721
+ axe.set_ylabel(notations.get_label(self.notation))
722
+
723
+ return axe
724
+
725
+
726
+ class StaticResults0D(StaticResults):
727
+ def __init__(self, d, name, notation=None):
728
+ super().__init__(name, d, notation=notation)
729
+
230
730
 
231
731
  class Results:
232
- """ Results of thin-layer model simulation
732
+ """Results of thin-layer model simulation
233
733
 
234
734
  This class is the parent class for all simulation results, whatever the
235
735
  kind of input data. Methods and functions for processing results are given
@@ -252,130 +752,212 @@ class Results:
252
752
  self._h = None
253
753
  self._costh = None
254
754
  self._zinit = None
255
-
256
755
 
257
756
  @property
258
757
  def zinit(self):
259
- """ Get initial topography """
758
+ """Get initial topography"""
260
759
  return self._zinit
261
-
760
+
262
761
  @property
263
762
  def z(self):
264
- """ Alias for zinit"""
763
+ """Alias for zinit"""
265
764
  return self.zinit
266
-
765
+
267
766
  @property
268
767
  def h(self):
269
- """ Get thickness """
768
+ """Get thickness"""
270
769
  if self._h is None:
271
- self._h = self.get_temporal_output('h').d
770
+ self._h = self.get_output("h").d
272
771
  return self._h
273
-
772
+
274
773
  @property
275
774
  def h_max(self):
276
- """ Get maximum thickness """
775
+ """Get maximum thickness"""
277
776
  if self._h_max is None:
278
- self._h_max = self.get_static_output('h', 'max').d
777
+ self._h_max = self.get_output("h_max").d
279
778
  return self._h_max
280
-
779
+
281
780
  def get_costh(self):
282
781
  """Get cos(slope) of topography"""
283
782
  [Fx, Fy] = np.gradient(self.zinit, self.y, self.x)
284
- costh = 1/np.sqrt(1 + Fx**2 + Fy**2)
783
+ costh = 1 / np.sqrt(1 + Fx**2 + Fy**2)
285
784
  return costh
286
785
 
287
786
  @property
288
787
  def costh(self):
289
- """ Compute or get cos(slope) of topography """
788
+ """Compute or get cos(slope) of topography"""
290
789
  if self._costh is None:
291
790
  self._costh = self.get_costh()
292
791
  return self._costh
293
792
 
294
- def get_temporal_output(self, name, h_thresh=None):
295
- return TemporalResults(name, None, None, h_thresh=h_thresh)
296
-
297
- def get_static_output(self, name, **kwargs):
298
- return StaticResults(name, None, **kwargs)
299
-
300
- def get_output(self, name, **kwargs):
301
- if name in TEMPORAL_DATA_2D:
302
- data = self.get_temporal_output(name, **kwargs)
303
- elif name in STATIC_DATA_2D:
304
- state, stat = name.split('_')
305
- data = self.get_static_output(name, **kwargs)
306
- return data
307
-
308
- def plot(self, name, save=True, folder_out=None, dpi=150, fmt='png',
309
- h_thresh=None, from_file=False, display_plot=True,
310
- **kwargs):
311
-
312
- assert(name in DATA_NAMES)
313
-
793
+ def get_output(self, output_name, from_file=True, **kwargs):
794
+ strs = output_name.split("_")
795
+ n_strs = len(strs)
796
+ # If no operator is called, call directly _get_output
797
+ if n_strs == 1:
798
+ res = self._get_output(output_name, **kwargs)
799
+ return res
800
+
801
+ # Otherwise, get name, operator and axis (optional)
802
+ name = strs[0]
803
+ operator = strs[1]
804
+ if n_strs == 3:
805
+ axis = strs[2]
806
+ else:
807
+ axis = None
808
+
809
+ res = None
810
+ # If processed output is read directly from file, call the child method
811
+ # _read_from_file.
812
+ if from_file:
813
+ res = self._read_from_file(name, operator, axis=axis, **kwargs)
814
+ # res is None in case of function failure
815
+
816
+ # If no results could be read from file, output must be
817
+ # processed by tilupy
818
+ if res is None:
819
+ # Get output from name
820
+ res = self._get_output(name, x=self.x, y=self.y, **kwargs)
821
+ if axis is None:
822
+ # If no axis is given, the operator operates over time by
823
+ # default
824
+ res = res.get_temporal_stat(operator)
825
+ else:
826
+ if axis == "t":
827
+ res = res.get_temporal_stat(operator)
828
+ else:
829
+ res = res.get_spatial_stat(operator, axis=axis)
830
+
831
+ return res
832
+
833
+ def _get_output(self):
834
+ raise NotImplementedError
835
+
836
+ def _read_from_file(self):
837
+ raise NotImplementedError
838
+
839
+ def plot(
840
+ self,
841
+ name,
842
+ save=True,
843
+ folder_out=None,
844
+ dpi=150,
845
+ fmt="png",
846
+ h_thresh=None,
847
+ from_file=False,
848
+ display_plot=True,
849
+ **kwargs
850
+ ):
851
+ """
852
+
853
+
854
+ Parameters
855
+ ----------
856
+ name : TYPE
857
+ DESCRIPTION.
858
+ save : TYPE, optional
859
+ DESCRIPTION. The default is True.
860
+ folder_out : TYPE, optional
861
+ DESCRIPTION. The default is None.
862
+ dpi : TYPE, optional
863
+ DESCRIPTION. The default is 150.
864
+ fmt : TYPE, optional
865
+ DESCRIPTION. The default is "png".
866
+ h_thresh : TYPE, optional
867
+ DESCRIPTION. The default is None.
868
+ from_file : TYPE, optional
869
+ DESCRIPTION. The default is False.
870
+ display_plot : TYPE, optional
871
+ DESCRIPTION. The default is True.
872
+ **kwargs : TYPE
873
+ DESCRIPTION.
874
+
875
+ Returns
876
+ -------
877
+ axe : TYPE
878
+ DESCRIPTION.
879
+ fig : TYPE
880
+ DESCRIPTION.
881
+
882
+ """
883
+ assert name in DATA_NAMES
884
+
314
885
  if save:
315
886
  if folder_out is None:
316
- folder_out = os.path.join(self.folder_output, 'plots')
887
+ folder_out = os.path.join(self.folder_output, "plots")
317
888
  os.makedirs(folder_out, exist_ok=True)
318
- kwargs['folder_out'] = folder_out
319
- kwargs['dpi'] = dpi
320
- kwargs['fmt'] = fmt
321
-
889
+ kwargs["folder_out"] = folder_out
890
+ kwargs["dpi"] = dpi
891
+ kwargs["fmt"] = fmt
892
+
322
893
  if not display_plot:
323
894
  backend = plt.get_backend()
324
- plt.switch_backend('Agg')
325
-
895
+ plt.switch_backend("Agg")
896
+
326
897
  data = self.get_output(name, from_file=from_file, h_thresh=h_thresh)
327
-
328
- if name in TEMPORAL_DATA_2D + STATIC_DATA_2D:
329
- if 'colorbar_kwargs' not in kwargs:
330
- kwargs['colorbar_kwargs'] = dict()
331
- if 'label' not in kwargs['colorbar_kwargs']:
332
- labels = notations.LABELS
333
- kwargs['colorbar_kwargs']['label'] = labels[name]
334
-
335
-
336
- if 'x' not in kwargs:
337
- kwargs['x'] = self.x
338
- if 'y' not in kwargs:
339
- kwargs['y'] = self.y
340
- if 'z' not in kwargs:
341
- kwargs['z'] = self.zinit
342
-
343
- axe, fig = data.plot(**kwargs)
344
-
898
+
899
+ # if name in TEMPORAL_DATA_2D + STATIC_DATA_2D:
900
+ # if "colorbar_kwargs" not in kwargs:
901
+ # kwargs["colorbar_kwargs"] = dict()
902
+ # if "label" not in kwargs["colorbar_kwargs"]:
903
+ # kwargs["colorbar_kwargs"]["label"] = notations.get_label(
904
+ # self.notation
905
+ # )
906
+
907
+ if "x" not in kwargs:
908
+ kwargs["x"] = self.x
909
+ if "y" not in kwargs:
910
+ kwargs["y"] = self.y
911
+ if "z" not in kwargs:
912
+ kwargs["z"] = self.zinit
913
+
914
+ axe = data.plot(**kwargs)
915
+
345
916
  if not display_plot:
346
917
  plt.switch_backend(backend)
347
-
348
- return axe, fig
349
-
350
- def save(self, name, folder=None, file_name=None, fmt='txt',
351
- from_file=True, **kwargs):
352
-
918
+
919
+ return axe
920
+
921
+ def save(
922
+ self,
923
+ name,
924
+ folder=None,
925
+ file_name=None,
926
+ fmt="txt",
927
+ from_file=True,
928
+ **kwargs
929
+ ):
353
930
  if folder is None:
354
- folder = os.path.join(self.folder_output, 'processed')
931
+ folder = os.path.join(self.folder_output, "processed")
355
932
  os.makedirs(folder, exist_ok=True)
356
-
933
+
357
934
  if name in DATA_NAMES:
358
935
  data = self.get_output(name, from_file=from_file)
359
- if data.d.ndim>1:
360
- if 'x' not in kwargs:
361
- kwargs['x'] = self.x
362
- if 'y' not in kwargs:
363
- kwargs['y'] = self.y
364
-
365
- data.save(folder=folder, file_name=file_name, fmt=fmt,
366
- **kwargs)
367
-
936
+ if data.d.ndim > 1:
937
+ if "x" not in kwargs:
938
+ kwargs["x"] = self.x
939
+ if "y" not in kwargs:
940
+ kwargs["y"] = self.y
941
+
942
+ data.save(folder=folder, file_name=file_name, fmt=fmt, **kwargs)
943
+
368
944
  elif name in TOPO_DATA_2D:
369
945
  if file_name is None:
370
946
  file_name = name
371
947
  file_out = os.path.join(folder, file_name)
372
- tilupy.raster.write_raster(self.x, self.y, getattr(self, name),
373
- file_out, fmt=fmt, **kwargs)
374
-
375
-
948
+ tilupy.raster.write_raster(
949
+ self.x,
950
+ self.y,
951
+ getattr(self, name),
952
+ file_out,
953
+ fmt=fmt,
954
+ **kwargs
955
+ )
956
+
957
+
376
958
  def get_results(code, **kwargs):
377
959
  """
378
- Get simulation results for a given code. This function calls the
960
+ Get simulation results for a given code. This function calls the
379
961
  appropriate module.
380
962
 
381
963
  Parameters
@@ -390,11 +972,11 @@ def get_results(code, **kwargs):
390
972
  None.
391
973
 
392
974
  """
393
- module = importlib.import_module('tilupy.models.'+code+'.read')
975
+ module = importlib.import_module("tilupy.models." + code + ".read")
394
976
  return module.Results(**kwargs)
395
977
 
978
+
396
979
  def use_thickness_threshold(simu, array, h_thresh):
397
-
398
- thickness = simu.get_temporal_output('h')
399
- array[thickness.d<h_thresh] = 0
400
- return array
980
+ thickness = simu.get_output("h")
981
+ array[thickness.d < h_thresh] = 0
982
+ return array