smashbox.stats.stats

Created on Tue Jul 22 10:47:46 2025

@author: maxime

   1#!/usr/bin/env python3
   2# -*- coding: utf-8 -*-
   3"""
   4Created on Tue Jul 22 10:47:46 2025
   5
   6@author: maxime
   7"""
   8
   9import numpy as np
  10import pandas as pd
  11import warnings
  12import scipy.stats as stats
  13import multiprocessing
  14import matplotlib.pyplot as plt
  15import os
  16from tqdm import tqdm
  17import threading
  18from smashbox.tools import tools
  19from smash.fcore import _mwd_metrics as smash_metrics
  20
  21
  22@tools.autocast_args
  23def _get_mask_colum_nodata(arr, nodata=-99.0, t_axis=0):
  24    """
  25    Test if all data for axis 1 of an array with 2 dimension are nodata
  26    The function return an array with shape equal of the shape of axis 1 of the
  27    orignal array `arr`. This returned array is zeros everywhere but np.nan if all
  28    values of arr are nodata for axis 1.
  29
  30    parameter:
  31    ----------
  32    arr: np.array
  33    nodata: float | int | np.nan
  34    t_axis: array axis for time
  35    """
  36
  37    arr = np.moveaxis(arr, t_axis, 0)
  38    c_axis = 1
  39
  40    res_nodata = None
  41    if len(arr.shape) == 2:
  42        res_nodata = np.zeros(shape=(arr.shape[c_axis]))
  43        for i in range(arr.shape[c_axis]):
  44            if np.all(arr[:, i] <= nodata):
  45                res_nodata[i] = np.nan
  46
  47    # print(res_nodata)
  48
  49    return res_nodata
  50
  51
  52def _test_input_shape(arr):
  53    """
  54    Test the shape of the array `arr`
  55    :param arr: np.ndarray
  56    :type arr: np.ndarray
  57
  58    """
  59
  60    if len(arr.shape) > 2:
  61        raise ValueError(
  62            "Dimension of the input array must not be greater than 2:"
  63            f"given shape is {arr.shape} with {len(arr.shape)} dimension."
  64        )
  65
  66    if len(arr.shape) == 1:
  67        arr = np.atleast_2d(arr)
  68        arr = np.moveaxis(arr, 0, 1)
  69
  70    return arr
  71
  72
  73@tools.autocast_args
  74def mse(
  75    obs: np.ndarray | None = None,
  76    sim: np.ndarray | None = None,
  77    nodata: float = -99.0,
  78    t_axis: int = 0,
  79):
  80    """
  81    Compute the misfit criteria mse:
  82        mse = (1.0 / nb_valid_data) * np.sum((obs - sim) ** 2.0)
  83    :param obs: Observed discharges, defaults to None
  84    :type obs: np.ndarray | None, optional
  85    :param sim: simulated discharges, defaults to None
  86    :type sim: np.ndarray | None, optional
  87    :param nodata: No data value, defaults to -99.0
  88    :type nodata: float, optional
  89    :param t_axis: Array axis of the time, defaults to 0
  90    :type t_axis: int, optional
  91
  92    """
  93
  94    t_axis = min(t_axis, len(obs.shape) - 1)
  95    obs = _test_input_shape(obs)
  96    sim = _test_input_shape(sim)
  97
  98    mask_nodata = obs != nodata
  99    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 100    res_nodata = _get_mask_colum_nodata(obs, nodata=nodata, t_axis=t_axis)
 101    mse = np.nan
 102
 103    if sum(nb_valid_data) > 0:
 104
 105        if isinstance(obs, np.ndarray) and isinstance(sim, np.ndarray):
 106
 107            if obs.shape == sim.shape:
 108                mse = (1.0 / nb_valid_data) * np.sum(
 109                    (obs - sim) ** 2.0, axis=t_axis, where=mask_nodata
 110                )
 111            else:
 112                raise ValueError(
 113                    f"Error: len(obs)!=len(sim), {len(obs)}!={len(sim)}"
 114                )
 115        else:
 116            raise ValueError(
 117                "Error: obs and sim must be an instance of np.ndarray"
 118            )
 119
 120    else:
 121        mse = nb_valid_data * np.nan
 122        print("Warning: no valid observation data.")
 123
 124    if res_nodata is not None:
 125        mse = mse + res_nodata
 126
 127    return mse
 128
 129
 130@tools.autocast_args
 131def sm_mse(
 132    obs: np.ndarray | None = None,
 133    sim: np.ndarray | None = None,
 134):
 135    """
 136    Compute the misfit criteria mse:
 137        mse = (1.0 / nb_valid_data) * np.sum((obs - sim) ** 2.0)
 138    :param obs: Observed discharges, defaults to None
 139    :type obs: np.ndarray | None, optional
 140    :param sim: simulated discharges, defaults to None
 141    :type sim: np.ndarray | None, optional
 142    """
 143
 144    mse = smash_metrics.mse(obs, sim)
 145
 146    return mse
 147
 148
 149@tools.autocast_args
 150def rmse(
 151    obs: np.ndarray | None = None,
 152    sim: np.ndarray | None = None,
 153    nodata: float = -99.0,
 154    t_axis: int = 0,
 155):
 156    """
 157    Compute the misfit criteria rmse:
 158        rmse = np.sqrt(res_mse)
 159
 160    :param obs: Observed discharges, defaults to None
 161    :type obs: np.ndarray | None, optional
 162    :param sim: simulated discharges, defaults to None
 163    :type sim: np.ndarray | None, optional
 164    :param nodata: No data value, defaults to -99.0
 165    :type nodata: float, optional
 166    :param t_axis: Array axis of the time, defaults to 0
 167    :type t_axis: int, optional
 168
 169    """
 170    t_axis = min(t_axis, len(obs.shape) - 1)
 171    res_mse = mse(obs, sim, nodata=nodata, t_axis=t_axis)
 172
 173    rmse = np.sqrt(res_mse)
 174
 175    return rmse
 176
 177
 178@tools.autocast_args
 179def sm_rmse(
 180    obs: np.ndarray | None = None,
 181    sim: np.ndarray | None = None,
 182):
 183    """
 184    Compute the misfit criteria rmse:
 185        rmse = np.sqrt(res_mse)
 186
 187    :param obs: Observed discharges, defaults to None
 188    :type obs: np.ndarray | None, optional
 189    :param sim: simulated discharges, defaults to None
 190    :type sim: np.ndarray | None, optional
 191
 192    """
 193
 194    rmse = smash_metrics.rmse(obs, sim)
 195
 196    return rmse
 197
 198
 199@tools.autocast_args
 200def nrmse(
 201    obs: np.ndarray | None = None,
 202    sim: np.ndarray | None = None,
 203    nodata: float = -99.0,
 204    t_axis: int = 0,
 205):
 206    """
 207    Compute the misfit criteria nrmse:
 208        nrmse = res_rmse / mean_obs
 209
 210    :param obs: Observed discharges, defaults to None
 211    :type obs: np.ndarray | None, optional
 212    :param sim: simulated discharges, defaults to None
 213    :type sim: np.ndarray | None, optional
 214    :param nodata: No data value, defaults to -99.0
 215    :type nodata: float, optional
 216    :param t_axis: Array axis of the time, defaults to 0
 217    :type t_axis: int, optional
 218
 219    """
 220
 221    t_axis = min(t_axis, len(obs.shape) - 1)
 222    res_rmse = rmse(obs, sim, nodata=nodata, t_axis=t_axis)
 223    mask_nodata = obs != nodata
 224
 225    with warnings.catch_warnings():
 226        warnings.simplefilter("ignore", category=RuntimeWarning)
 227        mean_obs = np.mean(obs, axis=t_axis, where=mask_nodata)
 228
 229    nrmse = res_rmse / mean_obs
 230
 231    return nrmse
 232
 233
 234@tools.autocast_args
 235def sm_nmse(
 236    obs: np.ndarray | None = None,
 237    sim: np.ndarray | None = None,
 238):
 239    """
 240    Compute the misfit criteria nrmse:
 241        nrmse = res_rmse / mean_obs
 242
 243    :param obs: Observed discharges, defaults to None
 244    :type obs: np.ndarray | None, optional
 245    :param sim: simulated discharges, defaults to None
 246    :type sim: np.ndarray | None, optional
 247
 248    """
 249
 250    mask_nodata = obs < 0.0
 251    mean_obs = np.nanmean(obs, where=mask_nodata)
 252    mse = smash_metrics.mse(obs, sim)
 253
 254    return mse
 255
 256
 257@tools.autocast_args
 258def se(
 259    obs: np.ndarray | None = None,
 260    sim: np.ndarray | None = None,
 261    nodata: float = -99.0,
 262    t_axis: int = 0,
 263):
 264    """
 265    Compute the misfit criteria se:
 266        se = (
 267            np.sum((obs - sim)** 2.0, axis=t_axis, where=mask_nodata)
 268        )
 269
 270    :param obs: Observed discharges, defaults to None
 271    :type obs: np.ndarray | None, optional
 272    :param sim: simulated discharges, defaults to None
 273    :type sim: np.ndarray | None, optional
 274    :param nodata: No data value, defaults to -99.0
 275    :type nodata: float, optional
 276    :param t_axis: Array axis of the time, defaults to 0
 277    :type t_axis: int, optional
 278
 279    """
 280
 281    t_axis = min(t_axis, len(obs.shape) - 1)
 282    obs = _test_input_shape(obs)
 283    sim = _test_input_shape(sim)
 284
 285    mask_nodata = obs != nodata
 286    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 287    res_nodata = _get_mask_colum_nodata(obs, nodata=nodata, t_axis=t_axis)
 288    se = np.nan
 289
 290    if sum(nb_valid_data) > 0:
 291
 292        if isinstance(obs, np.ndarray) and isinstance(sim, np.ndarray):
 293
 294            if obs.shape == sim.shape:
 295                se = np.sum((obs - sim) ** 2.0, axis=t_axis, where=mask_nodata)
 296            else:
 297                raise ValueError(
 298                    f"Error: len(obs)!=len(sim), {len(obs)}!={len(sim)}"
 299                )
 300        else:
 301            raise ValueError(
 302                "Error: obs and sim must be an instance of np.ndarray"
 303            )
 304
 305    else:
 306        se = nb_valid_data * np.nan
 307        print("Warning: no valid observation data.")
 308
 309    if res_nodata is not None:
 310        se = se + res_nodata
 311
 312    return se
 313
 314
 315@tools.autocast_args
 316def sm_se(
 317    obs: np.ndarray | None = None,
 318    sim: np.ndarray | None = None,
 319):
 320    """
 321    Compute the misfit criteria se:
 322        se = (
 323            np.sum((obs - sim)** 2.0, axis=t_axis, where=mask_nodata)
 324        )
 325
 326    :param obs: Observed discharges, defaults to None
 327    :type obs: np.ndarray | None, optional
 328    :param sim: simulated discharges, defaults to None
 329    :type sim: np.ndarray | None, optional
 330    """
 331
 332    se = smash_metrics.se(obs, sim)
 333
 334    return se
 335
 336
 337@tools.autocast_args
 338def mae(
 339    obs: np.ndarray | None = None,
 340    sim: np.ndarray | None = None,
 341    nodata: float = -99.0,
 342    t_axis: int = 0,
 343):
 344    """
 345    Compute the misfit criteria mae:
 346        mae = 1/n*(np.sum(abs(obs - sim))
 347
 348    :param obs: Observed discharges, defaults to None
 349    :type obs: np.ndarray | None, optional
 350    :param sim: simulated discharges, defaults to None
 351    :type sim: np.ndarray | None, optional
 352    :param nodata: No data value, defaults to -99.0
 353    :type nodata: float, optional
 354    :param t_axis: Array axis of the time, defaults to 0
 355    :type t_axis: int, optional
 356
 357    """
 358
 359    t_axis = min(t_axis, len(obs.shape) - 1)
 360    obs = _test_input_shape(obs)
 361    sim = _test_input_shape(sim)
 362
 363    mask_nodata = obs != nodata
 364    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 365    res_nodata = _get_mask_colum_nodata(obs, nodata=nodata, t_axis=t_axis)
 366    mae = np.nan
 367
 368    if sum(nb_valid_data) > 0:
 369
 370        if isinstance(obs, np.ndarray) and isinstance(sim, np.ndarray):
 371
 372            if obs.shape == sim.shape:
 373                mae = (1.0 / nb_valid_data) * (
 374                    np.sum(abs(obs - sim), axis=t_axis, where=mask_nodata)
 375                )
 376            else:
 377                raise ValueError(
 378                    f"Error: len(obs)!=len(sim), {len(obs)}!={len(sim)}"
 379                )
 380        else:
 381            raise ValueError(
 382                "Error: obs and sim must be an instance of np.ndarray"
 383            )
 384
 385    else:
 386        mae = nb_valid_data * np.nan
 387        print("Warning: no valid observation data.")
 388
 389    if res_nodata is not None:
 390        mae = mae + res_nodata
 391
 392    return mae
 393
 394
 395@tools.autocast_args
 396def sm_mae(
 397    obs: np.ndarray | None = None,
 398    sim: np.ndarray | None = None,
 399):
 400    """
 401    Compute the misfit criteria mae:
 402        mae = np.sqrt(np.sum(abs(obs - sim))
 403
 404    :param obs: Observed discharges, defaults to None
 405    :type obs: np.ndarray | None, optional
 406    :param sim: simulated discharges, defaults to None
 407    :type sim: np.ndarray | None, optional
 408    :param nodata: No data value, defaults to -99.0
 409    :type nodata: float, optional
 410    :param t_axis: Array axis of the time, defaults to 0
 411    :type t_axis: int, optional
 412
 413    """
 414
 415    mae = smash_metrics.mae(obs, sim)
 416
 417    return mae
 418
 419
 420@tools.autocast_args
 421def mape(
 422    obs: np.ndarray | None = None,
 423    sim: np.ndarray | None = None,
 424    nodata: float = -99.0,
 425    t_axis: int = 0,
 426):
 427    """
 428    Compute the misfit criteria mape:
 429        mape = 1/n*(
 430            np.sum(abs((obs - sim) / obs))
 431        )
 432
 433    :param obs: Observed discharges, defaults to None
 434    :type obs: np.ndarray | None, optional
 435    :param sim: simulated discharges, defaults to None
 436    :type sim: np.ndarray | None, optional
 437    :param nodata: No data value, defaults to -99.0
 438    :type nodata: float, optional
 439    :param t_axis: Array axis of the time, defaults to 0
 440    :type t_axis: int, optional
 441
 442    """
 443
 444    t_axis = min(t_axis, len(obs.shape) - 1)
 445    obs = _test_input_shape(obs)
 446    sim = _test_input_shape(sim)
 447
 448    mask_nodata = obs != nodata
 449    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 450    res_nodata = _get_mask_colum_nodata(obs, nodata=nodata, t_axis=t_axis)
 451    mape = np.nan
 452
 453    if sum(nb_valid_data) > 0:
 454
 455        if isinstance(obs, np.ndarray) and isinstance(sim, np.ndarray):
 456
 457            if obs.shape == sim.shape:
 458                mape = (1.0 / nb_valid_data) * (
 459                    np.sum(
 460                        abs((obs - sim) / obs), axis=t_axis, where=mask_nodata
 461                    )
 462                )
 463            else:
 464                raise ValueError(
 465                    f"Error: len(obs)!=len(sim), {len(obs)}!={len(sim)}"
 466                )
 467        else:
 468            raise ValueError(
 469                "Error: obs and sim must be an instance of np.ndarray"
 470            )
 471
 472    else:
 473        mape = nb_valid_data * np.nan
 474        print("Warning: no valid observation data.")
 475
 476    if res_nodata is not None:
 477        mape = mape + res_nodata
 478
 479    return mape
 480
 481
 482@tools.autocast_args
 483def sm_mape(
 484    obs: np.ndarray | None = None,
 485    sim: np.ndarray | None = None,
 486):
 487    """
 488    Compute the misfit criteria mape:
 489        mape = np.sqrt(
 490            np.sum(abs((obs - sim) / obs))
 491        )
 492
 493    :param obs: Observed discharges, defaults to None
 494    :type obs: np.ndarray | None, optional
 495    :param sim: simulated discharges, defaults to None
 496    :type sim: np.ndarray | None, optional
 497
 498    """
 499
 500    mape = smash_metrics.mape(obs, sim)
 501
 502    return mape
 503
 504
 505@tools.autocast_args
 506def lgrm(
 507    obs: np.ndarray | None = None,
 508    sim: np.ndarray | None = None,
 509    nodata: float = -99.0,
 510    t_axis: int = 0,
 511):
 512    """
 513    Compute the misfit criteria lgrm:
 514        lgrm = np.sum(
 515            obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata
 516        )
 517
 518    :param obs: Observed discharges, defaults to None
 519    :type obs: np.ndarray | None, optional
 520    :param sim: simulated discharges, defaults to None
 521    :type sim: np.ndarray | None, optional
 522    :param nodata: No data value, defaults to -99.0
 523    :type nodata: float, optional
 524    :param t_axis: Array axis of the time, defaults to 0
 525    :type t_axis: int, optional
 526
 527    """
 528
 529    t_axis = min(t_axis, len(obs.shape) - 1)
 530    obs = _test_input_shape(obs)
 531    sim = _test_input_shape(sim)
 532
 533    mask_nodata = obs != nodata
 534    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 535    res_nodata = _get_mask_colum_nodata(obs, nodata=nodata, t_axis=t_axis)
 536    lgrm = np.nan
 537
 538    if sum(nb_valid_data) > 0:
 539
 540        if isinstance(obs, np.ndarray) and isinstance(sim, np.ndarray):
 541
 542            if obs.shape == sim.shape:
 543                lgrm = np.sum(
 544                    obs * (np.log(sim / obs) ** 2.0),
 545                    axis=t_axis,
 546                    where=mask_nodata,
 547                )
 548
 549            else:
 550                raise ValueError(
 551                    f"Error: len(obs)!=len(sim), {len(obs)}!={len(sim)}"
 552                )
 553        else:
 554            raise ValueError(
 555                "Error: obs and sim must be an instance of np.ndarray"
 556            )
 557
 558    else:
 559        lgrm = nb_valid_data * np.nan
 560        print("Warning: no valid observation data.")
 561
 562    if res_nodata is not None:
 563        lgrm = lgrm + res_nodata
 564
 565    return lgrm
 566
 567
 568@tools.autocast_args
 569def sm_lgrm(
 570    obs: np.ndarray | None = None,
 571    sim: np.ndarray | None = None,
 572):
 573    """
 574    Compute the misfit criteria lgrm:
 575        lgrm = np.sum(
 576            obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata
 577        )
 578
 579    :param obs: Observed discharges, defaults to None
 580    :type obs: np.ndarray | None, optional
 581    :param sim: simulated discharges, defaults to None
 582    :type sim: np.ndarray | None, optional
 583    """
 584
 585    lgrm = smash_metrics.lgrm(obs, sim)
 586
 587    return lgrm
 588
 589
 590@tools.autocast_args
 591def nse(
 592    obs: np.ndarray | None = None,
 593    sim: np.ndarray | None = None,
 594    nodata: float = -99.0,
 595    t_axis: int = 0,
 596):
 597    """
 598    Compute the misfit criteria nse:
 599        numerator = np.sum((obs - sim) ** 2.0, axis=t_axis, where=mask_nodata)
 600
 601        denominator = np.sum(
 602            (obs - mean_obs) ** 2.0, axis=t_axis, where=mask_nodata
 603        )
 604
 605        denominator = np.where(denominator == 0, np.nan, denominator)
 606
 607        nse = 1 - numerator / denominator
 608
 609    :param obs: Observed discharges, defaults to None
 610    :type obs: np.ndarray | None, optional
 611    :param sim: simulated discharges, defaults to None
 612    :type sim: np.ndarray | None, optional
 613    :param nodata: No data value, defaults to -99.0
 614    :type nodata: float, optional
 615    :param t_axis: Array axis of the time, defaults to 0
 616    :type t_axis: int, optional
 617
 618    """
 619
 620    t_axis = min(t_axis, len(obs.shape) - 1)
 621    obs = _test_input_shape(obs)
 622    sim = _test_input_shape(sim)
 623
 624    mask_nodata = obs != nodata
 625    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 626
 627    nse = np.nan
 628
 629    if sum(nb_valid_data) > 0:
 630
 631        if isinstance(obs, np.ndarray) and isinstance(sim, np.ndarray):
 632            if obs.shape == sim.shape:
 633
 634                with warnings.catch_warnings():
 635                    warnings.simplefilter("ignore", category=RuntimeWarning)
 636                    mean_obs = np.mean(
 637                        obs, axis=t_axis, where=mask_nodata, keepdims=True
 638                    )
 639
 640                numerator = np.sum(
 641                    (obs - sim) ** 2.0, axis=t_axis, where=mask_nodata
 642                )
 643
 644                denominator = np.sum(
 645                    (obs - mean_obs) ** 2.0, axis=t_axis, where=mask_nodata
 646                )
 647
 648                denominator = np.where(denominator == 0, np.nan, denominator)
 649                nse = 1 - numerator / denominator
 650            else:
 651                raise ValueError(
 652                    f"Error: len(obs)!=len(sim), {len(obs)}!={len(sim)}"
 653                )
 654        else:
 655            raise ValueError(
 656                "Error: obs and sim must be an instance of np.ndarray"
 657            )
 658
 659    else:
 660        nse = nb_valid_data * np.nan
 661        print("Warning: no valid observation data.")
 662
 663    return nse
 664
 665
 666@tools.autocast_args
 667def sm_nse(
 668    obs: np.ndarray | None = None,
 669    sim: np.ndarray | None = None,
 670):
 671    """
 672    Compute the misfit criteria nse:
 673        numerator = np.sum((obs - sim) ** 2.0, axis=t_axis, where=mask_nodata)
 674
 675        denominator = np.sum(
 676            (obs - mean_obs) ** 2.0, axis=t_axis, where=mask_nodata
 677        )
 678
 679        denominator = np.where(denominator == 0, np.nan, denominator)
 680
 681        nse = 1 - numerator / denominator
 682
 683    :param obs: Observed discharges, defaults to None
 684    :type obs: np.ndarray | None, optional
 685    :param sim: simulated discharges, defaults to None
 686    :type sim: np.ndarray | None, optional
 687
 688    """
 689
 690    nse = smash_metrics.nse(obs, sim)
 691
 692    return nse
 693
 694
 695@tools.autocast_args
 696def nnse(
 697    obs: np.ndarray | None = None,
 698    sim: np.ndarray | None = None,
 699    nodata: float = -99.0,
 700    t_axis: int = 0,
 701):
 702    """
 703    Compute the misfit criteria nnse:
 704        nnse = 1.0 / (2.0 - res_nse)
 705
 706    :param obs: Observed discharges, defaults to None
 707    :type obs: np.ndarray | None, optional
 708    :param sim: simulated discharges, defaults to None
 709    :type sim: np.ndarray | None, optional
 710    :param nodata: No data value, defaults to -99.0
 711    :type nodata: float, optional
 712    :param t_axis: Array axis of the time, defaults to 0
 713    :type t_axis: int, optional
 714
 715    """
 716
 717    t_axis = min(t_axis, len(obs.shape) - 1)
 718    res_nse = nse(obs, sim, nodata=nodata, t_axis=t_axis)
 719    nnse = 1.0 / (2.0 - res_nse)
 720
 721    return nnse
 722
 723
 724@tools.autocast_args
 725def sm_nnse(
 726    obs: np.ndarray | None = None,
 727    sim: np.ndarray | None = None,
 728):
 729    """
 730    Compute the misfit criteria nnse:
 731        nnse = 1.0 / (2.0 - res_nse)
 732
 733    :param obs: Observed discharges, defaults to None
 734    :type obs: np.ndarray | None, optional
 735    :param sim: simulated discharges, defaults to None
 736    :type sim: np.ndarray | None, optional
 737
 738    """
 739
 740    nnse = smash_metrics.nnse(obs, sim)
 741
 742    return nse
 743
 744
 745@tools.autocast_args
 746def kge(
 747    obs: np.ndarray | None = None,
 748    sim: np.ndarray | None = None,
 749    nodata: float = -99.0,
 750    t_axis: int = 0,
 751):
 752    """
 753    Compute the Pearson correlation coefficient between observed and simulated data.
 754
 755    Parameters
 756    ----------
 757    obs : np.ndarray
 758        Observed data.
 759    sim : np.ndarray
 760        Simulated data.
 761    nodata : float, optional
 762        No-data value to ignore in the computation. Default is -99.0.
 763    t_axis : int, optional
 764        Axis along which to compute the Pearson coefficient. Default is 0.
 765
 766    Returns
 767    -------
 768    np.ndarray
 769        Pearson correlation coefficient for each non-time dimension.
 770    """
 771    if obs is None or sim is None:
 772        raise ValueError("obs and sim must not be None")
 773    if not isinstance(obs, np.ndarray) or not isinstance(sim, np.ndarray):
 774        raise ValueError("obs and sim must be np.ndarray")
 775    if obs.shape != sim.shape:
 776        raise ValueError(
 777            f"obs and sim must have the same shape, got {obs.shape} and {sim.shape}"
 778        )
 779
 780    obs = _test_input_shape(obs)
 781    sim = _test_input_shape(sim)
 782
 783    t_axis = min(t_axis, len(obs.shape) - 1)
 784    mask_nodata = (obs != nodata) & (sim != nodata)
 785    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 786
 787    if np.all(nb_valid_data == 0):
 788        raise ValueError(
 789            "Error: no valid observation/simulation data along t_axis."
 790        )
 791
 792    # Replace nodata by NaN for safe computation
 793    obs_masked = np.where(mask_nodata, obs, np.nan)
 794    sim_masked = np.where(mask_nodata, sim, np.nan)
 795
 796    mean_obs = np.nanmean(obs_masked, axis=t_axis, keepdims=True)
 797    mean_sim = np.nanmean(sim_masked, axis=t_axis, keepdims=True)
 798
 799    std_obs = np.nanstd(obs_masked, axis=t_axis, keepdims=True)
 800    std_sim = np.nanstd(sim_masked, axis=t_axis, keepdims=True)
 801
 802    beta = np.where(mean_obs == 0, np.nan, mean_sim / mean_obs)
 803    alpha = np.where(std_obs == 0, np.nan, std_sim / std_obs)
 804
 805    # Centered data
 806    obs_centered = obs_masked - mean_obs
 807    sim_centered = sim_masked - mean_sim
 808
 809    # Numerator: sum of products
 810    numerator = np.nansum(
 811        obs_centered * sim_centered, axis=t_axis, keepdims=True
 812    )
 813
 814    # Denominator: product of norms
 815    obs_norm = np.sqrt(np.nansum(obs_centered**2, axis=t_axis, keepdims=True))
 816    sim_norm = np.sqrt(np.nansum(sim_centered**2, axis=t_axis, keepdims=True))
 817    denominator = obs_norm * sim_norm
 818
 819    # Avoid division by zero
 820    r2 = np.where(denominator == 0, np.nan, numerator / denominator)
 821
 822    kge = 1 - np.sqrt(
 823        (r2 - 1.0) ** 2.0 + (alpha - 1.0) ** 2.0 + (beta - 1) ** 2.0
 824    )
 825
 826    return np.squeeze(kge)
 827
 828
 829@tools.autocast_args
 830def sm_kge(
 831    obs: np.ndarray | None = None,
 832    sim: np.ndarray | None = None,
 833):
 834    """
 835    Compute the misfit criteria kge, see the Smash documentation at https://smash.recover.inrae.fr/math_num_documentation/efficiency_error_metric.html
 836
 837    :param obs: Observed discharges, defaults to None
 838    :type obs: np.ndarray | None, optional
 839    :param sim: simulated discharges, defaults to None
 840    :type sim: np.ndarray | None, optional
 841    """
 842
 843    kge = smash_metrics.kge(obs, sim)
 844
 845    return kge
 846
 847
 848@tools.autocast_args
 849def pearson(
 850    obs: np.ndarray | None = None,
 851    sim: np.ndarray | None = None,
 852    nodata: float = -99.0,
 853    t_axis: int = 0,
 854):
 855    """
 856    Compute the Pearson correlation coefficient between observed and simulated data.
 857
 858    Parameters
 859    ----------
 860    obs : np.ndarray
 861        Observed data.
 862    sim : np.ndarray
 863        Simulated data.
 864    nodata : float, optional
 865        No-data value to ignore in the computation. Default is -99.0.
 866    t_axis : int, optional
 867        Axis along which to compute the Pearson coefficient. Default is 0.
 868
 869    Returns
 870    -------
 871    np.ndarray
 872        Pearson correlation coefficient for each non-time dimension.
 873    """
 874    if obs is None or sim is None:
 875        raise ValueError("obs and sim must not be None")
 876    if not isinstance(obs, np.ndarray) or not isinstance(sim, np.ndarray):
 877        raise ValueError("obs and sim must be np.ndarray")
 878    if obs.shape != sim.shape:
 879        raise ValueError(
 880            f"obs and sim must have the same shape, got {obs.shape} and {sim.shape}"
 881        )
 882
 883    obs = _test_input_shape(obs)
 884    sim = _test_input_shape(sim)
 885
 886    t_axis = min(t_axis, len(obs.shape) - 1)
 887    mask_nodata = (obs != nodata) & (sim != nodata)
 888    nb_valid_data = np.count_nonzero(mask_nodata, axis=t_axis)
 889
 890    if np.all(nb_valid_data == 0):
 891        raise ValueError(
 892            "Error: no valid observation/simulation data along t_axis."
 893        )
 894
 895    # Replace nodata by NaN for safe computation
 896    obs_masked = np.where(mask_nodata, obs, np.nan)
 897    sim_masked = np.where(mask_nodata, sim, np.nan)
 898
 899    mean_obs = np.nanmean(obs_masked, axis=t_axis, keepdims=True)
 900    mean_sim = np.nanmean(sim_masked, axis=t_axis, keepdims=True)
 901
 902    # Centered data
 903    obs_centered = obs_masked - mean_obs
 904    sim_centered = sim_masked - mean_sim
 905
 906    # Numerator: sum of products
 907    numerator = np.nansum(
 908        obs_centered * sim_centered, axis=t_axis, keepdims=True
 909    )
 910
 911    # Denominator: product of norms
 912    obs_norm = np.sqrt(np.nansum(obs_centered**2, axis=t_axis, keepdims=True))
 913    sim_norm = np.sqrt(np.nansum(sim_centered**2, axis=t_axis, keepdims=True))
 914    denominator = obs_norm * sim_norm
 915
 916    # Avoid division by zero
 917    pearson = np.where(denominator == 0, np.nan, numerator / denominator)
 918
 919    return np.squeeze(pearson)
 920
 921
 922# Fonction de calcul du débit de retour
 923def quantile_gumbel(T: float = 1.0, loc: float = 0.0, scale: float = 0.0):
 924    """
 925    Compute the quantile for a given return period using the Gumbel law.
 926    :param T: The return period, defaults to 1
 927    :type T: float, optional
 928    :param loc: The localisation parameter of the Gumbel law, defaults to 0.0
 929    :type loc: float, optional
 930    :param scale: The scale parameter of the Gumbel law, defaults to 0.0
 931    :type scale: float, optional
 932    :return: The value of the quantile
 933    :rtype: float
 934
 935    """
 936
 937    return stats.gumbel_r.ppf(1 - 1 / T, loc=loc, scale=scale)
 938
 939
 940# Fonction de calcul du débit de retour
 941def quantile_gev(
 942    T: float = 1.0, shape: float = 0.0, loc: float = 0.0, scale: float = 0.0
 943):
 944    """
 945    Compute the quantile for a given return period using the GEV law.
 946    :param T: The return period, defaults to 1
 947    :type T: float, optional
 948    :param shape: The shape parameter of the GEV law, defaults to 0.0
 949    :type shape: float, optional
 950    :param loc: The localisation parameter of the GEV law, defaults to 0.0
 951    :type loc: float, optional
 952    :param scale: The scale parameter of the GEV law, defaults to 0.0
 953    :type scale: float, optional
 954    :return: The value of the quantile
 955    :rtype: float
 956
 957    """
 958    return stats.genextreme.ppf(1 - 1 / T, shape, loc=loc, scale=scale)
 959
 960
 961def genextreme_fit(
 962    data: np.ndarray | None = None, estimate_method: str = "MLE"
 963):
 964    """
 965    Return estimates of shape, location, and scale parameters from data. The default
 966     estimation method is Maximum Likelihood Estimation (MLE), but Method of Moments (MM)
 967      is also available.
 968    :param data: Data of maximum values used to fit a GEV, defaults to None
 969    :type data: np.ndarray | None, optional
 970    :param estimate_method: Method to optimize the parameters: MLE for Maximum Likelihood
 971     Estimate, MM for  Method of Moments , defaults to "MLE"
 972    :type estimate_method: str, optional
 973    :return: Estimates for any shape parameters (if applicable), followed by those for
 974     location and scale.
 975    :rtype: tuple of float
 976
 977    """
 978    if data is None:
 979        raise ValueError("input data is None. You must provide valid data.")
 980    res = stats.genextreme.fit(data, method=estimate_method)
 981    return res
 982
 983
 984def gumbel_r_fit(data: np.ndarray | None = None, estimate_method: str = "MLE"):
 985    """
 986    Return estimates of shape, location, and scale parameters from data.
 987     The default estimation method is Maximum Likelihood Estimation (MLE),
 988      but Method of Moments (MM) is also available.
 989    :param data: Data of maximum values used to fit a Gumbel law, defaults to None
 990    :type data: np.ndarray | None, optional
 991    :param estimate_method: Method to optimize the parameters: MLE for Maximum Likelihood
 992     Estimate, MM for  Method of Moments , defaults to "MLE"
 993    :type estimate_method: str, optional
 994    :return: Estimates for any shape parameters (if applicable), followed by those for
 995     location and scale.
 996    :rtype: tuple of float
 997
 998    """
 999    if data is None:
1000        raise ValueError("input data is None. You must provide valid data.")
1001
1002    res = stats.gumbel_r.fit(data, method=estimate_method)
1003    return (0, *res)
1004
1005
1006@tools.autocast_args
1007def time_resample_array(
1008    array: np.ndarray | None,
1009    quantile_duration: int | float = 1,
1010    model_time_step: float = 3600,
1011    quantile_chunk_size: int | None = None,
1012    t_axis: int = 2,
1013):
1014    """
1015    Resample the discharges array for a given time-step.
1016    :param array: the matrix containing the discharge with shape (nbx, nby, nbts)
1017    :type array: np.ndarray | None
1018    :param quantile_duration: The duration of the quantile (hours), defaults to 1
1019    :type quantile_duration: int | float, optional
1020    :param model_time_step: the time-step of the Smash model (seconds), defaults to 3600
1021    :type model_time_step: float, optional
1022    :param quantile_chunk_size: the size of the quantile chunk in days
1023    :type quantile_chunk_size: int | None
1024    :param t_axis: The array axis direction of the time-step, defaults to 2
1025    :type t_axis: int, optional
1026    :return: The resampled array
1027    :rtype: np.ndarray
1028
1029    """
1030    # R workaround as normal number (int) are passed to float to python
1031    # t_axis = int(t_axis)
1032
1033    # resample array to the new duration
1034    if pd.Timedelta(hours=quantile_duration) > pd.Timedelta(
1035        seconds=model_time_step
1036    ):
1037        print(
1038            f"</> Resampling array with time-step `{pd.Timedelta(seconds=model_time_step)}`"
1039            f" to time-step `{pd.Timedelta(hours=quantile_duration)}`"
1040        )
1041        chunk_size = int(
1042            pd.Timedelta(hours=quantile_duration)
1043            / pd.Timedelta(seconds=model_time_step)
1044        )
1045
1046        array_trans = np.moveaxis(array, t_axis, 0)  # Axe à la position 0
1047
1048        new_shape = (
1049            array_trans.shape[0] // chunk_size,
1050            chunk_size,
1051        ) + array_trans.shape[1:]
1052
1053        array_trans_reshaped = array_trans[
1054            0 : chunk_size * (array_trans.shape[0] // chunk_size)
1055        ].reshape(new_shape)
1056
1057        array_trans_reshaped_mean = np.mean(array_trans_reshaped, axis=1)
1058
1059        if quantile_chunk_size is None:
1060            remainder = array_trans.shape[0] % chunk_size
1061        else:
1062            # The final shape must be extended only if it is not a multiple of quantile_chunk_size.
1063            remainder = (
1064                array_trans_reshaped_mean.shape[0]
1065                * pd.Timedelta(hours=quantile_duration)
1066                % pd.Timedelta(days=quantile_chunk_size)
1067            ).total_seconds()
1068
1069        del array_trans
1070
1071        if remainder > 0:
1072            # extend the array with the previous value
1073            array_trans_reshaped_mean = np.insert(
1074                array_trans_reshaped_mean,
1075                obj=-1,
1076                axis=0,
1077                values=array_trans_reshaped_mean[-1, :],
1078            )
1079
1080        del array_trans_reshaped
1081
1082        array = np.moveaxis(array_trans_reshaped_mean, 0, t_axis)
1083
1084        del array_trans_reshaped_mean
1085
1086    return array
1087
1088
1089@tools.autocast_args
1090def compute_maxima(
1091    array: np.ndarray | None,
1092    t_axis: int = 2,
1093    nb_minimum_chunks: int = 4,
1094    chunk_size: int = 365,
1095    quantile_duration: int | float = 1,
1096):
1097    """
1098    Compute the maxima of the discharges for a given chunk_size in the t_axis direction.
1099    :param array: the matrix containing the discharge with shape (nbx, nby, nbts). The
1100    shape can be smaller or higher. But t_axis must be set to target the nbts
1101    (number of time-step) dimension. Ex: if shape=(nbx, nbts), t_axis must be equal to 1.
1102    :type array: np.ndarray | None
1103    :param t_axis: The array axis direction of the time-step, defaults to 2, defaults to 2
1104    :type t_axis: int, optional
1105    :param nb_minimum_chunks: minimal number of chunk required adjust an extrem law and
1106    compute the quantile, defaults to 4
1107    :type nb_minimum_chunks: int, optional
1108    :param chunk_size: The chunk_size (days). It correspond to the 'unit' of the
1109    return period, defaults to 365 (year)
1110    :type chunk_size: int, optional
1111    :param quantile_duration: The duration of the quantile (hour), defaults to 1
1112    :type quantile_duration: int | float, optional
1113    :return: an np.ndarray of spatial maximal values for every chunk
1114    :rtype: np.ndarray
1115
1116    """
1117
1118    if pd.Timedelta(
1119        hours=quantile_duration,
1120    ) > pd.Timedelta(
1121        days=chunk_size,
1122    ):
1123        raise ValueError(
1124            "The chunk_size {chunk_size} (days) must be"
1125            " greater than the quantile duration {quantile_duration} (hours)"
1126        )
1127
1128    # compute the nb of chunk in the input data
1129    nbchunks = int(
1130        array.shape[t_axis]
1131        * pd.Timedelta(hours=quantile_duration)
1132        / pd.Timedelta(days=chunk_size)
1133    )
1134    print(
1135        f"</> Data contain {nbchunks} chunks of {pd.Timedelta(days=chunk_size)}"
1136    )
1137
1138    nb_ts_by_chunk = int(array.shape[t_axis] / nbchunks)
1139
1140    if nbchunks < nb_minimum_chunks:
1141        raise ValueError(
1142            "</> The number of simulated chunks is not enough to compute the quantile:"
1143            f" {nbchunks}<{nb_minimum_chunks}"
1144        )
1145
1146    # nb year limit to compute the quantile
1147    if nbchunks >= nb_minimum_chunks:
1148        print(
1149            f"</> Compute the maxima for every chunk with size"
1150            f" {nb_ts_by_chunk} time-steps"
1151            f" of {quantile_duration} hours."
1152        )
1153        out_shape = []
1154        list_axes = []
1155        for nshape in range(len(array.shape)):
1156            out_shape.append(0)
1157            list_axes.append(nshape)
1158
1159        out_shape[t_axis] = nbchunks
1160        list_axes.remove(t_axis)
1161
1162        for ax in list_axes:
1163            out_shape[ax] = array.shape[ax]
1164
1165        maxima = np.zeros(shape=out_shape)
1166
1167        nb_ts_by_chunk = int(array.shape[t_axis] / nbchunks)
1168        remainder = array.shape[t_axis] % nbchunks
1169        pos = 0
1170
1171        # original_axis = [i for i in range(len(array.shape))]
1172        # other_axis = original_axis.copy()
1173        # other_axis.remove(t_axis)
1174        # destination_axis = [original_axis[t_axis]] + other_axis
1175
1176        # print(original_axis, destination_axis)
1177
1178        # print(maxima.shape)
1179        # print(array.shape)
1180        # move_axis first to extend the possibiltiy to use this function with different array shape
1181        array = np.moveaxis(array, t_axis, 0)
1182        maxima = np.moveaxis(maxima, t_axis, 0)
1183        # print(maxima.shape)
1184        # print(array.shape)
1185        for i in range(nbchunks):
1186            # Alternate one more cell because nb_ts_by_chunk is not a factor of array.shape[t_axis]
1187            if i % 2 > 0 and remainder > 0:
1188                onemore = 1
1189            else:
1190                onemore = 0
1191
1192            # maxima[:, :, i] = np.max(
1193            #     array[:, :, int(i * nb_ts_by_chunk) : int((i + 1) * nb_ts_by_chunk)],
1194            #     axis=t_axis,
1195            # )
1196            # print(array.shape, pos, int(pos + nb_ts_by_chunk + onemore))
1197            # maxima[:, :, i] = np.max(
1198            #     array[:, :, pos : int(pos + nb_ts_by_chunk + onemore)],
1199            #     axis=t_axis,
1200            # )
1201
1202            # Check if value >-99 and check if nb lacuna <20% ? Or remove lowers maxima after all (10%)
1203            # if (array[pos : int(pos + nb_ts_by_chunk + onemore), :])
1204
1205            maxima[i, :] = np.max(
1206                array[pos : int(pos + nb_ts_by_chunk + onemore), :],
1207                axis=0,
1208            )
1209            pos = int(pos + nb_ts_by_chunk + onemore)
1210
1211        array = np.moveaxis(array, 0, t_axis)
1212        maxima = np.moveaxis(maxima, 0, t_axis)
1213
1214    return maxima
1215
1216
1217def quantil_obs(
1218    qobs_directory: str | None = None,
1219    code: np.ndarray | list = [],
1220    model_time_step: float = 3600,
1221    nb_minimum_chunks: int = 4,
1222    chunk_size: int = 365,
1223    quantile_duration: int = 1,
1224):
1225
1226    if qobs_directory is None:
1227        print("</> qobs_directory `{qobs_directory}` is not a valid directory")
1228        return
1229
1230    if isinstance(code, list):
1231        code = np.array(code)
1232
1233    qobs = tools.read_hourly_qobs(qobs_directory, code)
1234
1235    array = time_resample_array(
1236        array=qobs,
1237        quantile_duration=quantile_duration,
1238        model_time_step=model_time_step,
1239        quantile_chunk_size=chunk_size,
1240        t_axis=1,
1241    )
1242
1243    maxima = compute_maxima(
1244        array=array,
1245        t_axis=1,
1246        nb_minimum_chunks=nb_minimum_chunks,
1247        chunk_size=chunk_size,
1248        quantile_duration=quantile_duration,
1249    )
1250
1251    results = empirical_obs_quantile(
1252        maxima=maxima,
1253        nb_minimum_chunks=nb_minimum_chunks,
1254        chunk_size=chunk_size,
1255        quantile_duration=quantile_duration,
1256    )
1257
1258    return results
1259
1260
1261def empirical_obs_quantile(
1262    maxima: np.ndarray | None,
1263    t_axis=1,
1264    nb_minimum_chunks: int = 4,
1265    frac_to_remove: float = 0.1,
1266    quantile_duration: int | float = 1,
1267    chunk_size: int = 365,
1268):
1269    """
1270    :param maxima: Maximal discharge by chunk
1271    :type maxima: np.ndarray | None
1272    :param t_axis: axis of the time series, defaults to 1
1273    :type t_axis: TYPE, optional
1274    :param nb_minimum_chunks: number of minimum chunck required to remove `frac_to_remove` of the maxima distribution, i.e len of the maxima array in the t_axis direction, defaults to 4
1275    :type nb_minimum_chunks: int, optional
1276    :param frac_to_remove: fraction between 0 and 1 to remove lower value of the maxima distribution, incase of incomplete data chunk, defaults to 0.1
1277    :type frac_to_remove: float, default 0.1
1278    :param quantile_duration: duration of the quantile (hour)
1279    :type quantile_duration int | float, default 1
1280    :param chunk_size: size of the chunk used to comute the maxima
1281    :type chunk_size: int, default 365
1282    """
1283
1284    maxima = np.moveaxis(maxima, t_axis, 0)
1285    maxima_sorted = np.sort(maxima, axis=0)
1286    maxima_sorted = np.where(maxima_sorted < 0, np.nan, maxima_sorted)
1287    T_emp = np.zeros(shape=maxima.shape) + np.nan
1288    # n = maxima.shape[0]
1289
1290    for sta in range(maxima_sorted.shape[1]):
1291        n_valid = np.where(maxima_sorted[:, sta] > 0)
1292        n = len(n_valid[0])
1293        # remove 10% of the
1294        if n > nb_minimum_chunks:
1295            n_to_remove = max(1, int(frac_to_remove * n))
1296        else:
1297            n_to_remove = 0
1298
1299        for i in range(n_to_remove):
1300            ind = n_valid[0][i]
1301            maxima_sorted[i] = np.nan
1302
1303        if n > nb_minimum_chunks:
1304            rank = 1
1305            for i in range(n_to_remove, n):
1306                probs = (rank - 0.5) / n
1307                T_emp[n_valid[0][i], sta] = 1 / (1 - probs)
1308                rank = rank + 1
1309
1310    # trim/filter nan cells
1311    index = []
1312    for t in range(maxima_sorted.shape[0]):
1313        if np.any(maxima_sorted[t, :] >= 0):
1314            index.append(t)
1315
1316    maxima_sorted = maxima_sorted[index, :]
1317    T_emp = T_emp[index, :]
1318
1319    maxima_sorted = np.moveaxis(maxima_sorted, 0, t_axis)
1320    T_emp = np.moveaxis(T_emp, 0, t_axis)
1321
1322    results = {
1323        "maxima": maxima_sorted,
1324        "Temp": T_emp,
1325        "chunk_size": chunk_size,
1326        "nb_chunks": maxima.shape[t_axis],
1327        "quantile_duration": quantile_duration,
1328    }
1329    return results
1330
1331
1332@tools.autocast_args
1333def fit_quantile(
1334    maxima: np.ndarray | None,
1335    t_axis: int = 2,
1336    return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
1337    fit: str = "gumbel",
1338    estimate_method: str = "MLE",
1339    quantile_duration: int | float = 1,
1340    chunk_size: int = 365,
1341    ncpu: int | None = None,
1342):
1343    """
1344    Proceed to the qunaitle adjustment.
1345    :param maxima: an np.ndarray of spatial maximal values for every chunk
1346    :type maxima: np.ndarray | None
1347    :param t_axis: The array axis direction of the time-step, defaults to 2,
1348    defaults to 2, defaults to 2
1349    :type t_axis: int, optional
1350    :param return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
1351    :type return_periods: list | tuple, optional
1352    :param fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
1353    :type fit: str, optional
1354    :param estimate_method: The methode to use for calibrated the parameters
1355    of the extrem law (MLE or MM), defaults to "MLE"
1356    :type estimate_method: str, optional
1357    :param quantile_duration: The duration of the quantile (hour), defaults to 1
1358    :type quantile_duration: int | float, optional
1359    :param chunk_size: the size of the chunks in days, defaults to 365
1360    :type chunk_size: int, optional
1361    :param ncpu: Number of cpu to use, defaults to None
1362    :type ncpu: int | None, optional
1363    :return: A dictionary containing the results od the quantile computation:
1364     Results include the quantile, the empirical return period,
1365     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
1366    :rtype: dict
1367
1368    """
1369
1370    if ncpu is None:
1371        ncpu = int(os.cpu_count() / 2)
1372    else:
1373        ncpu = int(min(ncpu, os.cpu_count() - 1))
1374
1375    if len(maxima.shape) < 2 or len(maxima.shape) > 3:
1376        raise ValueError(
1377            f"Input array `maxima` must have a dimension of 2 or 3. "
1378            f"Actual input have a dimension of {len(maxima.shape)} with shape {maxima.shape}"
1379        )
1380
1381    grid_shape = list(maxima.shape)
1382    del grid_shape[t_axis]
1383
1384    fit_shape = np.zeros(shape=grid_shape)
1385    fit_loc = np.zeros(shape=grid_shape)
1386    fit_scale = np.zeros(shape=grid_shape)
1387    quantile = np.zeros(shape=grid_shape + [len(return_periods)]) * np.nan
1388
1389    # list_axes = [ax for ax in range(len(maxima.shape))]
1390    # list_axes.remove(t_axis)
1391
1392    # fit_shape = np.zeros(shape=(maxima.shape[list_axes[0]], maxima.shape[list_axes[1]]))
1393    # fit_loc = np.zeros(shape=(maxima.shape[list_axes[0]], maxima.shape[list_axes[1]]))
1394    # fit_scale = np.zeros(shape=(maxima.shape[list_axes[0]], maxima.shape[list_axes[1]]))
1395
1396    # quantile = (
1397    #     np.zeros(
1398    #         shape=(
1399    #             maxima.shape[list_axes[0]],
1400    #             maxima.shape[list_axes[1]],
1401    #             len(return_periods),
1402    #         )
1403    #     )
1404    #     * np.nan
1405    # )
1406
1407    # Total length of the grid, linearize the matrix => 1 dim
1408    nd = 1
1409    for d in grid_shape:
1410        nd = nd * d
1411
1412    original_shape = maxima.shape
1413    newshape = (nd, maxima.shape[-1])
1414
1415    maxima = maxima.reshape(newshape)
1416    fit_shape = fit_shape.reshape(nd)
1417    fit_loc = fit_loc.reshape(nd)
1418    fit_scale = fit_scale.reshape(nd)
1419    quantile = quantile.reshape((nd, len(return_periods)))
1420
1421    print(f"</> Fitting {fit} law on data using {estimate_method} method.")
1422
1423    pool = multiprocessing.Pool(ncpu)
1424
1425    # Split by chunk to display a progress bar. chunk have the size of cpu
1426    chunksize = ncpu
1427    nbchunk = int(maxima.shape[0] / chunksize)
1428
1429    for chunk in tqdm(range(nbchunk + 1)):
1430
1431        args = []
1432        index_i = []
1433
1434        for i in range(chunk * chunksize, chunk * chunksize + chunksize):
1435
1436            if i >= nd:
1437                break
1438
1439            if np.all(maxima[i, :] > 0.0):
1440                args.append((maxima[i, :], estimate_method))
1441                index_i.append(i)
1442
1443        if fit == "gumbel":
1444            res = pool.starmap(gumbel_r_fit, args, chunksize=1)
1445
1446        if fit == "gev":
1447            res = pool.starmap(genextreme_fit, args, chunksize=1)
1448
1449        k = 0
1450        for item in res:
1451            i = index_i[k]
1452            fit_shape[i] = item[0]
1453            fit_loc[i] = item[1]
1454            fit_scale[i] = item[2]
1455
1456            for index, T in enumerate(return_periods):
1457                if fit == "gumbel":
1458                    quantile[i, index] = quantile_gumbel(T, item[1], item[2])
1459                if fit == "gev":
1460                    quantile[i, index] = quantile_gev(
1461                        T, item[0], item[1], item[2]
1462                    )
1463
1464            k = k + 1
1465
1466    maxima = maxima.reshape(original_shape)
1467    fit_shape = fit_shape.reshape(grid_shape)
1468    fit_loc = fit_loc.reshape(grid_shape)
1469    fit_scale = fit_scale.reshape(grid_shape)
1470    quantile = quantile.reshape(grid_shape + [len(return_periods)])
1471
1472    # pool = multiprocessing.Pool(ncpu)
1473
1474    # print(f"</> Fitting {fit} law on data using {estimate_method} method.")
1475
1476    # for i in tqdm(range(maxima.shape[list_axes[0]])):
1477
1478    #     args = []
1479    #     index_j = []
1480
1481    #     for j in range(maxima.shape[list_axes[1]]):
1482    #         if np.all(maxima[i, j, :] > 0.0):
1483    #             args.append((maxima[i, j, :], estimate_method))
1484    #             index_j.append(j)
1485
1486    #     if fit == "gumbel":
1487    #         res = pool.starmap(gumbel_r_fit, args, chunksize=1)
1488
1489    #     if fit == "gev":
1490    #         res = pool.starmap(genextreme_fit, args, chunksize=1)
1491
1492    #     k = 0
1493    #     for item in res:
1494
1495    #         j = index_j[k]
1496
1497    #         fit_shape[i, j] = item[0]
1498    #         fit_loc[i, j] = item[1]
1499    #         fit_scale[i, j] = item[2]
1500
1501    #         for index, T in enumerate(return_periods):
1502    #             if fit == "gumbel":
1503    #                 quantile[i, j, index] = quantile_gumbel(T, item[1], item[2])
1504    #             if fit == "gev":
1505    #                 quantile[i, j, index] = quantile_gev(T, item[0], item[1], item[2])
1506
1507    #         k = k + 1
1508
1509    pool.close()
1510    pool.terminate()
1511
1512    n = maxima.shape[t_axis]
1513    ranks = np.arange(1, n + 1)
1514    # probs = (ranks - 0.44) / (n + 0.12)
1515    probs = (ranks - 0.5) / n
1516    T_emp = 1 / (1 - probs)
1517
1518    results = {
1519        "T": np.array(return_periods),
1520        "Q_th": quantile,
1521        "T_emp": T_emp,
1522        "maxima": maxima,
1523        "nb_chunks": maxima.shape[t_axis],
1524        "fit": fit,
1525        "fit_loc": fit_loc,
1526        "fit_scale": fit_scale,
1527        "fit_shape": fit_shape,
1528        "duration": quantile_duration,
1529        "chunk_size": chunk_size,
1530    }
1531
1532    return results
1533
1534
1535@tools.autocast_args
1536def parametric_bootstrap_uncertainties(
1537    q_results: dict | None,
1538    bootstrap_sample=100,
1539    estimate_method: str = "MLE",
1540    ncpu: int | None = None,
1541):
1542    """
1543    Proceed to the qunaitle adjustment.
1544    :param q_results: A dictionary containing the results of the quantile adjustment
1545    :type q_results: dict | None
1546    :param bootstrap_sample: the number of bootstrap sample, defaults to 100
1547    :type bootstrap_sample: int, optional
1548    :param estimate_method: The methode to use for calibrated the parameters
1549    of the extrem law (MLE or MM), defaults to "MLE"
1550    :type estimate_method: str, optional
1551    :param ncpu: Number of cpu to use, defaults to None
1552    :type ncpu: int | None, optional
1553    :return: A dictionary containing the results od the quantile computation:
1554     Results include the quantile, the empirical return period,
1555     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
1556    :rtype: dict
1557
1558    """
1559    # R workaround as normal number (int) are passed to float to python
1560    # ncpu = int(ncpu)
1561
1562    if ncpu is None:
1563        ncpu = int(os.cpu_count() / 2)
1564    else:
1565        ncpu = min(ncpu, os.cpu_count() - 1)
1566
1567    sample_size = q_results["nb_chunks"]
1568    quantile = q_results["Q_th"]
1569    fit_loc = q_results["fit_loc"]
1570    fit_scale = q_results["fit_scale"]
1571    fit_shape = q_results["fit_shape"]
1572
1573    grid_shape = list(quantile.shape)
1574    del grid_shape[-1]  # remove last dimension (return period)
1575
1576    parametric_quantile = (
1577        np.zeros(
1578            shape=(
1579                *grid_shape,
1580                len(q_results["T"]),
1581                bootstrap_sample,
1582            )
1583        )
1584        * np.nan
1585    )
1586
1587    nd = 1
1588    for d in grid_shape:
1589        nd = nd * d
1590
1591    # original_shape = parametric_quantile.shape
1592    newshape = (nd, len(q_results["T"]), bootstrap_sample)
1593
1594    parametric_quantile = parametric_quantile.reshape(newshape)
1595    fit_shape = fit_shape.reshape(nd)
1596    fit_loc = fit_loc.reshape(nd)
1597    fit_scale = fit_scale.reshape(nd)
1598    quantile = quantile.reshape(nd, len(q_results["T"]))
1599
1600    print(f"</> Compute bootstrap uncertainties...")
1601
1602    pool = multiprocessing.Pool(ncpu)
1603
1604    for i in tqdm(range(nd)):
1605
1606        if np.all(quantile[i, :] > 0.0):
1607
1608            args = []
1609            index_k = []
1610            for k in range(bootstrap_sample):
1611
1612                # generate sample
1613                if q_results["fit"] == "gumbel":
1614                    random_q = stats.gumbel_r.rvs(
1615                        loc=fit_loc[i],
1616                        scale=fit_scale[i],
1617                        size=sample_size,
1618                    )
1619                if q_results["fit"] == "gev":
1620                    random_q = stats.genextreme_fit.rvs(
1621                        fit_shape[i],
1622                        loc=fit_loc[i],
1623                        scale=fit_scale[i],
1624                        size=sample_size,
1625                    )
1626
1627                args.append((random_q, q_results["fit"]))
1628                index_k.append(k)
1629
1630            if q_results["fit"] == "gumbel":
1631                res = pool.starmap(gumbel_r_fit, args, chunksize=1)
1632
1633            if q_results["fit"] == "gev":
1634                res = pool.starmap(genextreme_fit, args, chunksize=1)
1635
1636            for k, item in enumerate(res):
1637
1638                ik = index_k[k]
1639
1640                for indexT, T in enumerate(q_results["T"]):
1641                    if q_results["fit"] == "gumbel":
1642                        parametric_quantile[i, indexT, ik] = quantile_gumbel(
1643                            T, item[1], item[2]
1644                        )
1645                    if q_results["fit"] == "gev":
1646                        parametric_quantile[i, indexT, ik] = quantile_gev(
1647                            T, item[0], item[1], item[2]
1648                        )
1649
1650    Umax = np.max(parametric_quantile, axis=2)
1651    Umin = np.min(parametric_quantile, axis=2)
1652
1653    Umax = Umax.reshape(grid_shape + [len(q_results["T"])])
1654    Umin = Umin.reshape(grid_shape + [len(q_results["T"])])
1655
1656    # parametric_quantile = parametric_quantile.reshape(original_shape)
1657    # fit_shape = fit_shape.reshape(grid_shape)
1658    # fit_loc = fit_loc.reshape(grid_shape)
1659    # fit_scale = fit_scale.reshape(grid_shape)
1660    # quantile = quantile.reshape(grid_shape + [len(q_results["T"])])
1661
1662    ##############################################"
1663    # for i in tqdm(range(q_results["Q_th"].shape[0])):
1664    #     for j in range(q_results["Q_th"].shape[1]):
1665
1666    #         if np.all(q_results["Q_th"][i, j] > 0.0):
1667
1668    #             args = []
1669    #             index_k = []
1670    #             for k in range(bootstrap_sample):
1671
1672    #                 # generate sample
1673    #                 if q_results["fit"] == "gumbel":
1674    #                     random_q = stats.gumbel_r.rvs(
1675    #                         loc=q_results["fit_loc"][i, j],
1676    #                         scale=q_results["fit_scale"][i, j],
1677    #                         size=sample_size,
1678    #                     )
1679    #                 if q_results["fit"] == "gev":
1680    #                     random_q = stats.genextreme_fit.rvs(
1681    #                         q_results["fit_shape"][i, j],
1682    #                         loc=q_results["fit_loc"][i, j],
1683    #                         scale=q_results["fit_scale"][i, j],
1684    #                         size=sample_size,
1685    #                     )
1686
1687    #                 args.append((random_q, q_results["fit"]))
1688    #                 index_k.append(k)
1689
1690    #             if q_results["fit"] == "gumbel":
1691    #                 res = pool.starmap(gumbel_r_fit, args, chunksize=1)
1692
1693    #             if q_results["fit"] == "gev":
1694    #                 res = pool.starmap(genextreme_fit, args, chunksize=1)
1695
1696    #             # k = 0
1697    #             for k, item in enumerate(res):
1698
1699    #                 ik = index_k[k]
1700
1701    #                 for indexT, T in enumerate(return_periods):
1702    #                     if q_results["fit"] == "gumbel":
1703    #                         parametric_quantile[i, j, indexT, ik] = quantile_gumbel(
1704    #                             T, item[1], item[2]
1705    #                         )
1706    #                     if q_results["fit"] == "gev":
1707    #                         parametric_quantile[i, j, indexT, ik] = quantile_gev(
1708    #                             T, item[0], item[1], item[2]
1709    #                         )
1710
1711    pool.close()
1712    pool.terminate()
1713
1714    q_results.update({"Umax": Umax, "Umin": Umin})
1715
1716    return q_results
1717
1718
1719@tools.autocast_args
1720def fit_quantile_unparallel(
1721    maxima: np.ndarray | None,
1722    t_axis: int = 2,
1723    return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
1724    fit: str = "gumbel",
1725    estimate_method: str = "MLE",
1726    quantile_duration: int | float = 1.0,
1727    chunk_size: int = 365,
1728):
1729    """
1730    Proceed to the qunaitle adjustment (unparrallel computation).
1731    :param maxima: an np.ndarray of spatial maximal values for every chunk
1732    :type maxima: np.ndarray | None
1733    :param t_axis: The array axis direction of the time-step, defaults to 2,
1734    defaults to 2, defaults to 2
1735    :type t_axis: int, optional
1736    :param return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
1737    :type return_periods: list | tuple, optional
1738    :param fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
1739    :type fit: str, optional
1740    :param estimate_method: The methode to use for calibrated the parameters
1741    of the extrem law (MLE or MM), defaults to "MLE"
1742    :type estimate_method: str, optional
1743    :param quantile_duration: The duration of the quantile (hour), defaults to 1
1744    :type quantile_duration: int | float, optional
1745    :param chunk_size: the size of the chunks in days, defaults to 365
1746    :type chunk_size: int, optional
1747    :return: A dictionary containing the results od the quantile computation:
1748     Results include the quantile, the empirical return period,
1749     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
1750    :rtype: dict
1751
1752    """
1753    # # R workaround as normal number (int) are passed to float to python
1754    # t_axis = int(t_axis)
1755    # chunk_size = int(chunk_size)
1756
1757    list_axes = [0, 1, 2]
1758    list_axes.remove(t_axis)
1759
1760    fit_shape = np.zeros(
1761        shape=(maxima.shape[list_axes[0]], maxima.shape[list_axes[1]])
1762    )
1763    fit_loc = np.zeros(
1764        shape=(maxima.shape[list_axes[0]], maxima.shape[list_axes[1]])
1765    )
1766    fit_scale = np.zeros(
1767        shape=(maxima.shape[list_axes[0]], maxima.shape[list_axes[1]])
1768    )
1769
1770    quantile = (
1771        np.zeros(
1772            shape=(
1773                maxima.shape[list_axes[0]],
1774                maxima.shape[list_axes[1]],
1775                len(return_periods),
1776            )
1777        )
1778        * np.nan
1779    )
1780
1781    print(f"</> Fitting {fit} law on data using {estimate_method} method.")
1782
1783    for i in range(maxima.shape[list_axes[0]]):
1784
1785        for j in range(maxima.shape[list_axes[1]]):
1786
1787            if np.all(maxima[i, j, :] > 0.0):
1788
1789                if fit == "gumbel":
1790                    res = gumbel_r_fit(maxima[i, j, :], estimate_method)
1791
1792                if fit == "gev":
1793                    res = genextreme_fit(maxima[i, j, :], estimate_method)
1794
1795                fit_shape[i, j] = res[0]
1796                fit_loc[i, j] = res[1]
1797                fit_scale[i, j] = res[2]
1798
1799                for index, T in enumerate(return_periods):
1800                    if fit == "gumbel":
1801                        quantile[i, j, index] = quantile_gumbel(
1802                            T, res[1], res[2]
1803                        )
1804                    if fit == "gev":
1805                        quantile[i, j, index] = quantile_gev(
1806                            T, res[0], res[1], res[2]
1807                        )
1808
1809    n = maxima.shape[t_axis]
1810    ranks = np.arange(1, n + 1)
1811    # probs = (ranks - 0.44) / (n + 0.12)
1812    probs = (ranks - 0.5) / n
1813    T_emp = 1 / (1 - probs)
1814
1815    results = {
1816        "T": np.array(return_periods),
1817        "Q_th": quantile,
1818        "T_emp": T_emp,
1819        "maxima": maxima,
1820        "nb_chunks": maxima.shape[t_axis],
1821        "fit": fit,
1822        "fit_loc": fit_loc,
1823        "fit_scale": fit_scale,
1824        "fit_shape": fit_shape,
1825        "duration": quantile_duration,
1826        "chunk_size": chunk_size,
1827    }
1828
1829    return results
1830
1831
1832@tools.autocast_args
1833def parametric_bootstrap_uncertainties_unparallel(
1834    q_results: dict | None,
1835    bootstrap_sample=100,
1836    return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
1837    estimate_method: str = "MLE",
1838):
1839    """
1840    Proceed to the qunaitle adjustment.
1841    :param q_results: A dictionary containing the results of the quantile adjustment
1842    :type q_results: dict | None
1843    :param bootstrap_sample: the number of bootstrap sample, defaults to 100
1844    :type bootstrap_sample: int, optional
1845    :param return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
1846    :type return_periods: list | tuple, optional
1847    :param estimate_method: The methode to use for calibrated the parameters
1848    of the extrem law (MLE or MM), defaults to "MLE"
1849    :type estimate_method: str, optional
1850    :return: A dictionary containing the results od the quantile computation:
1851     Results include the quantile, the empirical return period,
1852     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
1853    :rtype: dict
1854
1855    """
1856
1857    sample_size = q_results["nb_chunks"]
1858
1859    parametric_quantile = (
1860        np.zeros(
1861            shape=(
1862                q_results["Q_th"].shape[0],
1863                q_results["Q_th"].shape[1],
1864                len(q_results["T"]),
1865                bootstrap_sample,
1866            )
1867        )
1868        * np.nan
1869    )
1870
1871    for i in tqdm(range(q_results["Q_th"].shape[0])):
1872        for j in range(q_results["Q_th"].shape[1]):
1873
1874            if np.all(q_results["Q_th"][i, j] > 0.0):
1875
1876                for k in range(bootstrap_sample):
1877
1878                    # generate sample
1879                    if q_results["fit"] == "gumbel":
1880                        random_q = stats.gumbel_r.rvs(
1881                            loc=q_results["fit_loc"][i, j],
1882                            scale=q_results["fit_scale"][i, j],
1883                            size=sample_size,
1884                        )
1885                    if q_results["fit"] == "gev":
1886                        random_q = stats.genextreme_fit.rvs(
1887                            q_results["shape_loc"][i, j],
1888                            loc=q_results["fit_loc"][i, j],
1889                            scale=q_results["fit_scale"][i, j],
1890                            size=sample_size,
1891                        )
1892
1893                    if q_results["fit"] == "gumbel":
1894                        res = gumbel_r_fit(random_q, estimate_method)
1895
1896                    if q_results["fit"] == "gev":
1897                        res = genextreme_fit(random_q, estimate_method)
1898
1899                    for indexT, T in enumerate(return_periods):
1900                        if q_results["fit"] == "gumbel":
1901                            parametric_quantile[i, j, indexT, k] = (
1902                                quantile_gumbel(T, res[1], res[2])
1903                            )
1904                        if q_results["fit"] == "gev":
1905                            parametric_quantile[i, j, indexT, k] = (
1906                                quantile_gev(T, res[0], res[1], res[2])
1907                            )
1908
1909    Umax = np.max(parametric_quantile, axis=3)
1910    Umin = np.min(parametric_quantile, axis=3)
1911
1912    q_results.update({"Umax": Umax, "Umin": Umin})
1913
1914    return q_results
1915
1916
1917@tools.autocast_args
1918def spatial_quantiles(
1919    array: np.ndarray | None,
1920    t_axis: int = 2,
1921    return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
1922    fit: str = "gumbel",
1923    nb_minimum_chunks: int = 4,
1924    model_time_step: float = 3600,
1925    estimate_method: str = "MLE",
1926    chunk_size: int = 365,
1927    quantile_duration: int | float = 1,
1928    ncpu: int | None = None,
1929    compute_uncertainties=False,
1930    bootstrap_sample=100,
1931    maxima: np.ndarray | None = None,
1932):
1933    """
1934    Proceed to the quantile adjustment (parrallel computation).
1935    :param array: an np.ndarray of spatial dicharges values for every time-step
1936    :type array: np.ndarray | None
1937    :param t_axis: The array axis direction of the time-step, defaults to 2,
1938    defaults to 2, defaults to 2
1939    :type t_axis: int, optional
1940    :param return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
1941    :type return_periods: list | tuple, optional
1942    :param fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
1943    :type fit: str, optional
1944    :param estimate_method: The methode to use for calibrated the parameters
1945    of the extrem law (MLE or MM), defaults to "MLE"
1946    :type estimate_method: str, optional
1947    :param quantile_duration: The duration of the quantile (hour), defaults to 1
1948    :type quantile_duration: int | float, optional
1949    :param chunk_size: the size of the chunks in days, defaults to 365
1950    :type chunk_size: int, optional
1951    :param ncpu: Number of cpu to use, defaults to None
1952    :type ncpu: int | None, optional
1953    :param compute_uncertainties: Compute the uncertainties usung the parametric bootstrap method
1954    :type compute_uncertainties: bool, True
1955    :param bootstrap_sample: the size of bootstrap sample, default is 100
1956    :type bootstrap_sample: int, optional
1957    :return: A dictionary containing the results od the quantile computation:
1958     Results include the quantile, the empirical return period,
1959     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
1960    :rtype: dict
1961
1962    """
1963    # # R workaround as normal number (int) are passed to float to python
1964    # t_axis = int(t_axis)
1965    # chunk_size = int(chunk_size)
1966
1967    if ncpu is None:
1968        ncpu = int(os.cpu_count() / 2)
1969    else:
1970        ncpu = int(min(ncpu, os.cpu_count() - 1))
1971
1972    if pd.Timedelta(
1973        hours=quantile_duration,
1974    ) < pd.Timedelta(
1975        seconds=model_time_step,
1976    ):
1977        raise ValueError(
1978            "The quantile duration {quantile_duration} (hours) must be"
1979            "greater or equal than the model time step {model_time_step} (seconds)"
1980        )
1981
1982    if pd.Timedelta(
1983        hours=quantile_duration,
1984    ) > pd.Timedelta(
1985        days=chunk_size,
1986    ):
1987        raise ValueError(
1988            "The chunk_size {chunk_size} (days) must be"
1989            " greater or equal than the quantile duration {quantile_duration} (hours)"
1990        )
1991
1992    if array is None:
1993        raise ValueError("Input array is None, no data to compute quantile.")
1994
1995    if maxima is None:
1996        array = time_resample_array(
1997            array=array,
1998            quantile_duration=quantile_duration,
1999            model_time_step=model_time_step,
2000            quantile_chunk_size=chunk_size,
2001            t_axis=t_axis,
2002        )
2003
2004        maxima = compute_maxima(
2005            array=array,
2006            t_axis=t_axis,
2007            nb_minimum_chunks=nb_minimum_chunks,
2008            chunk_size=chunk_size,
2009            quantile_duration=quantile_duration,
2010        )
2011
2012    results = fit_quantile(
2013        maxima=maxima,
2014        t_axis=t_axis,
2015        return_periods=return_periods,
2016        fit=fit,
2017        estimate_method=estimate_method,
2018        quantile_duration=quantile_duration,
2019        chunk_size=chunk_size,
2020        ncpu=ncpu,
2021    )
2022
2023    if compute_uncertainties:
2024        # calcul des incertitudes méthode bootstrap
2025        results = parametric_bootstrap_uncertainties(
2026            q_results=results,
2027            bootstrap_sample=bootstrap_sample,
2028            estimate_method=estimate_method,
2029            ncpu=ncpu,
2030        )
2031
2032    return results
2033
2034
2035@tools.autocast_args
2036def spatial_quantiles_unparallel(
2037    array: np.ndarray | None = None,
2038    t_axis: int = 2,
2039    return_periods: list | tuple = [2, 5, 10, 20, 50, 100],
2040    fit: str = "gumbel",
2041    nb_minimum_chunks: int = 4,
2042    model_time_step: float = 3600,
2043    estimate_method: str = "MLE",
2044    chunk_size: int = 365,
2045    compute_uncertainties=False,
2046    bootstrap_sample=100,
2047    quantile_duration: int | float = 1,
2048    maxima: np.ndarray | None = None,
2049):
2050    """
2051    Proceed to the quantile adjustment (unparrallel computation).
2052    :param array: an np.ndarray of spatial dicharges values for every time-step
2053    :type array: np.ndarray | None
2054    :param t_axis: The array axis direction of the time-step, defaults to 2,
2055    defaults to 2, defaults to 2
2056    :type t_axis: int, optional
2057    :param return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
2058    :type return_periods: list | tuple, optional
2059    :param fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
2060    :type fit: str, optional
2061    :param estimate_method: The methode to use for calibrated the parameters
2062    of the extrem law (MLE or MM), defaults to "MLE"
2063    :type estimate_method: str, optional
2064    :param quantile_duration: The duration of the quantile (hour), defaults to 1
2065    :type quantile_duration: int | float, optional
2066    :param chunk_size: the size of the chunks in days, defaults to 365
2067    :type chunk_size: int, optional
2068    :param compute_uncertainties: Compute the uncertainties usung the parametric bootstrap method
2069    :type compute_uncertainties: bool, True
2070    :param bootstrap_sample: the size of bootstrap sample, default is 100
2071    :type bootstrap_sample: int, optional
2072    :return: A dictionary containing the results od the quantile computation:
2073     Results include the quantile, the empirical return period,
2074     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
2075    :rtype: dict
2076
2077    """
2078    # # R workaround as normal number (int) are passed to float to python
2079    # t_axis = int(t_axis)
2080    # chunk_size = int(chunk_size)
2081
2082    if pd.Timedelta(
2083        hours=quantile_duration,
2084    ) < pd.Timedelta(
2085        seconds=model_time_step,
2086    ):
2087        raise ValueError(
2088            "The quantile duration {quantile_duration} (hours) must be"
2089            "greater or equal than the model time step {model_time_step} (seconds)"
2090        )
2091
2092    if pd.Timedelta(
2093        hours=quantile_duration,
2094    ) > pd.Timedelta(
2095        days=chunk_size,
2096    ):
2097        raise ValueError(
2098            "The chunk_size {chunk_size} (days) must be"
2099            " greater or equal than the quantile duration {quantile_duration} (hours)"
2100        )
2101
2102    if array is None:
2103        raise ValueError("Input array is None, no data to compute quantile.")
2104
2105    if maxima is None:
2106        array = time_resample_array(
2107            array=array,
2108            quantile_duration=quantile_duration,
2109            model_time_step=model_time_step,
2110            quantile_chunk_size=chunk_size,
2111            t_axis=t_axis,
2112        )
2113
2114        maxima = compute_maxima(
2115            array=array,
2116            t_axis=t_axis,
2117            nb_minimum_chunks=nb_minimum_chunks,
2118            chunk_size=chunk_size,
2119            quantile_duration=quantile_duration,
2120        )
2121
2122    results = fit_quantile_unparallel(
2123        maxima=maxima,
2124        t_axis=t_axis,
2125        return_periods=return_periods,
2126        fit=fit,
2127        estimate_method=estimate_method,
2128        quantile_duration=quantile_duration,
2129        chunk_size=chunk_size,
2130    )
2131
2132    if compute_uncertainties:
2133        # calcul des incertitudes méthode bootstrap
2134        parametric_bootstrap_uncertainties_unparallel(
2135            q_results=results,
2136            bootstrap_sample=bootstrap_sample,
2137            return_periods=return_periods,
2138            estimate_method=estimate_method,
2139        )
2140
2141    return results
def mse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria mse: mse = (1.0 / nb_valid_data) * np.sum((obs - sim) ** 2.0)

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_mse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria mse: mse = (1.0 / nb_valid_data) * np.sum((obs - sim) ** 2.0)

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def rmse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria rmse: rmse = np.sqrt(res_mse)

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_rmse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria rmse: rmse = np.sqrt(res_mse)

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def nrmse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria nrmse: nrmse = res_rmse / mean_obs

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_nmse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria nrmse: nrmse = res_rmse / mean_obs

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def se(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria se: se = ( np.sum((obs - sim)** 2.0, axis=t_axis, where=mask_nodata) )

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_se(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria se: se = ( np.sum((obs - sim)** 2.0, axis=t_axis, where=mask_nodata) )

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def mae(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria mae: mae = 1/n*(np.sum(abs(obs - sim))

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_mae(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria mae: mae = np.sqrt(np.sum(abs(obs - sim))

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def mape(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria mape: mape = 1/n*( np.sum(abs((obs - sim) / obs)) )

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_mape(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria mape: mape = np.sqrt( np.sum(abs((obs - sim) / obs)) )

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def lgrm(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria lgrm: lgrm = np.sum( obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata )

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_lgrm(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria lgrm: lgrm = np.sum( obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata )

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def nse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria nse: numerator = np.sum((obs - sim) ** 2.0, axis=t_axis, where=mask_nodata)

denominator = np.sum(
    (obs - mean_obs) ** 2.0, axis=t_axis, where=mask_nodata
)

denominator = np.where(denominator == 0, np.nan, denominator)

nse = 1 - numerator / denominator
Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_nse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria nse: numerator = np.sum((obs - sim) ** 2.0, axis=t_axis, where=mask_nodata)

denominator = np.sum(
    (obs - mean_obs) ** 2.0, axis=t_axis, where=mask_nodata
)

denominator = np.where(denominator == 0, np.nan, denominator)

nse = 1 - numerator / denominator
Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def nnse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria nnse: nnse = 1.0 / (2.0 - res_nse)

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
  • nodata: No data value, defaults to -99.0
  • t_axis: Array axis of the time, defaults to 0
def sm_nnse(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria nnse: nnse = 1.0 / (2.0 - res_nse)

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def kge(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the Pearson correlation coefficient between observed and simulated data.

Parameters

obs : np.ndarray Observed data. sim : np.ndarray Simulated data. nodata : float, optional No-data value to ignore in the computation. Default is -99.0. t_axis : int, optional Axis along which to compute the Pearson coefficient. Default is 0.

Returns

np.ndarray Pearson correlation coefficient for each non-time dimension.

def sm_kge(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the misfit criteria kge, see the Smash documentation at https://smash.recover.inrae.fr/math_num_documentation/efficiency_error_metric.html

Parameters
  • obs: Observed discharges, defaults to None
  • sim: simulated discharges, defaults to None
def pearson(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the Pearson correlation coefficient between observed and simulated data.

Parameters

obs : np.ndarray Observed data. sim : np.ndarray Simulated data. nodata : float, optional No-data value to ignore in the computation. Default is -99.0. t_axis : int, optional Axis along which to compute the Pearson coefficient. Default is 0.

Returns

np.ndarray Pearson correlation coefficient for each non-time dimension.

def quantile_gumbel(T: float = 1.0, loc: float = 0.0, scale: float = 0.0):
924def quantile_gumbel(T: float = 1.0, loc: float = 0.0, scale: float = 0.0):
925    """
926    Compute the quantile for a given return period using the Gumbel law.
927    :param T: The return period, defaults to 1
928    :type T: float, optional
929    :param loc: The localisation parameter of the Gumbel law, defaults to 0.0
930    :type loc: float, optional
931    :param scale: The scale parameter of the Gumbel law, defaults to 0.0
932    :type scale: float, optional
933    :return: The value of the quantile
934    :rtype: float
935
936    """
937
938    return stats.gumbel_r.ppf(1 - 1 / T, loc=loc, scale=scale)

Compute the quantile for a given return period using the Gumbel law.

Parameters
  • T: The return period, defaults to 1
  • loc: The localisation parameter of the Gumbel law, defaults to 0.0
  • scale: The scale parameter of the Gumbel law, defaults to 0.0
Returns

The value of the quantile

def quantile_gev( T: float = 1.0, shape: float = 0.0, loc: float = 0.0, scale: float = 0.0):
942def quantile_gev(
943    T: float = 1.0, shape: float = 0.0, loc: float = 0.0, scale: float = 0.0
944):
945    """
946    Compute the quantile for a given return period using the GEV law.
947    :param T: The return period, defaults to 1
948    :type T: float, optional
949    :param shape: The shape parameter of the GEV law, defaults to 0.0
950    :type shape: float, optional
951    :param loc: The localisation parameter of the GEV law, defaults to 0.0
952    :type loc: float, optional
953    :param scale: The scale parameter of the GEV law, defaults to 0.0
954    :type scale: float, optional
955    :return: The value of the quantile
956    :rtype: float
957
958    """
959    return stats.genextreme.ppf(1 - 1 / T, shape, loc=loc, scale=scale)

Compute the quantile for a given return period using the GEV law.

Parameters
  • T: The return period, defaults to 1
  • shape: The shape parameter of the GEV law, defaults to 0.0
  • loc: The localisation parameter of the GEV law, defaults to 0.0
  • scale: The scale parameter of the GEV law, defaults to 0.0
Returns

The value of the quantile

def genextreme_fit(data: numpy.ndarray | None = None, estimate_method: str = 'MLE'):
962def genextreme_fit(
963    data: np.ndarray | None = None, estimate_method: str = "MLE"
964):
965    """
966    Return estimates of shape, location, and scale parameters from data. The default
967     estimation method is Maximum Likelihood Estimation (MLE), but Method of Moments (MM)
968      is also available.
969    :param data: Data of maximum values used to fit a GEV, defaults to None
970    :type data: np.ndarray | None, optional
971    :param estimate_method: Method to optimize the parameters: MLE for Maximum Likelihood
972     Estimate, MM for  Method of Moments , defaults to "MLE"
973    :type estimate_method: str, optional
974    :return: Estimates for any shape parameters (if applicable), followed by those for
975     location and scale.
976    :rtype: tuple of float
977
978    """
979    if data is None:
980        raise ValueError("input data is None. You must provide valid data.")
981    res = stats.genextreme.fit(data, method=estimate_method)
982    return res

Return estimates of shape, location, and scale parameters from data. The default estimation method is Maximum Likelihood Estimation (MLE), but Method of Moments (MM) is also available.

Parameters
  • data: Data of maximum values used to fit a GEV, defaults to None
  • estimate_method: Method to optimize the parameters: MLE for Maximum Likelihood Estimate, MM for Method of Moments , defaults to "MLE"
Returns

Estimates for any shape parameters (if applicable), followed by those for location and scale.

def gumbel_r_fit(data: numpy.ndarray | None = None, estimate_method: str = 'MLE'):
 985def gumbel_r_fit(data: np.ndarray | None = None, estimate_method: str = "MLE"):
 986    """
 987    Return estimates of shape, location, and scale parameters from data.
 988     The default estimation method is Maximum Likelihood Estimation (MLE),
 989      but Method of Moments (MM) is also available.
 990    :param data: Data of maximum values used to fit a Gumbel law, defaults to None
 991    :type data: np.ndarray | None, optional
 992    :param estimate_method: Method to optimize the parameters: MLE for Maximum Likelihood
 993     Estimate, MM for  Method of Moments , defaults to "MLE"
 994    :type estimate_method: str, optional
 995    :return: Estimates for any shape parameters (if applicable), followed by those for
 996     location and scale.
 997    :rtype: tuple of float
 998
 999    """
1000    if data is None:
1001        raise ValueError("input data is None. You must provide valid data.")
1002
1003    res = stats.gumbel_r.fit(data, method=estimate_method)
1004    return (0, *res)

Return estimates of shape, location, and scale parameters from data. The default estimation method is Maximum Likelihood Estimation (MLE), but Method of Moments (MM) is also available.

Parameters
  • data: Data of maximum values used to fit a Gumbel law, defaults to None
  • estimate_method: Method to optimize the parameters: MLE for Maximum Likelihood Estimate, MM for Method of Moments , defaults to "MLE"
Returns

Estimates for any shape parameters (if applicable), followed by those for location and scale.

def time_resample_array(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Resample the discharges array for a given time-step.

Parameters
  • array: the matrix containing the discharge with shape (nbx, nby, nbts)
  • quantile_duration: The duration of the quantile (hours), defaults to 1
  • model_time_step: the time-step of the Smash model (seconds), defaults to 3600
  • quantile_chunk_size: the size of the quantile chunk in days
  • t_axis: The array axis direction of the time-step, defaults to 2
Returns

The resampled array

def compute_maxima(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Compute the maxima of the discharges for a given chunk_size in the t_axis direction.

Parameters
  • array: the matrix containing the discharge with shape (nbx, nby, nbts). The shape can be smaller or higher. But t_axis must be set to target the nbts (number of time-step) dimension. Ex: if shape=(nbx, nbts), t_axis must be equal to 1.
  • t_axis: The array axis direction of the time-step, defaults to 2, defaults to 2
  • nb_minimum_chunks: minimal number of chunk required adjust an extrem law and compute the quantile, defaults to 4
  • chunk_size: The chunk_size (days). It correspond to the 'unit' of the return period, defaults to 365 (year)
  • quantile_duration: The duration of the quantile (hour), defaults to 1
Returns

an np.ndarray of spatial maximal values for every chunk

def quantil_obs( qobs_directory: str | None = None, code: numpy.ndarray | list = [], model_time_step: float = 3600, nb_minimum_chunks: int = 4, chunk_size: int = 365, quantile_duration: int = 1):
1218def quantil_obs(
1219    qobs_directory: str | None = None,
1220    code: np.ndarray | list = [],
1221    model_time_step: float = 3600,
1222    nb_minimum_chunks: int = 4,
1223    chunk_size: int = 365,
1224    quantile_duration: int = 1,
1225):
1226
1227    if qobs_directory is None:
1228        print("</> qobs_directory `{qobs_directory}` is not a valid directory")
1229        return
1230
1231    if isinstance(code, list):
1232        code = np.array(code)
1233
1234    qobs = tools.read_hourly_qobs(qobs_directory, code)
1235
1236    array = time_resample_array(
1237        array=qobs,
1238        quantile_duration=quantile_duration,
1239        model_time_step=model_time_step,
1240        quantile_chunk_size=chunk_size,
1241        t_axis=1,
1242    )
1243
1244    maxima = compute_maxima(
1245        array=array,
1246        t_axis=1,
1247        nb_minimum_chunks=nb_minimum_chunks,
1248        chunk_size=chunk_size,
1249        quantile_duration=quantile_duration,
1250    )
1251
1252    results = empirical_obs_quantile(
1253        maxima=maxima,
1254        nb_minimum_chunks=nb_minimum_chunks,
1255        chunk_size=chunk_size,
1256        quantile_duration=quantile_duration,
1257    )
1258
1259    return results
def empirical_obs_quantile( maxima: numpy.ndarray | None, t_axis=1, nb_minimum_chunks: int = 4, frac_to_remove: float = 0.1, quantile_duration: int | float = 1, chunk_size: int = 365):
1262def empirical_obs_quantile(
1263    maxima: np.ndarray | None,
1264    t_axis=1,
1265    nb_minimum_chunks: int = 4,
1266    frac_to_remove: float = 0.1,
1267    quantile_duration: int | float = 1,
1268    chunk_size: int = 365,
1269):
1270    """
1271    :param maxima: Maximal discharge by chunk
1272    :type maxima: np.ndarray | None
1273    :param t_axis: axis of the time series, defaults to 1
1274    :type t_axis: TYPE, optional
1275    :param nb_minimum_chunks: number of minimum chunck required to remove `frac_to_remove` of the maxima distribution, i.e len of the maxima array in the t_axis direction, defaults to 4
1276    :type nb_minimum_chunks: int, optional
1277    :param frac_to_remove: fraction between 0 and 1 to remove lower value of the maxima distribution, incase of incomplete data chunk, defaults to 0.1
1278    :type frac_to_remove: float, default 0.1
1279    :param quantile_duration: duration of the quantile (hour)
1280    :type quantile_duration int | float, default 1
1281    :param chunk_size: size of the chunk used to comute the maxima
1282    :type chunk_size: int, default 365
1283    """
1284
1285    maxima = np.moveaxis(maxima, t_axis, 0)
1286    maxima_sorted = np.sort(maxima, axis=0)
1287    maxima_sorted = np.where(maxima_sorted < 0, np.nan, maxima_sorted)
1288    T_emp = np.zeros(shape=maxima.shape) + np.nan
1289    # n = maxima.shape[0]
1290
1291    for sta in range(maxima_sorted.shape[1]):
1292        n_valid = np.where(maxima_sorted[:, sta] > 0)
1293        n = len(n_valid[0])
1294        # remove 10% of the
1295        if n > nb_minimum_chunks:
1296            n_to_remove = max(1, int(frac_to_remove * n))
1297        else:
1298            n_to_remove = 0
1299
1300        for i in range(n_to_remove):
1301            ind = n_valid[0][i]
1302            maxima_sorted[i] = np.nan
1303
1304        if n > nb_minimum_chunks:
1305            rank = 1
1306            for i in range(n_to_remove, n):
1307                probs = (rank - 0.5) / n
1308                T_emp[n_valid[0][i], sta] = 1 / (1 - probs)
1309                rank = rank + 1
1310
1311    # trim/filter nan cells
1312    index = []
1313    for t in range(maxima_sorted.shape[0]):
1314        if np.any(maxima_sorted[t, :] >= 0):
1315            index.append(t)
1316
1317    maxima_sorted = maxima_sorted[index, :]
1318    T_emp = T_emp[index, :]
1319
1320    maxima_sorted = np.moveaxis(maxima_sorted, 0, t_axis)
1321    T_emp = np.moveaxis(T_emp, 0, t_axis)
1322
1323    results = {
1324        "maxima": maxima_sorted,
1325        "Temp": T_emp,
1326        "chunk_size": chunk_size,
1327        "nb_chunks": maxima.shape[t_axis],
1328        "quantile_duration": quantile_duration,
1329    }
1330    return results
Parameters
  • maxima: Maximal discharge by chunk
  • t_axis: axis of the time series, defaults to 1
  • nb_minimum_chunks: number of minimum chunck required to remove frac_to_remove of the maxima distribution, i.e len of the maxima array in the t_axis direction, defaults to 4
  • frac_to_remove: fraction between 0 and 1 to remove lower value of the maxima distribution, incase of incomplete data chunk, defaults to 0.1
  • quantile_duration: duration of the quantile (hour) :type quantile_duration int | float, default 1
  • chunk_size: size of the chunk used to comute the maxima
def fit_quantile(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Proceed to the qunaitle adjustment.

Parameters
  • maxima: an np.ndarray of spatial maximal values for every chunk
  • t_axis: The array axis direction of the time-step, defaults to 2, defaults to 2, defaults to 2
  • return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
  • fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
  • estimate_method: The methode to use for calibrated the parameters of the extrem law (MLE or MM), defaults to "MLE"
  • quantile_duration: The duration of the quantile (hour), defaults to 1
  • chunk_size: the size of the chunks in days, defaults to 365
  • ncpu: Number of cpu to use, defaults to None
Returns

A dictionary containing the results od the quantile computation: Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.

def parametric_bootstrap_uncertainties(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Proceed to the qunaitle adjustment.

Parameters
  • q_results: A dictionary containing the results of the quantile adjustment
  • bootstrap_sample: the number of bootstrap sample, defaults to 100
  • estimate_method: The methode to use for calibrated the parameters of the extrem law (MLE or MM), defaults to "MLE"
  • ncpu: Number of cpu to use, defaults to None
Returns

A dictionary containing the results od the quantile computation: Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.

def fit_quantile_unparallel(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Proceed to the qunaitle adjustment (unparrallel computation).

Parameters
  • maxima: an np.ndarray of spatial maximal values for every chunk
  • t_axis: The array axis direction of the time-step, defaults to 2, defaults to 2, defaults to 2
  • return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
  • fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
  • estimate_method: The methode to use for calibrated the parameters of the extrem law (MLE or MM), defaults to "MLE"
  • quantile_duration: The duration of the quantile (hour), defaults to 1
  • chunk_size: the size of the chunks in days, defaults to 365
Returns

A dictionary containing the results od the quantile computation: Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.

def parametric_bootstrap_uncertainties_unparallel(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Proceed to the qunaitle adjustment.

Parameters
  • q_results: A dictionary containing the results of the quantile adjustment
  • bootstrap_sample: the number of bootstrap sample, defaults to 100
  • return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
  • estimate_method: The methode to use for calibrated the parameters of the extrem law (MLE or MM), defaults to "MLE"
Returns

A dictionary containing the results od the quantile computation: Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.

def spatial_quantiles(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Proceed to the quantile adjustment (parrallel computation).

Parameters
  • array: an np.ndarray of spatial dicharges values for every time-step
  • t_axis: The array axis direction of the time-step, defaults to 2, defaults to 2, defaults to 2
  • return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
  • fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
  • estimate_method: The methode to use for calibrated the parameters of the extrem law (MLE or MM), defaults to "MLE"
  • quantile_duration: The duration of the quantile (hour), defaults to 1
  • chunk_size: the size of the chunks in days, defaults to 365
  • ncpu: Number of cpu to use, defaults to None
  • compute_uncertainties: Compute the uncertainties usung the parametric bootstrap method
  • bootstrap_sample: the size of bootstrap sample, default is 100
Returns

A dictionary containing the results od the quantile computation: Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.

def spatial_quantiles_unparallel(*args, **kwargs):
 95    def wrapper(*args, **kwargs):
 96
 97        bound = sig.bind(*args, **kwargs)
 98        bound.apply_defaults()
 99
100        for name, value in bound.arguments.items():
101            if name in annotations:
102
103                target_type = annotations[name]
104
105                args_ = get_args(target_type)
106
107                if target_type is None and len(args_) == 0:
108                    args_ = (type(None),)
109                    target_type = type(None)
110
111                if not type(value) in args_:
112
113                    if len(args_) > 1 and type(None) in args_:
114
115                        converted = False
116                        for t in args_:
117
118                            if t is not type(None):
119
120                                if value is not None:
121                                    try:
122                                        print(
123                                            f"</> Warning: Arg '{name}' of type {type(value)} is being"
124                                            f" converted to {t}"
125                                        )
126                                        bound.arguments[name] = t(value)
127                                        converted = True
128                                    except:
129                                        pass
130
131                                if converted:
132                                    break
133
134                        if not converted:
135                            raise TypeError(
136                                f"</> Error: Arg '{name}' must be a type of "
137                                f" {args_}, got {value}"
138                                f" ({type(value).__name__})"
139                            )
140
141                    else:
142                        if not isinstance(value, target_type):
143                            try:
144                                print(
145                                    f"</> Warning: Arg '{name}' of type {type(value)} is being"
146                                    f" converted to {target_type}"
147                                )
148                                bound.arguments[name] = target_type(value)
149                            except Exception:
150                                raise TypeError(
151                                    f"</> Error: Arg '{name}' must be a type of "
152                                    f" {target_type.__name__}, got {value}"
153                                    f" ({type(value).__name__})"
154                                )
155
156        return func(*bound.args, **bound.kwargs)

Proceed to the quantile adjustment (unparrallel computation).

Parameters
  • array: an np.ndarray of spatial dicharges values for every time-step
  • t_axis: The array axis direction of the time-step, defaults to 2, defaults to 2, defaults to 2
  • return_periods: A list of the return period, defaults to [2, 5, 10, 20, 50, 100]
  • fit: The extrem law to use (gumbel or gev), defaults to "gumbel"
  • estimate_method: The methode to use for calibrated the parameters of the extrem law (MLE or MM), defaults to "MLE"
  • quantile_duration: The duration of the quantile (hour), defaults to 1
  • chunk_size: the size of the chunks in days, defaults to 365
  • compute_uncertainties: Compute the uncertainties usung the parametric bootstrap method
  • bootstrap_sample: the size of bootstrap sample, default is 100
Returns

A dictionary containing the results od the quantile computation: Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.