smashbox 1.0__py2.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.
Files changed (73) hide show
  1. smashbox/.spyproject/config/backups/codestyle.ini.bak +8 -0
  2. smashbox/.spyproject/config/backups/encoding.ini.bak +6 -0
  3. smashbox/.spyproject/config/backups/vcs.ini.bak +7 -0
  4. smashbox/.spyproject/config/backups/workspace.ini.bak +12 -0
  5. smashbox/.spyproject/config/codestyle.ini +8 -0
  6. smashbox/.spyproject/config/defaults/defaults-codestyle-0.2.0.ini +5 -0
  7. smashbox/.spyproject/config/defaults/defaults-encoding-0.2.0.ini +3 -0
  8. smashbox/.spyproject/config/defaults/defaults-vcs-0.2.0.ini +4 -0
  9. smashbox/.spyproject/config/defaults/defaults-workspace-0.2.0.ini +6 -0
  10. smashbox/.spyproject/config/encoding.ini +6 -0
  11. smashbox/.spyproject/config/vcs.ini +7 -0
  12. smashbox/.spyproject/config/workspace.ini +12 -0
  13. smashbox/__init__.py +8 -0
  14. smashbox/asset/flwdir/flowdir_fr_1000m.tif +0 -0
  15. smashbox/asset/outlets/.Rhistory +0 -0
  16. smashbox/asset/outlets/db_bnbv_fr.csv +142704 -0
  17. smashbox/asset/outlets/db_bnbv_light.csv +42084 -0
  18. smashbox/asset/outlets/db_sites.csv +8700 -0
  19. smashbox/asset/outlets/db_stations.csv +2916 -0
  20. smashbox/asset/outlets/db_stations_example.csv +19 -0
  21. smashbox/asset/outlets/edit_database.py +185 -0
  22. smashbox/asset/outlets/readme.txt +5 -0
  23. smashbox/asset/params/ci.tif +0 -0
  24. smashbox/asset/params/cp.tif +0 -0
  25. smashbox/asset/params/ct.tif +0 -0
  26. smashbox/asset/params/kexc.tif +0 -0
  27. smashbox/asset/params/kmlt.tif +0 -0
  28. smashbox/asset/params/llr.tif +0 -0
  29. smashbox/asset/setup/setup_rhax_gr4_dt3600.yaml +15 -0
  30. smashbox/asset/setup/setup_rhax_gr4_dt900.yaml +15 -0
  31. smashbox/asset/setup/setup_rhax_gr5_dt3600.yaml +15 -0
  32. smashbox/asset/setup/setup_rhax_gr5_dt900.yaml +15 -0
  33. smashbox/init/README.md +3 -0
  34. smashbox/init/__init__.py +3 -0
  35. smashbox/init/multimodel_statistics.py +405 -0
  36. smashbox/init/param.py +799 -0
  37. smashbox/init/smashbox.py +186 -0
  38. smashbox/model/__init__.py +1 -0
  39. smashbox/model/atmos_data_connector.py +518 -0
  40. smashbox/model/mesh.py +185 -0
  41. smashbox/model/model.py +829 -0
  42. smashbox/model/setup.py +109 -0
  43. smashbox/plot/__init__.py +1 -0
  44. smashbox/plot/myplot.py +1133 -0
  45. smashbox/plot/plot.py +1662 -0
  46. smashbox/read_inputdata/__init__.py +1 -0
  47. smashbox/read_inputdata/read_data.py +1229 -0
  48. smashbox/read_inputdata/smashmodel.py +395 -0
  49. smashbox/stats/__init__.py +1 -0
  50. smashbox/stats/mystats.py +1632 -0
  51. smashbox/stats/stats.py +2022 -0
  52. smashbox/test.py +532 -0
  53. smashbox/test_average_stats.py +122 -0
  54. smashbox/test_mesh.r +8 -0
  55. smashbox/test_mesh_from_graffas.py +69 -0
  56. smashbox/tools/__init__.py +1 -0
  57. smashbox/tools/geo_toolbox.py +1028 -0
  58. smashbox/tools/tools.py +461 -0
  59. smashbox/tutorial_R.r +182 -0
  60. smashbox/tutorial_R_graffas.r +88 -0
  61. smashbox/tutorial_R_graffas_local.r +33 -0
  62. smashbox/tutorial_python.py +102 -0
  63. smashbox/tutorial_readme.py +261 -0
  64. smashbox/tutorial_report.py +58 -0
  65. smashbox/tutorials/Python_tutorial.md +124 -0
  66. smashbox/tutorials/R_Graffas_tutorial.md +153 -0
  67. smashbox/tutorials/R_tutorial.md +121 -0
  68. smashbox/tutorials/__init__.py +6 -0
  69. smashbox/tutorials/generate_doc.md +7 -0
  70. smashbox-1.0.dist-info/METADATA +998 -0
  71. smashbox-1.0.dist-info/RECORD +73 -0
  72. smashbox-1.0.dist-info/WHEEL +5 -0
  73. smashbox-1.0.dist-info/licenses/LICENSE +100 -0
@@ -0,0 +1,1632 @@
1
+ from smashbox.stats import stats
2
+ from smashbox.tools import tools
3
+ import numpy as np
4
+ import multiprocessing
5
+ import os
6
+ from functools import partial
7
+ from tqdm import tqdm
8
+ import pandas as pd
9
+ from smash.fcore import _mwd_metrics as smash_metrics
10
+
11
+
12
+ class mystats:
13
+ """
14
+ The class mystats includes functions and children classes to compute basics statistics
15
+ with the results of the smash model.
16
+ """
17
+
18
+ def __init__(self, parent_class):
19
+ self._parent_class = parent_class
20
+ """_parent_class attribute stores the parent_class src.model() to be able to
21
+ access to the result of the smash simulation"""
22
+
23
+ self.misfit_stats = misfit_stats(self)
24
+ """Attribute misfit_stats stores the class src.mystats.misfit_stats(). Its goal
25
+ is to compute misfit criteria between simulated and observed discharges"""
26
+ self.quantile_stats = spatial_quantile()
27
+ """Attribute quantile_stats owns the class src.mystats.spatial_quantile().
28
+ Its goal is to compute the discharges quantiles for various return period."""
29
+ self.spatial_stats = spatial_stats(self)
30
+ """Atrribute spatial_stats owns the class src.mystats.spatial_stats(). Its goal
31
+ is to provide basic statistics on the discharges field over the time
32
+ (mean, median, q20, q80, maximum, minimum)"""
33
+ self.outlets_stats = outlets_stats(self)
34
+ """Atrribute outlets_stats owns the class src.mystats.outlets_stats().
35
+ Its goal is to provide basic statistics on the discharges at every outlets over the
36
+ time (mean, median, q20, q80, maximum, minimum)"""
37
+
38
+ def fmisfit_stats(self, nodata=-99.0, column=[], ret=False, use_smash_metrics=True):
39
+ """
40
+ Compute the misfit for every outlets between the simulated and the
41
+ observed discharges
42
+ :param nodata: No data values, defaults to -99.0
43
+ :type nodata: TYPE, optional
44
+ :param column: column on which to compute the statistics (gauge), defaults to []
45
+ :type column: TYPE, optional
46
+ :param ret: return the result, defaults to False
47
+ :type ret: TYPE, optional
48
+ :return: object with attributes with different statistics.
49
+ :rtype: class src.mystats.misfit.results()
50
+
51
+ """
52
+
53
+ if not use_smash_metrics:
54
+ self.misfit_stats.se(nodata=nodata, column=column)
55
+ self.misfit_stats.mse(nodata=nodata, column=column)
56
+ self.misfit_stats.rmse(nodata=nodata, column=column)
57
+ self.misfit_stats.nrmse(nodata=nodata, column=column)
58
+ self.misfit_stats.mae(nodata=nodata, column=column)
59
+ self.misfit_stats.mape(nodata=nodata, column=column)
60
+ self.misfit_stats.lgrm(nodata=nodata, column=column)
61
+ self.misfit_stats.nse(nodata=nodata, column=column)
62
+ self.misfit_stats.nnse(nodata=nodata, column=column)
63
+ self.misfit_stats.kge(nodata=nodata, column=column)
64
+ else:
65
+ self.misfit_stats.sm_se(column=column)
66
+ self.misfit_stats.sm_mse(column=column)
67
+ self.misfit_stats.sm_rmse(column=column)
68
+ self.misfit_stats.sm_nrmse(column=column)
69
+ self.misfit_stats.sm_mae(column=column)
70
+ self.misfit_stats.sm_mape(column=column)
71
+ self.misfit_stats.sm_lgrm(column=column)
72
+ self.misfit_stats.sm_nse(column=column)
73
+ self.misfit_stats.sm_nnse(column=column)
74
+ self.misfit_stats.sm_kge(column=column)
75
+
76
+ if ret:
77
+ return self.misfit.results
78
+
79
+ def fspatial_stats(self, ret=False):
80
+ """
81
+ Compute basics statistics over the time (mean, median, q20, q80, maximum, minimum)
82
+ on the spatial discharges field.
83
+ :param ret: return the result, defaults to False
84
+ :type ret: TYPE, optional
85
+ :return: object with attributes with different statistics.
86
+ :rtype: class src.mystats.spatial_stats.results()
87
+
88
+ """
89
+
90
+ if not hasattr(self._parent_class.extra_smash_results, "q_domain"):
91
+ raise ValueError(
92
+ "No smash extra results 'q_domain' found. Run forward_run()"
93
+ "with return_options={'q_domain': True}"
94
+ )
95
+
96
+ self.spatial_stats.mean()
97
+ self.spatial_stats.median()
98
+ self.spatial_stats.q20()
99
+ self.spatial_stats.q80()
100
+ self.spatial_stats.maximum()
101
+ self.spatial_stats.minimum()
102
+ self.spatial_stats.var()
103
+
104
+ if ret:
105
+ return self.spatial_stats.results
106
+
107
+ def foutlets_stats(self, ret=False):
108
+ """
109
+ Compute basices statistics over the time (mean, median, q20, q80, maximum, minimum)
110
+ at every outlets.
111
+ :param ret: return the result, defaults to False
112
+ :type ret: TYPE, optional
113
+ :return: object with attributes with different statistics.
114
+ :rtype: class src.mystats.outlets_stats.results()
115
+
116
+ """
117
+ if self._parent_class.mysmashmodel is None:
118
+ raise ValueError(
119
+ "Attribut mysmashmodel is None. Perhaps, you forget to buil and run the"
120
+ "smash model..."
121
+ )
122
+
123
+ self.outlets_stats.mean()
124
+ self.outlets_stats.median()
125
+ self.outlets_stats.q20()
126
+ self.outlets_stats.q80()
127
+ self.outlets_stats.maximum()
128
+ self.outlets_stats.minimum()
129
+ self.outlets_stats.var()
130
+
131
+ if ret:
132
+ return self.outlets_stats.results
133
+
134
+ @tools.autocast_args
135
+ def fmaxima_stats(
136
+ self,
137
+ t_axis: int = 2,
138
+ nb_minimum_chunks: int = 4,
139
+ chunk_size: int = 365,
140
+ quantile_duration: list | tuple = [1, 2, 3, 4, 6, 12, 24, 48, 72],
141
+ cumulated_maxima: bool = True,
142
+ ):
143
+ """
144
+ Compute the maximum discharge values of a 3D array by chunk, corresponding to `chunk_size` (days), along axis `t_axis` (time) for each pixel of the array.(nbX, nbY).
145
+
146
+ Parameters
147
+ ----------
148
+
149
+ t_axis : int
150
+ The axis along with the maximum will be computed. This axis should correspond
151
+ to the time.
152
+ nb_minimum_chunks: int
153
+ number of minimum chunks required to compute the maxima along
154
+ the t_axis. Default is set to 4. If the number of chunks is lower, the function
155
+ will return None.
156
+ chunk_size: int
157
+ Size of the chunks in days. Default is 365 days.
158
+ quantile_duration: list | tuple
159
+ The duration of every quantile (hours). The discharges will be resampled for every duration. Default is [1, 2, 3, 4, 6, 12, 24, 48, 72] hours.
160
+ cumulated_maxima: bool
161
+ For each call of the function, the maxima will accumulate in a matrix for
162
+ each quantil duration. This provide a convient way to compute the quantile
163
+ (fit gumbel/gev) after many successive simulations.
164
+
165
+
166
+ Examples
167
+ --------
168
+ >>> import smashbox
169
+ >>> import numpy as np
170
+ >>>
171
+ >>> graffas_prcp = np.zeros(shape=(142, 166, 1300))
172
+ >>> rng = np.random.default_rng()
173
+ >>> graffas_prcp = (
174
+ >>> graffas_prcp
175
+ >>> + rng.multinomial(20, [0.2, 0.8], size=graffas_prcp.shape)[:, :, :, 0]
176
+ >>> )
177
+ >>>
178
+ >>> es=smashbox.SmashBox()
179
+ >>> sb.newmodel("graffas_zone")
180
+ >>> sb.graffas_zone.generate_mesh()
181
+ >>> sb.graffas_zone.atmos_data_connector(input_prcp=graffas_prcp)
182
+ >>> sb.graffas_zone.model()
183
+ >>> sb.graffas_zone.forward_run(invert_states=True, return_options={"q_domain": True})
184
+ >>>
185
+ >>> sb.graffas_zone.mystats.stats_maxima(chunk_size=10)
186
+ >>> sb.graffas_zone.mystats.stats_maxima(chunk_size=10)
187
+ >>>
188
+ >>> results = stats.fit_quantile(
189
+ >>> maxima=es.graffas_zone.mystats.spatial_quantile.spatial_cumulated_maxima[
190
+ >>> :, :, :, 0
191
+ >>> ],
192
+ >>> t_axis=2,
193
+ >>> return_periods=[2, 5, 10, 20, 50, 100],
194
+ >>> fit="gumbel",
195
+ >>> estimate_method="MLE",
196
+ >>> quantile_duration=1,
197
+ >>> ncpu=6,
198
+ >>>)
199
+
200
+ """
201
+ if pd.Timedelta(
202
+ hours=max(quantile_duration),
203
+ ) > pd.Timedelta(
204
+ days=chunk_size,
205
+ ):
206
+ raise ValueError(
207
+ f"The chunk_size {chunk_size} (days) must be"
208
+ f" greater or equal than the quantile duration {max(quantile_duration)} (hours)"
209
+ )
210
+
211
+ if not hasattr(self._parent_class.extra_smash_results, "q_domain"):
212
+ raise ValueError(
213
+ "No smash extra results 'q_domain' found. Run forward_run()"
214
+ "with return_options={'q_domain': True}"
215
+ )
216
+
217
+ all_maxima = None
218
+ for id_dur, duration in enumerate(quantile_duration):
219
+ array = stats.time_resample_array(
220
+ array=self._parent_class.extra_smash_results.q_domain,
221
+ quantile_duration=duration,
222
+ model_time_step=self._parent_class.mysmashmodel.setup.dt,
223
+ quantile_chunk_size=chunk_size,
224
+ t_axis=t_axis,
225
+ )
226
+
227
+ maxima = stats.compute_maxima(
228
+ array=array,
229
+ t_axis=t_axis,
230
+ nb_minimum_chunks=nb_minimum_chunks,
231
+ chunk_size=chunk_size,
232
+ quantile_duration=duration,
233
+ )
234
+
235
+ if all_maxima is None:
236
+ all_maxima = (
237
+ np.zeros(shape=(*maxima.shape, len(quantile_duration))) + np.nan
238
+ )
239
+ all_maxima_outlets = (
240
+ np.zeros(
241
+ shape=(
242
+ len(self._parent_class.mysmashmodel.mesh.code),
243
+ maxima.shape[t_axis],
244
+ len(quantile_duration),
245
+ )
246
+ )
247
+ + np.nan
248
+ )
249
+
250
+ all_maxima[:, :, :, id_dur] = maxima
251
+
252
+ for i in range(len(self._parent_class.mymesh.mesh["code"])):
253
+ coords = self._parent_class.mysmashmodel.mesh.gauge_pos[i]
254
+ all_maxima_outlets[i, :, id_dur] = all_maxima[
255
+ coords[0], coords[1], :, id_dur
256
+ ]
257
+
258
+ if cumulated_maxima:
259
+ # if hasattr(self.quantile_stats, "spatial_cumulated_maxima"):
260
+ # self.quantile_stats.spatial_cumulated_maxima = np.concat(
261
+ # (self.quantile_stats.spatial_cumulated_maxima, all_maxima),
262
+ # axis=t_axis,
263
+ # )
264
+ # self.quantile_stats.spatial_cumulated_maxima_outlets = np.concat(
265
+ # (
266
+ # self.quantile_stats.spatial_cumulated_maxima_outlets,
267
+ # all_maxima_outlets,
268
+ # ),
269
+ # axis=1,
270
+ # )
271
+ # else:
272
+ # setattr(self.quantile_stats, "spatial_cumulated_maxima", all_maxima)
273
+ # setattr(
274
+ # self.quantile_stats,
275
+ # "spatial_cumulated_maxima_outlets",
276
+ # all_maxima_outlets,
277
+ # )
278
+
279
+ if self.quantile_stats.spatial_cumulated_maxima is None:
280
+ self.quantile_stats.spatial_cumulated_maxima = all_maxima
281
+ self.quantile_stats.spatial_cumulated_maxima_outlets = all_maxima_outlets
282
+ else:
283
+ self.quantile_stats.spatial_cumulated_maxima = np.concat(
284
+ (self.quantile_stats.spatial_cumulated_maxima, all_maxima),
285
+ axis=t_axis,
286
+ )
287
+ self.quantile_stats.spatial_cumulated_maxima_outlets = np.concat(
288
+ (
289
+ self.quantile_stats.spatial_cumulated_maxima_outlets,
290
+ all_maxima_outlets,
291
+ ),
292
+ axis=1,
293
+ )
294
+
295
+ # setattr(self.quantile_stats, "spatial_maxima", all_maxima)
296
+ # setattr(self.quantile_stats, "spatial_maxima_outlets", all_maxima_outlets)
297
+ self.quantile_stats.spatial_maxima = all_maxima
298
+ self.quantile_stats.spatial_maxima_outlets = all_maxima_outlets
299
+
300
+ @tools.autocast_args
301
+ def fquantile_stats2(
302
+ self,
303
+ t_axis: int = 2,
304
+ return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
305
+ fit: str = "gumbel",
306
+ nb_minimum_chunks: int = 4,
307
+ quantile_duration: list | tuple = [1, 2, 3, 4, 6, 12, 24, 48, 72],
308
+ estimate_method: str = "MLE",
309
+ chunk_size: int = 365,
310
+ ncpu: int | None = None,
311
+ # from_maxima: bool = False,
312
+ compute_uncertainties: bool = False,
313
+ bootstrap_sample: int = 100,
314
+ ):
315
+ """
316
+ Compute the discharge quantile of an 3D array by chunk, corresponding to
317
+ `chunk_size` (days), along axis `t_axis` (time) for each pixel of the array.
318
+ (nbX, nbY), for each duration `quantile_duration` and for each return period
319
+ `return_period`. This function perform the computation in parallel respect
320
+ to the the list of quantile duration.
321
+
322
+ Parameters
323
+ ----------
324
+
325
+ t_axis : int
326
+ The axis along with the maximum will be computed. This axis should correspond
327
+ to the time.
328
+ return_periods: list | tuple
329
+ The duration of every return period of unit `chunk_size`.
330
+ Default is [2, 5, 10, 20, 50, 100] with a chunk_size=365 days.
331
+ fit: str
332
+ The extrem law to use to compute the quantile. Choice are
333
+ 'gumbel' | 'gev'. Default is 'gumbel'.
334
+ nb_minimum_chunks: int
335
+ number of minimum chunks required to compute the maxima along
336
+ the t_axis. Default is set to 4. If the number of chunks is lower, the function
337
+ will return None.
338
+ estimate_method: str
339
+ The method to use to fit rhe Gumbel or GEV law. Choice are `MLE`
340
+ (Maximum Likelihood Estimate) or `MM` (Method of Moments). Default is `MLE`.
341
+ chunk_size: int
342
+ Size of the chunks in days. Default is 365 days.
343
+ quantile_duration: list | tuple
344
+ The duration of every quantile (hours). The discharges will be resampled for
345
+ every duration. Default is [1, 2, 3, 4, 6, 12, 24, 48, 72] hours.
346
+ ncpu: int
347
+ Number of cpu to use to parrallelize the computation. Default is set to
348
+ int(os.cpu_count() / 2).
349
+ compute_uncertainties: bool
350
+ Compute the uncertainties using the parametric bootstrap method
351
+ bootstrap_sample: int
352
+ Number of sample using by the bootstrap method, default is 100
353
+
354
+ Return:
355
+ ------
356
+ Results are stored in the class spatial_quantile wit different attributes:
357
+ - spatial_quantile_matrix : matrix of the spatial quantile for each duration
358
+ and each return period.
359
+ - spatial_maxima_matrix : matrix of the spatial maxima for each duration and
360
+ each return period.
361
+ - spatial_cumulated_maxima_matrix : matrix of the spatial accumulated maxima
362
+ for each duration and each return period.
363
+ - Quantile_{`duration`}h : class of spatial_quantile_results() with attributes:
364
+ - self.T : the return periods
365
+ - self.Q_th : the quantile matrix for every return periods
366
+ - self.T_emp : the empirical return period for each maximum
367
+ - self.maxima : the matrix of the maxima
368
+ - self.nb_chunks : nb of chunk, i.e data for each pixel
369
+ - self.fit : fitting law
370
+ - self.fit_shape : matrix of the shape coefficient
371
+ - self.fit_scale : matrix of the scale coefficient
372
+ - self.fit_loc : matrix of the localisation coefficient
373
+ - self.duration : duration of the quantile
374
+
375
+ Examples
376
+ --------
377
+ >>> import smashbox
378
+ >>> import numpy as np
379
+ >>>
380
+ >>> graffas_prcp = np.zeros(shape=(142, 166, 1300))
381
+ >>> rng = np.random.default_rng()
382
+ >>> graffas_prcp = (
383
+ >>> graffas_prcp
384
+ >>> + rng.multinomial(20, [0.2, 0.8], size=graffas_prcp.shape)[:, :, :, 0]
385
+ >>> )
386
+ >>>
387
+ >>> es=smashbox.SmashBox()
388
+ >>> sb.newmodel("graffas_zone")
389
+ >>> sb.graffas_zone.generate_mesh()
390
+ >>> sb.graffas_zone.atmos_data_connector(input_prcp=graffas_prcp)
391
+ >>> sb.graffas_zone.model()
392
+ >>> sb.graffas_zone.forward_run(invert_states=True, return_options={"q_domain": True})
393
+ >>>
394
+ >>> sb.graffas_zone.mystats.stats_quantile(chunk_size=10, ncpu=6)
395
+
396
+ """
397
+
398
+ if pd.Timedelta(
399
+ hours=max(quantile_duration),
400
+ ) > pd.Timedelta(
401
+ days=chunk_size,
402
+ ):
403
+ raise ValueError(
404
+ f"The chunk_size {chunk_size} (days) must be"
405
+ f"greater or equal than the quantile duration {max(quantile_duration)} (hours)"
406
+ )
407
+
408
+ if not hasattr(self._parent_class.extra_smash_results, "q_domain"):
409
+ raise ValueError(
410
+ "No smash extra results 'q_domain' found. Run forward_run()"
411
+ "with return_options={'q_domain': True}"
412
+ )
413
+
414
+ if ncpu is None:
415
+ ncpu = int(os.cpu_count() / 2)
416
+ else:
417
+ ncpu = int(min(ncpu, os.cpu_count() - 1))
418
+
419
+ model_time_step = self._parent_class.mysmashmodel.setup.dt
420
+
421
+ shape = list(self._parent_class.extra_smash_results.q_domain.shape)
422
+ shape.insert(0, shape.pop(t_axis))
423
+
424
+ spatial_quantile_matrix = np.zeros(
425
+ shape=(
426
+ shape[1],
427
+ shape[2],
428
+ len(quantile_duration),
429
+ len(return_periods),
430
+ )
431
+ )
432
+
433
+ spatial_quantile_matrix_outlets = np.zeros(
434
+ shape=(
435
+ len(self._parent_class.mysmashmodel.mesh.code),
436
+ len(quantile_duration),
437
+ len(return_periods),
438
+ )
439
+ )
440
+
441
+ spatial_maxima = None
442
+ spatial_maxima_outlets = None
443
+
444
+ q_domain = self._parent_class.extra_smash_results.q_domain
445
+
446
+ partial_sp_quantile = partial(
447
+ stats.spatial_quantiles_unparallel,
448
+ q_domain,
449
+ t_axis,
450
+ return_periods,
451
+ fit,
452
+ nb_minimum_chunks,
453
+ model_time_step,
454
+ estimate_method,
455
+ chunk_size,
456
+ compute_uncertainties,
457
+ bootstrap_sample,
458
+ )
459
+
460
+ if hasattr(self.quantile_stats, "spatial_cumulated_maxima"):
461
+ imap_args = []
462
+ for id_dur, duration in enumerate(quantile_duration):
463
+ imap_args.append(
464
+ [
465
+ duration,
466
+ self.quantile_stats.spatial_cumulated_maxima[:, :, :, id_dur],
467
+ ]
468
+ )
469
+ else:
470
+ imap_args = [[duration] for id_dur, duration in enumerate(quantile_duration)]
471
+
472
+ with multiprocessing.Pool(ncpu) as p, tqdm(total=len(quantile_duration)) as pbar:
473
+
474
+ for res in p.starmap(
475
+ partial_sp_quantile,
476
+ imap_args,
477
+ chunksize=1,
478
+ ):
479
+ pbar.update()
480
+ pbar.refresh()
481
+ pos = quantile_duration.index(res["duration"])
482
+ spatial_quantile_matrix[:, :, pos, :] = res["Q_th"]
483
+
484
+ for i in range(len(self._parent_class.mymesh.mesh["code"])):
485
+ coords = self._parent_class.mysmashmodel.mesh.gauge_pos[i]
486
+ spatial_quantile_matrix_outlets[i, pos, :] = spatial_quantile_matrix[
487
+ coords[0], coords[1], pos, :
488
+ ]
489
+
490
+ if spatial_maxima is None:
491
+ spatial_maxima = (
492
+ np.zeros(shape=(*res["maxima"].shape, len(quantile_duration)))
493
+ + np.nan
494
+ )
495
+ spatial_maxima_outlets = (
496
+ np.zeros(
497
+ shape=(
498
+ len(self._parent_class.mysmashmodel.mesh.code),
499
+ spatial_quantile["maxima"].shape[t_axis],
500
+ len(quantile_duration),
501
+ )
502
+ )
503
+ + np.nan
504
+ )
505
+
506
+ spatial_maxima[:, :, :, pos] = res["maxima"]
507
+
508
+ for i in range(len(self._parent_class.mymesh.mesh["code"])):
509
+ coords = self._parent_class.mysmashmodel.mesh.gauge_pos[i]
510
+ spatial_maxima_outlets[i, :, pos] = spatial_maxima[
511
+ coords[0], coords[1], :, pos
512
+ ]
513
+
514
+ if not hasattr(self.quantile_stats, f"Quantile_{res['duration']}h"):
515
+ setattr(
516
+ self.quantile_stats,
517
+ f"Quantile_{res['duration']}h",
518
+ spatial_quantile_results(),
519
+ )
520
+
521
+ eval(
522
+ f"self.quantile_stats.Quantile_{res['duration']}h."
523
+ f"fill_attribute(res)"
524
+ )
525
+
526
+ # setattr(self.quantile_stats, "spatial_quantile", spatial_quantile_matrix)
527
+ self.quantile_stats.spatial_quantile = spatial_quantile_matrix
528
+ self.quantile_stats.spatial_quantile_outlets = (
529
+ spatial_quantile_matrix_outlets
530
+ )
531
+
532
+ # if not from_maxima:
533
+ # setattr(self.quantile_stats, "spatial_maxima", spatial_maxima)
534
+ self.quantile_stats.spatial_maxima = spatial_maxima
535
+ self.quantile_stats.spatial_maxima_outlets = spatial_maxima_outlets
536
+
537
+ @tools.autocast_args
538
+ def fquantile_stats(
539
+ self,
540
+ t_axis: int = 2,
541
+ return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
542
+ fit: str = "gumbel",
543
+ nb_minimum_chunks: int = 4,
544
+ quantile_duration: list | tuple = [1, 2, 3, 4, 6, 12, 24, 48, 72],
545
+ estimate_method: str = "MLE",
546
+ chunk_size: int = 365,
547
+ ncpu: int | None = None,
548
+ # from_maxima: bool = False,
549
+ compute_uncertainties: bool = False,
550
+ bootstrap_sample: int = 100,
551
+ ):
552
+ """
553
+ Compute the discharge quantile of an 3D array by chunk, corresponding to
554
+ `chunk_size` (days), along axis `t_axis` (time) for each pixel of the array
555
+ (nbX, nbY), for each duration `quantile_duration` and for each return period
556
+ `return_period`.
557
+ This function fit the coefficient of the extrem law in parallel along the Y axis
558
+ of the input maxima array (shape=(nbX,nbY,nbchunks)).
559
+
560
+ Parameters
561
+ ----------
562
+
563
+ t_axis : int
564
+ The axis along with the maximum will be computed. This axis should correspond
565
+ to the time.
566
+ return_periods: list | tuple
567
+ The duration of every return period of unit `chunk_size`.
568
+ Default is [2, 5, 10, 20, 50, 100] with a chunk_size=365 days.
569
+ fit: str
570
+ The extrem law to use to compute the quantile. Choice are
571
+ 'gumbel' | 'gev'. Default is 'gumbel'.
572
+ nb_minimum_chunks: int
573
+ number of minimum chunks required to compute the maxima along
574
+ the t_axis. Default is set to 4. If the number of chunks is lower, the function
575
+ will return None.
576
+ estimate_method: str
577
+ The method to use to fit rhe Gumbel or GEV law. Choice are `MLE`
578
+ (Maximum Likelihood Estimate) or `MM` (Method of Moments). Default is `MLE`.
579
+ chunk_size: int
580
+ Size of the chunks in days. Default is 365 days.
581
+ quantile_duration: list | tuple
582
+ The duration of every quantile (hours). The discharges will be resampled for
583
+ every duration. Default is [1, 2, 3, 4, 6, 12, 24, 48, 72] hours.
584
+ ncpu: int
585
+ Number of cpu to use to parrallelize the computation. Default is set to
586
+ int(os.cpu_count() / 2).
587
+ compute_uncertainties: bool
588
+ Compute the uncertainties using the parametric bootstrap method
589
+ bootstrap_sample: int
590
+ Number of sample using by the bootstrap method, default is 100
591
+
592
+ Return:
593
+ ------
594
+ Results are stored in the class spatial_quantile wit different attributes:
595
+ - spatial_quantile_matrix : matrix of the spatial quantile for each duration
596
+ and each return period.
597
+ - spatial_maxima_matrix : matrix of the spatial maxima for each duration and
598
+ each return period.
599
+ - spatial_cumulated_maxima_matrix : matrix of the spatial accumulated maxima
600
+ for each duration and each return period.
601
+ - Quantile_{`duration`}h : class of spatial_quantile_results() with attributes:
602
+ - self.T : the return periods
603
+ - self.Q_th : the quantile matrix for every return periods
604
+ - self.T_emp : the empirical return period for each maximum
605
+ - self.maxima : the matrix of the maxima
606
+ - self.nb_chunks : nb of chunk, i.e data for each pixel
607
+ - self.fit : fitting law
608
+ - self.fit_shape : matrix of the shape coefficient
609
+ - self.fit_scale : matrix of the scale coefficient
610
+ - self.fit_loc : matrix of the localisation coefficient
611
+ - self.duration : duration of the quantile
612
+
613
+ Examples
614
+ --------
615
+ >>> import smashbox
616
+ >>> import numpy as np
617
+ >>>
618
+ >>> graffas_prcp = np.zeros(shape=(142, 166, 1300))
619
+ >>> rng = np.random.default_rng()
620
+ >>> graffas_prcp = (
621
+ >>> graffas_prcp
622
+ >>> + rng.multinomial(20, [0.2, 0.8], size=graffas_prcp.shape)[:, :, :, 0]
623
+ >>> )
624
+ >>>
625
+ >>> es=smashbox.SmashBox()
626
+ >>> sb.newmodel("graffas_zone")
627
+ >>> sb.graffas_zone.generate_mesh()
628
+ >>> sb.graffas_zone.atmos_data_connector(input_prcp=graffas_prcp)
629
+ >>> sb.graffas_zone.model()
630
+ >>> sb.graffas_zone.forward_run(invert_states=True, return_options={"q_domain": True})
631
+ >>>
632
+ >>> sb.graffas_zone.mystats.stats_quantile(chunk_size=10, ncpu=6)
633
+
634
+ """
635
+
636
+ if pd.Timedelta(
637
+ hours=max(quantile_duration),
638
+ ) > pd.Timedelta(
639
+ days=chunk_size,
640
+ ):
641
+ raise ValueError(
642
+ f"The chunk_size {chunk_size} (days) must be"
643
+ f" greater or equal than the quantile duration {max(quantile_duration)} (hours)"
644
+ )
645
+
646
+ if not hasattr(self._parent_class.extra_smash_results, "q_domain"):
647
+ raise ValueError(
648
+ "No smash extra results 'q_domain' found. Run forward_run()"
649
+ "with return_options={'q_domain': True}"
650
+ )
651
+
652
+ model_time_step = self._parent_class.mysmashmodel.setup.dt
653
+
654
+ shape = list(self._parent_class.extra_smash_results.q_domain.shape)
655
+ shape.insert(0, shape.pop(t_axis))
656
+
657
+ spatial_quantile_matrix = np.zeros(
658
+ shape=(
659
+ shape[1],
660
+ shape[2],
661
+ len(quantile_duration),
662
+ len(return_periods),
663
+ )
664
+ )
665
+ spatial_quantile_matrix_outlets = np.zeros(
666
+ shape=(
667
+ len(self._parent_class.mysmashmodel.mesh.code),
668
+ len(quantile_duration),
669
+ len(return_periods),
670
+ )
671
+ )
672
+
673
+ spatial_maxima = None
674
+ spatial_maxima_outlets = None
675
+
676
+ for id_dur, duration in tqdm(enumerate(quantile_duration)):
677
+
678
+ print(f"</> Computing spatial quantile for duration {duration}h")
679
+
680
+ # if hasattr(self.quantile_stats, "spatial_cumulated_maxima"):
681
+ if self.quantile_stats.spatial_cumulated_maxima is not None:
682
+ maxima = self.quantile_stats.spatial_cumulated_maxima[:, :, :, id_dur]
683
+ else:
684
+ maxima = None
685
+
686
+ spatial_quantile = stats.spatial_quantiles(
687
+ array=self._parent_class.extra_smash_results.q_domain,
688
+ t_axis=t_axis,
689
+ return_periods=return_periods,
690
+ fit=fit,
691
+ nb_minimum_chunks=nb_minimum_chunks,
692
+ model_time_step=model_time_step,
693
+ quantile_duration=duration,
694
+ estimate_method=estimate_method,
695
+ chunk_size=chunk_size,
696
+ ncpu=ncpu,
697
+ compute_uncertainties=compute_uncertainties,
698
+ bootstrap_sample=bootstrap_sample,
699
+ maxima=maxima,
700
+ )
701
+ # else:
702
+ # spatial_quantile = stats.spatial_quantiles(
703
+ # array=self._parent_class.extra_smash_results.q_domain,
704
+ # t_axis=t_axis,
705
+ # return_periods=return_periods,
706
+ # fit=fit,
707
+ # nb_minimum_chunks=nb_minimum_chunks,
708
+ # model_time_step=model_time_step,
709
+ # quantile_duration=duration,
710
+ # estimate_method=estimate_method,
711
+ # chunk_size=chunk_size,
712
+ # ncpu=ncpu,
713
+ # compute_uncertainties=compute_uncertainties,
714
+ # bootstrap_sample=bootstrap_sample,
715
+ # maxima=None,
716
+ # )
717
+
718
+ print("</>")
719
+
720
+ pos = quantile_duration.index(spatial_quantile["duration"])
721
+ spatial_quantile_matrix[:, :, pos, :] = spatial_quantile["Q_th"]
722
+
723
+ for i in range(len(self._parent_class.mymesh.mesh["code"])):
724
+ coords = self._parent_class.mysmashmodel.mesh.gauge_pos[i]
725
+ spatial_quantile_matrix_outlets[i, :, :] = spatial_quantile_matrix[
726
+ coords[0], coords[1], :, :
727
+ ]
728
+
729
+ if spatial_maxima is None:
730
+ spatial_maxima = (
731
+ np.zeros(
732
+ shape=(
733
+ *spatial_quantile["maxima"].shape,
734
+ len(quantile_duration),
735
+ )
736
+ )
737
+ + np.nan
738
+ )
739
+ spatial_maxima_outlets = (
740
+ np.zeros(
741
+ shape=(
742
+ len(self._parent_class.mysmashmodel.mesh.code),
743
+ spatial_quantile["maxima"].shape[t_axis],
744
+ len(quantile_duration),
745
+ )
746
+ )
747
+ + np.nan
748
+ )
749
+
750
+ spatial_maxima[:, :, :, pos] = spatial_quantile["maxima"]
751
+
752
+ for i in range(len(self._parent_class.mymesh.mesh["code"])):
753
+ coords = self._parent_class.mysmashmodel.mesh.gauge_pos[i]
754
+ spatial_maxima_outlets[i, :, id_dur] = spatial_maxima[
755
+ coords[0], coords[1], :, id_dur
756
+ ]
757
+
758
+ if not hasattr(self.quantile_stats, f"Quantile_{duration}h"):
759
+ setattr(
760
+ self.quantile_stats,
761
+ f"Quantile_{duration}h",
762
+ spatial_quantile_results(),
763
+ )
764
+
765
+ eval(
766
+ f"self.quantile_stats.Quantile_{duration}h."
767
+ f"fill_attribute(spatial_quantile)"
768
+ )
769
+
770
+ # setattr(self.quantile_stats, "spatial_quantile", spatial_quantile_matrix)
771
+ self.quantile_stats.spatial_quantile = spatial_quantile_matrix
772
+ self.quantile_stats.spatial_quantile_outlets = spatial_quantile_matrix_outlets
773
+
774
+ # if not from_maxima:
775
+ # setattr(self.quantile_stats, "spatial_maxima", spatial_maxima)
776
+ self.quantile_stats.spatial_maxima = spatial_maxima
777
+ self.quantile_stats.spatial_maxima_outlets = spatial_maxima_outlets
778
+
779
+
780
+ class misfit_results:
781
+ """
782
+ The class misfit_results stores the results of the misfits criterium
783
+ """
784
+
785
+ def __init__(self):
786
+ self.mse = None
787
+ """MSE for each outlets of the Smash model.
788
+ mse = (1.0 / nb_valid_data)* np.sum((obs - sim) ** 2.0)
789
+ """
790
+ self.rmse = None
791
+ """RMSE for each outlets of the Smash model.
792
+ rmse = np.sqrt((1.0 / nb_valid_data)* np.sum((obs - sim) ** 2.0))
793
+ """
794
+ self.nrmse = None
795
+ """Normalized-RMSE for each outlets of the Smash model.
796
+ nrmse = res_rmse / mean_obs
797
+ """
798
+ self.se = None
799
+ """SE for each outlets of the Smash model. se =
800
+ np.sum((obs - sim) ** 2.0)
801
+ )"""
802
+ self.mae = None
803
+ """MAE for each outlets of the Smash model.
804
+ mae = np.sqrt(np.sum(abs(obs - sim))
805
+ )"""
806
+ self.mape = None
807
+ """MAPE for each outlets of the Smash model.
808
+ mape = np.sqrt(
809
+ np.sum(abs((obs - sim) / obs))
810
+ )
811
+ )"""
812
+ self.lgrm = None
813
+ """LGRM for each outlets of the Smash model.
814
+ lgrm = np.sum(
815
+ obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata
816
+ )
817
+ )"""
818
+ self.nse = None
819
+ """NSE for each outlets of the Smash model."""
820
+ self.nnse = None
821
+ """NNSE for each outlets of the Smash model. nnse = 1.0 / (2.0 - nse)"""
822
+ self.kge = None
823
+ """KGE for each outlets of the Smash model."""
824
+
825
+
826
+ class spatial_stats_results:
827
+ """
828
+ The class spatial_stats_results stores the results of the spatial statistics
829
+ on discharges at every pixel.
830
+ """
831
+
832
+ def __init__(self):
833
+ self.min = None
834
+ """The minimum values for each pixel"""
835
+ self.max = None
836
+ """The maximum values for each pixel"""
837
+ self.mean = None
838
+ """The mean value for each pixel"""
839
+ self.median = None
840
+ """The median values for each pixel"""
841
+ self.q20 = None
842
+ """The percentile 20% for each pixel"""
843
+ self.q80 = None
844
+ """The percentile 80% for each pixel"""
845
+ self.var = None
846
+ """The variance for each pixel"""
847
+
848
+
849
+ class outlets_stats_results:
850
+ """
851
+ The class outlets_stats_results stores the results of the statistics
852
+ on discharges at every outlets.
853
+ """
854
+
855
+ def __init__(self):
856
+ self.min = None
857
+ """The minimum values for each outlets"""
858
+ self.max = None
859
+ """The maximum values for each outlets"""
860
+ self.mean = None
861
+ """The mean values for each outlets"""
862
+ self.median = None
863
+ """The median values for each outlets"""
864
+ self.q20 = None
865
+ """The percentile 20% values for each outlets"""
866
+ self.q80 = None
867
+ """The percentile 80% values for each outlets"""
868
+ self.var = None
869
+ """The variance for each pixel"""
870
+
871
+
872
+ class spatial_quantile_results:
873
+ """
874
+ The class spatial_quantile_results stores the results of the quantiles computed for
875
+ different return period. Results include the quantile, the empirical return period,
876
+ the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
877
+ """
878
+
879
+ def __init__(self):
880
+ self.T = None
881
+ """The returns periods"""
882
+ self.Q_th = None
883
+ """The theorical discharge quantiles for each return period"""
884
+ self.T_emp = None
885
+ """The empirical return period for each maxima"""
886
+ self.maxima = None
887
+ """The maxima for each chunk of size chunk_size (default is annual)"""
888
+ self.nb_chunks = None
889
+ """The number of chunk (default is number of years)"""
890
+ self.fit = None
891
+ """The extrem law used to fit the maxima and the empirical quantile"""
892
+ self.fit_shape = None
893
+ """The shape coefficient of the extrem law"""
894
+ self.fit_scale = None
895
+ """The scale coefficient of the scale law"""
896
+ self.fit_loc = None
897
+ """The fit coefficient of the extream law"""
898
+ self.duration = None
899
+ """The duration of the quantile (hours)"""
900
+ self.chunk_size = None
901
+ """The size of the chunk on which the maxima are computed (unit of the return
902
+ period, default is 365 days (1 year))"""
903
+ self.Umin = None
904
+ """Uncertainties minimum values"""
905
+ self.Umax = None
906
+ """Uncertainties maximum values"""
907
+
908
+ def fill_attribute(self, stats_spatial_quantile: dict = None):
909
+ """
910
+ Fill the attribute of the class spatial_quantile_results.
911
+
912
+ :param stats_spatial_quantile: Dict of the spatial quantile results, defaults to None
913
+ :type stats_spatial_quantile: dict, optional
914
+
915
+ """
916
+
917
+ self.T = stats_spatial_quantile["T"]
918
+ self.Q_th = stats_spatial_quantile["Q_th"]
919
+ self.T_emp = stats_spatial_quantile["T_emp"]
920
+ self.maxima = stats_spatial_quantile["maxima"]
921
+ self.nb_chunks = stats_spatial_quantile["nb_chunks"]
922
+ self.fit = stats_spatial_quantile["fit"]
923
+ self.fit_shape = stats_spatial_quantile["fit_shape"]
924
+ self.fit_scale = stats_spatial_quantile["fit_scale"]
925
+ self.fit_loc = stats_spatial_quantile["fit_loc"]
926
+ self.duration = stats_spatial_quantile["duration"]
927
+ self.chunk_size = stats_spatial_quantile["chunk_size"]
928
+ if "Umin" in stats_spatial_quantile:
929
+ self.Umin = stats_spatial_quantile["Umin"]
930
+ if "Umax" in stats_spatial_quantile:
931
+ self.Umax = stats_spatial_quantile["Umax"]
932
+
933
+
934
+ class spatial_quantile:
935
+ """Parent class spatial quantile. Class to store results of the spatial quantile."""
936
+
937
+ def __init__(self):
938
+ self.spatial_maxima = None
939
+ """Spatial matrix of the maximum discharges computed on period long of `chunk_size`. If chunk_size is 365, the matrix store the maximum annual discharges. Shape=(nbx, nby, nb_chunk, duration)"""
940
+ self.spatial_maxima_outlets = None
941
+ """Outlets matrix of the maximum discharges computed on period long of `chunk_size`. If chunk_size is 365, the matrix store the maximum annual discharges.Shape=(nb_gauge, nb_chunk, duration)"""
942
+ self.spatial_cumulated_maxima = None
943
+ """Spatial matrix of the maximum discharges computed on period long of `chunk_size` and accumulated over several simulation. If chunk_size is 365, the matrix store the maximum annual discharges. These maximal values are stacked to the matrix through every simulations.Shape=(nbx, nby, nb_chunk*nb_simulations, duration)"""
944
+ self.spatial_cumulated_maxima_outlets = None
945
+ """Outlets matrix of the maximum discharges computed on period long of `chunk_size`. If chunk_size is 365, the matrix store the maximum annual discharges. These maximal values are stacked to the matrix through every simulations.Shape=(nb_gauge, nb_chunk*nb_simulations, duration)"""
946
+ self.spatial_quantile = None
947
+ """Spatial matrix of the discharges quantile computed from the maximum discharges.Shape=(nbx, nby, duration, return_period)"""
948
+ self.spatial_quantile_outlets = None
949
+ """Outlets matrix of the discharges quantile computed from the maximum discharges.Shape=(nb_gauge, duration, return_period)"""
950
+ # pass
951
+
952
+
953
+ class spatial_stats:
954
+ """Class for computing the spatial statistics on the discharges."""
955
+
956
+ def __init__(self, parent_class):
957
+ self._parent_class = parent_class
958
+ """The parent class in order to access to the results of the smash simulation"""
959
+ self.results = spatial_stats_results()
960
+ """The results of the spatial statistics"""
961
+
962
+ def mean(self):
963
+ """Compute the spatial mean of the discharges."""
964
+ self.results.mean = np.nanmean(
965
+ self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
966
+ )
967
+
968
+ def minimum(self):
969
+ """Compute the spatial minimum of the discharges."""
970
+ self.results.min = np.nanmin(
971
+ self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
972
+ )
973
+
974
+ def maximum(self):
975
+ """Compute the spatial maximum of the discharges."""
976
+ self.results.max = np.nanmax(
977
+ self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
978
+ )
979
+
980
+ def median(self):
981
+ """Compute the spatial median of the discharges."""
982
+ self.results.median = np.quantile(
983
+ self._parent_class._parent_class.extra_smash_results.q_domain, 0.5, axis=2
984
+ )
985
+
986
+ def q20(self):
987
+ """Compute the spatial percentile 20% of the discharges."""
988
+ self.results.q20 = np.quantile(
989
+ self._parent_class._parent_class.extra_smash_results.q_domain, 0.2, axis=2
990
+ )
991
+
992
+ def q80(self):
993
+ """Compute the spatial percentile 80% of the discharges."""
994
+ self.results.q80 = np.quantile(
995
+ self._parent_class._parent_class.extra_smash_results.q_domain, 0.8, axis=2
996
+ )
997
+
998
+ def var(self):
999
+ """Compute the variance of the discharges for each pixel."""
1000
+ self.results.var = np.var(
1001
+ self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
1002
+ )
1003
+
1004
+
1005
+ class outlets_stats:
1006
+ """Class for computing the statistics on the discharges at every outlets."""
1007
+
1008
+ def __init__(self, parent_class):
1009
+ self._parent_class = parent_class
1010
+ """The parent class in order to access to the results of the smash simulation"""
1011
+ self.results_sim = outlets_stats_results()
1012
+ """The results of the simulated dicharges statistics at every outlets"""
1013
+ self.results_obs = outlets_stats_results()
1014
+ """The results of the observed discharges statistics at every outlets"""
1015
+
1016
+ def mean(self):
1017
+ """Compute the mean of the discharges at every outlets."""
1018
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1019
+ qobs = np.where(qobs < 0, np.nan, qobs)
1020
+
1021
+ self.results_sim.mean = np.nanmean(
1022
+ self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1023
+ )
1024
+ self.results_obs.mean = np.nanmean(qobs, axis=1)
1025
+
1026
+ def minimum(self):
1027
+ """Compute the minimum of the discharges at every outlets."""
1028
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1029
+ qobs = np.where(qobs < 0, np.nan, qobs)
1030
+
1031
+ self.results_sim.min = np.nanmin(
1032
+ self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1033
+ )
1034
+ self.results_obs.min = np.nanmin(qobs, axis=1)
1035
+
1036
+ def maximum(self):
1037
+ """Compute the maximum of the discharges at every outlets."""
1038
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1039
+ qobs = np.where(qobs < 0, np.nan, qobs)
1040
+
1041
+ self.results_sim.max = np.nanmax(
1042
+ self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1043
+ )
1044
+ self.results_obs.max = np.nanmax(qobs, axis=1)
1045
+
1046
+ def median(self):
1047
+ """Compute the median of the discharges at every outlets."""
1048
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1049
+ qobs = np.where(qobs < 0, np.nan, qobs)
1050
+
1051
+ self.results_sim.median = np.nanquantile(
1052
+ self._parent_class._parent_class.mysmashmodel.response.q, 0.5, axis=1
1053
+ )
1054
+ self.results_obs.median = np.nanquantile(qobs, 0.5, axis=1)
1055
+
1056
+ def q20(self):
1057
+ """Compute the percentile 20% of the discharges at every outlets."""
1058
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1059
+ qobs = np.where(qobs < 0, np.nan, qobs)
1060
+
1061
+ self.results_sim.q20 = np.nanquantile(
1062
+ self._parent_class._parent_class.mysmashmodel.response.q, 0.2, axis=1
1063
+ )
1064
+ self.results_obs.q20 = np.nanquantile(qobs, 0.2, axis=1)
1065
+
1066
+ def q80(self):
1067
+ """Compute the percentile 80% of the discharges at every outlets."""
1068
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1069
+ qobs = np.where(qobs < 0, np.nan, qobs)
1070
+
1071
+ self.results_sim.q80 = np.nanquantile(
1072
+ self._parent_class._parent_class.mysmashmodel.response.q, 0.8, axis=1
1073
+ )
1074
+ self.results_obs.q80 = np.nanquantile(qobs, 0.8, axis=1)
1075
+
1076
+ def var(self):
1077
+ """Compute the variance of the discharges at every outlets"""
1078
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1079
+ qobs = np.where(qobs < 0, np.nan, qobs)
1080
+
1081
+ self.results_sim.var = np.var(
1082
+ self._parent_class._parent_class.extra_smash_results.q_domain, axis=1
1083
+ )
1084
+ self.results_obs.var = np.var(qobs, axis=1)
1085
+
1086
+
1087
+ class misfit_stats:
1088
+ """Class for computing the misfit criterium on the discharges at every outlets."""
1089
+
1090
+ def __init__(self, parent_class):
1091
+ self._parent_class = parent_class
1092
+ """The parent class in order to access to the results of the smash simulation"""
1093
+ self.results = misfit_results()
1094
+ """The results of the misfit criterium at every outlets"""
1095
+
1096
+ def _update_column(self, column, outlets_name):
1097
+ if len(outlets_name) > 0:
1098
+ column = tools.array_isin(
1099
+ self._parent_class._parent_class.mysmashmodel.mesh.code,
1100
+ np.array(outlets_name),
1101
+ )
1102
+
1103
+ if len(column) == 0:
1104
+ column = list(
1105
+ range(
1106
+ 0,
1107
+ self._parent_class._parent_class.mysmashmodel.response.q.shape[0],
1108
+ )
1109
+ )
1110
+
1111
+ return column
1112
+
1113
+ def mse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1114
+ """
1115
+ Compute the mse between the oberved and simulated discharges.
1116
+ :param nodata: The no data value, defaults to -99.0
1117
+ :type nodata: float, optional
1118
+ :param column: the column nuber on which we want to compute the misfit,
1119
+ defaults to []
1120
+ :type column: list, optional
1121
+ :param outlets_name: The names of the outlets for which we want to compute
1122
+ the misfit, defaults to []
1123
+ :type outlets_name: list, optional
1124
+
1125
+ """
1126
+
1127
+ column = self._update_column(column, outlets_name)
1128
+
1129
+ self.results.mse = stats.mse(
1130
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1131
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1132
+ nodata=-99.0,
1133
+ t_axis=1,
1134
+ )
1135
+
1136
+ def sm_mse(self, column: list = [], outlets_name: list = []):
1137
+ """
1138
+ Compute the mse between the oberved and simulated discharges.
1139
+ :param nodata: The no data value, defaults to -99.0
1140
+ :type nodata: float, optional
1141
+ :param column: the column nuber on which we want to compute the misfit,
1142
+ defaults to []
1143
+ :type column: list, optional
1144
+ :param outlets_name: The names of the outlets for which we want to compute
1145
+ the misfit, defaults to []
1146
+ :type outlets_name: list, optional
1147
+
1148
+ """
1149
+
1150
+ column = self._update_column(column, outlets_name)
1151
+
1152
+ metric = np.zeros(shape=(len(column))) + np.nan
1153
+
1154
+ for i in range(len(column)):
1155
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1156
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1157
+
1158
+ metric[i] = smash_metrics.mse(
1159
+ qobs,
1160
+ qsim,
1161
+ )
1162
+
1163
+ self.results.mse = metric
1164
+
1165
+ def rmse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1166
+ """
1167
+ Compute the rmse between the oberved and simulated discharges.
1168
+ :param nodata: The no data value, defaults to -99.0
1169
+ :type nodata: float, optional
1170
+ :param column: the column nuber on which we want to compute the misfit,
1171
+ defaults to []
1172
+ :type column: list, optional
1173
+ :param outlets_name: The names of the outlets for which we want to compute
1174
+ the misfit, defaults to []
1175
+ :type outlets_name: list, optional
1176
+
1177
+ """
1178
+
1179
+ if len(outlets_name) > 0:
1180
+ column = tools.array_isin(
1181
+ self._parent_class._parent_class.mysmashmodel.mesh.code,
1182
+ np.array(outlets_name),
1183
+ )
1184
+
1185
+ if len(column) == 0:
1186
+ column = list(
1187
+ range(
1188
+ 0, self._parent_class._parent_class.mysmashmodel.response.q.shape[0]
1189
+ )
1190
+ )
1191
+
1192
+ self.results.rmse = stats.rmse(
1193
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1194
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1195
+ nodata=-99.0,
1196
+ t_axis=1,
1197
+ )
1198
+
1199
+ def sm_rmse(self, column: list = [], outlets_name: list = []):
1200
+ """
1201
+ Compute the rmse between the oberved and simulated discharges.
1202
+ :param nodata: The no data value, defaults to -99.0
1203
+ :type nodata: float, optional
1204
+ :param column: the column nuber on which we want to compute the misfit,
1205
+ defaults to []
1206
+ :type column: list, optional
1207
+ :param outlets_name: The names of the outlets for which we want to compute
1208
+ the misfit, defaults to []
1209
+ :type outlets_name: list, optional
1210
+
1211
+ """
1212
+
1213
+ column = self._update_column(column, outlets_name)
1214
+
1215
+ metric = np.zeros(shape=(len(column))) + np.nan
1216
+
1217
+ for i in range(len(column)):
1218
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1219
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1220
+
1221
+ metric[i] = smash_metrics.rmse(
1222
+ qobs,
1223
+ qsim,
1224
+ )
1225
+
1226
+ self.results.rmse = metric
1227
+
1228
+ def nrmse(self, nodata=-99.0, column=[], outlets_name=[]):
1229
+ """
1230
+ Compute the nrmse between the oberved and simulated discharges.
1231
+ :param nodata: The no data value, defaults to -99.0
1232
+ :type nodata: float, optional
1233
+ :param column: the column nuber on which we want to compute the misfit,
1234
+ defaults to []
1235
+ :type column: list, optional
1236
+ :param outlets_name: The names of the outlets for which we want to compute
1237
+ the misfit, defaults to []
1238
+ :type outlets_name: list, optional
1239
+
1240
+ """
1241
+
1242
+ if len(outlets_name) > 0:
1243
+ column = tools.array_isin(
1244
+ self._parent_class._parent_class.mysmashmodel.mesh.code,
1245
+ np.array(outlets_name),
1246
+ )
1247
+
1248
+ if len(column) == 0:
1249
+ column = list(
1250
+ range(
1251
+ 0, self._parent_class._parent_class.mysmashmodel.response.q.shape[0]
1252
+ )
1253
+ )
1254
+
1255
+ self.results.nrmse = stats.nrmse(
1256
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1257
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1258
+ nodata=-99.0,
1259
+ t_axis=1,
1260
+ )
1261
+
1262
+ def sm_nrmse(self, column: list = [], outlets_name: list = []):
1263
+ """
1264
+ Compute the nrmse between the oberved and simulated discharges.
1265
+ :param nodata: The no data value, defaults to -99.0
1266
+ :type nodata: float, optional
1267
+ :param column: the column nuber on which we want to compute the misfit,
1268
+ defaults to []
1269
+ :type column: list, optional
1270
+ :param outlets_name: The names of the outlets for which we want to compute
1271
+ the misfit, defaults to []
1272
+ :type outlets_name: list, optional
1273
+
1274
+ """
1275
+
1276
+ column = self._update_column(column, outlets_name)
1277
+
1278
+ metric = np.zeros(shape=(len(column))) + np.nan
1279
+
1280
+ for i in range(len(column)):
1281
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1282
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1283
+
1284
+ mean_qobs = np.mean(qobs)
1285
+ metric[i] = smash_metrics.rmse(qobs, qsim) / mean_qobs
1286
+
1287
+ self.results.nrmse = metric
1288
+
1289
+ def se(self, nodata=-99.0, column=[], outlets_name=[]):
1290
+ """
1291
+ Compute the se between the oberved and simulated discharges.
1292
+ :param nodata: The no data value, defaults to -99.0
1293
+ :type nodata: float, optional
1294
+ :param column: the column nuber on which we want to compute the misfit,
1295
+ defaults to []
1296
+ :type column: list, optional
1297
+ :param outlets_name: The names of the outlets for which we want to compute
1298
+ the misfit, defaults to []
1299
+ :type outlets_name: list, optional
1300
+
1301
+ """
1302
+ column = self._update_column(column, outlets_name)
1303
+
1304
+ self.results.se = stats.se(
1305
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1306
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1307
+ nodata=-99.0,
1308
+ t_axis=1,
1309
+ )
1310
+
1311
+ def sm_se(self, column: list = [], outlets_name: list = []):
1312
+ """
1313
+ Compute the se between the oberved and simulated discharges.
1314
+ :param nodata: The no data value, defaults to -99.0
1315
+ :type nodata: float, optional
1316
+ :param column: the column nuber on which we want to compute the misfit,
1317
+ defaults to []
1318
+ :type column: list, optional
1319
+ :param outlets_name: The names of the outlets for which we want to compute
1320
+ the misfit, defaults to []
1321
+ :type outlets_name: list, optional
1322
+
1323
+ """
1324
+ column = self._update_column(column, outlets_name)
1325
+ metric = np.zeros(shape=(len(column))) + np.nan
1326
+
1327
+ for i in range(len(column)):
1328
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1329
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1330
+
1331
+ if not np.all(qobs < 0):
1332
+ metric[i] = smash_metrics.se(
1333
+ qobs,
1334
+ qsim,
1335
+ )
1336
+
1337
+ self.results.se = metric
1338
+
1339
+ def mae(self, nodata=-99.0, column=[], outlets_name=[]):
1340
+ """
1341
+ Compute the mae between the oberved and simulated discharges.
1342
+ :param nodata: The no data value, defaults to -99.0
1343
+ :type nodata: float, optional
1344
+ :param column: the column nuber on which we want to compute the misfit,
1345
+ defaults to []
1346
+ :type column: list, optional
1347
+ :param outlets_name: The names of the outlets for which we want to compute
1348
+ the misfit, defaults to []
1349
+ :type outlets_name: list, optional
1350
+
1351
+ """
1352
+ column = self._update_column(column, outlets_name)
1353
+
1354
+ self.results.mae = stats.mae(
1355
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1356
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1357
+ nodata=-99.0,
1358
+ t_axis=1,
1359
+ )
1360
+
1361
+ def sm_mae(self, column=[], outlets_name=[]):
1362
+ """
1363
+ Compute the mae between the oberved and simulated discharges.
1364
+ :param nodata: The no data value, defaults to -99.0
1365
+ :type nodata: float, optional
1366
+ :param column: the column nuber on which we want to compute the misfit,
1367
+ defaults to []
1368
+ :type column: list, optional
1369
+ :param outlets_name: The names of the outlets for which we want to compute
1370
+ the misfit, defaults to []
1371
+ :type outlets_name: list, optional
1372
+
1373
+ """
1374
+ column = self._update_column(column, outlets_name)
1375
+ metric = np.zeros(shape=(len(column))) + np.nan
1376
+
1377
+ for i in range(len(column)):
1378
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1379
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1380
+
1381
+ metric[i] = smash_metrics.mae(
1382
+ qobs,
1383
+ qsim,
1384
+ )
1385
+
1386
+ self.results.mae = metric
1387
+
1388
+ def mape(self, nodata=-99.0, column=[], outlets_name=[]):
1389
+ """
1390
+ Compute the mape between the oberved and simulated discharges.
1391
+ :param nodata: The no data value, defaults to -99.0
1392
+ :type nodata: float, optional
1393
+ :param column: the column nuber on which we want to compute the misfit,
1394
+ defaults to []
1395
+ :type column: list, optional
1396
+ :param outlets_name: The names of the outlets for which we want to compute
1397
+ the misfit, defaults to []
1398
+ :type outlets_name: list, optional
1399
+
1400
+ """
1401
+ column = self._update_column(column, outlets_name)
1402
+
1403
+ self.results.mape = stats.mape(
1404
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1405
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1406
+ nodata=-99.0,
1407
+ t_axis=1,
1408
+ )
1409
+
1410
+ def sm_mape(self, column=[], outlets_name=[]):
1411
+ """
1412
+ Compute the mape between the oberved and simulated discharges.
1413
+ :param nodata: The no data value, defaults to -99.0
1414
+ :type nodata: float, optional
1415
+ :param column: the column nuber on which we want to compute the misfit,
1416
+ defaults to []
1417
+ :type column: list, optional
1418
+ :param outlets_name: The names of the outlets for which we want to compute
1419
+ the misfit, defaults to []
1420
+ :type outlets_name: list, optional
1421
+
1422
+ """
1423
+ column = self._update_column(column, outlets_name)
1424
+ metric = np.zeros(shape=(len(column))) + np.nan
1425
+
1426
+ for i in range(len(column)):
1427
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1428
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1429
+
1430
+ metric[i] = smash_metrics.mape(
1431
+ qobs,
1432
+ qsim,
1433
+ )
1434
+
1435
+ self.results.mape = metric
1436
+
1437
+ def lgrm(self, nodata=-99.0, column=[], outlets_name=[]):
1438
+ """
1439
+ Compute the lgrm between the oberved and simulated discharges.
1440
+ :param nodata: The no data value, defaults to -99.0
1441
+ :type nodata: float, optional
1442
+ :param column: the column nuber on which we want to compute the misfit,
1443
+ defaults to []
1444
+ :type column: list, optional
1445
+ :param outlets_name: The names of the outlets for which we want to compute
1446
+ the misfit, defaults to []
1447
+ :type outlets_name: list, optional
1448
+
1449
+ """
1450
+ column = self._update_column(column, outlets_name)
1451
+
1452
+ self.results.lgrm = stats.lgrm(
1453
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1454
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1455
+ nodata=-99.0,
1456
+ t_axis=1,
1457
+ )
1458
+
1459
+ def sm_lgrm(self, column=[], outlets_name=[]):
1460
+ """
1461
+ Compute the lgrm between the oberved and simulated discharges.
1462
+ :param nodata: The no data value, defaults to -99.0
1463
+ :type nodata: float, optional
1464
+ :param column: the column nuber on which we want to compute the misfit,
1465
+ defaults to []
1466
+ :type column: list, optional
1467
+ :param outlets_name: The names of the outlets for which we want to compute
1468
+ the misfit, defaults to []
1469
+ :type outlets_name: list, optional
1470
+
1471
+ """
1472
+ column = self._update_column(column, outlets_name)
1473
+ metric = np.zeros(shape=(len(column))) + np.nan
1474
+
1475
+ for i in range(len(column)):
1476
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1477
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1478
+
1479
+ if not np.all(qobs < 0):
1480
+ metric[i] = smash_metrics.lgrm(
1481
+ qobs,
1482
+ qsim,
1483
+ )
1484
+
1485
+ self.results.lgrm = metric
1486
+
1487
+ def nse(self, nodata=-99.0, column=[], outlets_name=[]):
1488
+ """
1489
+ Compute the nse between the oberved and simulated discharges.
1490
+ :param nodata: The no data value, defaults to -99.0
1491
+ :type nodata: float, optional
1492
+ :param column: the column nuber on which we want to compute the misfit,
1493
+ defaults to []
1494
+ :type column: list, optional
1495
+ :param outlets_name: The names of the outlets for which we want to compute
1496
+ the misfit, defaults to []
1497
+ :type outlets_name: list, optional
1498
+
1499
+ """
1500
+ column = self._update_column(column, outlets_name)
1501
+
1502
+ self.results.nse = stats.nse(
1503
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1504
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1505
+ nodata=-99.0,
1506
+ t_axis=1,
1507
+ )
1508
+
1509
+ def sm_nse(self, column=[], outlets_name=[]):
1510
+ """
1511
+ Compute the nse between the oberved and simulated discharges.
1512
+ :param nodata: The no data value, defaults to -99.0
1513
+ :type nodata: float, optional
1514
+ :param column: the column nuber on which we want to compute the misfit,
1515
+ defaults to []
1516
+ :type column: list, optional
1517
+ :param outlets_name: The names of the outlets for which we want to compute
1518
+ the misfit, defaults to []
1519
+ :type outlets_name: list, optional
1520
+
1521
+ """
1522
+ column = self._update_column(column, outlets_name)
1523
+ metric = np.zeros(shape=(len(column))) + np.nan
1524
+
1525
+ for i in range(len(column)):
1526
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1527
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1528
+
1529
+ metric[i] = smash_metrics.nse(
1530
+ qobs,
1531
+ qsim,
1532
+ )
1533
+
1534
+ self.results.nse = metric
1535
+
1536
+ def nnse(self, nodata=-99.0, column=[], outlets_name=[]):
1537
+ """
1538
+ Compute the nnse between the oberved and simulated discharges.
1539
+ :param nodata: The no data value, defaults to -99.0
1540
+ :type nodata: float, optional
1541
+ :param column: the column nuber on which we want to compute the misfit,
1542
+ defaults to []
1543
+ :type column: list, optional
1544
+ :param outlets_name: The names of the outlets for which we want to compute
1545
+ the misfit, defaults to []
1546
+ :type outlets_name: list, optional
1547
+
1548
+ """
1549
+ column = self._update_column(column, outlets_name)
1550
+
1551
+ self.results.nnse = stats.nnse(
1552
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1553
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1554
+ nodata=-99.0,
1555
+ t_axis=1,
1556
+ )
1557
+
1558
+ def sm_nnse(self, column=[], outlets_name=[]):
1559
+ """
1560
+ Compute the nnse between the oberved and simulated discharges.
1561
+ :param nodata: The no data value, defaults to -99.0
1562
+ :type nodata: float, optional
1563
+ :param column: the column nuber on which we want to compute the misfit,
1564
+ defaults to []
1565
+ :type column: list, optional
1566
+ :param outlets_name: The names of the outlets for which we want to compute
1567
+ the misfit, defaults to []
1568
+ :type outlets_name: list, optional
1569
+
1570
+ """
1571
+ column = self._update_column(column, outlets_name)
1572
+ metric = np.zeros(shape=(len(column))) + np.nan
1573
+
1574
+ for i in range(len(column)):
1575
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1576
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1577
+
1578
+ metric[i] = smash_metrics.nnse(
1579
+ qobs,
1580
+ qsim,
1581
+ )
1582
+
1583
+ self.results.nnse = metric
1584
+
1585
+ def kge(self, nodata=-99.0, column=[], outlets_name=[]):
1586
+ """
1587
+ Compute the kge between the oberved and simulated discharges.
1588
+ :param nodata: The no data value, defaults to -99.0
1589
+ :type nodata: float, optional
1590
+ :param column: the column nuber on which we want to compute the misfit,
1591
+ defaults to []
1592
+ :type column: list, optional
1593
+ :param outlets_name: The names of the outlets for which we want to compute
1594
+ the misfit, defaults to []
1595
+ :type outlets_name: list, optional
1596
+
1597
+ """
1598
+ column = self._update_column(column, outlets_name)
1599
+
1600
+ self.results.kge = stats.kge(
1601
+ self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1602
+ self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1603
+ nodata=-99.0,
1604
+ t_axis=1,
1605
+ )
1606
+
1607
+ def sm_kge(self, column=[], outlets_name=[]):
1608
+ """
1609
+ Compute the kge between the oberved and simulated discharges.
1610
+ :param nodata: The no data value, defaults to -99.0
1611
+ :type nodata: float, optional
1612
+ :param column: the column nuber on which we want to compute the misfit,
1613
+ defaults to []
1614
+ :type column: list, optional
1615
+ :param outlets_name: The names of the outlets for which we want to compute
1616
+ the misfit, defaults to []
1617
+ :type outlets_name: list, optional
1618
+
1619
+ """
1620
+ column = self._update_column(column, outlets_name)
1621
+ metric = np.zeros(shape=(len(column))) + np.nan
1622
+
1623
+ for i in range(len(column)):
1624
+ qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1625
+ qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1626
+
1627
+ metric[i] = smash_metrics.kge(
1628
+ qobs,
1629
+ qsim,
1630
+ )
1631
+
1632
+ self.results.kge = metric