smashbox.stats.mystats

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

The class mystats includes functions and children classes to compute basics statistics with the results of the smash model.

mystats(parent_class)
19    def __init__(self, parent_class):
20        self._parent_class = parent_class
21        """_parent_class attribute stores the parent_class src.model() to be able to
22        access to the result of the smash simulation"""
23
24        self.misfit_stats = misfit_stats(self)
25        """Attribute misfit_stats stores the class src.mystats.misfit_stats(). Its goal
26         is to compute misfit criteria between simulated and observed discharges"""
27        self.quantile_stats = spatial_quantile()
28        """Attribute quantile_stats owns the class src.mystats.spatial_quantile().
29         Its goal is to compute the discharges quantiles for various return period."""
30        self.spatial_stats = spatial_stats(self)
31        """Atrribute spatial_stats owns the class src.mystats.spatial_stats(). Its goal
32        is to provide basic statistics on the discharges field over the time
33        (mean, median, q20, q80, maximum, minimum)"""
34        self.outlets_stats = outlets_stats(self)
35        """Atrribute outlets_stats owns the class src.mystats.outlets_stats().
36        Its goal is to provide basic statistics on the discharges at every outlets over the
37        time (mean, median, q20, q80, maximum, minimum)"""
misfit_stats

Attribute misfit_stats stores the class src.mystats.misfit_stats(). Its goal is to compute misfit criteria between simulated and observed discharges

quantile_stats

Attribute quantile_stats owns the class src.mystats.spatial_quantile(). Its goal is to compute the discharges quantiles for various return period.

spatial_stats

Atrribute spatial_stats owns the class src.mystats.spatial_stats(). Its goal is to provide basic statistics on the discharges field over the time (mean, median, q20, q80, maximum, minimum)

outlets_stats

Atrribute outlets_stats owns the class src.mystats.outlets_stats(). Its goal is to provide basic statistics on the discharges at every outlets over the time (mean, median, q20, q80, maximum, minimum)

def fmisfit_stats(self, nodata=-99.0, column=[], ret=False, use_smash_metrics=True):
39    def fmisfit_stats(
40        self, nodata=-99.0, column=[], ret=False, use_smash_metrics=True
41    ):
42        """
43        Compute the misfit for every outlets between the simulated and the
44         observed discharges
45        :param nodata: No data values, defaults to -99.0
46        :type nodata: TYPE, optional
47        :param column: column on which to compute the statistics (gauge), defaults to []
48        :type column: TYPE, optional
49        :param ret: return the result, defaults to False
50        :type ret: TYPE, optional
51        :return: object with attributes with different statistics.
52        :rtype: class src.mystats.misfit.results()
53
54        """
55
56        if not use_smash_metrics:
57            self.misfit_stats.se(nodata=nodata, column=column)
58            self.misfit_stats.mse(nodata=nodata, column=column)
59            self.misfit_stats.rmse(nodata=nodata, column=column)
60            self.misfit_stats.nrmse(nodata=nodata, column=column)
61            self.misfit_stats.mae(nodata=nodata, column=column)
62            self.misfit_stats.mape(nodata=nodata, column=column)
63            self.misfit_stats.lgrm(nodata=nodata, column=column)
64            self.misfit_stats.nse(nodata=nodata, column=column)
65            self.misfit_stats.nnse(nodata=nodata, column=column)
66            self.misfit_stats.kge(nodata=nodata, column=column)
67
68            self.misfit_stats.pearson(nodata=nodata, column=column)
69        else:
70            self.misfit_stats.sm_se(column=column)
71            self.misfit_stats.sm_mse(column=column)
72            self.misfit_stats.sm_rmse(column=column)
73            self.misfit_stats.sm_nrmse(column=column)
74            self.misfit_stats.sm_mae(column=column)
75            self.misfit_stats.sm_mape(column=column)
76            self.misfit_stats.sm_lgrm(column=column)
77            self.misfit_stats.sm_nse(column=column)
78            self.misfit_stats.sm_nnse(column=column)
79            self.misfit_stats.sm_kge(column=column)
80
81            self.misfit_stats.pearson(nodata=nodata, column=column)
82
83        if ret:
84            return self.misfit.results

Compute the misfit for every outlets between the simulated and the observed discharges

Parameters
  • nodata: No data values, defaults to -99.0
  • column: column on which to compute the statistics (gauge), defaults to []
  • ret: return the result, defaults to False
Returns

object with attributes with different statistics.

def fspatial_stats(self, ret=False):
 86    def fspatial_stats(self, ret=False):
 87        """
 88        Compute basics statistics over the time (mean, median, q20, q80, maximum, minimum)
 89         on the spatial discharges field.
 90        :param ret: return the result, defaults to False
 91        :type ret: TYPE, optional
 92        :return: object with attributes with different statistics.
 93        :rtype: class src.mystats.spatial_stats.results()
 94
 95        """
 96
 97        if not hasattr(self._parent_class.extra_smash_results, "q_domain"):
 98            raise ValueError(
 99                "No smash extra results  'q_domain' found. Run forward_run()"
100                "with return_options={'q_domain': True}"
101            )
102
103        self.spatial_stats.mean()
104        self.spatial_stats.median()
105        self.spatial_stats.q20()
106        self.spatial_stats.q80()
107        self.spatial_stats.maximum()
108        self.spatial_stats.minimum()
109        self.spatial_stats.var()
110
111        if ret:
112            return self.spatial_stats.results

Compute basics statistics over the time (mean, median, q20, q80, maximum, minimum) on the spatial discharges field.

Parameters
  • ret: return the result, defaults to False
Returns

object with attributes with different statistics.

def foutlets_stats(self, ret=False):
114    def foutlets_stats(self, ret=False):
115        """
116        Compute basices statistics over the time (mean, median, q20, q80, maximum, minimum)
117         at every outlets.
118        :param ret: return the result, defaults to False
119        :type ret: TYPE, optional
120        :return: object with attributes with different statistics.
121        :rtype: class src.mystats.outlets_stats.results()
122
123        """
124        if self._parent_class.smash is None:
125            raise ValueError(
126                "Attribut smash is None. Perhaps, you forget to buil and run the"
127                "smash model..."
128            )
129
130        self.outlets_stats.mean()
131        self.outlets_stats.median()
132        self.outlets_stats.q20()
133        self.outlets_stats.q80()
134        self.outlets_stats.maximum()
135        self.outlets_stats.minimum()
136        self.outlets_stats.var()
137
138        if ret:
139            return self.outlets_stats.results

Compute basices statistics over the time (mean, median, q20, q80, maximum, minimum) at every outlets.

Parameters
  • ret: return the result, defaults to False
Returns

object with attributes with different statistics.

def fmaxima_stats(*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 maximum discharge values of a 3D array by chunk, corresponding to chunk_size (days), along axis t_axis (time) for each pixel of the array.(nbX, nbY).

Parameters

t_axis : int The axis along with the maximum will be computed. This axis should correspond to the time. nb_minimum_chunks: int number of minimum chunks required to compute the maxima along the t_axis. Default is set to 4. If the number of chunks is lower, the function will return None. chunk_size: int Size of the chunks in days. Default is 365 days. quantile_duration: list | tuple The duration of every quantile (hours). The discharges will be resampled for every duration. Default is [1, 2, 3, 4, 6, 12, 24, 48, 72] hours. cumulated_maxima: bool For each call of the function, the maxima will accumulate in a matrix for each quantil duration. This provide a convient way to compute the quantile (fit gumbel/gev) after many successive simulations.

Examples

>>> import smashbox
>>> import numpy as np
>>>
>>> graffas_prcp = np.zeros(shape=(142, 166, 1300))
>>> rng = np.random.default_rng()
>>> graffas_prcp = (
>>>     graffas_prcp
>>>     + rng.multinomial(20, [0.2, 0.8], size=graffas_prcp.shape)[:, :, :, 0]
>>> )
>>>
>>> es=smashbox.SmashBox()
>>> sb.newmodel("graffas_zone")
>>> sb.graffas_zone.generate_mesh()
>>> sb.graffas_zone.atmos_data_connector(input_prcp=graffas_prcp)
>>> sb.graffas_zone.model()
>>> sb.graffas_zone.forward_run(invert_states=True, return_options={"q_domain": True})
>>>
>>> sb.graffas_zone.mysmashmodel.mystats.stats_maxima(chunk_size=10)
>>> sb.graffas_zone.mysmashmodel.mystats.stats_maxima(chunk_size=10)
>>>
>>> results = stats.fit_quantile(
>>>    maxima=es.graffas_zone.mysmashmodel.mystats.spatial_quantile.spatial_cumulated_maxima[
>>>        :, :, :, 0
>>>    ],
>>>    t_axis=2,
>>>    return_periods=[2, 5, 10, 20, 50, 100],
>>>    fit="gumbel",
>>>    estimate_method="MLE",
>>>    quantile_duration=1,
>>>    ncpu=6,
>>>)
def fquantile_stats2(*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 discharge quantile of an 3D array by chunk, corresponding to chunk_size (days), along axis t_axis (time) for each pixel of the array. (nbX, nbY), for each duration quantile_duration and for each return period return_period. This function perform the computation in parallel respect to the the list of quantile duration.

Parameters

t_axis : int The axis along with the maximum will be computed. This axis should correspond to the time. return_periods: list | tuple The duration of every return period of unit chunk_size. Default is [2, 5, 10, 20, 50, 100] with a chunk_size=365 days. fit: str The extrem law to use to compute the quantile. Choice are 'gumbel' | 'gev'. Default is 'gumbel'. nb_minimum_chunks: int number of minimum chunks required to compute the maxima along the t_axis. Default is set to 4. If the number of chunks is lower, the function will return None. estimate_method: str The method to use to fit rhe Gumbel or GEV law. Choice are MLE (Maximum Likelihood Estimate) or MM (Method of Moments). Default is MLE. chunk_size: int Size of the chunks in days. Default is 365 days. quantile_duration: list | tuple The duration of every quantile (hours). The discharges will be resampled for every duration. Default is [1, 2, 3, 4, 6, 12, 24, 48, 72] hours. ncpu: int Number of cpu to use to parrallelize the computation. Default is set to int(os.cpu_count() / 2). compute_uncertainties: bool Compute the uncertainties using the parametric bootstrap method bootstrap_sample: int Number of sample using by the bootstrap method, default is 100

Return:

Results are stored in the class spatial_quantile wit different attributes: - spatial_quantile_matrix : matrix of the spatial quantile for each duration and each return period. - spatial_maxima_matrix : matrix of the spatial maxima for each duration and each return period. - spatial_cumulated_maxima_matrix : matrix of the spatial accumulated maxima for each duration and each return period. - Quantile_{duration}h : class of spatial_quantile_results() with attributes: - self.T : the return periods - self.Q_th : the quantile matrix for every return periods - self.T_emp : the empirical return period for each maximum - self.maxima : the matrix of the maxima - self.nb_chunks : nb of chunk, i.e data for each pixel - self.fit : fitting law - self.fit_shape : matrix of the shape coefficient - self.fit_scale : matrix of the scale coefficient - self.fit_loc : matrix of the localisation coefficient - self.duration : duration of the quantile

Examples

>>> import smashbox
>>> import numpy as np
>>>
>>> graffas_prcp = np.zeros(shape=(142, 166, 1300))
>>> rng = np.random.default_rng()
>>> graffas_prcp = (
>>>     graffas_prcp
>>>     + rng.multinomial(20, [0.2, 0.8], size=graffas_prcp.shape)[:, :, :, 0]
>>> )
>>>
>>> es=smashbox.SmashBox()
>>> sb.newmodel("graffas_zone")
>>> sb.graffas_zone.generate_mesh()
>>> sb.graffas_zone.atmos_data_connector(input_prcp=graffas_prcp)
>>> sb.graffas_zone.model()
>>> sb.graffas_zone.forward_run(invert_states=True, return_options={"q_domain": True})
>>>
>>> sb.graffas_zone.mysmashmodel.mystats.stats_quantile(chunk_size=10, ncpu=6)
def fquantile_stats(*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 discharge quantile of an 3D array by chunk, corresponding to chunk_size (days), along axis t_axis (time) for each pixel of the array (nbX, nbY), for each duration quantile_duration and for each return period return_period. This function fit the coefficient of the extrem law in parallel along the Y axis of the input maxima array (shape=(nbX,nbY,nbchunks)).

Parameters

t_axis : int The axis along with the maximum will be computed. This axis should correspond to the time. return_periods: list | tuple The duration of every return period of unit chunk_size. Default is [2, 5, 10, 20, 50, 100] with a chunk_size=365 days. fit: str The extrem law to use to compute the quantile. Choice are 'gumbel' | 'gev'. Default is 'gumbel'. nb_minimum_chunks: int number of minimum chunks required to compute the maxima along the t_axis. Default is set to 4. If the number of chunks is lower, the function will return None. estimate_method: str The method to use to fit rhe Gumbel or GEV law. Choice are MLE (Maximum Likelihood Estimate) or MM (Method of Moments). Default is MLE. chunk_size: int Size of the chunks in days. Default is 365 days. quantile_duration: list | tuple The duration of every quantile (hours). The discharges will be resampled for every duration. Default is [1, 2, 3, 4, 6, 12, 24, 48, 72] hours. ncpu: int Number of cpu to use to parrallelize the computation. Default is set to int(os.cpu_count() / 2). compute_uncertainties: bool Compute the uncertainties using the parametric bootstrap method bootstrap_sample: int Number of sample using by the bootstrap method, default is 100

Return:

Results are stored in the class spatial_quantile wit different attributes: - spatial_quantile_matrix : matrix of the spatial quantile for each duration and each return period. - spatial_maxima_matrix : matrix of the spatial maxima for each duration and each return period. - spatial_cumulated_maxima_matrix : matrix of the spatial accumulated maxima for each duration and each return period. - Quantile_{duration}h : class of spatial_quantile_results() with attributes: - self.T : the return periods - self.Q_th : the quantile matrix for every return periods - self.T_emp : the empirical return period for each maximum - self.maxima : the matrix of the maxima - self.nb_chunks : nb of chunk, i.e data for each pixel - self.fit : fitting law - self.fit_shape : matrix of the shape coefficient - self.fit_scale : matrix of the scale coefficient - self.fit_loc : matrix of the localisation coefficient - self.duration : duration of the quantile

Examples

>>> import smashbox
>>> import numpy as np
>>>
>>> graffas_prcp = np.zeros(shape=(142, 166, 1300))
>>> rng = np.random.default_rng()
>>> graffas_prcp = (
>>>     graffas_prcp
>>>     + rng.multinomial(20, [0.2, 0.8], size=graffas_prcp.shape)[:, :, :, 0]
>>> )
>>>
>>> es=smashbox.SmashBox()
>>> sb.newmodel("graffas_zone")
>>> sb.graffas_zone.generate_mesh()
>>> sb.graffas_zone.atmos_data_connector(input_prcp=graffas_prcp)
>>> sb.graffas_zone.model()
>>> sb.graffas_zone.forward_run(invert_states=True, return_options={"q_domain": True})
>>>
>>> sb.graffas_zone.mysmashmodel.mystats.stats_quantile(chunk_size=10, ncpu=6)
class misfit_results:
809class misfit_results:
810    """
811    The class misfit_results stores the results of the misfits criterium
812    """
813
814    def __init__(self):
815        self.mse = None
816        """MSE for each outlets of the Smash model.
817         mse = (1.0 / nb_valid_data)* np.sum((obs - sim) ** 2.0)
818        """
819        self.rmse = None
820        """RMSE for each outlets of the Smash model.
821         rmse = np.sqrt((1.0 / nb_valid_data)* np.sum((obs - sim) ** 2.0))
822        """
823        self.nrmse = None
824        """Normalized-RMSE for each outlets of the Smash model.
825         nrmse = res_rmse / mean_obs
826        """
827        self.se = None
828        """SE for each outlets of the Smash model. se =
829            np.sum((obs - sim) ** 2.0)
830        )"""
831        self.mae = None
832        """MAE for each outlets of the Smash model. 
833            mae = np.sqrt(np.sum(abs(obs - sim))
834        )"""
835        self.mape = None
836        """MAPE for each outlets of the Smash model. 
837            mape = np.sqrt(
838                np.sum(abs((obs - sim) / obs))
839            )
840        )"""
841        self.lgrm = None
842        """LGRM for each outlets of the Smash model. 
843            lgrm = np.sum(
844                obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata
845            )
846        )"""
847        self.nse = None
848        """NSE for each outlets of the Smash model."""
849        self.nnse = None
850        """NNSE for each outlets of the Smash model. nnse = 1.0 / (2.0 - nse)"""
851        self.kge = None
852        """KGE for each outlets of the Smash model."""
853        self.pearson = None
854        """Pearson coefficient for each outlets of the Smash model."""

The class misfit_results stores the results of the misfits criterium

mse

MSE for each outlets of the Smash model. mse = (1.0 / nb_valid_data)* np.sum((obs - sim) ** 2.0)

rmse

RMSE for each outlets of the Smash model. rmse = np.sqrt((1.0 / nb_valid_data)* np.sum((obs - sim) ** 2.0))

nrmse

Normalized-RMSE for each outlets of the Smash model. nrmse = res_rmse / mean_obs

se

SE for each outlets of the Smash model. se = np.sum((obs - sim) ** 2.0) )

mae

MAE for each outlets of the Smash model. mae = np.sqrt(np.sum(abs(obs - sim)) )

mape

MAPE for each outlets of the Smash model. mape = np.sqrt( np.sum(abs((obs - sim) / obs)) ) )

lgrm

LGRM for each outlets of the Smash model. lgrm = np.sum( obs * (np.log((obs / sim) ** 2.0)), axis=t_axis, where=mask_nodata ) )

nse

NSE for each outlets of the Smash model.

nnse

NNSE for each outlets of the Smash model. nnse = 1.0 / (2.0 - nse)

kge

KGE for each outlets of the Smash model.

pearson

Pearson coefficient for each outlets of the Smash model.

class spatial_stats_results:
857class spatial_stats_results:
858    """
859    The class spatial_stats_results stores the results of the spatial statistics
860    on discharges at every pixel.
861    """
862
863    def __init__(self):
864        self.min = None
865        """The minimum values for each pixel"""
866        self.max = None
867        """The maximum values for each pixel"""
868        self.mean = None
869        """The mean value for each pixel"""
870        self.median = None
871        """The median values for each pixel"""
872        self.q20 = None
873        """The percentile 20% for each pixel"""
874        self.q80 = None
875        """The percentile 80% for each pixel"""
876        self.var = None
877        """The variance for each pixel"""

The class spatial_stats_results stores the results of the spatial statistics on discharges at every pixel.

min

The minimum values for each pixel

max

The maximum values for each pixel

mean

The mean value for each pixel

median

The median values for each pixel

q20

The percentile 20% for each pixel

q80

The percentile 80% for each pixel

var

The variance for each pixel

class outlets_stats_results:
880class outlets_stats_results:
881    """
882    The class outlets_stats_results stores the results of the statistics
883    on discharges at every outlets.
884    """
885
886    def __init__(self):
887        self.min = None
888        """The minimum values for each outlets"""
889        self.max = None
890        """The maximum values for each outlets"""
891        self.mean = None
892        """The mean values for each outlets"""
893        self.median = None
894        """The median values for each outlets"""
895        self.q20 = None
896        """The percentile 20%  values for each outlets"""
897        self.q80 = None
898        """The percentile 80%  values for each outlets"""
899        self.var = None
900        """The variance for each pixel"""

The class outlets_stats_results stores the results of the statistics on discharges at every outlets.

min

The minimum values for each outlets

max

The maximum values for each outlets

mean

The mean values for each outlets

median

The median values for each outlets

q20

The percentile 20% values for each outlets

q80

The percentile 80% values for each outlets

var

The variance for each pixel

class spatial_quantile_results:
903class spatial_quantile_results:
904    """
905    The class spatial_quantile_results stores the results of the quantiles computed for
906     different return period. Results include the quantile, the empirical return period,
907     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
908    """
909
910    def __init__(self):
911        self.T = None
912        """The returns periods"""
913        self.Q_th = None
914        """The theorical discharge quantiles for each return period"""
915        self.T_emp = None
916        """The empirical return period for each maxima"""
917        self.maxima = None
918        """The maxima for each chunk of size chunk_size (default is annual)"""
919        self.nb_chunks = None
920        """The number of chunk (default is number of years)"""
921        self.fit = None
922        """The extrem law used to fit the maxima and the empirical quantile"""
923        self.fit_shape = None
924        """The shape coefficient of the extrem law"""
925        self.fit_scale = None
926        """The scale coefficient of the scale law"""
927        self.fit_loc = None
928        """The fit coefficient of the extream law"""
929        self.duration = None
930        """The duration of the quantile (hours)"""
931        self.chunk_size = None
932        """The size of the chunk on which the maxima are computed (unit of the return
933         period, default is 365 days (1 year))"""
934        self.Umin = None
935        """Uncertainties minimum values"""
936        self.Umax = None
937        """Uncertainties maximum values"""
938
939    def fill_attribute(self, stats_spatial_quantile: dict = None):
940        """
941        Fill the attribute of the class spatial_quantile_results.
942
943        :param stats_spatial_quantile: Dict of the spatial quantile results, defaults to None
944        :type stats_spatial_quantile: dict, optional
945
946        """
947
948        self.T = stats_spatial_quantile["T"]
949        self.Q_th = stats_spatial_quantile["Q_th"]
950        self.T_emp = stats_spatial_quantile["T_emp"]
951        self.maxima = stats_spatial_quantile["maxima"]
952        self.nb_chunks = stats_spatial_quantile["nb_chunks"]
953        self.fit = stats_spatial_quantile["fit"]
954        self.fit_shape = stats_spatial_quantile["fit_shape"]
955        self.fit_scale = stats_spatial_quantile["fit_scale"]
956        self.fit_loc = stats_spatial_quantile["fit_loc"]
957        self.duration = stats_spatial_quantile["duration"]
958        self.chunk_size = stats_spatial_quantile["chunk_size"]
959        if "Umin" in stats_spatial_quantile:
960            self.Umin = stats_spatial_quantile["Umin"]
961        if "Umax" in stats_spatial_quantile:
962            self.Umax = stats_spatial_quantile["Umax"]

The class spatial_quantile_results stores the results of the quantiles computed for different return period. Results include the quantile, the empirical return period, the maxima for each chunk of chunk_size, the fit parameters of the fit extrem law.

T

The returns periods

Q_th

The theorical discharge quantiles for each return period

T_emp

The empirical return period for each maxima

maxima

The maxima for each chunk of size chunk_size (default is annual)

nb_chunks

The number of chunk (default is number of years)

fit

The extrem law used to fit the maxima and the empirical quantile

fit_shape

The shape coefficient of the extrem law

fit_scale

The scale coefficient of the scale law

fit_loc

The fit coefficient of the extream law

duration

The duration of the quantile (hours)

chunk_size

The size of the chunk on which the maxima are computed (unit of the return period, default is 365 days (1 year))

Umin

Uncertainties minimum values

Umax

Uncertainties maximum values

def fill_attribute(self, stats_spatial_quantile: dict = None):
939    def fill_attribute(self, stats_spatial_quantile: dict = None):
940        """
941        Fill the attribute of the class spatial_quantile_results.
942
943        :param stats_spatial_quantile: Dict of the spatial quantile results, defaults to None
944        :type stats_spatial_quantile: dict, optional
945
946        """
947
948        self.T = stats_spatial_quantile["T"]
949        self.Q_th = stats_spatial_quantile["Q_th"]
950        self.T_emp = stats_spatial_quantile["T_emp"]
951        self.maxima = stats_spatial_quantile["maxima"]
952        self.nb_chunks = stats_spatial_quantile["nb_chunks"]
953        self.fit = stats_spatial_quantile["fit"]
954        self.fit_shape = stats_spatial_quantile["fit_shape"]
955        self.fit_scale = stats_spatial_quantile["fit_scale"]
956        self.fit_loc = stats_spatial_quantile["fit_loc"]
957        self.duration = stats_spatial_quantile["duration"]
958        self.chunk_size = stats_spatial_quantile["chunk_size"]
959        if "Umin" in stats_spatial_quantile:
960            self.Umin = stats_spatial_quantile["Umin"]
961        if "Umax" in stats_spatial_quantile:
962            self.Umax = stats_spatial_quantile["Umax"]

Fill the attribute of the class spatial_quantile_results.

Parameters
  • stats_spatial_quantile: Dict of the spatial quantile results, defaults to None
class spatial_quantile:
965class spatial_quantile:
966    """Parent class spatial quantile. Class to store results of the spatial quantile."""
967
968    def __init__(self):
969        self.spatial_maxima = None
970        """Spatial matrix of the maximum discharges computed on period long of `chunk_size`. If chunk_size is 365, the matrix store the maximum annual discharges. Shape=(nbx, nby, nb_chunk, duration)"""
971        self.spatial_maxima_outlets = None
972        """Outlets matrix of the maximum discharges computed on period long of `chunk_size`. If chunk_size is 365, the matrix store the maximum annual discharges.Shape=(nb_gauge, nb_chunk, duration)"""
973        self.spatial_cumulated_maxima = None
974        """Spatial matrix of the maximum discharges computed on period long of `chunk_size` and accumulated over several simulation. If chunk_size is 365, the matrix store the maximum annual discharges. These maximal values are stacked to the matrix through every simulations.Shape=(nbx, nby, nb_chunk*nb_simulations, duration)"""
975        self.spatial_cumulated_maxima_outlets = None
976        """Outlets matrix of the maximum discharges computed on period long of `chunk_size`. If chunk_size is 365, the matrix store the maximum annual discharges. These maximal values are stacked to the matrix through every simulations.Shape=(nb_gauge, nb_chunk*nb_simulations, duration)"""
977        self.spatial_quantile = None
978        """Spatial matrix of the discharges quantile computed from the maximum discharges.Shape=(nbx, nby, duration, return_period)"""
979        self.spatial_quantile_outlets = None
980        """Outlets matrix of the discharges quantile computed from the maximum discharges.Shape=(nb_gauge, duration, return_period)"""
981        # pass

Parent class spatial quantile. Class to store results of the spatial quantile.

spatial_maxima

Spatial matrix of the maximum discharges computed on period long of chunk_size. If chunk_size is 365, the matrix store the maximum annual discharges. Shape=(nbx, nby, nb_chunk, duration)

spatial_maxima_outlets

Outlets matrix of the maximum discharges computed on period long of chunk_size. If chunk_size is 365, the matrix store the maximum annual discharges.Shape=(nb_gauge, nb_chunk, duration)

spatial_cumulated_maxima

Spatial matrix of the maximum discharges computed on period long of chunk_size and accumulated over several simulation. If chunk_size is 365, the matrix store the maximum annual discharges. These maximal values are stacked to the matrix through every simulations.Shape=(nbx, nby, nb_chunk*nb_simulations, duration)

spatial_cumulated_maxima_outlets

Outlets matrix of the maximum discharges computed on period long of chunk_size. If chunk_size is 365, the matrix store the maximum annual discharges. These maximal values are stacked to the matrix through every simulations.Shape=(nb_gauge, nb_chunk*nb_simulations, duration)

spatial_quantile

Spatial matrix of the discharges quantile computed from the maximum discharges.Shape=(nbx, nby, duration, return_period)

spatial_quantile_outlets

Outlets matrix of the discharges quantile computed from the maximum discharges.Shape=(nb_gauge, duration, return_period)

class spatial_stats:
 984class spatial_stats:
 985    """Class for computing the spatial statistics on the discharges."""
 986
 987    def __init__(self, parent_class):
 988        self._parent_class = parent_class
 989        """The parent class in order to access to the results of the smash simulation"""
 990        self.results = spatial_stats_results()
 991        """The results of the spatial statistics"""
 992
 993    def mean(self):
 994        """Compute the spatial mean of the discharges."""
 995        self.results.mean = np.nanmean(
 996            self._parent_class._parent_class.extra_smash_results.q_domain,
 997            axis=2,
 998        )
 999
1000    def minimum(self):
1001        """Compute the spatial minimum of the discharges."""
1002        self.results.min = np.nanmin(
1003            self._parent_class._parent_class.extra_smash_results.q_domain,
1004            axis=2,
1005        )
1006
1007    def maximum(self):
1008        """Compute the spatial maximum of the discharges."""
1009        self.results.max = np.nanmax(
1010            self._parent_class._parent_class.extra_smash_results.q_domain,
1011            axis=2,
1012        )
1013
1014    def median(self):
1015        """Compute the spatial median of the discharges."""
1016        self.results.median = np.quantile(
1017            self._parent_class._parent_class.extra_smash_results.q_domain,
1018            0.5,
1019            axis=2,
1020        )
1021
1022    def q20(self):
1023        """Compute the spatial percentile 20% of the discharges."""
1024        self.results.q20 = np.quantile(
1025            self._parent_class._parent_class.extra_smash_results.q_domain,
1026            0.2,
1027            axis=2,
1028        )
1029
1030    def q80(self):
1031        """Compute the spatial percentile 80% of the discharges."""
1032        self.results.q80 = np.quantile(
1033            self._parent_class._parent_class.extra_smash_results.q_domain,
1034            0.8,
1035            axis=2,
1036        )
1037
1038    def var(self):
1039        """Compute the variance of the discharges for each pixel."""
1040        self.results.var = np.var(
1041            self._parent_class._parent_class.extra_smash_results.q_domain,
1042            axis=2,
1043        )

Class for computing the spatial statistics on the discharges.

spatial_stats(parent_class)
987    def __init__(self, parent_class):
988        self._parent_class = parent_class
989        """The parent class in order to access to the results of the smash simulation"""
990        self.results = spatial_stats_results()
991        """The results of the spatial statistics"""
results

The results of the spatial statistics

def mean(self):
993    def mean(self):
994        """Compute the spatial mean of the discharges."""
995        self.results.mean = np.nanmean(
996            self._parent_class._parent_class.extra_smash_results.q_domain,
997            axis=2,
998        )

Compute the spatial mean of the discharges.

def minimum(self):
1000    def minimum(self):
1001        """Compute the spatial minimum of the discharges."""
1002        self.results.min = np.nanmin(
1003            self._parent_class._parent_class.extra_smash_results.q_domain,
1004            axis=2,
1005        )

Compute the spatial minimum of the discharges.

def maximum(self):
1007    def maximum(self):
1008        """Compute the spatial maximum of the discharges."""
1009        self.results.max = np.nanmax(
1010            self._parent_class._parent_class.extra_smash_results.q_domain,
1011            axis=2,
1012        )

Compute the spatial maximum of the discharges.

def median(self):
1014    def median(self):
1015        """Compute the spatial median of the discharges."""
1016        self.results.median = np.quantile(
1017            self._parent_class._parent_class.extra_smash_results.q_domain,
1018            0.5,
1019            axis=2,
1020        )

Compute the spatial median of the discharges.

def q20(self):
1022    def q20(self):
1023        """Compute the spatial percentile 20% of the discharges."""
1024        self.results.q20 = np.quantile(
1025            self._parent_class._parent_class.extra_smash_results.q_domain,
1026            0.2,
1027            axis=2,
1028        )

Compute the spatial percentile 20% of the discharges.

def q80(self):
1030    def q80(self):
1031        """Compute the spatial percentile 80% of the discharges."""
1032        self.results.q80 = np.quantile(
1033            self._parent_class._parent_class.extra_smash_results.q_domain,
1034            0.8,
1035            axis=2,
1036        )

Compute the spatial percentile 80% of the discharges.

def var(self):
1038    def var(self):
1039        """Compute the variance of the discharges for each pixel."""
1040        self.results.var = np.var(
1041            self._parent_class._parent_class.extra_smash_results.q_domain,
1042            axis=2,
1043        )

Compute the variance of the discharges for each pixel.

class outlets_stats:
1046class outlets_stats:
1047    """Class for computing the statistics on the discharges at every outlets."""
1048
1049    def __init__(self, parent_class):
1050        self._parent_class = parent_class
1051        """The parent class in order to access to the results of the smash simulation"""
1052        self.results_sim = outlets_stats_results()
1053        """The results of the simulated dicharges statistics at every outlets"""
1054        self.results_obs = outlets_stats_results()
1055        """The results of the observed discharges statistics at every outlets"""
1056
1057    def mean(self):
1058        """Compute the mean of the discharges at every outlets."""
1059        qobs = self._parent_class._parent_class.smash.response_data.q
1060        qobs = np.where(qobs < 0, np.nan, qobs)
1061
1062        self.results_sim.mean = np.nanmean(
1063            self._parent_class._parent_class.smash.response.q, axis=1
1064        )
1065        self.results_obs.mean = np.nanmean(qobs, axis=1)
1066
1067    def minimum(self):
1068        """Compute the minimum of the discharges at every outlets."""
1069        qobs = self._parent_class._parent_class.smash.response_data.q
1070        qobs = np.where(qobs < 0, np.nan, qobs)
1071
1072        self.results_sim.min = np.nanmin(
1073            self._parent_class._parent_class.smash.response.q, axis=1
1074        )
1075        self.results_obs.min = np.nanmin(qobs, axis=1)
1076
1077    def maximum(self):
1078        """Compute the maximum of the discharges at every outlets."""
1079        qobs = self._parent_class._parent_class.smash.response_data.q
1080        qobs = np.where(qobs < 0, np.nan, qobs)
1081
1082        self.results_sim.max = np.nanmax(
1083            self._parent_class._parent_class.smash.response.q, axis=1
1084        )
1085        self.results_obs.max = np.nanmax(qobs, axis=1)
1086
1087    def median(self):
1088        """Compute the median of the discharges at every outlets."""
1089        qobs = self._parent_class._parent_class.smash.response_data.q
1090        qobs = np.where(qobs < 0, np.nan, qobs)
1091
1092        self.results_sim.median = np.nanquantile(
1093            self._parent_class._parent_class.smash.response.q,
1094            0.5,
1095            axis=1,
1096        )
1097        self.results_obs.median = np.nanquantile(qobs, 0.5, axis=1)
1098
1099    def q20(self):
1100        """Compute the percentile 20% of the discharges at every outlets."""
1101        qobs = self._parent_class._parent_class.smash.response_data.q
1102        qobs = np.where(qobs < 0, np.nan, qobs)
1103
1104        self.results_sim.q20 = np.nanquantile(
1105            self._parent_class._parent_class.smash.response.q,
1106            0.2,
1107            axis=1,
1108        )
1109        self.results_obs.q20 = np.nanquantile(qobs, 0.2, axis=1)
1110
1111    def q80(self):
1112        """Compute the percentile 80% of the discharges at every outlets."""
1113        qobs = self._parent_class._parent_class.smash.response_data.q
1114        qobs = np.where(qobs < 0, np.nan, qobs)
1115
1116        self.results_sim.q80 = np.nanquantile(
1117            self._parent_class._parent_class.smash.response.q,
1118            0.8,
1119            axis=1,
1120        )
1121        self.results_obs.q80 = np.nanquantile(qobs, 0.8, axis=1)
1122
1123    def var(self):
1124        """Compute the variance of the discharges at every outlets"""
1125        qobs = self._parent_class._parent_class.smash.response_data.q
1126        qobs = np.where(qobs < 0, np.nan, qobs)
1127
1128        self.results_sim.var = np.var(
1129            self._parent_class._parent_class.smash.response.q, axis=1
1130        )
1131        self.results_obs.var = np.var(qobs, axis=1)

Class for computing the statistics on the discharges at every outlets.

outlets_stats(parent_class)
1049    def __init__(self, parent_class):
1050        self._parent_class = parent_class
1051        """The parent class in order to access to the results of the smash simulation"""
1052        self.results_sim = outlets_stats_results()
1053        """The results of the simulated dicharges statistics at every outlets"""
1054        self.results_obs = outlets_stats_results()
1055        """The results of the observed discharges statistics at every outlets"""
results_sim

The results of the simulated dicharges statistics at every outlets

results_obs

The results of the observed discharges statistics at every outlets

def mean(self):
1057    def mean(self):
1058        """Compute the mean of the discharges at every outlets."""
1059        qobs = self._parent_class._parent_class.smash.response_data.q
1060        qobs = np.where(qobs < 0, np.nan, qobs)
1061
1062        self.results_sim.mean = np.nanmean(
1063            self._parent_class._parent_class.smash.response.q, axis=1
1064        )
1065        self.results_obs.mean = np.nanmean(qobs, axis=1)

Compute the mean of the discharges at every outlets.

def minimum(self):
1067    def minimum(self):
1068        """Compute the minimum of the discharges at every outlets."""
1069        qobs = self._parent_class._parent_class.smash.response_data.q
1070        qobs = np.where(qobs < 0, np.nan, qobs)
1071
1072        self.results_sim.min = np.nanmin(
1073            self._parent_class._parent_class.smash.response.q, axis=1
1074        )
1075        self.results_obs.min = np.nanmin(qobs, axis=1)

Compute the minimum of the discharges at every outlets.

def maximum(self):
1077    def maximum(self):
1078        """Compute the maximum of the discharges at every outlets."""
1079        qobs = self._parent_class._parent_class.smash.response_data.q
1080        qobs = np.where(qobs < 0, np.nan, qobs)
1081
1082        self.results_sim.max = np.nanmax(
1083            self._parent_class._parent_class.smash.response.q, axis=1
1084        )
1085        self.results_obs.max = np.nanmax(qobs, axis=1)

Compute the maximum of the discharges at every outlets.

def median(self):
1087    def median(self):
1088        """Compute the median of the discharges at every outlets."""
1089        qobs = self._parent_class._parent_class.smash.response_data.q
1090        qobs = np.where(qobs < 0, np.nan, qobs)
1091
1092        self.results_sim.median = np.nanquantile(
1093            self._parent_class._parent_class.smash.response.q,
1094            0.5,
1095            axis=1,
1096        )
1097        self.results_obs.median = np.nanquantile(qobs, 0.5, axis=1)

Compute the median of the discharges at every outlets.

def q20(self):
1099    def q20(self):
1100        """Compute the percentile 20% of the discharges at every outlets."""
1101        qobs = self._parent_class._parent_class.smash.response_data.q
1102        qobs = np.where(qobs < 0, np.nan, qobs)
1103
1104        self.results_sim.q20 = np.nanquantile(
1105            self._parent_class._parent_class.smash.response.q,
1106            0.2,
1107            axis=1,
1108        )
1109        self.results_obs.q20 = np.nanquantile(qobs, 0.2, axis=1)

Compute the percentile 20% of the discharges at every outlets.

def q80(self):
1111    def q80(self):
1112        """Compute the percentile 80% of the discharges at every outlets."""
1113        qobs = self._parent_class._parent_class.smash.response_data.q
1114        qobs = np.where(qobs < 0, np.nan, qobs)
1115
1116        self.results_sim.q80 = np.nanquantile(
1117            self._parent_class._parent_class.smash.response.q,
1118            0.8,
1119            axis=1,
1120        )
1121        self.results_obs.q80 = np.nanquantile(qobs, 0.8, axis=1)

Compute the percentile 80% of the discharges at every outlets.

def var(self):
1123    def var(self):
1124        """Compute the variance of the discharges at every outlets"""
1125        qobs = self._parent_class._parent_class.smash.response_data.q
1126        qobs = np.where(qobs < 0, np.nan, qobs)
1127
1128        self.results_sim.var = np.var(
1129            self._parent_class._parent_class.smash.response.q, axis=1
1130        )
1131        self.results_obs.var = np.var(qobs, axis=1)

Compute the variance of the discharges at every outlets

class misfit_stats:
1134class misfit_stats:
1135    """Class for computing the misfit criterium on the discharges at every outlets."""
1136
1137    def __init__(self, parent_class):
1138        self._parent_class = parent_class
1139        """The parent class in order to access to the results of the smash simulation"""
1140        self.results = misfit_results()
1141        """The results of the misfit criterium at every outlets"""
1142
1143    def _update_column(self, column, outlets_name):
1144        if len(outlets_name) > 0:
1145            column = tools.array_isin(
1146                self._parent_class._parent_class.smash.mesh.code,
1147                np.array(outlets_name),
1148            )
1149
1150        if len(column) == 0:
1151            column = list(
1152                range(
1153                    0,
1154                    self._parent_class._parent_class.smash.response.q.shape[0],
1155                )
1156            )
1157
1158        return column
1159
1160    def mse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1161        """
1162        Compute the mse between the oberved and simulated discharges.
1163        :param nodata: The no data value, defaults to -99.0
1164        :type nodata: float, optional
1165        :param column: the column nuber on which we want to compute the misfit,
1166         defaults to []
1167        :type column: list, optional
1168        :param outlets_name: The names of the outlets for which we want to compute
1169         the misfit, defaults to []
1170        :type outlets_name: list, optional
1171
1172        """
1173
1174        column = self._update_column(column, outlets_name)
1175
1176        self.results.mse = stats.mse(
1177            self._parent_class._parent_class.smash.response_data.q[column, :],
1178            self._parent_class._parent_class.smash.response.q[column, :],
1179            nodata=-99.0,
1180            t_axis=1,
1181        )
1182
1183    def sm_mse(self, column: list = [], outlets_name: list = []):
1184        """
1185        Compute the mse between the oberved and simulated discharges.
1186        :param nodata: The no data value, defaults to -99.0
1187        :type nodata: float, optional
1188        :param column: the column nuber on which we want to compute the misfit,
1189         defaults to []
1190        :type column: list, optional
1191        :param outlets_name: The names of the outlets for which we want to compute
1192         the misfit, defaults to []
1193        :type outlets_name: list, optional
1194
1195        """
1196
1197        column = self._update_column(column, outlets_name)
1198
1199        metric = np.zeros(shape=(len(column))) + np.nan
1200
1201        for i in range(len(column)):
1202            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1203            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1204
1205            metric[i] = smash_metrics.mse(
1206                qobs,
1207                qsim,
1208            )
1209
1210        self.results.mse = metric
1211
1212    def rmse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1213        """
1214        Compute the rmse between the oberved and simulated discharges.
1215        :param nodata: The no data value, defaults to -99.0
1216        :type nodata: float, optional
1217        :param column: the column nuber on which we want to compute the misfit,
1218         defaults to []
1219        :type column: list, optional
1220        :param outlets_name: The names of the outlets for which we want to compute
1221         the misfit, defaults to []
1222        :type outlets_name: list, optional
1223
1224        """
1225
1226        if len(outlets_name) > 0:
1227            column = tools.array_isin(
1228                self._parent_class._parent_class.smash.mesh.code,
1229                np.array(outlets_name),
1230            )
1231
1232        if len(column) == 0:
1233            column = list(
1234                range(
1235                    0,
1236                    self._parent_class._parent_class.smash.response.q.shape[0],
1237                )
1238            )
1239
1240        self.results.rmse = stats.rmse(
1241            self._parent_class._parent_class.smash.response_data.q[column, :],
1242            self._parent_class._parent_class.smash.response.q[column, :],
1243            nodata=-99.0,
1244            t_axis=1,
1245        )
1246
1247    def sm_rmse(self, column: list = [], outlets_name: list = []):
1248        """
1249        Compute the rmse between the oberved and simulated discharges.
1250        :param nodata: The no data value, defaults to -99.0
1251        :type nodata: float, optional
1252        :param column: the column nuber on which we want to compute the misfit,
1253         defaults to []
1254        :type column: list, optional
1255        :param outlets_name: The names of the outlets for which we want to compute
1256         the misfit, defaults to []
1257        :type outlets_name: list, optional
1258
1259        """
1260
1261        column = self._update_column(column, outlets_name)
1262
1263        metric = np.zeros(shape=(len(column))) + np.nan
1264
1265        for i in range(len(column)):
1266            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1267            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1268
1269            metric[i] = smash_metrics.rmse(
1270                qobs,
1271                qsim,
1272            )
1273
1274        self.results.rmse = metric
1275
1276    def nrmse(self, nodata=-99.0, column=[], outlets_name=[]):
1277        """
1278        Compute the nrmse between the oberved and simulated discharges.
1279        :param nodata: The no data value, defaults to -99.0
1280        :type nodata: float, optional
1281        :param column: the column nuber on which we want to compute the misfit,
1282         defaults to []
1283        :type column: list, optional
1284        :param outlets_name: The names of the outlets for which we want to compute
1285         the misfit, defaults to []
1286        :type outlets_name: list, optional
1287
1288        """
1289
1290        if len(outlets_name) > 0:
1291            column = tools.array_isin(
1292                self._parent_class._parent_class.smash.mesh.code,
1293                np.array(outlets_name),
1294            )
1295
1296        if len(column) == 0:
1297            column = list(
1298                range(
1299                    0,
1300                    self._parent_class._parent_class.smash.response.q.shape[0],
1301                )
1302            )
1303
1304        self.results.nrmse = stats.nrmse(
1305            self._parent_class._parent_class.smash.response_data.q[column, :],
1306            self._parent_class._parent_class.smash.response.q[column, :],
1307            nodata=-99.0,
1308            t_axis=1,
1309        )
1310
1311    def sm_nrmse(self, column: list = [], outlets_name: list = []):
1312        """
1313        Compute the nrmse between the oberved and simulated discharges.
1314        :param nodata: The no data value, defaults to -99.0
1315        :type nodata: float, optional
1316        :param column: the column nuber on which we want to compute the misfit,
1317         defaults to []
1318        :type column: list, optional
1319        :param outlets_name: The names of the outlets for which we want to compute
1320         the misfit, defaults to []
1321        :type outlets_name: list, optional
1322
1323        """
1324
1325        column = self._update_column(column, outlets_name)
1326
1327        metric = np.zeros(shape=(len(column))) + np.nan
1328
1329        for i in range(len(column)):
1330            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1331            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1332
1333            mean_qobs = np.mean(qobs)
1334            metric[i] = smash_metrics.rmse(qobs, qsim) / mean_qobs
1335
1336        self.results.nrmse = metric
1337
1338    def se(self, nodata=-99.0, column=[], outlets_name=[]):
1339        """
1340        Compute the se between the oberved and simulated discharges.
1341        :param nodata: The no data value, defaults to -99.0
1342        :type nodata: float, optional
1343        :param column: the column nuber on which we want to compute the misfit,
1344         defaults to []
1345        :type column: list, optional
1346        :param outlets_name: The names of the outlets for which we want to compute
1347         the misfit, defaults to []
1348        :type outlets_name: list, optional
1349
1350        """
1351        column = self._update_column(column, outlets_name)
1352
1353        self.results.se = stats.se(
1354            self._parent_class._parent_class.smash.response_data.q[column, :],
1355            self._parent_class._parent_class.smash.response.q[column, :],
1356            nodata=-99.0,
1357            t_axis=1,
1358        )
1359
1360    def sm_se(self, column: list = [], outlets_name: list = []):
1361        """
1362        Compute the se between the oberved and simulated discharges.
1363        :param nodata: The no data value, defaults to -99.0
1364        :type nodata: float, optional
1365        :param column: the column nuber on which we want to compute the misfit,
1366         defaults to []
1367        :type column: list, optional
1368        :param outlets_name: The names of the outlets for which we want to compute
1369         the misfit, defaults to []
1370        :type outlets_name: list, optional
1371
1372        """
1373        column = self._update_column(column, outlets_name)
1374        metric = np.zeros(shape=(len(column))) + np.nan
1375
1376        for i in range(len(column)):
1377            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1378            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1379
1380            if not np.all(qobs < 0):
1381                metric[i] = smash_metrics.se(
1382                    qobs,
1383                    qsim,
1384                )
1385
1386        self.results.se = metric
1387
1388    def mae(self, nodata=-99.0, column=[], outlets_name=[]):
1389        """
1390        Compute the mae between the oberved and simulated discharges.
1391        :param nodata: The no data value, defaults to -99.0
1392        :type nodata: float, optional
1393        :param column: the column nuber on which we want to compute the misfit,
1394         defaults to []
1395        :type column: list, optional
1396        :param outlets_name: The names of the outlets for which we want to compute
1397         the misfit, defaults to []
1398        :type outlets_name: list, optional
1399
1400        """
1401        column = self._update_column(column, outlets_name)
1402
1403        self.results.mae = stats.mae(
1404            self._parent_class._parent_class.smash.response_data.q[column, :],
1405            self._parent_class._parent_class.smash.response.q[column, :],
1406            nodata=-99.0,
1407            t_axis=1,
1408        )
1409
1410    def sm_mae(self, column=[], outlets_name=[]):
1411        """
1412        Compute the mae between the oberved and simulated discharges.
1413        :param nodata: The no data value, defaults to -99.0
1414        :type nodata: float, optional
1415        :param column: the column nuber on which we want to compute the misfit,
1416         defaults to []
1417        :type column: list, optional
1418        :param outlets_name: The names of the outlets for which we want to compute
1419         the misfit, defaults to []
1420        :type outlets_name: list, optional
1421
1422        """
1423        column = self._update_column(column, outlets_name)
1424        metric = np.zeros(shape=(len(column))) + np.nan
1425
1426        for i in range(len(column)):
1427            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1428            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1429
1430            metric[i] = smash_metrics.mae(
1431                qobs,
1432                qsim,
1433            )
1434
1435        self.results.mae = metric
1436
1437    def mape(self, nodata=-99.0, column=[], outlets_name=[]):
1438        """
1439        Compute the mape between the oberved and simulated discharges.
1440        :param nodata: The no data value, defaults to -99.0
1441        :type nodata: float, optional
1442        :param column: the column nuber on which we want to compute the misfit,
1443         defaults to []
1444        :type column: list, optional
1445        :param outlets_name: The names of the outlets for which we want to compute
1446         the misfit, defaults to []
1447        :type outlets_name: list, optional
1448
1449        """
1450        column = self._update_column(column, outlets_name)
1451
1452        self.results.mape = stats.mape(
1453            self._parent_class._parent_class.smash.response_data.q[column, :],
1454            self._parent_class._parent_class.smash.response.q[column, :],
1455            nodata=-99.0,
1456            t_axis=1,
1457        )
1458
1459    def sm_mape(self, column=[], outlets_name=[]):
1460        """
1461        Compute the mape between the oberved and simulated discharges.
1462        :param nodata: The no data value, defaults to -99.0
1463        :type nodata: float, optional
1464        :param column: the column nuber on which we want to compute the misfit,
1465         defaults to []
1466        :type column: list, optional
1467        :param outlets_name: The names of the outlets for which we want to compute
1468         the misfit, defaults to []
1469        :type outlets_name: list, optional
1470
1471        """
1472        column = self._update_column(column, outlets_name)
1473        metric = np.zeros(shape=(len(column))) + np.nan
1474
1475        for i in range(len(column)):
1476            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1477            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1478
1479            metric[i] = smash_metrics.mape(
1480                qobs,
1481                qsim,
1482            )
1483
1484        self.results.mape = metric
1485
1486    def lgrm(self, nodata=-99.0, column=[], outlets_name=[]):
1487        """
1488        Compute the lgrm between the oberved and simulated discharges.
1489        :param nodata: The no data value, defaults to -99.0
1490        :type nodata: float, optional
1491        :param column: the column nuber on which we want to compute the misfit,
1492         defaults to []
1493        :type column: list, optional
1494        :param outlets_name: The names of the outlets for which we want to compute
1495         the misfit, defaults to []
1496        :type outlets_name: list, optional
1497
1498        """
1499        column = self._update_column(column, outlets_name)
1500
1501        self.results.lgrm = stats.lgrm(
1502            self._parent_class._parent_class.smash.response_data.q[column, :],
1503            self._parent_class._parent_class.smash.response.q[column, :],
1504            nodata=-99.0,
1505            t_axis=1,
1506        )
1507
1508    def sm_lgrm(self, column=[], outlets_name=[]):
1509        """
1510        Compute the lgrm between the oberved and simulated discharges.
1511        :param nodata: The no data value, defaults to -99.0
1512        :type nodata: float, optional
1513        :param column: the column nuber on which we want to compute the misfit,
1514         defaults to []
1515        :type column: list, optional
1516        :param outlets_name: The names of the outlets for which we want to compute
1517         the misfit, defaults to []
1518        :type outlets_name: list, optional
1519
1520        """
1521        column = self._update_column(column, outlets_name)
1522        metric = np.zeros(shape=(len(column))) + np.nan
1523
1524        for i in range(len(column)):
1525            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1526            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1527
1528            if not np.all(qobs < 0):
1529                metric[i] = smash_metrics.lgrm(
1530                    qobs,
1531                    qsim,
1532                )
1533
1534        self.results.lgrm = metric
1535
1536    def nse(self, nodata=-99.0, column=[], outlets_name=[]):
1537        """
1538        Compute the nse between the oberved and simulated discharges.
1539        :param nodata: The no data value, defaults to -99.0
1540        :type nodata: float, optional
1541        :param column: the column nuber on which we want to compute the misfit,
1542         defaults to []
1543        :type column: list, optional
1544        :param outlets_name: The names of the outlets for which we want to compute
1545         the misfit, defaults to []
1546        :type outlets_name: list, optional
1547
1548        """
1549        column = self._update_column(column, outlets_name)
1550
1551        self.results.nse = stats.nse(
1552            self._parent_class._parent_class.smash.response_data.q[column, :],
1553            self._parent_class._parent_class.smash.response.q[column, :],
1554            nodata=-99.0,
1555            t_axis=1,
1556        )
1557
1558    def sm_nse(self, column=[], outlets_name=[]):
1559        """
1560        Compute the nse between the oberved and simulated discharges.
1561        :param nodata: The no data value, defaults to -99.0
1562        :type nodata: float, optional
1563        :param column: the column nuber on which we want to compute the misfit,
1564         defaults to []
1565        :type column: list, optional
1566        :param outlets_name: The names of the outlets for which we want to compute
1567         the misfit, defaults to []
1568        :type outlets_name: list, optional
1569
1570        """
1571        column = self._update_column(column, outlets_name)
1572        metric = np.zeros(shape=(len(column))) + np.nan
1573
1574        for i in range(len(column)):
1575            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1576            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1577
1578            metric[i] = smash_metrics.nse(
1579                qobs,
1580                qsim,
1581            )
1582
1583        self.results.nse = metric
1584
1585    def nnse(self, nodata=-99.0, column=[], outlets_name=[]):
1586        """
1587        Compute the nnse between the oberved and simulated discharges.
1588        :param nodata: The no data value, defaults to -99.0
1589        :type nodata: float, optional
1590        :param column: the column nuber on which we want to compute the misfit,
1591         defaults to []
1592        :type column: list, optional
1593        :param outlets_name: The names of the outlets for which we want to compute
1594         the misfit, defaults to []
1595        :type outlets_name: list, optional
1596
1597        """
1598        column = self._update_column(column, outlets_name)
1599
1600        self.results.nnse = stats.nnse(
1601            self._parent_class._parent_class.smash.response_data.q[column, :],
1602            self._parent_class._parent_class.smash.response.q[column, :],
1603            nodata=-99.0,
1604            t_axis=1,
1605        )
1606
1607    def sm_nnse(self, column=[], outlets_name=[]):
1608        """
1609        Compute the nnse between the oberved and simulated discharges.
1610        :param nodata: The no data value, defaults to -99.0
1611        :type nodata: float, optional
1612        :param column: the column nuber on which we want to compute the misfit,
1613         defaults to []
1614        :type column: list, optional
1615        :param outlets_name: The names of the outlets for which we want to compute
1616         the misfit, defaults to []
1617        :type outlets_name: list, optional
1618
1619        """
1620        column = self._update_column(column, outlets_name)
1621        metric = np.zeros(shape=(len(column))) + np.nan
1622
1623        for i in range(len(column)):
1624            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1625            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1626
1627            metric[i] = smash_metrics.nnse(
1628                qobs,
1629                qsim,
1630            )
1631
1632        self.results.nnse = metric
1633
1634    def kge(self, nodata=-99.0, column=[], outlets_name=[]):
1635        """
1636        Compute the kge between the oberved and simulated discharges.
1637        :param nodata: The no data value, defaults to -99.0
1638        :type nodata: float, optional
1639        :param column: the column nuber on which we want to compute the misfit,
1640         defaults to []
1641        :type column: list, optional
1642        :param outlets_name: The names of the outlets for which we want to compute
1643         the misfit, defaults to []
1644        :type outlets_name: list, optional
1645
1646        """
1647        column = self._update_column(column, outlets_name)
1648
1649        self.results.kge = stats.kge(
1650            self._parent_class._parent_class.smash.response_data.q[column, :],
1651            self._parent_class._parent_class.smash.response.q[column, :],
1652            nodata=-99.0,
1653            t_axis=1,
1654        )
1655
1656    def sm_kge(self, column=[], outlets_name=[]):
1657        """
1658        Compute the kge between the oberved and simulated discharges.
1659        :param nodata: The no data value, defaults to -99.0
1660        :type nodata: float, optional
1661        :param column: the column nuber on which we want to compute the misfit,
1662         defaults to []
1663        :type column: list, optional
1664        :param outlets_name: The names of the outlets for which we want to compute
1665         the misfit, defaults to []
1666        :type outlets_name: list, optional
1667
1668        """
1669        column = self._update_column(column, outlets_name)
1670        metric = np.zeros(shape=(len(column))) + np.nan
1671
1672        for i in range(len(column)):
1673            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1674            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1675
1676            metric[i] = smash_metrics.kge(
1677                qobs,
1678                qsim,
1679            )
1680
1681        self.results.kge = metric
1682
1683    def pearson(self, nodata=-99.0, column=[], outlets_name=[]):
1684        """
1685        Compute the pearson coefficient between the oberved and simulated discharges.
1686        :param nodata: The no data value, defaults to -99.0
1687        :type nodata: float, optional
1688        :param column: the column nuber on which we want to compute the misfit,
1689         defaults to []
1690        :type column: list, optional
1691        :param outlets_name: The names of the outlets for which we want to compute
1692         the misfit, defaults to []
1693        :type outlets_name: list, optional
1694
1695        """
1696        column = self._update_column(column, outlets_name)
1697
1698        self.results.pearson = stats.pearson(
1699            self._parent_class._parent_class.smash.response_data.q[column, :],
1700            self._parent_class._parent_class.smash.response.q[column, :],
1701            nodata=-99.0,
1702            t_axis=1,
1703        )

Class for computing the misfit criterium on the discharges at every outlets.

misfit_stats(parent_class)
1137    def __init__(self, parent_class):
1138        self._parent_class = parent_class
1139        """The parent class in order to access to the results of the smash simulation"""
1140        self.results = misfit_results()
1141        """The results of the misfit criterium at every outlets"""
results

The results of the misfit criterium at every outlets

def mse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1160    def mse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1161        """
1162        Compute the mse between the oberved and simulated discharges.
1163        :param nodata: The no data value, defaults to -99.0
1164        :type nodata: float, optional
1165        :param column: the column nuber on which we want to compute the misfit,
1166         defaults to []
1167        :type column: list, optional
1168        :param outlets_name: The names of the outlets for which we want to compute
1169         the misfit, defaults to []
1170        :type outlets_name: list, optional
1171
1172        """
1173
1174        column = self._update_column(column, outlets_name)
1175
1176        self.results.mse = stats.mse(
1177            self._parent_class._parent_class.smash.response_data.q[column, :],
1178            self._parent_class._parent_class.smash.response.q[column, :],
1179            nodata=-99.0,
1180            t_axis=1,
1181        )

Compute the mse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_mse(self, column: list = [], outlets_name: list = []):
1183    def sm_mse(self, column: list = [], outlets_name: list = []):
1184        """
1185        Compute the mse between the oberved and simulated discharges.
1186        :param nodata: The no data value, defaults to -99.0
1187        :type nodata: float, optional
1188        :param column: the column nuber on which we want to compute the misfit,
1189         defaults to []
1190        :type column: list, optional
1191        :param outlets_name: The names of the outlets for which we want to compute
1192         the misfit, defaults to []
1193        :type outlets_name: list, optional
1194
1195        """
1196
1197        column = self._update_column(column, outlets_name)
1198
1199        metric = np.zeros(shape=(len(column))) + np.nan
1200
1201        for i in range(len(column)):
1202            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1203            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1204
1205            metric[i] = smash_metrics.mse(
1206                qobs,
1207                qsim,
1208            )
1209
1210        self.results.mse = metric

Compute the mse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def rmse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1212    def rmse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1213        """
1214        Compute the rmse between the oberved and simulated discharges.
1215        :param nodata: The no data value, defaults to -99.0
1216        :type nodata: float, optional
1217        :param column: the column nuber on which we want to compute the misfit,
1218         defaults to []
1219        :type column: list, optional
1220        :param outlets_name: The names of the outlets for which we want to compute
1221         the misfit, defaults to []
1222        :type outlets_name: list, optional
1223
1224        """
1225
1226        if len(outlets_name) > 0:
1227            column = tools.array_isin(
1228                self._parent_class._parent_class.smash.mesh.code,
1229                np.array(outlets_name),
1230            )
1231
1232        if len(column) == 0:
1233            column = list(
1234                range(
1235                    0,
1236                    self._parent_class._parent_class.smash.response.q.shape[0],
1237                )
1238            )
1239
1240        self.results.rmse = stats.rmse(
1241            self._parent_class._parent_class.smash.response_data.q[column, :],
1242            self._parent_class._parent_class.smash.response.q[column, :],
1243            nodata=-99.0,
1244            t_axis=1,
1245        )

Compute the rmse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_rmse(self, column: list = [], outlets_name: list = []):
1247    def sm_rmse(self, column: list = [], outlets_name: list = []):
1248        """
1249        Compute the rmse between the oberved and simulated discharges.
1250        :param nodata: The no data value, defaults to -99.0
1251        :type nodata: float, optional
1252        :param column: the column nuber on which we want to compute the misfit,
1253         defaults to []
1254        :type column: list, optional
1255        :param outlets_name: The names of the outlets for which we want to compute
1256         the misfit, defaults to []
1257        :type outlets_name: list, optional
1258
1259        """
1260
1261        column = self._update_column(column, outlets_name)
1262
1263        metric = np.zeros(shape=(len(column))) + np.nan
1264
1265        for i in range(len(column)):
1266            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1267            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1268
1269            metric[i] = smash_metrics.rmse(
1270                qobs,
1271                qsim,
1272            )
1273
1274        self.results.rmse = metric

Compute the rmse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def nrmse(self, nodata=-99.0, column=[], outlets_name=[]):
1276    def nrmse(self, nodata=-99.0, column=[], outlets_name=[]):
1277        """
1278        Compute the nrmse between the oberved and simulated discharges.
1279        :param nodata: The no data value, defaults to -99.0
1280        :type nodata: float, optional
1281        :param column: the column nuber on which we want to compute the misfit,
1282         defaults to []
1283        :type column: list, optional
1284        :param outlets_name: The names of the outlets for which we want to compute
1285         the misfit, defaults to []
1286        :type outlets_name: list, optional
1287
1288        """
1289
1290        if len(outlets_name) > 0:
1291            column = tools.array_isin(
1292                self._parent_class._parent_class.smash.mesh.code,
1293                np.array(outlets_name),
1294            )
1295
1296        if len(column) == 0:
1297            column = list(
1298                range(
1299                    0,
1300                    self._parent_class._parent_class.smash.response.q.shape[0],
1301                )
1302            )
1303
1304        self.results.nrmse = stats.nrmse(
1305            self._parent_class._parent_class.smash.response_data.q[column, :],
1306            self._parent_class._parent_class.smash.response.q[column, :],
1307            nodata=-99.0,
1308            t_axis=1,
1309        )

Compute the nrmse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_nrmse(self, column: list = [], outlets_name: list = []):
1311    def sm_nrmse(self, column: list = [], outlets_name: list = []):
1312        """
1313        Compute the nrmse between the oberved and simulated discharges.
1314        :param nodata: The no data value, defaults to -99.0
1315        :type nodata: float, optional
1316        :param column: the column nuber on which we want to compute the misfit,
1317         defaults to []
1318        :type column: list, optional
1319        :param outlets_name: The names of the outlets for which we want to compute
1320         the misfit, defaults to []
1321        :type outlets_name: list, optional
1322
1323        """
1324
1325        column = self._update_column(column, outlets_name)
1326
1327        metric = np.zeros(shape=(len(column))) + np.nan
1328
1329        for i in range(len(column)):
1330            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1331            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1332
1333            mean_qobs = np.mean(qobs)
1334            metric[i] = smash_metrics.rmse(qobs, qsim) / mean_qobs
1335
1336        self.results.nrmse = metric

Compute the nrmse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def se(self, nodata=-99.0, column=[], outlets_name=[]):
1338    def se(self, nodata=-99.0, column=[], outlets_name=[]):
1339        """
1340        Compute the se between the oberved and simulated discharges.
1341        :param nodata: The no data value, defaults to -99.0
1342        :type nodata: float, optional
1343        :param column: the column nuber on which we want to compute the misfit,
1344         defaults to []
1345        :type column: list, optional
1346        :param outlets_name: The names of the outlets for which we want to compute
1347         the misfit, defaults to []
1348        :type outlets_name: list, optional
1349
1350        """
1351        column = self._update_column(column, outlets_name)
1352
1353        self.results.se = stats.se(
1354            self._parent_class._parent_class.smash.response_data.q[column, :],
1355            self._parent_class._parent_class.smash.response.q[column, :],
1356            nodata=-99.0,
1357            t_axis=1,
1358        )

Compute the se between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_se(self, column: list = [], outlets_name: list = []):
1360    def sm_se(self, column: list = [], outlets_name: list = []):
1361        """
1362        Compute the se between the oberved and simulated discharges.
1363        :param nodata: The no data value, defaults to -99.0
1364        :type nodata: float, optional
1365        :param column: the column nuber on which we want to compute the misfit,
1366         defaults to []
1367        :type column: list, optional
1368        :param outlets_name: The names of the outlets for which we want to compute
1369         the misfit, defaults to []
1370        :type outlets_name: list, optional
1371
1372        """
1373        column = self._update_column(column, outlets_name)
1374        metric = np.zeros(shape=(len(column))) + np.nan
1375
1376        for i in range(len(column)):
1377            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1378            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1379
1380            if not np.all(qobs < 0):
1381                metric[i] = smash_metrics.se(
1382                    qobs,
1383                    qsim,
1384                )
1385
1386        self.results.se = metric

Compute the se between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def mae(self, nodata=-99.0, column=[], outlets_name=[]):
1388    def mae(self, nodata=-99.0, column=[], outlets_name=[]):
1389        """
1390        Compute the mae between the oberved and simulated discharges.
1391        :param nodata: The no data value, defaults to -99.0
1392        :type nodata: float, optional
1393        :param column: the column nuber on which we want to compute the misfit,
1394         defaults to []
1395        :type column: list, optional
1396        :param outlets_name: The names of the outlets for which we want to compute
1397         the misfit, defaults to []
1398        :type outlets_name: list, optional
1399
1400        """
1401        column = self._update_column(column, outlets_name)
1402
1403        self.results.mae = stats.mae(
1404            self._parent_class._parent_class.smash.response_data.q[column, :],
1405            self._parent_class._parent_class.smash.response.q[column, :],
1406            nodata=-99.0,
1407            t_axis=1,
1408        )

Compute the mae between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_mae(self, column=[], outlets_name=[]):
1410    def sm_mae(self, column=[], outlets_name=[]):
1411        """
1412        Compute the mae between the oberved and simulated discharges.
1413        :param nodata: The no data value, defaults to -99.0
1414        :type nodata: float, optional
1415        :param column: the column nuber on which we want to compute the misfit,
1416         defaults to []
1417        :type column: list, optional
1418        :param outlets_name: The names of the outlets for which we want to compute
1419         the misfit, defaults to []
1420        :type outlets_name: list, optional
1421
1422        """
1423        column = self._update_column(column, outlets_name)
1424        metric = np.zeros(shape=(len(column))) + np.nan
1425
1426        for i in range(len(column)):
1427            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1428            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1429
1430            metric[i] = smash_metrics.mae(
1431                qobs,
1432                qsim,
1433            )
1434
1435        self.results.mae = metric

Compute the mae between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def mape(self, nodata=-99.0, column=[], outlets_name=[]):
1437    def mape(self, nodata=-99.0, column=[], outlets_name=[]):
1438        """
1439        Compute the mape between the oberved and simulated discharges.
1440        :param nodata: The no data value, defaults to -99.0
1441        :type nodata: float, optional
1442        :param column: the column nuber on which we want to compute the misfit,
1443         defaults to []
1444        :type column: list, optional
1445        :param outlets_name: The names of the outlets for which we want to compute
1446         the misfit, defaults to []
1447        :type outlets_name: list, optional
1448
1449        """
1450        column = self._update_column(column, outlets_name)
1451
1452        self.results.mape = stats.mape(
1453            self._parent_class._parent_class.smash.response_data.q[column, :],
1454            self._parent_class._parent_class.smash.response.q[column, :],
1455            nodata=-99.0,
1456            t_axis=1,
1457        )

Compute the mape between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_mape(self, column=[], outlets_name=[]):
1459    def sm_mape(self, column=[], outlets_name=[]):
1460        """
1461        Compute the mape between the oberved and simulated discharges.
1462        :param nodata: The no data value, defaults to -99.0
1463        :type nodata: float, optional
1464        :param column: the column nuber on which we want to compute the misfit,
1465         defaults to []
1466        :type column: list, optional
1467        :param outlets_name: The names of the outlets for which we want to compute
1468         the misfit, defaults to []
1469        :type outlets_name: list, optional
1470
1471        """
1472        column = self._update_column(column, outlets_name)
1473        metric = np.zeros(shape=(len(column))) + np.nan
1474
1475        for i in range(len(column)):
1476            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1477            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1478
1479            metric[i] = smash_metrics.mape(
1480                qobs,
1481                qsim,
1482            )
1483
1484        self.results.mape = metric

Compute the mape between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def lgrm(self, nodata=-99.0, column=[], outlets_name=[]):
1486    def lgrm(self, nodata=-99.0, column=[], outlets_name=[]):
1487        """
1488        Compute the lgrm between the oberved and simulated discharges.
1489        :param nodata: The no data value, defaults to -99.0
1490        :type nodata: float, optional
1491        :param column: the column nuber on which we want to compute the misfit,
1492         defaults to []
1493        :type column: list, optional
1494        :param outlets_name: The names of the outlets for which we want to compute
1495         the misfit, defaults to []
1496        :type outlets_name: list, optional
1497
1498        """
1499        column = self._update_column(column, outlets_name)
1500
1501        self.results.lgrm = stats.lgrm(
1502            self._parent_class._parent_class.smash.response_data.q[column, :],
1503            self._parent_class._parent_class.smash.response.q[column, :],
1504            nodata=-99.0,
1505            t_axis=1,
1506        )

Compute the lgrm between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_lgrm(self, column=[], outlets_name=[]):
1508    def sm_lgrm(self, column=[], outlets_name=[]):
1509        """
1510        Compute the lgrm between the oberved and simulated discharges.
1511        :param nodata: The no data value, defaults to -99.0
1512        :type nodata: float, optional
1513        :param column: the column nuber on which we want to compute the misfit,
1514         defaults to []
1515        :type column: list, optional
1516        :param outlets_name: The names of the outlets for which we want to compute
1517         the misfit, defaults to []
1518        :type outlets_name: list, optional
1519
1520        """
1521        column = self._update_column(column, outlets_name)
1522        metric = np.zeros(shape=(len(column))) + np.nan
1523
1524        for i in range(len(column)):
1525            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1526            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1527
1528            if not np.all(qobs < 0):
1529                metric[i] = smash_metrics.lgrm(
1530                    qobs,
1531                    qsim,
1532                )
1533
1534        self.results.lgrm = metric

Compute the lgrm between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def nse(self, nodata=-99.0, column=[], outlets_name=[]):
1536    def nse(self, nodata=-99.0, column=[], outlets_name=[]):
1537        """
1538        Compute the nse between the oberved and simulated discharges.
1539        :param nodata: The no data value, defaults to -99.0
1540        :type nodata: float, optional
1541        :param column: the column nuber on which we want to compute the misfit,
1542         defaults to []
1543        :type column: list, optional
1544        :param outlets_name: The names of the outlets for which we want to compute
1545         the misfit, defaults to []
1546        :type outlets_name: list, optional
1547
1548        """
1549        column = self._update_column(column, outlets_name)
1550
1551        self.results.nse = stats.nse(
1552            self._parent_class._parent_class.smash.response_data.q[column, :],
1553            self._parent_class._parent_class.smash.response.q[column, :],
1554            nodata=-99.0,
1555            t_axis=1,
1556        )

Compute the nse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_nse(self, column=[], outlets_name=[]):
1558    def sm_nse(self, column=[], outlets_name=[]):
1559        """
1560        Compute the nse between the oberved and simulated discharges.
1561        :param nodata: The no data value, defaults to -99.0
1562        :type nodata: float, optional
1563        :param column: the column nuber on which we want to compute the misfit,
1564         defaults to []
1565        :type column: list, optional
1566        :param outlets_name: The names of the outlets for which we want to compute
1567         the misfit, defaults to []
1568        :type outlets_name: list, optional
1569
1570        """
1571        column = self._update_column(column, outlets_name)
1572        metric = np.zeros(shape=(len(column))) + np.nan
1573
1574        for i in range(len(column)):
1575            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1576            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1577
1578            metric[i] = smash_metrics.nse(
1579                qobs,
1580                qsim,
1581            )
1582
1583        self.results.nse = metric

Compute the nse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def nnse(self, nodata=-99.0, column=[], outlets_name=[]):
1585    def nnse(self, nodata=-99.0, column=[], outlets_name=[]):
1586        """
1587        Compute the nnse between the oberved and simulated discharges.
1588        :param nodata: The no data value, defaults to -99.0
1589        :type nodata: float, optional
1590        :param column: the column nuber on which we want to compute the misfit,
1591         defaults to []
1592        :type column: list, optional
1593        :param outlets_name: The names of the outlets for which we want to compute
1594         the misfit, defaults to []
1595        :type outlets_name: list, optional
1596
1597        """
1598        column = self._update_column(column, outlets_name)
1599
1600        self.results.nnse = stats.nnse(
1601            self._parent_class._parent_class.smash.response_data.q[column, :],
1602            self._parent_class._parent_class.smash.response.q[column, :],
1603            nodata=-99.0,
1604            t_axis=1,
1605        )

Compute the nnse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_nnse(self, column=[], outlets_name=[]):
1607    def sm_nnse(self, column=[], outlets_name=[]):
1608        """
1609        Compute the nnse between the oberved and simulated discharges.
1610        :param nodata: The no data value, defaults to -99.0
1611        :type nodata: float, optional
1612        :param column: the column nuber on which we want to compute the misfit,
1613         defaults to []
1614        :type column: list, optional
1615        :param outlets_name: The names of the outlets for which we want to compute
1616         the misfit, defaults to []
1617        :type outlets_name: list, optional
1618
1619        """
1620        column = self._update_column(column, outlets_name)
1621        metric = np.zeros(shape=(len(column))) + np.nan
1622
1623        for i in range(len(column)):
1624            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1625            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1626
1627            metric[i] = smash_metrics.nnse(
1628                qobs,
1629                qsim,
1630            )
1631
1632        self.results.nnse = metric

Compute the nnse between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def kge(self, nodata=-99.0, column=[], outlets_name=[]):
1634    def kge(self, nodata=-99.0, column=[], outlets_name=[]):
1635        """
1636        Compute the kge between the oberved and simulated discharges.
1637        :param nodata: The no data value, defaults to -99.0
1638        :type nodata: float, optional
1639        :param column: the column nuber on which we want to compute the misfit,
1640         defaults to []
1641        :type column: list, optional
1642        :param outlets_name: The names of the outlets for which we want to compute
1643         the misfit, defaults to []
1644        :type outlets_name: list, optional
1645
1646        """
1647        column = self._update_column(column, outlets_name)
1648
1649        self.results.kge = stats.kge(
1650            self._parent_class._parent_class.smash.response_data.q[column, :],
1651            self._parent_class._parent_class.smash.response.q[column, :],
1652            nodata=-99.0,
1653            t_axis=1,
1654        )

Compute the kge between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def sm_kge(self, column=[], outlets_name=[]):
1656    def sm_kge(self, column=[], outlets_name=[]):
1657        """
1658        Compute the kge between the oberved and simulated discharges.
1659        :param nodata: The no data value, defaults to -99.0
1660        :type nodata: float, optional
1661        :param column: the column nuber on which we want to compute the misfit,
1662         defaults to []
1663        :type column: list, optional
1664        :param outlets_name: The names of the outlets for which we want to compute
1665         the misfit, defaults to []
1666        :type outlets_name: list, optional
1667
1668        """
1669        column = self._update_column(column, outlets_name)
1670        metric = np.zeros(shape=(len(column))) + np.nan
1671
1672        for i in range(len(column)):
1673            qobs = self._parent_class._parent_class.smash.response_data.q[i, :]
1674            qsim = self._parent_class._parent_class.smash.response.q[i, :]
1675
1676            metric[i] = smash_metrics.kge(
1677                qobs,
1678                qsim,
1679            )
1680
1681        self.results.kge = metric

Compute the kge between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []
def pearson(self, nodata=-99.0, column=[], outlets_name=[]):
1683    def pearson(self, nodata=-99.0, column=[], outlets_name=[]):
1684        """
1685        Compute the pearson coefficient between the oberved and simulated discharges.
1686        :param nodata: The no data value, defaults to -99.0
1687        :type nodata: float, optional
1688        :param column: the column nuber on which we want to compute the misfit,
1689         defaults to []
1690        :type column: list, optional
1691        :param outlets_name: The names of the outlets for which we want to compute
1692         the misfit, defaults to []
1693        :type outlets_name: list, optional
1694
1695        """
1696        column = self._update_column(column, outlets_name)
1697
1698        self.results.pearson = stats.pearson(
1699            self._parent_class._parent_class.smash.response_data.q[column, :],
1700            self._parent_class._parent_class.smash.response.q[column, :],
1701            nodata=-99.0,
1702            t_axis=1,
1703        )

Compute the pearson coefficient between the oberved and simulated discharges.

Parameters
  • nodata: The no data value, defaults to -99.0
  • column: the column nuber on which we want to compute the misfit, defaults to []
  • outlets_name: The names of the outlets for which we want to compute the misfit, defaults to []