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

class spatial_stats_results:
827class spatial_stats_results:
828    """
829    The class spatial_stats_results stores the results of the spatial statistics
830    on discharges at every pixel.
831    """
832
833    def __init__(self):
834        self.min = None
835        """The minimum values for each pixel"""
836        self.max = None
837        """The maximum values for each pixel"""
838        self.mean = None
839        """The mean value for each pixel"""
840        self.median = None
841        """The median values for each pixel"""
842        self.q20 = None
843        """The percentile 20% for each pixel"""
844        self.q80 = None
845        """The percentile 80% for each pixel"""
846        self.var = None
847        """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:
850class outlets_stats_results:
851    """
852    The class outlets_stats_results stores the results of the statistics
853    on discharges at every outlets.
854    """
855
856    def __init__(self):
857        self.min = None
858        """The minimum values for each outlets"""
859        self.max = None
860        """The maximum values for each outlets"""
861        self.mean = None
862        """The mean values for each outlets"""
863        self.median = None
864        """The median values for each outlets"""
865        self.q20 = None
866        """The percentile 20%  values for each outlets"""
867        self.q80 = None
868        """The percentile 80%  values for each outlets"""
869        self.var = None
870        """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:
873class spatial_quantile_results:
874    """
875    The class spatial_quantile_results stores the results of the quantiles computed for
876     different return period. Results include the quantile, the empirical return period,
877     the maxima for each chunk of chunk_size, the fit parameters of the `fit` extrem law.
878    """
879
880    def __init__(self):
881        self.T = None
882        """The returns periods"""
883        self.Q_th = None
884        """The theorical discharge quantiles for each return period"""
885        self.T_emp = None
886        """The empirical return period for each maxima"""
887        self.maxima = None
888        """The maxima for each chunk of size chunk_size (default is annual)"""
889        self.nb_chunks = None
890        """The number of chunk (default is number of years)"""
891        self.fit = None
892        """The extrem law used to fit the maxima and the empirical quantile"""
893        self.fit_shape = None
894        """The shape coefficient of the extrem law"""
895        self.fit_scale = None
896        """The scale coefficient of the scale law"""
897        self.fit_loc = None
898        """The fit coefficient of the extream law"""
899        self.duration = None
900        """The duration of the quantile (hours)"""
901        self.chunk_size = None
902        """The size of the chunk on which the maxima are computed (unit of the return
903         period, default is 365 days (1 year))"""
904        self.Umin = None
905        """Uncertainties minimum values"""
906        self.Umax = None
907        """Uncertainties maximum values"""
908
909    def fill_attribute(self, stats_spatial_quantile: dict = None):
910        """
911        Fill the attribute of the class spatial_quantile_results.
912
913        :param stats_spatial_quantile: Dict of the spatial quantile results, defaults to None
914        :type stats_spatial_quantile: dict, optional
915
916        """
917
918        self.T = stats_spatial_quantile["T"]
919        self.Q_th = stats_spatial_quantile["Q_th"]
920        self.T_emp = stats_spatial_quantile["T_emp"]
921        self.maxima = stats_spatial_quantile["maxima"]
922        self.nb_chunks = stats_spatial_quantile["nb_chunks"]
923        self.fit = stats_spatial_quantile["fit"]
924        self.fit_shape = stats_spatial_quantile["fit_shape"]
925        self.fit_scale = stats_spatial_quantile["fit_scale"]
926        self.fit_loc = stats_spatial_quantile["fit_loc"]
927        self.duration = stats_spatial_quantile["duration"]
928        self.chunk_size = stats_spatial_quantile["chunk_size"]
929        if "Umin" in stats_spatial_quantile:
930            self.Umin = stats_spatial_quantile["Umin"]
931        if "Umax" in stats_spatial_quantile:
932            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):
909    def fill_attribute(self, stats_spatial_quantile: dict = None):
910        """
911        Fill the attribute of the class spatial_quantile_results.
912
913        :param stats_spatial_quantile: Dict of the spatial quantile results, defaults to None
914        :type stats_spatial_quantile: dict, optional
915
916        """
917
918        self.T = stats_spatial_quantile["T"]
919        self.Q_th = stats_spatial_quantile["Q_th"]
920        self.T_emp = stats_spatial_quantile["T_emp"]
921        self.maxima = stats_spatial_quantile["maxima"]
922        self.nb_chunks = stats_spatial_quantile["nb_chunks"]
923        self.fit = stats_spatial_quantile["fit"]
924        self.fit_shape = stats_spatial_quantile["fit_shape"]
925        self.fit_scale = stats_spatial_quantile["fit_scale"]
926        self.fit_loc = stats_spatial_quantile["fit_loc"]
927        self.duration = stats_spatial_quantile["duration"]
928        self.chunk_size = stats_spatial_quantile["chunk_size"]
929        if "Umin" in stats_spatial_quantile:
930            self.Umin = stats_spatial_quantile["Umin"]
931        if "Umax" in stats_spatial_quantile:
932            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:
935class spatial_quantile:
936    """Parent class spatial quantile. Class to store results of the spatial quantile."""
937
938    def __init__(self):
939        self.spatial_maxima = None
940        """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)"""
941        self.spatial_maxima_outlets = None
942        """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)"""
943        self.spatial_cumulated_maxima = None
944        """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)"""
945        self.spatial_cumulated_maxima_outlets = None
946        """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)"""
947        self.spatial_quantile = None
948        """Spatial matrix of the discharges quantile computed from the maximum discharges.Shape=(nbx, nby, duration, return_period)"""
949        self.spatial_quantile_outlets = None
950        """Outlets matrix of the discharges quantile computed from the maximum discharges.Shape=(nb_gauge, duration, return_period)"""
951        # 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:
 954class spatial_stats:
 955    """Class for computing the spatial statistics on the discharges."""
 956
 957    def __init__(self, parent_class):
 958        self._parent_class = parent_class
 959        """The parent class in order to access to the results of the smash simulation"""
 960        self.results = spatial_stats_results()
 961        """The results of the spatial statistics"""
 962
 963    def mean(self):
 964        """Compute the spatial mean of the discharges."""
 965        self.results.mean = np.nanmean(
 966            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
 967        )
 968
 969    def minimum(self):
 970        """Compute the spatial minimum of the discharges."""
 971        self.results.min = np.nanmin(
 972            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
 973        )
 974
 975    def maximum(self):
 976        """Compute the spatial maximum of the discharges."""
 977        self.results.max = np.nanmax(
 978            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
 979        )
 980
 981    def median(self):
 982        """Compute the spatial median of the discharges."""
 983        self.results.median = np.quantile(
 984            self._parent_class._parent_class.extra_smash_results.q_domain, 0.5, axis=2
 985        )
 986
 987    def q20(self):
 988        """Compute the spatial percentile 20% of the discharges."""
 989        self.results.q20 = np.quantile(
 990            self._parent_class._parent_class.extra_smash_results.q_domain, 0.2, axis=2
 991        )
 992
 993    def q80(self):
 994        """Compute the spatial percentile 80% of the discharges."""
 995        self.results.q80 = np.quantile(
 996            self._parent_class._parent_class.extra_smash_results.q_domain, 0.8, axis=2
 997        )
 998
 999    def var(self):
1000        """Compute the variance of the discharges for each pixel."""
1001        self.results.var = np.var(
1002            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
1003        )

Class for computing the spatial statistics on the discharges.

spatial_stats(parent_class)
957    def __init__(self, parent_class):
958        self._parent_class = parent_class
959        """The parent class in order to access to the results of the smash simulation"""
960        self.results = spatial_stats_results()
961        """The results of the spatial statistics"""
results

The results of the spatial statistics

def mean(self):
963    def mean(self):
964        """Compute the spatial mean of the discharges."""
965        self.results.mean = np.nanmean(
966            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
967        )

Compute the spatial mean of the discharges.

def minimum(self):
969    def minimum(self):
970        """Compute the spatial minimum of the discharges."""
971        self.results.min = np.nanmin(
972            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
973        )

Compute the spatial minimum of the discharges.

def maximum(self):
975    def maximum(self):
976        """Compute the spatial maximum of the discharges."""
977        self.results.max = np.nanmax(
978            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
979        )

Compute the spatial maximum of the discharges.

def median(self):
981    def median(self):
982        """Compute the spatial median of the discharges."""
983        self.results.median = np.quantile(
984            self._parent_class._parent_class.extra_smash_results.q_domain, 0.5, axis=2
985        )

Compute the spatial median of the discharges.

def q20(self):
987    def q20(self):
988        """Compute the spatial percentile 20% of the discharges."""
989        self.results.q20 = np.quantile(
990            self._parent_class._parent_class.extra_smash_results.q_domain, 0.2, axis=2
991        )

Compute the spatial percentile 20% of the discharges.

def q80(self):
993    def q80(self):
994        """Compute the spatial percentile 80% of the discharges."""
995        self.results.q80 = np.quantile(
996            self._parent_class._parent_class.extra_smash_results.q_domain, 0.8, axis=2
997        )

Compute the spatial percentile 80% of the discharges.

def var(self):
 999    def var(self):
1000        """Compute the variance of the discharges for each pixel."""
1001        self.results.var = np.var(
1002            self._parent_class._parent_class.extra_smash_results.q_domain, axis=2
1003        )

Compute the variance of the discharges for each pixel.

class outlets_stats:
1006class outlets_stats:
1007    """Class for computing the statistics on the discharges at every outlets."""
1008
1009    def __init__(self, parent_class):
1010        self._parent_class = parent_class
1011        """The parent class in order to access to the results of the smash simulation"""
1012        self.results_sim = outlets_stats_results()
1013        """The results of the simulated dicharges statistics at every outlets"""
1014        self.results_obs = outlets_stats_results()
1015        """The results of the observed discharges statistics at every outlets"""
1016
1017    def mean(self):
1018        """Compute the mean of the discharges at every outlets."""
1019        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1020        qobs = np.where(qobs < 0, np.nan, qobs)
1021
1022        self.results_sim.mean = np.nanmean(
1023            self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1024        )
1025        self.results_obs.mean = np.nanmean(qobs, axis=1)
1026
1027    def minimum(self):
1028        """Compute the minimum of the discharges at every outlets."""
1029        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1030        qobs = np.where(qobs < 0, np.nan, qobs)
1031
1032        self.results_sim.min = np.nanmin(
1033            self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1034        )
1035        self.results_obs.min = np.nanmin(qobs, axis=1)
1036
1037    def maximum(self):
1038        """Compute the maximum of the discharges at every outlets."""
1039        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1040        qobs = np.where(qobs < 0, np.nan, qobs)
1041
1042        self.results_sim.max = np.nanmax(
1043            self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1044        )
1045        self.results_obs.max = np.nanmax(qobs, axis=1)
1046
1047    def median(self):
1048        """Compute the median of the discharges at every outlets."""
1049        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1050        qobs = np.where(qobs < 0, np.nan, qobs)
1051
1052        self.results_sim.median = np.nanquantile(
1053            self._parent_class._parent_class.mysmashmodel.response.q, 0.5, axis=1
1054        )
1055        self.results_obs.median = np.nanquantile(qobs, 0.5, axis=1)
1056
1057    def q20(self):
1058        """Compute the percentile 20% of the discharges at every outlets."""
1059        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1060        qobs = np.where(qobs < 0, np.nan, qobs)
1061
1062        self.results_sim.q20 = np.nanquantile(
1063            self._parent_class._parent_class.mysmashmodel.response.q, 0.2, axis=1
1064        )
1065        self.results_obs.q20 = np.nanquantile(qobs, 0.2, axis=1)
1066
1067    def q80(self):
1068        """Compute the percentile 80% of the discharges at every outlets."""
1069        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1070        qobs = np.where(qobs < 0, np.nan, qobs)
1071
1072        self.results_sim.q80 = np.nanquantile(
1073            self._parent_class._parent_class.mysmashmodel.response.q, 0.8, axis=1
1074        )
1075        self.results_obs.q80 = np.nanquantile(qobs, 0.8, axis=1)
1076
1077    def var(self):
1078        """Compute the variance of the discharges at every outlets"""
1079        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1080        qobs = np.where(qobs < 0, np.nan, qobs)
1081
1082        self.results_sim.var = np.var(
1083            self._parent_class._parent_class.extra_smash_results.q_domain, axis=1
1084        )
1085        self.results_obs.var = np.var(qobs, axis=1)

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

outlets_stats(parent_class)
1009    def __init__(self, parent_class):
1010        self._parent_class = parent_class
1011        """The parent class in order to access to the results of the smash simulation"""
1012        self.results_sim = outlets_stats_results()
1013        """The results of the simulated dicharges statistics at every outlets"""
1014        self.results_obs = outlets_stats_results()
1015        """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):
1017    def mean(self):
1018        """Compute the mean of the discharges at every outlets."""
1019        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1020        qobs = np.where(qobs < 0, np.nan, qobs)
1021
1022        self.results_sim.mean = np.nanmean(
1023            self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1024        )
1025        self.results_obs.mean = np.nanmean(qobs, axis=1)

Compute the mean of the discharges at every outlets.

def minimum(self):
1027    def minimum(self):
1028        """Compute the minimum of the discharges at every outlets."""
1029        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1030        qobs = np.where(qobs < 0, np.nan, qobs)
1031
1032        self.results_sim.min = np.nanmin(
1033            self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1034        )
1035        self.results_obs.min = np.nanmin(qobs, axis=1)

Compute the minimum of the discharges at every outlets.

def maximum(self):
1037    def maximum(self):
1038        """Compute the maximum of the discharges at every outlets."""
1039        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1040        qobs = np.where(qobs < 0, np.nan, qobs)
1041
1042        self.results_sim.max = np.nanmax(
1043            self._parent_class._parent_class.mysmashmodel.response.q, axis=1
1044        )
1045        self.results_obs.max = np.nanmax(qobs, axis=1)

Compute the maximum of the discharges at every outlets.

def median(self):
1047    def median(self):
1048        """Compute the median of the discharges at every outlets."""
1049        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1050        qobs = np.where(qobs < 0, np.nan, qobs)
1051
1052        self.results_sim.median = np.nanquantile(
1053            self._parent_class._parent_class.mysmashmodel.response.q, 0.5, axis=1
1054        )
1055        self.results_obs.median = np.nanquantile(qobs, 0.5, axis=1)

Compute the median of the discharges at every outlets.

def q20(self):
1057    def q20(self):
1058        """Compute the percentile 20% of the discharges at every outlets."""
1059        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1060        qobs = np.where(qobs < 0, np.nan, qobs)
1061
1062        self.results_sim.q20 = np.nanquantile(
1063            self._parent_class._parent_class.mysmashmodel.response.q, 0.2, axis=1
1064        )
1065        self.results_obs.q20 = np.nanquantile(qobs, 0.2, axis=1)

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

def q80(self):
1067    def q80(self):
1068        """Compute the percentile 80% of the discharges at every outlets."""
1069        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1070        qobs = np.where(qobs < 0, np.nan, qobs)
1071
1072        self.results_sim.q80 = np.nanquantile(
1073            self._parent_class._parent_class.mysmashmodel.response.q, 0.8, axis=1
1074        )
1075        self.results_obs.q80 = np.nanquantile(qobs, 0.8, axis=1)

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

def var(self):
1077    def var(self):
1078        """Compute the variance of the discharges at every outlets"""
1079        qobs = self._parent_class._parent_class.mysmashmodel.response_data.q
1080        qobs = np.where(qobs < 0, np.nan, qobs)
1081
1082        self.results_sim.var = np.var(
1083            self._parent_class._parent_class.extra_smash_results.q_domain, axis=1
1084        )
1085        self.results_obs.var = np.var(qobs, axis=1)

Compute the variance of the discharges at every outlets

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

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

misfit_stats(parent_class)
1091    def __init__(self, parent_class):
1092        self._parent_class = parent_class
1093        """The parent class in order to access to the results of the smash simulation"""
1094        self.results = misfit_results()
1095        """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 = []):
1114    def mse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1115        """
1116        Compute the mse between the oberved and simulated discharges.
1117        :param nodata: The no data value, defaults to -99.0
1118        :type nodata: float, optional
1119        :param column: the column nuber on which we want to compute the misfit,
1120         defaults to []
1121        :type column: list, optional
1122        :param outlets_name: The names of the outlets for which we want to compute
1123         the misfit, defaults to []
1124        :type outlets_name: list, optional
1125
1126        """
1127
1128        column = self._update_column(column, outlets_name)
1129
1130        self.results.mse = stats.mse(
1131            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1132            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1133            nodata=-99.0,
1134            t_axis=1,
1135        )

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 = []):
1137    def sm_mse(self, column: list = [], outlets_name: list = []):
1138        """
1139        Compute the mse between the oberved and simulated discharges.
1140        :param nodata: The no data value, defaults to -99.0
1141        :type nodata: float, optional
1142        :param column: the column nuber on which we want to compute the misfit,
1143         defaults to []
1144        :type column: list, optional
1145        :param outlets_name: The names of the outlets for which we want to compute
1146         the misfit, defaults to []
1147        :type outlets_name: list, optional
1148
1149        """
1150
1151        column = self._update_column(column, outlets_name)
1152
1153        metric = np.zeros(shape=(len(column))) + np.nan
1154
1155        for i in range(len(column)):
1156            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1157            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1158
1159            metric[i] = smash_metrics.mse(
1160                qobs,
1161                qsim,
1162            )
1163
1164        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 = []):
1166    def rmse(self, nodata=-99.0, column: list = [], outlets_name: list = []):
1167        """
1168        Compute the rmse between the oberved and simulated discharges.
1169        :param nodata: The no data value, defaults to -99.0
1170        :type nodata: float, optional
1171        :param column: the column nuber on which we want to compute the misfit,
1172         defaults to []
1173        :type column: list, optional
1174        :param outlets_name: The names of the outlets for which we want to compute
1175         the misfit, defaults to []
1176        :type outlets_name: list, optional
1177
1178        """
1179
1180        if len(outlets_name) > 0:
1181            column = tools.array_isin(
1182                self._parent_class._parent_class.mysmashmodel.mesh.code,
1183                np.array(outlets_name),
1184            )
1185
1186        if len(column) == 0:
1187            column = list(
1188                range(
1189                    0, self._parent_class._parent_class.mysmashmodel.response.q.shape[0]
1190                )
1191            )
1192
1193        self.results.rmse = stats.rmse(
1194            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1195            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1196            nodata=-99.0,
1197            t_axis=1,
1198        )

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 = []):
1200    def sm_rmse(self, column: list = [], outlets_name: list = []):
1201        """
1202        Compute the rmse between the oberved and simulated discharges.
1203        :param nodata: The no data value, defaults to -99.0
1204        :type nodata: float, optional
1205        :param column: the column nuber on which we want to compute the misfit,
1206         defaults to []
1207        :type column: list, optional
1208        :param outlets_name: The names of the outlets for which we want to compute
1209         the misfit, defaults to []
1210        :type outlets_name: list, optional
1211
1212        """
1213
1214        column = self._update_column(column, outlets_name)
1215
1216        metric = np.zeros(shape=(len(column))) + np.nan
1217
1218        for i in range(len(column)):
1219            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1220            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1221
1222            metric[i] = smash_metrics.rmse(
1223                qobs,
1224                qsim,
1225            )
1226
1227        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=[]):
1229    def nrmse(self, nodata=-99.0, column=[], outlets_name=[]):
1230        """
1231        Compute the nrmse between the oberved and simulated discharges.
1232        :param nodata: The no data value, defaults to -99.0
1233        :type nodata: float, optional
1234        :param column: the column nuber on which we want to compute the misfit,
1235         defaults to []
1236        :type column: list, optional
1237        :param outlets_name: The names of the outlets for which we want to compute
1238         the misfit, defaults to []
1239        :type outlets_name: list, optional
1240
1241        """
1242
1243        if len(outlets_name) > 0:
1244            column = tools.array_isin(
1245                self._parent_class._parent_class.mysmashmodel.mesh.code,
1246                np.array(outlets_name),
1247            )
1248
1249        if len(column) == 0:
1250            column = list(
1251                range(
1252                    0, self._parent_class._parent_class.mysmashmodel.response.q.shape[0]
1253                )
1254            )
1255
1256        self.results.nrmse = stats.nrmse(
1257            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1258            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1259            nodata=-99.0,
1260            t_axis=1,
1261        )

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 = []):
1263    def sm_nrmse(self, column: list = [], outlets_name: list = []):
1264        """
1265        Compute the nrmse between the oberved and simulated discharges.
1266        :param nodata: The no data value, defaults to -99.0
1267        :type nodata: float, optional
1268        :param column: the column nuber on which we want to compute the misfit,
1269         defaults to []
1270        :type column: list, optional
1271        :param outlets_name: The names of the outlets for which we want to compute
1272         the misfit, defaults to []
1273        :type outlets_name: list, optional
1274
1275        """
1276
1277        column = self._update_column(column, outlets_name)
1278
1279        metric = np.zeros(shape=(len(column))) + np.nan
1280
1281        for i in range(len(column)):
1282            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1283            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1284
1285            mean_qobs = np.mean(qobs)
1286            metric[i] = smash_metrics.rmse(qobs, qsim) / mean_qobs
1287
1288        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=[]):
1290    def se(self, nodata=-99.0, column=[], outlets_name=[]):
1291        """
1292        Compute the se between the oberved and simulated discharges.
1293        :param nodata: The no data value, defaults to -99.0
1294        :type nodata: float, optional
1295        :param column: the column nuber on which we want to compute the misfit,
1296         defaults to []
1297        :type column: list, optional
1298        :param outlets_name: The names of the outlets for which we want to compute
1299         the misfit, defaults to []
1300        :type outlets_name: list, optional
1301
1302        """
1303        column = self._update_column(column, outlets_name)
1304
1305        self.results.se = stats.se(
1306            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1307            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1308            nodata=-99.0,
1309            t_axis=1,
1310        )

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 = []):
1312    def sm_se(self, column: list = [], outlets_name: list = []):
1313        """
1314        Compute the se between the oberved and simulated discharges.
1315        :param nodata: The no data value, defaults to -99.0
1316        :type nodata: float, optional
1317        :param column: the column nuber on which we want to compute the misfit,
1318         defaults to []
1319        :type column: list, optional
1320        :param outlets_name: The names of the outlets for which we want to compute
1321         the misfit, defaults to []
1322        :type outlets_name: list, optional
1323
1324        """
1325        column = self._update_column(column, outlets_name)
1326        metric = np.zeros(shape=(len(column))) + np.nan
1327
1328        for i in range(len(column)):
1329            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1330            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1331
1332            if not np.all(qobs < 0):
1333                metric[i] = smash_metrics.se(
1334                    qobs,
1335                    qsim,
1336                )
1337
1338        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=[]):
1340    def mae(self, nodata=-99.0, column=[], outlets_name=[]):
1341        """
1342        Compute the mae between the oberved and simulated discharges.
1343        :param nodata: The no data value, defaults to -99.0
1344        :type nodata: float, optional
1345        :param column: the column nuber on which we want to compute the misfit,
1346         defaults to []
1347        :type column: list, optional
1348        :param outlets_name: The names of the outlets for which we want to compute
1349         the misfit, defaults to []
1350        :type outlets_name: list, optional
1351
1352        """
1353        column = self._update_column(column, outlets_name)
1354
1355        self.results.mae = stats.mae(
1356            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1357            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1358            nodata=-99.0,
1359            t_axis=1,
1360        )

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=[]):
1362    def sm_mae(self, column=[], outlets_name=[]):
1363        """
1364        Compute the mae between the oberved and simulated discharges.
1365        :param nodata: The no data value, defaults to -99.0
1366        :type nodata: float, optional
1367        :param column: the column nuber on which we want to compute the misfit,
1368         defaults to []
1369        :type column: list, optional
1370        :param outlets_name: The names of the outlets for which we want to compute
1371         the misfit, defaults to []
1372        :type outlets_name: list, optional
1373
1374        """
1375        column = self._update_column(column, outlets_name)
1376        metric = np.zeros(shape=(len(column))) + np.nan
1377
1378        for i in range(len(column)):
1379            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1380            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1381
1382            metric[i] = smash_metrics.mae(
1383                qobs,
1384                qsim,
1385            )
1386
1387        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=[]):
1389    def mape(self, nodata=-99.0, column=[], outlets_name=[]):
1390        """
1391        Compute the mape between the oberved and simulated discharges.
1392        :param nodata: The no data value, defaults to -99.0
1393        :type nodata: float, optional
1394        :param column: the column nuber on which we want to compute the misfit,
1395         defaults to []
1396        :type column: list, optional
1397        :param outlets_name: The names of the outlets for which we want to compute
1398         the misfit, defaults to []
1399        :type outlets_name: list, optional
1400
1401        """
1402        column = self._update_column(column, outlets_name)
1403
1404        self.results.mape = stats.mape(
1405            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1406            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1407            nodata=-99.0,
1408            t_axis=1,
1409        )

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=[]):
1411    def sm_mape(self, column=[], outlets_name=[]):
1412        """
1413        Compute the mape between the oberved and simulated discharges.
1414        :param nodata: The no data value, defaults to -99.0
1415        :type nodata: float, optional
1416        :param column: the column nuber on which we want to compute the misfit,
1417         defaults to []
1418        :type column: list, optional
1419        :param outlets_name: The names of the outlets for which we want to compute
1420         the misfit, defaults to []
1421        :type outlets_name: list, optional
1422
1423        """
1424        column = self._update_column(column, outlets_name)
1425        metric = np.zeros(shape=(len(column))) + np.nan
1426
1427        for i in range(len(column)):
1428            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1429            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1430
1431            metric[i] = smash_metrics.mape(
1432                qobs,
1433                qsim,
1434            )
1435
1436        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=[]):
1438    def lgrm(self, nodata=-99.0, column=[], outlets_name=[]):
1439        """
1440        Compute the lgrm between the oberved and simulated discharges.
1441        :param nodata: The no data value, defaults to -99.0
1442        :type nodata: float, optional
1443        :param column: the column nuber on which we want to compute the misfit,
1444         defaults to []
1445        :type column: list, optional
1446        :param outlets_name: The names of the outlets for which we want to compute
1447         the misfit, defaults to []
1448        :type outlets_name: list, optional
1449
1450        """
1451        column = self._update_column(column, outlets_name)
1452
1453        self.results.lgrm = stats.lgrm(
1454            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1455            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1456            nodata=-99.0,
1457            t_axis=1,
1458        )

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=[]):
1460    def sm_lgrm(self, column=[], outlets_name=[]):
1461        """
1462        Compute the lgrm between the oberved and simulated discharges.
1463        :param nodata: The no data value, defaults to -99.0
1464        :type nodata: float, optional
1465        :param column: the column nuber on which we want to compute the misfit,
1466         defaults to []
1467        :type column: list, optional
1468        :param outlets_name: The names of the outlets for which we want to compute
1469         the misfit, defaults to []
1470        :type outlets_name: list, optional
1471
1472        """
1473        column = self._update_column(column, outlets_name)
1474        metric = np.zeros(shape=(len(column))) + np.nan
1475
1476        for i in range(len(column)):
1477            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1478            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1479
1480            if not np.all(qobs < 0):
1481                metric[i] = smash_metrics.lgrm(
1482                    qobs,
1483                    qsim,
1484                )
1485
1486        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=[]):
1488    def nse(self, nodata=-99.0, column=[], outlets_name=[]):
1489        """
1490        Compute the nse between the oberved and simulated discharges.
1491        :param nodata: The no data value, defaults to -99.0
1492        :type nodata: float, optional
1493        :param column: the column nuber on which we want to compute the misfit,
1494         defaults to []
1495        :type column: list, optional
1496        :param outlets_name: The names of the outlets for which we want to compute
1497         the misfit, defaults to []
1498        :type outlets_name: list, optional
1499
1500        """
1501        column = self._update_column(column, outlets_name)
1502
1503        self.results.nse = stats.nse(
1504            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1505            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1506            nodata=-99.0,
1507            t_axis=1,
1508        )

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=[]):
1510    def sm_nse(self, column=[], outlets_name=[]):
1511        """
1512        Compute the nse between the oberved and simulated discharges.
1513        :param nodata: The no data value, defaults to -99.0
1514        :type nodata: float, optional
1515        :param column: the column nuber on which we want to compute the misfit,
1516         defaults to []
1517        :type column: list, optional
1518        :param outlets_name: The names of the outlets for which we want to compute
1519         the misfit, defaults to []
1520        :type outlets_name: list, optional
1521
1522        """
1523        column = self._update_column(column, outlets_name)
1524        metric = np.zeros(shape=(len(column))) + np.nan
1525
1526        for i in range(len(column)):
1527            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1528            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1529
1530            metric[i] = smash_metrics.nse(
1531                qobs,
1532                qsim,
1533            )
1534
1535        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=[]):
1537    def nnse(self, nodata=-99.0, column=[], outlets_name=[]):
1538        """
1539        Compute the nnse between the oberved and simulated discharges.
1540        :param nodata: The no data value, defaults to -99.0
1541        :type nodata: float, optional
1542        :param column: the column nuber on which we want to compute the misfit,
1543         defaults to []
1544        :type column: list, optional
1545        :param outlets_name: The names of the outlets for which we want to compute
1546         the misfit, defaults to []
1547        :type outlets_name: list, optional
1548
1549        """
1550        column = self._update_column(column, outlets_name)
1551
1552        self.results.nnse = stats.nnse(
1553            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1554            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1555            nodata=-99.0,
1556            t_axis=1,
1557        )

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=[]):
1559    def sm_nnse(self, column=[], outlets_name=[]):
1560        """
1561        Compute the nnse between the oberved and simulated discharges.
1562        :param nodata: The no data value, defaults to -99.0
1563        :type nodata: float, optional
1564        :param column: the column nuber on which we want to compute the misfit,
1565         defaults to []
1566        :type column: list, optional
1567        :param outlets_name: The names of the outlets for which we want to compute
1568         the misfit, defaults to []
1569        :type outlets_name: list, optional
1570
1571        """
1572        column = self._update_column(column, outlets_name)
1573        metric = np.zeros(shape=(len(column))) + np.nan
1574
1575        for i in range(len(column)):
1576            qobs = self._parent_class._parent_class.mysmashmodel.response_data.q[i, :]
1577            qsim = self._parent_class._parent_class.mysmashmodel.response.q[i, :]
1578
1579            metric[i] = smash_metrics.nnse(
1580                qobs,
1581                qsim,
1582            )
1583
1584        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=[]):
1586    def kge(self, nodata=-99.0, column=[], outlets_name=[]):
1587        """
1588        Compute the kge between the oberved and simulated discharges.
1589        :param nodata: The no data value, defaults to -99.0
1590        :type nodata: float, optional
1591        :param column: the column nuber on which we want to compute the misfit,
1592         defaults to []
1593        :type column: list, optional
1594        :param outlets_name: The names of the outlets for which we want to compute
1595         the misfit, defaults to []
1596        :type outlets_name: list, optional
1597
1598        """
1599        column = self._update_column(column, outlets_name)
1600
1601        self.results.kge = stats.kge(
1602            self._parent_class._parent_class.mysmashmodel.response_data.q[column, :],
1603            self._parent_class._parent_class.mysmashmodel.response.q[column, :],
1604            nodata=-99.0,
1605            t_axis=1,
1606        )

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