smashbox.model.model

   1import os
   2import smash
   3
   4# import pyhdf5_handler
   5# from pyhdf5_handler.src import hdf5_handler
   6import numpy as np
   7import pandas as pd
   8import datetime
   9
  10# from smashbox.src import param
  11from smashbox.model import setup
  12from smashbox.model import mesh
  13from smashbox.plot import myplot
  14from smashbox.tools import geo_toolbox
  15from smashbox.tools import tools
  16from smashbox.model import atmos_data_connector
  17from smashbox.stats import mystats
  18from smashbox.read_inputdata import smashmodel
  19from smashbox.model import smash_model
  20
  21import pyhdf5_handler
  22import copy
  23import pickle
  24
  25
  26class model:
  27    """Main class model() which has a complete set of functions, class and attributes to
  28    build, run and manipulate the Smash model, compute statistical criterium and
  29    plot graphics."""
  30
  31    def __init__(self, name, myparam):
  32
  33        self._model_name = name
  34        """The attribute _model_name is contain the name of the current object"""
  35
  36        self._myparam = copy.deepcopy(myparam)
  37        """The attribute _myparam is a copy of the parent class smashbox.myparam.
  38        This class stores the main parameters used for building the Smash model"""
  39
  40        self.mysetup = setup.setup(self._myparam.param)
  41        """The attribute mysetup own the class setup.setup(). This class stores the Smash
  42        setup for the hydrological simulation and some helpers to manipulate these
  43        parameters."""
  44
  45        self.mymesh = mesh.mesh(self.mysetup)
  46        """The attribute mymesh own the class mesh.mesh(). This class stores the Smash
  47        mesh used for the hydrological simulation and some helpers to manipulate this mesh.
  48        """
  49
  50        self._fstates = None
  51        self._istates = None
  52        self._myatmos_data_connector = None
  53
  54        self.mysmashmodel = smash_model.smash_model()
  55        """The attribute mysmashmodel stores the Smash model object created with
  56        attributes mysetup and -mymesh."""
  57
  58        self.warmup_model = smash_model.smash_model()
  59        """The attribute warmup_model store a smash model used for warmup and compatible 
  60        with the model in attribute mysmashmodel"""
  61
  62        self.optimize_model = smash_model.smash_model()
  63        """The attribute optimize_model store a smash model used for optimization and compatible 
  64        with the model in attribute mysmashmodel"""
  65
  66        self.validation_model = smash_model.smash_model()
  67        """The attribute validation_model stores the Smash model object used for validation"""
  68
  69        self.myplot = myplot.myplot(self)
  70        """The attribute myplot own the class myplot.myplot(). This class stores plotting
  71        # capabilities on the smash model object."""
  72
  73    def generate_mesh(
  74        self,
  75        max_depth: float = 1.0,
  76        query: str | None = None,
  77        area_error_th: None | float = None,
  78        lacuna_threshold: None | float = None,
  79    ):
  80        """
  81        Generate the mesh of the Smash model
  82
  83        Parameters
  84        ----------
  85
  86        max_depth : `int`, default 1
  87            The maximum depth accepted by the algorithm to find the catchment outlet.
  88            A **max_depth** of 1 means that the algorithm will search among the
  89            combinations in
  90            (``row - 1``, ``row``, ``row + 1``; ``col - 1``, ``col``, ``col + 1``),
  91            the coordinates that minimize
  92            the relative error between the given catchment area and the modeled
  93            catchment area calculated from the
  94            flow directions file.
  95        :param query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query
  96        must be build using the field (column name) in the outlet database.
  97        https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method
  98        :type query: str
  99        area_error_th: float | None
 100            The tolerance error for the difference between the observed and simulated
 101             surface. The error is computed as follow:
 102                 Serror=abs(Ssim-Sobs)/Sobs
 103            All outlets where `Serror > area_error_th` will be automatically removed from
 104            the mesh.
 105        :type area_error_th: float
 106        :param lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed.
 107        :type: float | Nonetype
 108
 109        Examples
 110        --------
 111
 112        >>> es=smashbox.SmashBox()
 113        >>> sb.newmodel("RealCollobrier")
 114        >>> sb.RealCollobrier.generate_mesh(min_surf=5, max_surf=100)
 115
 116        """
 117        self.mymesh.generate_mesh(
 118            self._myparam.param,
 119            max_depth=max_depth,
 120            query=query,
 121            area_error_th=area_error_th,
 122            lacuna_threshold=lacuna_threshold,
 123        )
 124
 125    def model(
 126        self,
 127        setup: dict | None = None,
 128        mesh: dict | None = None,
 129        read_data=None,
 130    ):
 131        """
 132        Smash model object creation. This function wrap smash.Model(). Setup and mesh
 133        argument are optional since these dictionnary are hosted by the smashbox object.
 134
 135        Parameters
 136        ----------
 137        setup: dict | None
 138            The Smash setup (optionnal), if None the smashbox setup will be used
 139        mesh: dict | None
 140            The Smash mesh (optionnal), if None the smashbox mesh will be used
 141
 142        Examples
 143        --------
 144
 145        >>> es=smashbox.SmashBox()
 146        >>> sb.newmodel("RealCollobrier")
 147        >>> sb.RealCollobrier.generate_mesh(run=False)
 148        >>> sb.RealCollobrier.model()
 149
 150        """
 151        if setup is None:
 152            setup = self.mysetup.setup.copy()
 153
 154        if mesh is None:
 155            mesh = self.mymesh.mesh.copy()
 156
 157        if read_data is False:
 158            setup.update(
 159                {
 160                    "read_prcp": False,
 161                    "read_pet": False,
 162                    "read_snow": False,
 163                    "read_qobs": False,
 164                    "read_temp": False,
 165                }
 166            )
 167        if read_data is True:
 168            setup.update(
 169                {
 170                    "read_prcp": True,
 171                    "read_pet": True,
 172                    "read_snow": True,
 173                    "read_qobs": True,
 174                    "read_temp": True,
 175                }
 176            )
 177
 178        self.mysmashmodel.smash = self._model(setup=setup, mesh=mesh)
 179
 180        if read_data is True:
 181            self._gathering_atmosdata()
 182
 183        self._gathering_parameters(self.mysmashmodel.smash)
 184
 185    def _model(self, setup=None, mesh=None):
 186        """
 187        Smash model object creation. This function wrap smash.Model()
 188
 189        Parameters
 190        ----------
 191        setup: dict | None
 192            The Smash setup (optionnal), if None the smashbox setup will be used
 193        mesh: dict | None
 194            The Smash mesh (optionnal), if None the smashbox mesh will be used
 195
 196        Examples
 197        --------
 198
 199        >>> es=smashbox.SmashBox()
 200        >>> sb.newmodel("RealCollobrier")
 201        >>> sb.RealCollobrier.generate_mesh(run=False)
 202        >>> sb.RealCollobrier.model()
 203
 204        """
 205        if setup is None:
 206            print("</> The input setup is None ")
 207            return None
 208
 209        if mesh is None:
 210            print(
 211                "</> input mesh is None. use smashbox.model.model.generate_mesh()"
 212            )
 213            return None
 214
 215        if self._myparam.param.enhanced_smash_input_data:
 216            model = smashmodel.SmashModel(setup, mesh)
 217        else:
 218            model = smash.Model(setup, mesh)
 219
 220        return model
 221
 222    def _gathering_parameters(self, model):
 223
 224        if self.optimize_model.smash is not None:
 225            print(
 226                f"</> Getting parameters from previously optimized model ..."
 227            )
 228            model.rr_parameters = self.optimize_model.smash.rr_parameters
 229        else:
 230            print(
 231                f"</> Importing parameters from {self._myparam.param._smash_parameters} ..."
 232            )
 233            self.import_parameters(model=model)
 234            self._transform_parameters(
 235                model=model, dt_origin=self._myparam.param._smash_parameters_dt
 236            )
 237
 238    def _transform_parameters(
 239        self, model=None, dt_origin: None | float = None
 240    ):
 241        """
 242        Function to transform the parameters according the model time-step and the original
 243        time-step used for calibrated the parameters.
 244        :param dt_origin: Original time-step used for generate the calibrated parameter,
 245        defaults to None
 246        :type dt_origin: None | float, optional
 247
 248        """
 249        # if dt_origin is None:
 250        #     raise ValueError(
 251        #         " Argument dt_origin is None. This must be filled with the value "
 252        #         "of the timestep used to calibrate the parameters."
 253        #     )
 254
 255        # if not hasattr(self, "mysmashmodel.smash") or self.mysmashmodel.smash is None:
 256        #     raise ValueError(
 257        #         "</> mysmashmodel.smash attribute does not exist or is None. "
 258        #         "The model must be created first..."
 259        #     )
 260        if model is None:
 261            return
 262
 263        dt_target = model.setup.dt
 264
 265        if dt_origin is None:
 266            return
 267
 268        if dt_target == dt_origin:
 269            return
 270
 271        print(
 272            f"</> Tranforming parameters `ct`, `kexec`, `llr` calibrated with a time-step "
 273            f"of {dt_origin}s to the modeled time-step {dt_target}s."
 274        )
 275        parameters_tranfsorm = ["ct", "kexc", "llr"]
 276        power_tranform = [1.0 / 4.0, -1.0 / 8.0, 1.0]
 277
 278        for i, param in enumerate(parameters_tranfsorm):
 279            index_param = np.where(param == model.rr_parameters.keys)[0]
 280            if len(index_param) > 0:
 281                model.rr_parameters.values[:, :, index_param[0]] = (
 282                    model.rr_parameters.values[:, :, index_param[0]]
 283                    * (dt_origin / dt_target) ** power_tranform[i]
 284                )
 285
 286    def _gathering_atmosdata(self):
 287
 288        if self._myatmos_data_connector is not None:
 289
 290            print("</> Gathering atmos data ...")
 291
 292            if (
 293                self._myatmos_data_connector.input_ntimestep
 294                != self.mysmashmodel.smash.setup.ntime_step
 295            ):
 296                print(
 297                    "</> Warnings: Inconsistant ntime_step "
 298                    f"{self._myatmos_data_connector.input_ntimestep}"
 299                    f"!={self.mysmashmodel.setup.ntime_step}, "
 300                    "the Smash model will be rebuild."
 301                )
 302
 303                self._model()
 304                self._gathering_parameters(self.mysmashmodel.smash)
 305
 306            if self._myatmos_data_connector.smash_prcp is not None:
 307                self.mysmashmodel.smash.atmos_data.prcp = (
 308                    self._myatmos_data_connector.smash_prcp
 309                )
 310
 311            if self._myatmos_data_connector.smash_pet is not None:
 312                self.mysmashmodel.smash.atmos_data.pet = (
 313                    self._myatmos_data_connector.smash_pet
 314                )
 315
 316            if self.mysmashmodel.smash.setup.prcp_partitioning:
 317                print("</> Compute prcp partitionning ...")
 318                smash.fcore._mw_atmos_statistic.compute_prcp_partitioning(
 319                    model.setup, model.mesh, model._input_data
 320                )
 321
 322            if self.mysmashmodel.smash.setup.compute_mean_atmos:
 323                print("</> Computing mean atmospheric data")
 324                smash.fcore._mw_atmos_statistic.compute_mean_atmos(
 325                    self.mysmashmodel.smash.setup,
 326                    self.mysmashmodel.smash.mesh,
 327                    self.mysmashmodel.smash._input_data,
 328                )
 329
 330    @tools.autocast_args
 331    def model_warmup(self, warmup: int = 365):
 332        """
 333        Smash model warmup function. This function warm the curent model by creating a new
 334        on with attribute warmup_model.smash. The final states of warmup_model.smash are copied to the
 335        initial states of mysmashmodel.smash.
 336
 337        Parameters
 338        ----------
 339
 340        warmup: None | int
 341            a integer of the number of days used for warming the model.
 342
 343        """
 344        print("</> Warmup the Smash-model...")
 345
 346        if warmup is not None:
 347            try:
 348                timedelta = datetime.timedelta(days=warmup)
 349            except:
 350                raise ValueError(
 351                    f"warmup arg `{warmup}` is not a valid time delta."
 352                )
 353
 354            w_setup = copy.deepcopy(self.mysetup.setup)
 355
 356            w_setup["end_time"] = w_setup["start_time"]
 357            w_setup["start_time"] = datetime.datetime.strftime(
 358                datetime.datetime.fromisoformat(w_setup["start_time"])
 359                - timedelta,
 360                "%Y-%m-%d %H:%M",
 361            )
 362            w_setup["read_qobs"] = False
 363            w_setup["read_prcp"] = True
 364            w_setup["read_pet"] = True
 365
 366            if self.warmup_model.smash is None:
 367                self.warmup_model.smash = self._model(
 368                    setup=w_setup, mesh=self.mymesh.mesh
 369                )
 370
 371            self._gathering_parameters(self.warmup_model.smash)
 372            self.warmup_model.smash.forward_run()
 373
 374            # TODO FIX BUG IN SMASH when states > 1.0
 375            mask_sup = np.where(
 376                self.warmup_model.smash.rr_final_states.values > 1
 377            )
 378            self.warmup_model.smash.rr_final_states.values[mask_sup] = 0.9999
 379
 380            if (
 381                hasattr(self, "mysmashmodel")
 382                and self.mysmashmodel.smash is not None
 383            ):
 384
 385                self.mysmashmodel.smash.rr_initial_states = (
 386                    self.warmup_model.smash.rr_final_states
 387                )
 388
 389    @tools.autocast_args
 390    def validate(
 391        self,
 392        start_time=None,
 393        end_time=None,
 394        warmup: int = 365,
 395        cost_options: dict | None = None,
 396        common_options: dict | None = None,
 397        return_options: dict | None = None,
 398    ):
 399        """
 400        Smash model validation function. This function run the smash model on a other period
 401        where start_time and end_time may differ from the setup. Currently, it won't work with the
 402        atmos_data_connector wich only apply to the main model 'mysmashmodel'.
 403
 404        Parameters
 405        ----------
 406
 407        :param start_time: The start time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 408        :type start_time: None | str, optional
 409        :param end_time: The end time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 410        :type end_time: None | str, optional
 411        :param warmup: a integer of the number of days used for warming the model.
 412        :type warmup: int, optional, default 365
 413        """
 414        print("</> Validation of the Smash-model...")
 415
 416        v_setup = copy.deepcopy(self.mysetup.setup)
 417
 418        v_setup["start_time"] = start_time
 419        v_setup["end_time"] = end_time
 420        v_setup["read_qobs"] = True
 421        v_setup["read_prcp"] = True
 422        v_setup["read_pet"] = True
 423
 424        if self.validation_model.smash is None:
 425            self.validation_model.smash = self._model(
 426                setup=v_setup, mesh=self.mymesh.mesh
 427            )
 428            print(
 429                f"</> Getting parameters from the validation model `validation_model.smash` ..."
 430            )
 431
 432            self._gathering_parameters(self.validation_model.smash)
 433
 434        if warmup > 0:
 435
 436            try:
 437                timedelta = datetime.timedelta(days=warmup)
 438            except:
 439                raise ValueError(
 440                    f"warmup arg `{warmup}` is not a valid time delta."
 441                )
 442
 443            v_setup["end_time"] = v_setup["start_time"]
 444            v_setup["start_time"] = datetime.datetime.strftime(
 445                datetime.datetime.fromisoformat(v_setup["start_time"])
 446                - timedelta,
 447                "%Y-%m-%d %H:%M",
 448            )
 449            warming_model = self._model(setup=v_setup, mesh=self.mymesh.mesh)
 450            print(
 451                f"</> Getting parameters from the warming model model `warming_model` ..."
 452            )
 453            self._gathering_parameters(warming_model)
 454            warming_model.forward_run()
 455            self.validation_model.smash.rr_initial_states = (
 456                warming_model.rr_final_states.copy()
 457            )
 458
 459        self.validation_model.extra_smash_results = (
 460            self.validation_model.smash.forward_run(
 461                cost_options=cost_options,
 462                common_options=common_options,
 463                return_options=return_options,
 464            )
 465        )
 466
 467    def optimize(
 468        self,
 469        start_time: None | str = None,
 470        end_time: None | str = None,
 471        mapping: str = "uniform",
 472        optimizer: None | str = None,
 473        optimize_options: None | str = None,
 474        cost_options: None | str = None,
 475        common_options: None | str = None,
 476        return_options: None | str = None,
 477        callback=None,
 478    ):
 479        """
 480        Optimize the current model (with the current setup and mesh),
 481        store the model in the attribute optimize_model.smash and set the calibrated
 482        parameters to the model behind the attribute mysmashmodel.smash.
 483        Start_time and end_time can be specified here to change the period of the
 484        calibration compare to the current setup. All other arguments are
 485        equivalent to the smash.model.optimize function
 486        (see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize).
 487         One difference from Smash is that gauges with no data are
 488         automatically removed from the optimization without raising an error.
 489         This provide a convient way to calibrate quickly the parameters using the current
 490         mesh which may include gauges with data for calibration
 491         and location gauge for discharges computation.
 492
 493        :param start_time: The start time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 494        :type start_time: None | str, optional
 495        :param end_time: The end time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 496        :type end_time: None | str, optional
 497        :param mapping: Type of mapping, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to "uniform"
 498        :type mapping: str, optional
 499        :param optimizer: Name of optimizer, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 500        :type optimizer: None | str, optional
 501        :param optimize_options: Dictionary containing optimization options for fine-tuning the optimization process, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 502        :type optimize_options: None | str, optional
 503        :param cost_options: Dictionary containing computation cost options for simulated and observed responses. see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 504        :type cost_options: None | str, optional
 505        :param common_options: Dictionary containing common options with two elements: ncpu (int, default 1) and verbose (bool, default is False), defaults to None
 506        :type common_options: None | str, optional
 507        :param return_options: Dictionary containing return options to save additional simulation results, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 508        :type return_options: None | str, optional
 509        :param callback: A callable called after each iteration with the signature callback(iopt: Optimize), see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 510        :type callback: TYPE, optional
 511
 512        """
 513        o_setup = copy.deepcopy(self.mysetup.setup)
 514
 515        if start_time is not None and end_time is not None:
 516            o_setup["start_time"] = start_time
 517            o_setup["end_time"] = end_time
 518
 519        o_setup["read_qobs"] = True
 520        o_setup["read_prcp"] = True
 521        o_setup["read_pet"] = True
 522
 523        if self.optimize_model.smash is None:
 524            self.optimize_model.smash = self._model(
 525                setup=o_setup, mesh=self.mymesh.mesh
 526            )
 527            print(
 528                f"</> Getting parameters from the default model `mysmashmodel.smash` ..."
 529            )
 530
 531            for key in list(self.optimize_model.smash.rr_parameters.keys):
 532                pos = list(self.optimize_model.smash.rr_parameters.keys).index(
 533                    key
 534                )
 535                pos_w = list(self.mysmashmodel.smash.rr_parameters.keys).index(
 536                    key
 537                )
 538                self.optimize_model.smash.rr_parameters.values[:, :, pos] = (
 539                    self.mysmashmodel.smash.rr_parameters.values[:, :, pos_w]
 540                )
 541
 542        if self.warmup_model.smash is not None:
 543            for key in list(self.optimize_model.smash.rr_initial_states.keys):
 544                pos = list(
 545                    self.optimize_model.smash.rr_initial_states.keys
 546                ).index(key)
 547                pos_w = list(
 548                    self.warmup_model.smash.rr_initial_states.keys
 549                ).index(key)
 550                self.optimize_model.smash.rr_initial_states.values[
 551                    :, :, pos
 552                ] = self.warmup_model.smash.rr_final_states.values[:, :, pos_w]
 553
 554        default_cost_option = {
 555            "end_warmup": o_setup["start_time"],
 556            "gauge": "all",
 557        }
 558        if cost_options is not None:
 559            default_cost_option.update(cost_options)
 560
 561        # auto remove gauge if no observation
 562        if isinstance(default_cost_option["gauge"], str):
 563            if default_cost_option["gauge"] == "dws":
 564                gauge = np.empty(shape=0)
 565
 566                for i, pos in enumerate(
 567                    self.optimize_model.smash.mesh.gauge_pos
 568                ):
 569                    if (
 570                        self.optimize_model.smash.mesh.flwdst[tuple(pos)]
 571                        == 0.0
 572                    ):
 573                        gauge = np.append(
 574                            gauge, self.optimize_model.smash.mesh.code[i]
 575                        )
 576
 577            elif default_cost_option["gauge"] == "all":
 578                gauge = np.array(self.optimize_model.smash.mesh.code, ndmin=1)
 579            else:
 580                gauge = np.array(default_cost_option["gauge"], ndmin=1)
 581        elif isinstance(default_cost_option["gauge"], list):
 582            gauge = np.array(default_cost_option["gauge"], ndmin=1)
 583
 584        st = pd.Timestamp(self.optimize_model.smash.setup.start_time)
 585        et = pd.Timestamp(self.optimize_model.smash.setup.end_time)
 586        ew = pd.Timestamp(default_cost_option["end_warmup"])
 587        start_slice = int(
 588            (ew - st).total_seconds() / self.optimize_model.smash.setup.dt
 589        )
 590        # end_slice = start_slice+int((et - ew).total_seconds() / self.optimize_model.smash.setup.dt)
 591        end_slice = -1
 592        time_slice = slice(start_slice, end_slice)
 593
 594        del_gauge = []
 595        for i in range(len(gauge)):
 596            pos = np.where(self.optimize_model.smash.mesh.code == gauge[i])[0][
 597                0
 598            ]
 599            # print(i, pos)
 600            if np.all(
 601                self.optimize_model.smash.response_data.q[pos, time_slice] < 0
 602            ):
 603                del_gauge.append(i)
 604
 605                print(
 606                    f"No observed discharge available at gauge '{gauge[i]}' for the selected "
 607                    f"optimization period ['{ew}', '{et}']. This gauge is removed "
 608                    f"from the optimization."
 609                )
 610
 611        gauge = np.delete(gauge, del_gauge)
 612        default_cost_option.update({"gauge": gauge})
 613
 614        self.optimize_model.smash.optimize(
 615            mapping,
 616            optimizer,
 617            optimize_options,
 618            default_cost_option,
 619            common_options,
 620            return_options,
 621            callback,
 622        )
 623
 624        if (
 625            hasattr(self, "mysmashmodel")
 626            and self.mysmashmodel.smash is not None
 627        ):
 628            self.mysmashmodel.smash.rr_parameters = (
 629                self.optimize_model.smash.rr_parameters.copy()
 630            )
 631
 632    @tools.autocast_args
 633    def forward_run(
 634        self,
 635        warmup: int | None = None,
 636        invert_states: bool | None = False,
 637        cost_options: dict | None = None,
 638        common_options: dict | None = None,
 639        return_options: dict | None = None,
 640    ):
 641        """
 642        Smash model forward run.This function wrap smash.model.forward_run().
 643
 644        Parameters
 645        ----------
 646
 647        warmup: None | int
 648            a integer of the number of days used for warming the model.
 649        invert_states : bool = False
 650            invert states of the model, so that the final states are used
 651            for the initial states.
 652        cost_options : dict | None,
 653            Dictionary containing computation cost options for simulated and observed responses (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html).
 654        common_options: dict| None,
 655            Dictionary containing common options with two elements, ncpu and verbose (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html)
 656        return_options : dict | None,
 657            Dictionary containing return options to save additional simulation results. (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html)
 658
 659        Examples
 660        --------
 661
 662        >>> es=smashbox.SmashBox()
 663        >>> sb.newmodel("RealCollobrier")
 664        >>> sb.RealCollobrier.generate_mesh(run=False)
 665        >>> sb.RealCollobrier.model()
 666        >>> sb.RealCollobrier.forward_run()
 667        """
 668
 669        if self.mysmashmodel.smash is None:
 670            self.model()
 671
 672        if warmup is not None:
 673            self.model_warmup(warmup=warmup)
 674
 675        if self.warmup_model.smash is not None:
 676            for key in list(self.mysmashmodel.smash.rr_initial_states.keys):
 677                pos = list(
 678                    self.mysmashmodel.smash.rr_initial_states.keys
 679                ).index(key)
 680                pos_w = list(
 681                    self.warmup_model.smash.rr_initial_states.keys
 682                ).index(key)
 683                self.mysmashmodel.smash.rr_initial_states.values[:, :, pos] = (
 684                    self.warmup_model.smash.rr_final_states.values[:, :, pos_w]
 685                )
 686
 687        if self.optimize_model.smash is not None:
 688            for key in list(self.mysmashmodel.smash.rr_parameters.keys):
 689                pos = list(self.mysmashmodel.smash.rr_parameters.keys).index(
 690                    key
 691                )
 692                pos_w = list(
 693                    self.optimize_model.smash.rr_parameters.keys
 694                ).index(key)
 695                self.mysmashmodel.smash.rr_parameters.values[:, :, pos] = (
 696                    self.optimize_model.smash.rr_parameters.values[:, :, pos_w]
 697                )
 698
 699        # needed here in order to replace the rainfall without rebuild the model
 700        self._gathering_atmosdata()
 701
 702        if invert_states:
 703            if self._fstates is not None and not np.all(
 704                self._fstates == -99.0
 705            ):
 706
 707                # TODO FIX BUG IN SMASH when states > 1.0
 708                mask_inf = np.where(
 709                    self.mysmashmodel.smash.mesh.active_cell == 1
 710                )
 711                mask_sup = np.where(self._fstates > 1)
 712                self._fstates[mask_sup] = 0.9999
 713
 714                self.mysmashmodel.smash.rr_initial_states.values[mask_inf] = (
 715                    self._fstates[mask_inf].copy()
 716                )
 717
 718            else:
 719                print(
 720                    "</> model final states does not exist."
 721                    " Invert states is not possible."
 722                    " Smash default initial states is used."
 723                )
 724
 725        self.mysmashmodel.extra_smash_results = (
 726            self.mysmashmodel.smash.forward_run(
 727                cost_options=cost_options,
 728                common_options=common_options,
 729                return_options=return_options,
 730            )
 731        )
 732
 733        # states backup
 734        self._fstates = self.mysmashmodel.smash.rr_final_states.values.copy()
 735        self._istates = self.mysmashmodel.smash.rr_initial_states.values.copy()
 736
 737    @tools.autocast_args
 738    def atmos_data_connector(
 739        self,
 740        input_prcp: np.ndarray | None = None,
 741        input_pet: np.ndarray | None = None,
 742        input_dt: float | None = None,
 743        input_res: tuple | list = (1000.0, 1000.0),
 744        input_start_time: str = "2050-01-01 01:00",
 745        input_bbox: dict | None = None,
 746        input_epsg: int = 2154,
 747        resampling_method="home_made_with_scipy_zoom",
 748    ):
 749        """
 750        Generate a connector for the external atmos_data comming from
 751        other model such as Graffas (spatial rainfall generator).
 752
 753        Parameters
 754        ----------
 755
 756        input_prcp : np.ndarray | None = None
 757            An np.ndarray with shape (nrow, ncol, npdt) wich stores the spatial rainfall
 758            which will be used by Smash.Ideally, the extend of this array match
 759            exactlly the extend of the Smash domain.
 760        input_pet : np.ndarray | None = None
 761            An np.ndarray with shape (nrow, ncol, npdt) wich stores the spatial
 762            evapotranspiration which will be used by Smash.
 763        input_dt : float = 3600.
 764            Time step in seconds of the input precipitation
 765        input_res : tuple | list = (1000., 1000.)
 766            The resolution of the input precipitation
 767        input_start_time : str = "2050-01-01 01:00"
 768            The date of the start_time
 769        input_bbox : dict | None = None
 770            The extend of the domain using a bbox.
 771            The convention used here is a dictionary like
 772            bbox={"left":xmin, "top": ymax, "right": xmax, "bottom": ymin}.
 773            If not provided, the extend of the smash domain will be used starting
 774            from xmin and ymin.
 775        input_epsg : int = 2154
 776            The epsg code of the coordinate system. If not provided,
 777            the coordinate system used in Smash will be used.
 778        resampling_method: str
 779            The method to use to resample and crop the input array. Default is
 780            'home_made_with_scipy_zoom' Choice are:
 781            ['rasterio_1', 'rasterio_2', 'home_made_with_scipy_zoom'].
 782            'home_made_with_scipy_zoom' is the fastest method. 'rasterio_1'
 783            is the slowest method. However, 'rasterio' method use much tested and
 784            reliable method to resample and crop the array.
 785
 786        Examples
 787        --------
 788
 789        >>> prcp_array=np.zeros(200,200,20)+1.
 790        >>> es=smashbox.SmashBox()
 791        >>> sb.newmodel("RealCollobrier")
 792        >>> sb.RealCollobrier.generate_mesh()
 793        >>> sb.RealCollobrier.atmos_data_connector(input_prcp=prcp_array)
 794
 795        """
 796        if input_prcp is None and input_pet is None:
 797            raise ValueError(
 798                "</> input_prcp and input_pet are None. input_prcp or input_pet must be"
 799                " numpy.ndarray with shape (nrow, ncol, ntimesteps)."
 800            )
 801
 802        if input_dt is None:
 803            print(
 804                "</> We suppose the input atmos data time-step equal to the model time-step."
 805            )
 806            input_dt = self.mysetup.setup["dt"]
 807
 808        if input_dt != self.mysetup.setup["dt"]:
 809            print(
 810                "</> Model and input atmos data have different time-step. "
 811                "Input atmos data will be resampled."
 812            )
 813            if input_prcp is not None:
 814                input_prcp = tools.time_resample_prcp_array(
 815                    input_prcp,
 816                    input_dt,
 817                    self.mysetup.setup["dt"],
 818                    t_axis=2,
 819                )
 820            if input_pet is not None:
 821                input_pet = tools.time_resample_prcp_array(
 822                    input_pet,
 823                    input_dt,
 824                    self.mysetup.setup["dt"],
 825                    t_axis=2,
 826                )
 827            input_dt = self.mysetup.setup["dt"]
 828
 829        # use conversion factor defined in setup
 830        if (
 831            "prcp_conversion_factor" in self.mysetup.setup
 832            and input_prcp is not None
 833        ):
 834            input_prcp = (
 835                input_prcp * self.mysetup.setup["prcp_conversion_factor"]
 836            )
 837        if (
 838            "pet_conversion_factor" in self.mysetup.setup
 839            and input_pet is not None
 840        ):
 841            input_pet = input_pet * self.mysetup.setup["pet_conversion_factor"]
 842
 843        self._myatmos_data_connector = (
 844            atmos_data_connector.atmos_data_connector(
 845                input_prcp=input_prcp,
 846                input_pet=input_pet,
 847                input_dt=input_dt,
 848                input_res=input_res,
 849                input_start_time=input_start_time,
 850                input_bbox=input_bbox,
 851                input_epsg=input_epsg,
 852            )
 853        )
 854
 855        smash_bbox = geo_toolbox.get_bbox_from_smash_mesh(self.mymesh.mesh)
 856
 857        if self.mymesh.mesh["nrow"] > input_prcp.shape[0]:
 858            print(
 859                f"</> Warning !! Input rainfall domain is smaller than the smash domain  {input_prcp.shape[0]}<{self.mymesh.mesh['nrow']}"
 860            )
 861        if self.mymesh.mesh["ncol"] > input_prcp.shape[1]:
 862            print(
 863                f"</> Warning !! Input rainfall domain is smaller than the smash domain  {input_prcp.shape[1]}<{self.mymesh.mesh['ncol']}"
 864            )
 865
 866        self._myatmos_data_connector.read_input_atmos_data(
 867            output_bbox=smash_bbox,
 868            output_epsg=self._myparam.param.epsg,
 869            output_res=(self.mymesh.mesh["xres"], self.mymesh.mesh["yres"]),
 870            output_shape=(self.mymesh.mesh["nrow"], self.mymesh.mesh["ncol"]),
 871            resampling_method=resampling_method,
 872        )
 873
 874        self._myatmos_data_connector.change_setup(self.mysetup)
 875
 876    def import_parameters(self, model=None):
 877        """
 878        Import Geotiff parameter in Smash. This function wrap
 879        smash.io.read_grid_parameters(). Path to the parameters is defined in
 880        self._myparam.param.smash_parameters.
 881
 882        Parameter
 883        ---------
 884
 885        model: smash.Model object
 886            A Smash model object. If None, the Smash model of smashbox will be used
 887
 888        Examples
 889        --------
 890
 891        >>> es=smashbox.SmashBox()
 892        >>> sb.newmodel("RealCollobrier")
 893        >>> sb.RealCollobrier.generate_mesh(run=False)
 894        >>> sb.RealCollobrier.model()
 895        >>> sb.RealCollobrier.import_parameters()
 896        >>> sb.RealCollobrier.forward_run()
 897        """
 898        if self._myparam.param.smash_parameters is None:
 899            print(
 900                "</> Warning: no calibrated Smash parameters is used, leaving it to"
 901                "  default."
 902            )
 903            return
 904
 905        if model is None:
 906            model = self.mysmashmodel.smash
 907
 908        smash.io.read_grid_parameters(
 909            model=model,
 910            path=self._myparam.param.smash_parameters,
 911        )
 912
 913    def export_parameters(self, path: os.PathLike = "./output_smash_param"):
 914        """
 915        Export Geotiff Smash parameter as Geotiff. This function wrap
 916        smash.io.save_grid_parameters().
 917
 918        Parameters
 919        ----------
 920
 921        path : os.PathLike = "./output_smash_param"
 922            path to a directory where the parameter will be saved.
 923
 924        Examples
 925        --------
 926
 927        >>> es=smashbox.SmashBox()
 928        >>> sb.newmodel("RealCollobrier")
 929        >>> sb.RealCollobrier.generate_mesh(run=False)
 930        >>> sb.RealCollobrier.model()
 931        >>> sb.RealCollobrier.import_parameters()
 932        >>> sb.RealCollobrier.export_parameters()
 933        """
 934        smash.io.export_parameters(self.mysmashmodel.smash, path)
 935
 936    def save_model_container_hdf5(
 937        self, path_to_hdf5: str | None = None, save_smash_model: bool = True
 938    ):
 939        """
 940
 941        :param path_to_hdf5: Path to the hdf5 file, defaults to None. If None,
 942        the fucntion will return a dictionnary containing all data of the self object.
 943        :type path_to_hdf5: str | None, optional
 944        :param save_smash_model: Savec the smash models objects or no, defaults
 945        to True. If False the Smash models objects are not saved.
 946        :type save_smash_model: str, optional
 947        :return: if a path_to_hdf5 is None, a dictionary containing all
 948        attribute of the input object self is returned.
 949        :rtype: dict | None
 950
 951        """
 952
 953        if path_to_hdf5 is not None:
 954
 955            structure = (
 956                pyhdf5_handler.src.object_handler.generate_object_structure(
 957                    self, include_method=False
 958                )
 959            )
 960
 961            if not save_smash_model:
 962                del structure["warmup_model"]["smash"]
 963                del structure["mysmashmodel"]["smash"]
 964                del structure["optimize_model"]["smash"]
 965                del structure["validation_model"]["smash"]
 966
 967            pyhdf5_handler.save_object_to_hdf5file(
 968                path_to_hdf5=path_to_hdf5,
 969                instance=self,
 970                keys_data=structure,
 971                location=f"./{self._model_name}/",
 972            )
 973
 974            # Add missing data
 975            structure = (
 976                pyhdf5_handler.src.object_handler.generate_object_structure(
 977                    self._myparam, include_method=False
 978                )
 979            )
 980
 981            pyhdf5_handler.save_object_to_hdf5file(
 982                path_to_hdf5=path_to_hdf5,
 983                instance=self._myparam,
 984                keys_data=structure,
 985                location=f"./{self._model_name}/",
 986            )
 987            pyhdf5_handler.save_dict_to_hdf5file(
 988                path_to_hdf5=path_to_hdf5,
 989                dictionary={"istates": self._istates},
 990                location=f"./{self._model_name}/",
 991            )
 992            pyhdf5_handler.save_dict_to_hdf5file(
 993                path_to_hdf5=path_to_hdf5,
 994                dictionary={"fstates": self._fstates},
 995                location=f"./{self._model_name}/",
 996            )
 997
 998        else:
 999            dict_results = (
1000                pyhdf5_handler.src.object_handler.read_object_as_dict(self)
1001            )
1002
1003            if not save_smash_model:
1004                del dict_results["warmup_model"]["smash"]
1005                del dict_results["mysmashmodel"]["smash"]
1006                del structure["optimize_model"]["smash"]
1007                del structure["validation_model"]["smash"]
1008
1009            return dict_results
1010
1011    def save_model_container(
1012        self,
1013        path: str | None = None,
1014        save_full_model=False,
1015    ):
1016        """
1017
1018        :param path: Path to the hdf5 file, defaults to None. If None,
1019        the fucntion will return a dictionnary containing all data of the self object.
1020        :type path: str | None, optional
1021
1022        """
1023
1024        if path is None:
1025            raise ValueError("Argument `path` is None.")
1026            return
1027
1028        if save_full_model is True:
1029            save_func = getattr(smash.io, "save_model")
1030        else:
1031            save_func = getattr(smash.io, "save_model_ddt")
1032
1033        if not os.path.exists(os.path.join(path, self._model_name)):
1034            os.makedirs(os.path.join(path, self._model_name))
1035
1036        structure = (
1037            pyhdf5_handler.src.object_handler.generate_object_structure(
1038                self, include_method=False
1039            )
1040        )
1041
1042        if "warmup_model" in structure:
1043            del structure["warmup_model"]["smash"]
1044        if "mysmashmodel" in structure:
1045            del structure["mysmashmodel"]["smash"]
1046        if "optimize_model" in structure:
1047            del structure["optimize_model"]["smash"]
1048        if "validation_model" in structure:
1049            del structure["validation_model"]["smash"]
1050
1051        pyhdf5_handler.save_object_to_hdf5file(
1052            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1053            instance=self,
1054            keys_data=structure,
1055            location=f"./",
1056        )
1057
1058        structure = (
1059            pyhdf5_handler.src.object_handler.generate_object_structure(
1060                self._myparam, include_method=False
1061            )
1062        )
1063        pyhdf5_handler.save_object_to_hdf5file(
1064            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1065            instance=self._myparam,
1066            keys_data=structure,
1067            location=f"./",
1068        )
1069
1070        pyhdf5_handler.save_dict_to_hdf5file(
1071            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1072            dictionary={"istates": self._istates},
1073            location=f"./",
1074        )
1075        pyhdf5_handler.save_dict_to_hdf5file(
1076            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1077            dictionary={"fstates": self._fstates},
1078            location=f"./",
1079        )
1080
1081        if self.mysmashmodel.smash is not None:
1082            save_func(
1083                self.mysmashmodel.smash,
1084                os.path.join(path, self._model_name, "mysmashmodel.hdf5"),
1085            )
1086        if self.warmup_model.smash is not None:
1087            save_func(
1088                self.warmup_model.smash,
1089                os.path.join(path, self._model_name, "warmup_model.hdf5"),
1090            )
1091        if self.optimize_model.smash is not None:
1092            save_func(
1093                self.optimize_model.smash,
1094                os.path.join(path, self._model_name, "optimize_model.hdf5"),
1095            )
1096        if self.validation_model.smash is not None:
1097            save_func(
1098                self.validation_model.smash,
1099                os.path.join(path, self._model_name, "validation_model.hdf5"),
1100            )
1101
1102    # def save_model_container_pickle(
1103    #     self,
1104    #     path: str | None = None,
1105    # ):
1106    #     """
1107
1108    #     :param path: Path to the hdf5 file, defaults to None. If None,
1109    #     the fucntion will return a dictionnary containing all data of the self object.
1110    #     :type path: str | None, optional
1111
1112    #     """
1113
1114    #     if path is not None:
1115    #         if not os.path.exists(os.path.join(path, self._model_name)):
1116    #             os.makedirs(os.path.join(path, self._model_name))
1117
1118    #         if self.mysmashmodel.smash is not None:
1119    #             smash.io.save_model(
1120    #                 self.mysmashmodel.smash,
1121    #                 os.path.join(path, self._model_name, "mysmashmodel.smash.hdf5"),
1122    #             )
1123    #         if self.warmup_model.smash is not None:
1124    #             smash.io.save_model(
1125    #                 self.warmup_model.smash,
1126    #                 os.path.join(path, self._model_name, "warmup_model.hdf5"),
1127    #             )
1128    #         if self.optimize_model.smash is not None:
1129    #             smash.io.save_model(
1130    #                 self.optimize_model.smash,
1131    #                 os.path.join(path, self._model_name, "optimize_model.hdf5"),
1132    #             )
1133
1134    #         structure = pyhdf5_handler.src.object_handler.generate_object_structure(
1135    #             self, include_method=False
1136    #         )
1137
1138    #         structure = [
1139    #             "mysetup",
1140    #             "mymesh",
1141    #             "mysmashmodel",
1142    #             "warmup_model",
1143    #             "optimize_model",
1144    #             "extra_smash_results",
1145    #             # "mystats", #input arg is self, and it is a weak ref (cannot pickle it)
1146    #             # "myplot", #input arg is self, and it is a weak ref (cannot pickle it)
1147    #             "_model_name",
1148    #             "_myparam",
1149    #             "_istates",
1150    #             "_fstates",
1151    #             "_myatmos_data_connector",
1152    #         ]
1153
1154    #         if "warmup_model" in structure:
1155    #             structure.remove("warmup_model")
1156    #         if "mysmashmodel" in structure:
1157    #             structure.remove("mysmashmodel")
1158    #         if "optimize_model" in structure:
1159    #             structure.remove("optimize_model")
1160
1161    #         for key in structure:
1162    #             print(key)
1163    #             with open(os.path.join(path, self._model_name, f"{key}.pkl"), "wb") as f:
1164    #                 obj = getattr(self, key)
1165    #                 if obj is not None:
1166    #                     pickle.dump(obj, f)
1167
1168    # def import_parameters(self)
1169    # if os.path.exists(self._myparam.param.smash_parameters) and self._myparam.param.smash_parameters.endswith(".hdf5"):
1170
1171    #     hdf5= pyhdf5_handler.open_hdf5(self._myparam.param.smash_parameters)
1172    #     hdf5_key=list(hdf5.keys())
1173    #     hdf5.close()
1174
1175    #     if "model_ddt" in hdf5_key:
1176    #         with_param = pyhdf5_handler.read_hdf5file_as_dict(self._myparam.param.smash_parameters,location="model_ddt/rr_parameters")
1177    #         with_mesh = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="model_ddt",item="mesh")
1178
1179    #         smash_parameters.transfert_params_to_model(
1180    #             from_mesh=
1181    #             with_mesh,
1182    #             with_param=with_param,
1183    #             to_model=self.mysmashmodel.smash)
1184
1185    #     elif "model" in hdf5_key:
1186    #         with_param = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="model",item="rr_parameters")
1187    #         with_mesh = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="model",item="mesh")
1188
1189    #         smash_parameters.transfert_params_to_model(
1190    #             from_mesh=
1191    #             with_mesh,
1192    #             with_param=with_param,
1193    #             to_model=self.mysmashmodel.smash)
1194
1195    #     elif "rr_parameters" in hdf5.keys() and "mesh" in hdf5.keys():
1196    #         with_param = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="./",item="rr_parameters")
1197    #         with_mesh = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="./",item="mesh")
1198
1199    #         smash_parameters.transfert_params_to_model(
1200    #             from_mesh=with_mesh,
1201    #             with_param=with_param,
1202    #             to_model=self.mysmashmodel.smash)
1203
1204    #     else:
1205    #         raise ValueError("</> Error: cannot load the parameters ... unknown format...")
1206
1207    # elif os.path.isdir(self._myparam.param.smash_parameters):
1208
1209    #     smash_parameters.load_param_from_tiffformat(model=self.mysmashmodel.smash, path_to_parameters=self._myparam.param.smash_parameters)
1210
1211    # else:
1212    #     raise ValueError(f"</> Error: cannot load the parameters. Path or file '{self._myparam.param.smash_parameters}' does not exist ... ")
1213
1214    # def write_smashparam(self, hdf5file : str | os.PathLike = "output_smash_param.hdf5"):
1215
1216    #     rr_parameters=pyhdf5_handler.read_object_as_dict(self.mysmashmodel.smash.rr_parameters)
1217    #     pyhdf5_handler.save_dict_to_hdf5(hdf5file, rr_parameters)
1218
1219    # def export_parameters(self, path : os.path = "./output_smash_param")
1220
1221    # list_param=list(self.mysmashmodel.smash.rr_parameters.keys)
1222
1223    # for smparam in list_param:
1224
1225    #     array=self.mysmashmodel.smash.rr_parameters.values[:,:,list_param.index(smparam)]
1226
1227    #     smash_parameters.write_array_to_geotiff(os.path.join(path, smparam+".tif"),
1228    #                            array,
1229    #                            self.mysmashmodel.smash.mesh.xmin,
1230    #                            self.mysmashmodel.smash.mesh.ymax,
1231    #                            output_res = (self.mymesh.mesh["xres"],
1232    #                                          self.mymesh.mesh["yres"])
1233    #                            )
class model:
  27class model:
  28    """Main class model() which has a complete set of functions, class and attributes to
  29    build, run and manipulate the Smash model, compute statistical criterium and
  30    plot graphics."""
  31
  32    def __init__(self, name, myparam):
  33
  34        self._model_name = name
  35        """The attribute _model_name is contain the name of the current object"""
  36
  37        self._myparam = copy.deepcopy(myparam)
  38        """The attribute _myparam is a copy of the parent class smashbox.myparam.
  39        This class stores the main parameters used for building the Smash model"""
  40
  41        self.mysetup = setup.setup(self._myparam.param)
  42        """The attribute mysetup own the class setup.setup(). This class stores the Smash
  43        setup for the hydrological simulation and some helpers to manipulate these
  44        parameters."""
  45
  46        self.mymesh = mesh.mesh(self.mysetup)
  47        """The attribute mymesh own the class mesh.mesh(). This class stores the Smash
  48        mesh used for the hydrological simulation and some helpers to manipulate this mesh.
  49        """
  50
  51        self._fstates = None
  52        self._istates = None
  53        self._myatmos_data_connector = None
  54
  55        self.mysmashmodel = smash_model.smash_model()
  56        """The attribute mysmashmodel stores the Smash model object created with
  57        attributes mysetup and -mymesh."""
  58
  59        self.warmup_model = smash_model.smash_model()
  60        """The attribute warmup_model store a smash model used for warmup and compatible 
  61        with the model in attribute mysmashmodel"""
  62
  63        self.optimize_model = smash_model.smash_model()
  64        """The attribute optimize_model store a smash model used for optimization and compatible 
  65        with the model in attribute mysmashmodel"""
  66
  67        self.validation_model = smash_model.smash_model()
  68        """The attribute validation_model stores the Smash model object used for validation"""
  69
  70        self.myplot = myplot.myplot(self)
  71        """The attribute myplot own the class myplot.myplot(). This class stores plotting
  72        # capabilities on the smash model object."""
  73
  74    def generate_mesh(
  75        self,
  76        max_depth: float = 1.0,
  77        query: str | None = None,
  78        area_error_th: None | float = None,
  79        lacuna_threshold: None | float = None,
  80    ):
  81        """
  82        Generate the mesh of the Smash model
  83
  84        Parameters
  85        ----------
  86
  87        max_depth : `int`, default 1
  88            The maximum depth accepted by the algorithm to find the catchment outlet.
  89            A **max_depth** of 1 means that the algorithm will search among the
  90            combinations in
  91            (``row - 1``, ``row``, ``row + 1``; ``col - 1``, ``col``, ``col + 1``),
  92            the coordinates that minimize
  93            the relative error between the given catchment area and the modeled
  94            catchment area calculated from the
  95            flow directions file.
  96        :param query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query
  97        must be build using the field (column name) in the outlet database.
  98        https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method
  99        :type query: str
 100        area_error_th: float | None
 101            The tolerance error for the difference between the observed and simulated
 102             surface. The error is computed as follow:
 103                 Serror=abs(Ssim-Sobs)/Sobs
 104            All outlets where `Serror > area_error_th` will be automatically removed from
 105            the mesh.
 106        :type area_error_th: float
 107        :param lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed.
 108        :type: float | Nonetype
 109
 110        Examples
 111        --------
 112
 113        >>> es=smashbox.SmashBox()
 114        >>> sb.newmodel("RealCollobrier")
 115        >>> sb.RealCollobrier.generate_mesh(min_surf=5, max_surf=100)
 116
 117        """
 118        self.mymesh.generate_mesh(
 119            self._myparam.param,
 120            max_depth=max_depth,
 121            query=query,
 122            area_error_th=area_error_th,
 123            lacuna_threshold=lacuna_threshold,
 124        )
 125
 126    def model(
 127        self,
 128        setup: dict | None = None,
 129        mesh: dict | None = None,
 130        read_data=None,
 131    ):
 132        """
 133        Smash model object creation. This function wrap smash.Model(). Setup and mesh
 134        argument are optional since these dictionnary are hosted by the smashbox object.
 135
 136        Parameters
 137        ----------
 138        setup: dict | None
 139            The Smash setup (optionnal), if None the smashbox setup will be used
 140        mesh: dict | None
 141            The Smash mesh (optionnal), if None the smashbox mesh will be used
 142
 143        Examples
 144        --------
 145
 146        >>> es=smashbox.SmashBox()
 147        >>> sb.newmodel("RealCollobrier")
 148        >>> sb.RealCollobrier.generate_mesh(run=False)
 149        >>> sb.RealCollobrier.model()
 150
 151        """
 152        if setup is None:
 153            setup = self.mysetup.setup.copy()
 154
 155        if mesh is None:
 156            mesh = self.mymesh.mesh.copy()
 157
 158        if read_data is False:
 159            setup.update(
 160                {
 161                    "read_prcp": False,
 162                    "read_pet": False,
 163                    "read_snow": False,
 164                    "read_qobs": False,
 165                    "read_temp": False,
 166                }
 167            )
 168        if read_data is True:
 169            setup.update(
 170                {
 171                    "read_prcp": True,
 172                    "read_pet": True,
 173                    "read_snow": True,
 174                    "read_qobs": True,
 175                    "read_temp": True,
 176                }
 177            )
 178
 179        self.mysmashmodel.smash = self._model(setup=setup, mesh=mesh)
 180
 181        if read_data is True:
 182            self._gathering_atmosdata()
 183
 184        self._gathering_parameters(self.mysmashmodel.smash)
 185
 186    def _model(self, setup=None, mesh=None):
 187        """
 188        Smash model object creation. This function wrap smash.Model()
 189
 190        Parameters
 191        ----------
 192        setup: dict | None
 193            The Smash setup (optionnal), if None the smashbox setup will be used
 194        mesh: dict | None
 195            The Smash mesh (optionnal), if None the smashbox mesh will be used
 196
 197        Examples
 198        --------
 199
 200        >>> es=smashbox.SmashBox()
 201        >>> sb.newmodel("RealCollobrier")
 202        >>> sb.RealCollobrier.generate_mesh(run=False)
 203        >>> sb.RealCollobrier.model()
 204
 205        """
 206        if setup is None:
 207            print("</> The input setup is None ")
 208            return None
 209
 210        if mesh is None:
 211            print(
 212                "</> input mesh is None. use smashbox.model.model.generate_mesh()"
 213            )
 214            return None
 215
 216        if self._myparam.param.enhanced_smash_input_data:
 217            model = smashmodel.SmashModel(setup, mesh)
 218        else:
 219            model = smash.Model(setup, mesh)
 220
 221        return model
 222
 223    def _gathering_parameters(self, model):
 224
 225        if self.optimize_model.smash is not None:
 226            print(
 227                f"</> Getting parameters from previously optimized model ..."
 228            )
 229            model.rr_parameters = self.optimize_model.smash.rr_parameters
 230        else:
 231            print(
 232                f"</> Importing parameters from {self._myparam.param._smash_parameters} ..."
 233            )
 234            self.import_parameters(model=model)
 235            self._transform_parameters(
 236                model=model, dt_origin=self._myparam.param._smash_parameters_dt
 237            )
 238
 239    def _transform_parameters(
 240        self, model=None, dt_origin: None | float = None
 241    ):
 242        """
 243        Function to transform the parameters according the model time-step and the original
 244        time-step used for calibrated the parameters.
 245        :param dt_origin: Original time-step used for generate the calibrated parameter,
 246        defaults to None
 247        :type dt_origin: None | float, optional
 248
 249        """
 250        # if dt_origin is None:
 251        #     raise ValueError(
 252        #         " Argument dt_origin is None. This must be filled with the value "
 253        #         "of the timestep used to calibrate the parameters."
 254        #     )
 255
 256        # if not hasattr(self, "mysmashmodel.smash") or self.mysmashmodel.smash is None:
 257        #     raise ValueError(
 258        #         "</> mysmashmodel.smash attribute does not exist or is None. "
 259        #         "The model must be created first..."
 260        #     )
 261        if model is None:
 262            return
 263
 264        dt_target = model.setup.dt
 265
 266        if dt_origin is None:
 267            return
 268
 269        if dt_target == dt_origin:
 270            return
 271
 272        print(
 273            f"</> Tranforming parameters `ct`, `kexec`, `llr` calibrated with a time-step "
 274            f"of {dt_origin}s to the modeled time-step {dt_target}s."
 275        )
 276        parameters_tranfsorm = ["ct", "kexc", "llr"]
 277        power_tranform = [1.0 / 4.0, -1.0 / 8.0, 1.0]
 278
 279        for i, param in enumerate(parameters_tranfsorm):
 280            index_param = np.where(param == model.rr_parameters.keys)[0]
 281            if len(index_param) > 0:
 282                model.rr_parameters.values[:, :, index_param[0]] = (
 283                    model.rr_parameters.values[:, :, index_param[0]]
 284                    * (dt_origin / dt_target) ** power_tranform[i]
 285                )
 286
 287    def _gathering_atmosdata(self):
 288
 289        if self._myatmos_data_connector is not None:
 290
 291            print("</> Gathering atmos data ...")
 292
 293            if (
 294                self._myatmos_data_connector.input_ntimestep
 295                != self.mysmashmodel.smash.setup.ntime_step
 296            ):
 297                print(
 298                    "</> Warnings: Inconsistant ntime_step "
 299                    f"{self._myatmos_data_connector.input_ntimestep}"
 300                    f"!={self.mysmashmodel.setup.ntime_step}, "
 301                    "the Smash model will be rebuild."
 302                )
 303
 304                self._model()
 305                self._gathering_parameters(self.mysmashmodel.smash)
 306
 307            if self._myatmos_data_connector.smash_prcp is not None:
 308                self.mysmashmodel.smash.atmos_data.prcp = (
 309                    self._myatmos_data_connector.smash_prcp
 310                )
 311
 312            if self._myatmos_data_connector.smash_pet is not None:
 313                self.mysmashmodel.smash.atmos_data.pet = (
 314                    self._myatmos_data_connector.smash_pet
 315                )
 316
 317            if self.mysmashmodel.smash.setup.prcp_partitioning:
 318                print("</> Compute prcp partitionning ...")
 319                smash.fcore._mw_atmos_statistic.compute_prcp_partitioning(
 320                    model.setup, model.mesh, model._input_data
 321                )
 322
 323            if self.mysmashmodel.smash.setup.compute_mean_atmos:
 324                print("</> Computing mean atmospheric data")
 325                smash.fcore._mw_atmos_statistic.compute_mean_atmos(
 326                    self.mysmashmodel.smash.setup,
 327                    self.mysmashmodel.smash.mesh,
 328                    self.mysmashmodel.smash._input_data,
 329                )
 330
 331    @tools.autocast_args
 332    def model_warmup(self, warmup: int = 365):
 333        """
 334        Smash model warmup function. This function warm the curent model by creating a new
 335        on with attribute warmup_model.smash. The final states of warmup_model.smash are copied to the
 336        initial states of mysmashmodel.smash.
 337
 338        Parameters
 339        ----------
 340
 341        warmup: None | int
 342            a integer of the number of days used for warming the model.
 343
 344        """
 345        print("</> Warmup the Smash-model...")
 346
 347        if warmup is not None:
 348            try:
 349                timedelta = datetime.timedelta(days=warmup)
 350            except:
 351                raise ValueError(
 352                    f"warmup arg `{warmup}` is not a valid time delta."
 353                )
 354
 355            w_setup = copy.deepcopy(self.mysetup.setup)
 356
 357            w_setup["end_time"] = w_setup["start_time"]
 358            w_setup["start_time"] = datetime.datetime.strftime(
 359                datetime.datetime.fromisoformat(w_setup["start_time"])
 360                - timedelta,
 361                "%Y-%m-%d %H:%M",
 362            )
 363            w_setup["read_qobs"] = False
 364            w_setup["read_prcp"] = True
 365            w_setup["read_pet"] = True
 366
 367            if self.warmup_model.smash is None:
 368                self.warmup_model.smash = self._model(
 369                    setup=w_setup, mesh=self.mymesh.mesh
 370                )
 371
 372            self._gathering_parameters(self.warmup_model.smash)
 373            self.warmup_model.smash.forward_run()
 374
 375            # TODO FIX BUG IN SMASH when states > 1.0
 376            mask_sup = np.where(
 377                self.warmup_model.smash.rr_final_states.values > 1
 378            )
 379            self.warmup_model.smash.rr_final_states.values[mask_sup] = 0.9999
 380
 381            if (
 382                hasattr(self, "mysmashmodel")
 383                and self.mysmashmodel.smash is not None
 384            ):
 385
 386                self.mysmashmodel.smash.rr_initial_states = (
 387                    self.warmup_model.smash.rr_final_states
 388                )
 389
 390    @tools.autocast_args
 391    def validate(
 392        self,
 393        start_time=None,
 394        end_time=None,
 395        warmup: int = 365,
 396        cost_options: dict | None = None,
 397        common_options: dict | None = None,
 398        return_options: dict | None = None,
 399    ):
 400        """
 401        Smash model validation function. This function run the smash model on a other period
 402        where start_time and end_time may differ from the setup. Currently, it won't work with the
 403        atmos_data_connector wich only apply to the main model 'mysmashmodel'.
 404
 405        Parameters
 406        ----------
 407
 408        :param start_time: The start time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 409        :type start_time: None | str, optional
 410        :param end_time: The end time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 411        :type end_time: None | str, optional
 412        :param warmup: a integer of the number of days used for warming the model.
 413        :type warmup: int, optional, default 365
 414        """
 415        print("</> Validation of the Smash-model...")
 416
 417        v_setup = copy.deepcopy(self.mysetup.setup)
 418
 419        v_setup["start_time"] = start_time
 420        v_setup["end_time"] = end_time
 421        v_setup["read_qobs"] = True
 422        v_setup["read_prcp"] = True
 423        v_setup["read_pet"] = True
 424
 425        if self.validation_model.smash is None:
 426            self.validation_model.smash = self._model(
 427                setup=v_setup, mesh=self.mymesh.mesh
 428            )
 429            print(
 430                f"</> Getting parameters from the validation model `validation_model.smash` ..."
 431            )
 432
 433            self._gathering_parameters(self.validation_model.smash)
 434
 435        if warmup > 0:
 436
 437            try:
 438                timedelta = datetime.timedelta(days=warmup)
 439            except:
 440                raise ValueError(
 441                    f"warmup arg `{warmup}` is not a valid time delta."
 442                )
 443
 444            v_setup["end_time"] = v_setup["start_time"]
 445            v_setup["start_time"] = datetime.datetime.strftime(
 446                datetime.datetime.fromisoformat(v_setup["start_time"])
 447                - timedelta,
 448                "%Y-%m-%d %H:%M",
 449            )
 450            warming_model = self._model(setup=v_setup, mesh=self.mymesh.mesh)
 451            print(
 452                f"</> Getting parameters from the warming model model `warming_model` ..."
 453            )
 454            self._gathering_parameters(warming_model)
 455            warming_model.forward_run()
 456            self.validation_model.smash.rr_initial_states = (
 457                warming_model.rr_final_states.copy()
 458            )
 459
 460        self.validation_model.extra_smash_results = (
 461            self.validation_model.smash.forward_run(
 462                cost_options=cost_options,
 463                common_options=common_options,
 464                return_options=return_options,
 465            )
 466        )
 467
 468    def optimize(
 469        self,
 470        start_time: None | str = None,
 471        end_time: None | str = None,
 472        mapping: str = "uniform",
 473        optimizer: None | str = None,
 474        optimize_options: None | str = None,
 475        cost_options: None | str = None,
 476        common_options: None | str = None,
 477        return_options: None | str = None,
 478        callback=None,
 479    ):
 480        """
 481        Optimize the current model (with the current setup and mesh),
 482        store the model in the attribute optimize_model.smash and set the calibrated
 483        parameters to the model behind the attribute mysmashmodel.smash.
 484        Start_time and end_time can be specified here to change the period of the
 485        calibration compare to the current setup. All other arguments are
 486        equivalent to the smash.model.optimize function
 487        (see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize).
 488         One difference from Smash is that gauges with no data are
 489         automatically removed from the optimization without raising an error.
 490         This provide a convient way to calibrate quickly the parameters using the current
 491         mesh which may include gauges with data for calibration
 492         and location gauge for discharges computation.
 493
 494        :param start_time: The start time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 495        :type start_time: None | str, optional
 496        :param end_time: The end time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
 497        :type end_time: None | str, optional
 498        :param mapping: Type of mapping, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to "uniform"
 499        :type mapping: str, optional
 500        :param optimizer: Name of optimizer, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 501        :type optimizer: None | str, optional
 502        :param optimize_options: Dictionary containing optimization options for fine-tuning the optimization process, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 503        :type optimize_options: None | str, optional
 504        :param cost_options: Dictionary containing computation cost options for simulated and observed responses. see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 505        :type cost_options: None | str, optional
 506        :param common_options: Dictionary containing common options with two elements: ncpu (int, default 1) and verbose (bool, default is False), defaults to None
 507        :type common_options: None | str, optional
 508        :param return_options: Dictionary containing return options to save additional simulation results, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 509        :type return_options: None | str, optional
 510        :param callback: A callable called after each iteration with the signature callback(iopt: Optimize), see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
 511        :type callback: TYPE, optional
 512
 513        """
 514        o_setup = copy.deepcopy(self.mysetup.setup)
 515
 516        if start_time is not None and end_time is not None:
 517            o_setup["start_time"] = start_time
 518            o_setup["end_time"] = end_time
 519
 520        o_setup["read_qobs"] = True
 521        o_setup["read_prcp"] = True
 522        o_setup["read_pet"] = True
 523
 524        if self.optimize_model.smash is None:
 525            self.optimize_model.smash = self._model(
 526                setup=o_setup, mesh=self.mymesh.mesh
 527            )
 528            print(
 529                f"</> Getting parameters from the default model `mysmashmodel.smash` ..."
 530            )
 531
 532            for key in list(self.optimize_model.smash.rr_parameters.keys):
 533                pos = list(self.optimize_model.smash.rr_parameters.keys).index(
 534                    key
 535                )
 536                pos_w = list(self.mysmashmodel.smash.rr_parameters.keys).index(
 537                    key
 538                )
 539                self.optimize_model.smash.rr_parameters.values[:, :, pos] = (
 540                    self.mysmashmodel.smash.rr_parameters.values[:, :, pos_w]
 541                )
 542
 543        if self.warmup_model.smash is not None:
 544            for key in list(self.optimize_model.smash.rr_initial_states.keys):
 545                pos = list(
 546                    self.optimize_model.smash.rr_initial_states.keys
 547                ).index(key)
 548                pos_w = list(
 549                    self.warmup_model.smash.rr_initial_states.keys
 550                ).index(key)
 551                self.optimize_model.smash.rr_initial_states.values[
 552                    :, :, pos
 553                ] = self.warmup_model.smash.rr_final_states.values[:, :, pos_w]
 554
 555        default_cost_option = {
 556            "end_warmup": o_setup["start_time"],
 557            "gauge": "all",
 558        }
 559        if cost_options is not None:
 560            default_cost_option.update(cost_options)
 561
 562        # auto remove gauge if no observation
 563        if isinstance(default_cost_option["gauge"], str):
 564            if default_cost_option["gauge"] == "dws":
 565                gauge = np.empty(shape=0)
 566
 567                for i, pos in enumerate(
 568                    self.optimize_model.smash.mesh.gauge_pos
 569                ):
 570                    if (
 571                        self.optimize_model.smash.mesh.flwdst[tuple(pos)]
 572                        == 0.0
 573                    ):
 574                        gauge = np.append(
 575                            gauge, self.optimize_model.smash.mesh.code[i]
 576                        )
 577
 578            elif default_cost_option["gauge"] == "all":
 579                gauge = np.array(self.optimize_model.smash.mesh.code, ndmin=1)
 580            else:
 581                gauge = np.array(default_cost_option["gauge"], ndmin=1)
 582        elif isinstance(default_cost_option["gauge"], list):
 583            gauge = np.array(default_cost_option["gauge"], ndmin=1)
 584
 585        st = pd.Timestamp(self.optimize_model.smash.setup.start_time)
 586        et = pd.Timestamp(self.optimize_model.smash.setup.end_time)
 587        ew = pd.Timestamp(default_cost_option["end_warmup"])
 588        start_slice = int(
 589            (ew - st).total_seconds() / self.optimize_model.smash.setup.dt
 590        )
 591        # end_slice = start_slice+int((et - ew).total_seconds() / self.optimize_model.smash.setup.dt)
 592        end_slice = -1
 593        time_slice = slice(start_slice, end_slice)
 594
 595        del_gauge = []
 596        for i in range(len(gauge)):
 597            pos = np.where(self.optimize_model.smash.mesh.code == gauge[i])[0][
 598                0
 599            ]
 600            # print(i, pos)
 601            if np.all(
 602                self.optimize_model.smash.response_data.q[pos, time_slice] < 0
 603            ):
 604                del_gauge.append(i)
 605
 606                print(
 607                    f"No observed discharge available at gauge '{gauge[i]}' for the selected "
 608                    f"optimization period ['{ew}', '{et}']. This gauge is removed "
 609                    f"from the optimization."
 610                )
 611
 612        gauge = np.delete(gauge, del_gauge)
 613        default_cost_option.update({"gauge": gauge})
 614
 615        self.optimize_model.smash.optimize(
 616            mapping,
 617            optimizer,
 618            optimize_options,
 619            default_cost_option,
 620            common_options,
 621            return_options,
 622            callback,
 623        )
 624
 625        if (
 626            hasattr(self, "mysmashmodel")
 627            and self.mysmashmodel.smash is not None
 628        ):
 629            self.mysmashmodel.smash.rr_parameters = (
 630                self.optimize_model.smash.rr_parameters.copy()
 631            )
 632
 633    @tools.autocast_args
 634    def forward_run(
 635        self,
 636        warmup: int | None = None,
 637        invert_states: bool | None = False,
 638        cost_options: dict | None = None,
 639        common_options: dict | None = None,
 640        return_options: dict | None = None,
 641    ):
 642        """
 643        Smash model forward run.This function wrap smash.model.forward_run().
 644
 645        Parameters
 646        ----------
 647
 648        warmup: None | int
 649            a integer of the number of days used for warming the model.
 650        invert_states : bool = False
 651            invert states of the model, so that the final states are used
 652            for the initial states.
 653        cost_options : dict | None,
 654            Dictionary containing computation cost options for simulated and observed responses (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html).
 655        common_options: dict| None,
 656            Dictionary containing common options with two elements, ncpu and verbose (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html)
 657        return_options : dict | None,
 658            Dictionary containing return options to save additional simulation results. (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html)
 659
 660        Examples
 661        --------
 662
 663        >>> es=smashbox.SmashBox()
 664        >>> sb.newmodel("RealCollobrier")
 665        >>> sb.RealCollobrier.generate_mesh(run=False)
 666        >>> sb.RealCollobrier.model()
 667        >>> sb.RealCollobrier.forward_run()
 668        """
 669
 670        if self.mysmashmodel.smash is None:
 671            self.model()
 672
 673        if warmup is not None:
 674            self.model_warmup(warmup=warmup)
 675
 676        if self.warmup_model.smash is not None:
 677            for key in list(self.mysmashmodel.smash.rr_initial_states.keys):
 678                pos = list(
 679                    self.mysmashmodel.smash.rr_initial_states.keys
 680                ).index(key)
 681                pos_w = list(
 682                    self.warmup_model.smash.rr_initial_states.keys
 683                ).index(key)
 684                self.mysmashmodel.smash.rr_initial_states.values[:, :, pos] = (
 685                    self.warmup_model.smash.rr_final_states.values[:, :, pos_w]
 686                )
 687
 688        if self.optimize_model.smash is not None:
 689            for key in list(self.mysmashmodel.smash.rr_parameters.keys):
 690                pos = list(self.mysmashmodel.smash.rr_parameters.keys).index(
 691                    key
 692                )
 693                pos_w = list(
 694                    self.optimize_model.smash.rr_parameters.keys
 695                ).index(key)
 696                self.mysmashmodel.smash.rr_parameters.values[:, :, pos] = (
 697                    self.optimize_model.smash.rr_parameters.values[:, :, pos_w]
 698                )
 699
 700        # needed here in order to replace the rainfall without rebuild the model
 701        self._gathering_atmosdata()
 702
 703        if invert_states:
 704            if self._fstates is not None and not np.all(
 705                self._fstates == -99.0
 706            ):
 707
 708                # TODO FIX BUG IN SMASH when states > 1.0
 709                mask_inf = np.where(
 710                    self.mysmashmodel.smash.mesh.active_cell == 1
 711                )
 712                mask_sup = np.where(self._fstates > 1)
 713                self._fstates[mask_sup] = 0.9999
 714
 715                self.mysmashmodel.smash.rr_initial_states.values[mask_inf] = (
 716                    self._fstates[mask_inf].copy()
 717                )
 718
 719            else:
 720                print(
 721                    "</> model final states does not exist."
 722                    " Invert states is not possible."
 723                    " Smash default initial states is used."
 724                )
 725
 726        self.mysmashmodel.extra_smash_results = (
 727            self.mysmashmodel.smash.forward_run(
 728                cost_options=cost_options,
 729                common_options=common_options,
 730                return_options=return_options,
 731            )
 732        )
 733
 734        # states backup
 735        self._fstates = self.mysmashmodel.smash.rr_final_states.values.copy()
 736        self._istates = self.mysmashmodel.smash.rr_initial_states.values.copy()
 737
 738    @tools.autocast_args
 739    def atmos_data_connector(
 740        self,
 741        input_prcp: np.ndarray | None = None,
 742        input_pet: np.ndarray | None = None,
 743        input_dt: float | None = None,
 744        input_res: tuple | list = (1000.0, 1000.0),
 745        input_start_time: str = "2050-01-01 01:00",
 746        input_bbox: dict | None = None,
 747        input_epsg: int = 2154,
 748        resampling_method="home_made_with_scipy_zoom",
 749    ):
 750        """
 751        Generate a connector for the external atmos_data comming from
 752        other model such as Graffas (spatial rainfall generator).
 753
 754        Parameters
 755        ----------
 756
 757        input_prcp : np.ndarray | None = None
 758            An np.ndarray with shape (nrow, ncol, npdt) wich stores the spatial rainfall
 759            which will be used by Smash.Ideally, the extend of this array match
 760            exactlly the extend of the Smash domain.
 761        input_pet : np.ndarray | None = None
 762            An np.ndarray with shape (nrow, ncol, npdt) wich stores the spatial
 763            evapotranspiration which will be used by Smash.
 764        input_dt : float = 3600.
 765            Time step in seconds of the input precipitation
 766        input_res : tuple | list = (1000., 1000.)
 767            The resolution of the input precipitation
 768        input_start_time : str = "2050-01-01 01:00"
 769            The date of the start_time
 770        input_bbox : dict | None = None
 771            The extend of the domain using a bbox.
 772            The convention used here is a dictionary like
 773            bbox={"left":xmin, "top": ymax, "right": xmax, "bottom": ymin}.
 774            If not provided, the extend of the smash domain will be used starting
 775            from xmin and ymin.
 776        input_epsg : int = 2154
 777            The epsg code of the coordinate system. If not provided,
 778            the coordinate system used in Smash will be used.
 779        resampling_method: str
 780            The method to use to resample and crop the input array. Default is
 781            'home_made_with_scipy_zoom' Choice are:
 782            ['rasterio_1', 'rasterio_2', 'home_made_with_scipy_zoom'].
 783            'home_made_with_scipy_zoom' is the fastest method. 'rasterio_1'
 784            is the slowest method. However, 'rasterio' method use much tested and
 785            reliable method to resample and crop the array.
 786
 787        Examples
 788        --------
 789
 790        >>> prcp_array=np.zeros(200,200,20)+1.
 791        >>> es=smashbox.SmashBox()
 792        >>> sb.newmodel("RealCollobrier")
 793        >>> sb.RealCollobrier.generate_mesh()
 794        >>> sb.RealCollobrier.atmos_data_connector(input_prcp=prcp_array)
 795
 796        """
 797        if input_prcp is None and input_pet is None:
 798            raise ValueError(
 799                "</> input_prcp and input_pet are None. input_prcp or input_pet must be"
 800                " numpy.ndarray with shape (nrow, ncol, ntimesteps)."
 801            )
 802
 803        if input_dt is None:
 804            print(
 805                "</> We suppose the input atmos data time-step equal to the model time-step."
 806            )
 807            input_dt = self.mysetup.setup["dt"]
 808
 809        if input_dt != self.mysetup.setup["dt"]:
 810            print(
 811                "</> Model and input atmos data have different time-step. "
 812                "Input atmos data will be resampled."
 813            )
 814            if input_prcp is not None:
 815                input_prcp = tools.time_resample_prcp_array(
 816                    input_prcp,
 817                    input_dt,
 818                    self.mysetup.setup["dt"],
 819                    t_axis=2,
 820                )
 821            if input_pet is not None:
 822                input_pet = tools.time_resample_prcp_array(
 823                    input_pet,
 824                    input_dt,
 825                    self.mysetup.setup["dt"],
 826                    t_axis=2,
 827                )
 828            input_dt = self.mysetup.setup["dt"]
 829
 830        # use conversion factor defined in setup
 831        if (
 832            "prcp_conversion_factor" in self.mysetup.setup
 833            and input_prcp is not None
 834        ):
 835            input_prcp = (
 836                input_prcp * self.mysetup.setup["prcp_conversion_factor"]
 837            )
 838        if (
 839            "pet_conversion_factor" in self.mysetup.setup
 840            and input_pet is not None
 841        ):
 842            input_pet = input_pet * self.mysetup.setup["pet_conversion_factor"]
 843
 844        self._myatmos_data_connector = (
 845            atmos_data_connector.atmos_data_connector(
 846                input_prcp=input_prcp,
 847                input_pet=input_pet,
 848                input_dt=input_dt,
 849                input_res=input_res,
 850                input_start_time=input_start_time,
 851                input_bbox=input_bbox,
 852                input_epsg=input_epsg,
 853            )
 854        )
 855
 856        smash_bbox = geo_toolbox.get_bbox_from_smash_mesh(self.mymesh.mesh)
 857
 858        if self.mymesh.mesh["nrow"] > input_prcp.shape[0]:
 859            print(
 860                f"</> Warning !! Input rainfall domain is smaller than the smash domain  {input_prcp.shape[0]}<{self.mymesh.mesh['nrow']}"
 861            )
 862        if self.mymesh.mesh["ncol"] > input_prcp.shape[1]:
 863            print(
 864                f"</> Warning !! Input rainfall domain is smaller than the smash domain  {input_prcp.shape[1]}<{self.mymesh.mesh['ncol']}"
 865            )
 866
 867        self._myatmos_data_connector.read_input_atmos_data(
 868            output_bbox=smash_bbox,
 869            output_epsg=self._myparam.param.epsg,
 870            output_res=(self.mymesh.mesh["xres"], self.mymesh.mesh["yres"]),
 871            output_shape=(self.mymesh.mesh["nrow"], self.mymesh.mesh["ncol"]),
 872            resampling_method=resampling_method,
 873        )
 874
 875        self._myatmos_data_connector.change_setup(self.mysetup)
 876
 877    def import_parameters(self, model=None):
 878        """
 879        Import Geotiff parameter in Smash. This function wrap
 880        smash.io.read_grid_parameters(). Path to the parameters is defined in
 881        self._myparam.param.smash_parameters.
 882
 883        Parameter
 884        ---------
 885
 886        model: smash.Model object
 887            A Smash model object. If None, the Smash model of smashbox will be used
 888
 889        Examples
 890        --------
 891
 892        >>> es=smashbox.SmashBox()
 893        >>> sb.newmodel("RealCollobrier")
 894        >>> sb.RealCollobrier.generate_mesh(run=False)
 895        >>> sb.RealCollobrier.model()
 896        >>> sb.RealCollobrier.import_parameters()
 897        >>> sb.RealCollobrier.forward_run()
 898        """
 899        if self._myparam.param.smash_parameters is None:
 900            print(
 901                "</> Warning: no calibrated Smash parameters is used, leaving it to"
 902                "  default."
 903            )
 904            return
 905
 906        if model is None:
 907            model = self.mysmashmodel.smash
 908
 909        smash.io.read_grid_parameters(
 910            model=model,
 911            path=self._myparam.param.smash_parameters,
 912        )
 913
 914    def export_parameters(self, path: os.PathLike = "./output_smash_param"):
 915        """
 916        Export Geotiff Smash parameter as Geotiff. This function wrap
 917        smash.io.save_grid_parameters().
 918
 919        Parameters
 920        ----------
 921
 922        path : os.PathLike = "./output_smash_param"
 923            path to a directory where the parameter will be saved.
 924
 925        Examples
 926        --------
 927
 928        >>> es=smashbox.SmashBox()
 929        >>> sb.newmodel("RealCollobrier")
 930        >>> sb.RealCollobrier.generate_mesh(run=False)
 931        >>> sb.RealCollobrier.model()
 932        >>> sb.RealCollobrier.import_parameters()
 933        >>> sb.RealCollobrier.export_parameters()
 934        """
 935        smash.io.export_parameters(self.mysmashmodel.smash, path)
 936
 937    def save_model_container_hdf5(
 938        self, path_to_hdf5: str | None = None, save_smash_model: bool = True
 939    ):
 940        """
 941
 942        :param path_to_hdf5: Path to the hdf5 file, defaults to None. If None,
 943        the fucntion will return a dictionnary containing all data of the self object.
 944        :type path_to_hdf5: str | None, optional
 945        :param save_smash_model: Savec the smash models objects or no, defaults
 946        to True. If False the Smash models objects are not saved.
 947        :type save_smash_model: str, optional
 948        :return: if a path_to_hdf5 is None, a dictionary containing all
 949        attribute of the input object self is returned.
 950        :rtype: dict | None
 951
 952        """
 953
 954        if path_to_hdf5 is not None:
 955
 956            structure = (
 957                pyhdf5_handler.src.object_handler.generate_object_structure(
 958                    self, include_method=False
 959                )
 960            )
 961
 962            if not save_smash_model:
 963                del structure["warmup_model"]["smash"]
 964                del structure["mysmashmodel"]["smash"]
 965                del structure["optimize_model"]["smash"]
 966                del structure["validation_model"]["smash"]
 967
 968            pyhdf5_handler.save_object_to_hdf5file(
 969                path_to_hdf5=path_to_hdf5,
 970                instance=self,
 971                keys_data=structure,
 972                location=f"./{self._model_name}/",
 973            )
 974
 975            # Add missing data
 976            structure = (
 977                pyhdf5_handler.src.object_handler.generate_object_structure(
 978                    self._myparam, include_method=False
 979                )
 980            )
 981
 982            pyhdf5_handler.save_object_to_hdf5file(
 983                path_to_hdf5=path_to_hdf5,
 984                instance=self._myparam,
 985                keys_data=structure,
 986                location=f"./{self._model_name}/",
 987            )
 988            pyhdf5_handler.save_dict_to_hdf5file(
 989                path_to_hdf5=path_to_hdf5,
 990                dictionary={"istates": self._istates},
 991                location=f"./{self._model_name}/",
 992            )
 993            pyhdf5_handler.save_dict_to_hdf5file(
 994                path_to_hdf5=path_to_hdf5,
 995                dictionary={"fstates": self._fstates},
 996                location=f"./{self._model_name}/",
 997            )
 998
 999        else:
1000            dict_results = (
1001                pyhdf5_handler.src.object_handler.read_object_as_dict(self)
1002            )
1003
1004            if not save_smash_model:
1005                del dict_results["warmup_model"]["smash"]
1006                del dict_results["mysmashmodel"]["smash"]
1007                del structure["optimize_model"]["smash"]
1008                del structure["validation_model"]["smash"]
1009
1010            return dict_results
1011
1012    def save_model_container(
1013        self,
1014        path: str | None = None,
1015        save_full_model=False,
1016    ):
1017        """
1018
1019        :param path: Path to the hdf5 file, defaults to None. If None,
1020        the fucntion will return a dictionnary containing all data of the self object.
1021        :type path: str | None, optional
1022
1023        """
1024
1025        if path is None:
1026            raise ValueError("Argument `path` is None.")
1027            return
1028
1029        if save_full_model is True:
1030            save_func = getattr(smash.io, "save_model")
1031        else:
1032            save_func = getattr(smash.io, "save_model_ddt")
1033
1034        if not os.path.exists(os.path.join(path, self._model_name)):
1035            os.makedirs(os.path.join(path, self._model_name))
1036
1037        structure = (
1038            pyhdf5_handler.src.object_handler.generate_object_structure(
1039                self, include_method=False
1040            )
1041        )
1042
1043        if "warmup_model" in structure:
1044            del structure["warmup_model"]["smash"]
1045        if "mysmashmodel" in structure:
1046            del structure["mysmashmodel"]["smash"]
1047        if "optimize_model" in structure:
1048            del structure["optimize_model"]["smash"]
1049        if "validation_model" in structure:
1050            del structure["validation_model"]["smash"]
1051
1052        pyhdf5_handler.save_object_to_hdf5file(
1053            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1054            instance=self,
1055            keys_data=structure,
1056            location=f"./",
1057        )
1058
1059        structure = (
1060            pyhdf5_handler.src.object_handler.generate_object_structure(
1061                self._myparam, include_method=False
1062            )
1063        )
1064        pyhdf5_handler.save_object_to_hdf5file(
1065            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1066            instance=self._myparam,
1067            keys_data=structure,
1068            location=f"./",
1069        )
1070
1071        pyhdf5_handler.save_dict_to_hdf5file(
1072            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1073            dictionary={"istates": self._istates},
1074            location=f"./",
1075        )
1076        pyhdf5_handler.save_dict_to_hdf5file(
1077            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1078            dictionary={"fstates": self._fstates},
1079            location=f"./",
1080        )
1081
1082        if self.mysmashmodel.smash is not None:
1083            save_func(
1084                self.mysmashmodel.smash,
1085                os.path.join(path, self._model_name, "mysmashmodel.hdf5"),
1086            )
1087        if self.warmup_model.smash is not None:
1088            save_func(
1089                self.warmup_model.smash,
1090                os.path.join(path, self._model_name, "warmup_model.hdf5"),
1091            )
1092        if self.optimize_model.smash is not None:
1093            save_func(
1094                self.optimize_model.smash,
1095                os.path.join(path, self._model_name, "optimize_model.hdf5"),
1096            )
1097        if self.validation_model.smash is not None:
1098            save_func(
1099                self.validation_model.smash,
1100                os.path.join(path, self._model_name, "validation_model.hdf5"),
1101            )
1102
1103    # def save_model_container_pickle(
1104    #     self,
1105    #     path: str | None = None,
1106    # ):
1107    #     """
1108
1109    #     :param path: Path to the hdf5 file, defaults to None. If None,
1110    #     the fucntion will return a dictionnary containing all data of the self object.
1111    #     :type path: str | None, optional
1112
1113    #     """
1114
1115    #     if path is not None:
1116    #         if not os.path.exists(os.path.join(path, self._model_name)):
1117    #             os.makedirs(os.path.join(path, self._model_name))
1118
1119    #         if self.mysmashmodel.smash is not None:
1120    #             smash.io.save_model(
1121    #                 self.mysmashmodel.smash,
1122    #                 os.path.join(path, self._model_name, "mysmashmodel.smash.hdf5"),
1123    #             )
1124    #         if self.warmup_model.smash is not None:
1125    #             smash.io.save_model(
1126    #                 self.warmup_model.smash,
1127    #                 os.path.join(path, self._model_name, "warmup_model.hdf5"),
1128    #             )
1129    #         if self.optimize_model.smash is not None:
1130    #             smash.io.save_model(
1131    #                 self.optimize_model.smash,
1132    #                 os.path.join(path, self._model_name, "optimize_model.hdf5"),
1133    #             )
1134
1135    #         structure = pyhdf5_handler.src.object_handler.generate_object_structure(
1136    #             self, include_method=False
1137    #         )
1138
1139    #         structure = [
1140    #             "mysetup",
1141    #             "mymesh",
1142    #             "mysmashmodel",
1143    #             "warmup_model",
1144    #             "optimize_model",
1145    #             "extra_smash_results",
1146    #             # "mystats", #input arg is self, and it is a weak ref (cannot pickle it)
1147    #             # "myplot", #input arg is self, and it is a weak ref (cannot pickle it)
1148    #             "_model_name",
1149    #             "_myparam",
1150    #             "_istates",
1151    #             "_fstates",
1152    #             "_myatmos_data_connector",
1153    #         ]
1154
1155    #         if "warmup_model" in structure:
1156    #             structure.remove("warmup_model")
1157    #         if "mysmashmodel" in structure:
1158    #             structure.remove("mysmashmodel")
1159    #         if "optimize_model" in structure:
1160    #             structure.remove("optimize_model")
1161
1162    #         for key in structure:
1163    #             print(key)
1164    #             with open(os.path.join(path, self._model_name, f"{key}.pkl"), "wb") as f:
1165    #                 obj = getattr(self, key)
1166    #                 if obj is not None:
1167    #                     pickle.dump(obj, f)
1168
1169    # def import_parameters(self)
1170    # if os.path.exists(self._myparam.param.smash_parameters) and self._myparam.param.smash_parameters.endswith(".hdf5"):
1171
1172    #     hdf5= pyhdf5_handler.open_hdf5(self._myparam.param.smash_parameters)
1173    #     hdf5_key=list(hdf5.keys())
1174    #     hdf5.close()
1175
1176    #     if "model_ddt" in hdf5_key:
1177    #         with_param = pyhdf5_handler.read_hdf5file_as_dict(self._myparam.param.smash_parameters,location="model_ddt/rr_parameters")
1178    #         with_mesh = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="model_ddt",item="mesh")
1179
1180    #         smash_parameters.transfert_params_to_model(
1181    #             from_mesh=
1182    #             with_mesh,
1183    #             with_param=with_param,
1184    #             to_model=self.mysmashmodel.smash)
1185
1186    #     elif "model" in hdf5_key:
1187    #         with_param = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="model",item="rr_parameters")
1188    #         with_mesh = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="model",item="mesh")
1189
1190    #         smash_parameters.transfert_params_to_model(
1191    #             from_mesh=
1192    #             with_mesh,
1193    #             with_param=with_param,
1194    #             to_model=self.mysmashmodel.smash)
1195
1196    #     elif "rr_parameters" in hdf5.keys() and "mesh" in hdf5.keys():
1197    #         with_param = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="./",item="rr_parameters")
1198    #         with_mesh = pyhdf5_handler.get_hdf5file_item(self._myparam.param.smash_parameters,location="./",item="mesh")
1199
1200    #         smash_parameters.transfert_params_to_model(
1201    #             from_mesh=with_mesh,
1202    #             with_param=with_param,
1203    #             to_model=self.mysmashmodel.smash)
1204
1205    #     else:
1206    #         raise ValueError("</> Error: cannot load the parameters ... unknown format...")
1207
1208    # elif os.path.isdir(self._myparam.param.smash_parameters):
1209
1210    #     smash_parameters.load_param_from_tiffformat(model=self.mysmashmodel.smash, path_to_parameters=self._myparam.param.smash_parameters)
1211
1212    # else:
1213    #     raise ValueError(f"</> Error: cannot load the parameters. Path or file '{self._myparam.param.smash_parameters}' does not exist ... ")
1214
1215    # def write_smashparam(self, hdf5file : str | os.PathLike = "output_smash_param.hdf5"):
1216
1217    #     rr_parameters=pyhdf5_handler.read_object_as_dict(self.mysmashmodel.smash.rr_parameters)
1218    #     pyhdf5_handler.save_dict_to_hdf5(hdf5file, rr_parameters)
1219
1220    # def export_parameters(self, path : os.path = "./output_smash_param")
1221
1222    # list_param=list(self.mysmashmodel.smash.rr_parameters.keys)
1223
1224    # for smparam in list_param:
1225
1226    #     array=self.mysmashmodel.smash.rr_parameters.values[:,:,list_param.index(smparam)]
1227
1228    #     smash_parameters.write_array_to_geotiff(os.path.join(path, smparam+".tif"),
1229    #                            array,
1230    #                            self.mysmashmodel.smash.mesh.xmin,
1231    #                            self.mysmashmodel.smash.mesh.ymax,
1232    #                            output_res = (self.mymesh.mesh["xres"],
1233    #                                          self.mymesh.mesh["yres"])
1234    #                            )

Main class model() which has a complete set of functions, class and attributes to build, run and manipulate the Smash model, compute statistical criterium and plot graphics.

model(name, myparam)
32    def __init__(self, name, myparam):
33
34        self._model_name = name
35        """The attribute _model_name is contain the name of the current object"""
36
37        self._myparam = copy.deepcopy(myparam)
38        """The attribute _myparam is a copy of the parent class smashbox.myparam.
39        This class stores the main parameters used for building the Smash model"""
40
41        self.mysetup = setup.setup(self._myparam.param)
42        """The attribute mysetup own the class setup.setup(). This class stores the Smash
43        setup for the hydrological simulation and some helpers to manipulate these
44        parameters."""
45
46        self.mymesh = mesh.mesh(self.mysetup)
47        """The attribute mymesh own the class mesh.mesh(). This class stores the Smash
48        mesh used for the hydrological simulation and some helpers to manipulate this mesh.
49        """
50
51        self._fstates = None
52        self._istates = None
53        self._myatmos_data_connector = None
54
55        self.mysmashmodel = smash_model.smash_model()
56        """The attribute mysmashmodel stores the Smash model object created with
57        attributes mysetup and -mymesh."""
58
59        self.warmup_model = smash_model.smash_model()
60        """The attribute warmup_model store a smash model used for warmup and compatible 
61        with the model in attribute mysmashmodel"""
62
63        self.optimize_model = smash_model.smash_model()
64        """The attribute optimize_model store a smash model used for optimization and compatible 
65        with the model in attribute mysmashmodel"""
66
67        self.validation_model = smash_model.smash_model()
68        """The attribute validation_model stores the Smash model object used for validation"""
69
70        self.myplot = myplot.myplot(self)
71        """The attribute myplot own the class myplot.myplot(). This class stores plotting
72        # capabilities on the smash model object."""
mysetup

The attribute mysetup own the class setup.setup(). This class stores the Smash setup for the hydrological simulation and some helpers to manipulate these parameters.

mymesh

The attribute mymesh own the class mesh.mesh(). This class stores the Smash mesh used for the hydrological simulation and some helpers to manipulate this mesh.

mysmashmodel

The attribute mysmashmodel stores the Smash model object created with attributes mysetup and -mymesh.

warmup_model

The attribute warmup_model store a smash model used for warmup and compatible with the model in attribute mysmashmodel

optimize_model

The attribute optimize_model store a smash model used for optimization and compatible with the model in attribute mysmashmodel

validation_model

The attribute validation_model stores the Smash model object used for validation

myplot

The attribute myplot own the class myplot.myplot(). This class stores plotting

capabilities on the smash model object.

def generate_mesh( self, max_depth: float = 1.0, query: str | None = None, area_error_th: None | float = None, lacuna_threshold: None | float = None):
 74    def generate_mesh(
 75        self,
 76        max_depth: float = 1.0,
 77        query: str | None = None,
 78        area_error_th: None | float = None,
 79        lacuna_threshold: None | float = None,
 80    ):
 81        """
 82        Generate the mesh of the Smash model
 83
 84        Parameters
 85        ----------
 86
 87        max_depth : `int`, default 1
 88            The maximum depth accepted by the algorithm to find the catchment outlet.
 89            A **max_depth** of 1 means that the algorithm will search among the
 90            combinations in
 91            (``row - 1``, ``row``, ``row + 1``; ``col - 1``, ``col``, ``col + 1``),
 92            the coordinates that minimize
 93            the relative error between the given catchment area and the modeled
 94            catchment area calculated from the
 95            flow directions file.
 96        :param query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query
 97        must be build using the field (column name) in the outlet database.
 98        https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method
 99        :type query: str
100        area_error_th: float | None
101            The tolerance error for the difference between the observed and simulated
102             surface. The error is computed as follow:
103                 Serror=abs(Ssim-Sobs)/Sobs
104            All outlets where `Serror > area_error_th` will be automatically removed from
105            the mesh.
106        :type area_error_th: float
107        :param lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed.
108        :type: float | Nonetype
109
110        Examples
111        --------
112
113        >>> es=smashbox.SmashBox()
114        >>> sb.newmodel("RealCollobrier")
115        >>> sb.RealCollobrier.generate_mesh(min_surf=5, max_surf=100)
116
117        """
118        self.mymesh.generate_mesh(
119            self._myparam.param,
120            max_depth=max_depth,
121            query=query,
122            area_error_th=area_error_th,
123            lacuna_threshold=lacuna_threshold,
124        )

Generate the mesh of the Smash model

Parameters

max_depth : int, default 1 The maximum depth accepted by the algorithm to find the catchment outlet. A max_depth of 1 means that the algorithm will search among the combinations in (row - 1, row, row + 1; col - 1, col, col + 1), the coordinates that minimize the relative error between the given catchment area and the modeled catchment area calculated from the flow directions file.

Parameters
  • query: Any pandas dataframe query as string: '(SURF>20) & (SURF<100)'. This query must be build using the field (column name) in the outlet database. https://pandas.pydata.org/docs/user_guide/indexing.html#the-query-method area_error_th: float | None The tolerance error for the difference between the observed and simulated surface. The error is computed as follow: Serror=abs(Ssim-Sobs)/Sobs All outlets where Serror > area_error_th will be automatically removed from the mesh.
  • lacuna_threshold: Lacuna threshold for the discharges in percent. All gauge where the proportion of lacuna exceed this threshold will be removed.

Examples

>>> es=smashbox.SmashBox()
>>> sb.newmodel("RealCollobrier")
>>> sb.RealCollobrier.generate_mesh(min_surf=5, max_surf=100)
def model( self, setup: dict | None = None, mesh: dict | None = None, read_data=None):
126    def model(
127        self,
128        setup: dict | None = None,
129        mesh: dict | None = None,
130        read_data=None,
131    ):
132        """
133        Smash model object creation. This function wrap smash.Model(). Setup and mesh
134        argument are optional since these dictionnary are hosted by the smashbox object.
135
136        Parameters
137        ----------
138        setup: dict | None
139            The Smash setup (optionnal), if None the smashbox setup will be used
140        mesh: dict | None
141            The Smash mesh (optionnal), if None the smashbox mesh will be used
142
143        Examples
144        --------
145
146        >>> es=smashbox.SmashBox()
147        >>> sb.newmodel("RealCollobrier")
148        >>> sb.RealCollobrier.generate_mesh(run=False)
149        >>> sb.RealCollobrier.model()
150
151        """
152        if setup is None:
153            setup = self.mysetup.setup.copy()
154
155        if mesh is None:
156            mesh = self.mymesh.mesh.copy()
157
158        if read_data is False:
159            setup.update(
160                {
161                    "read_prcp": False,
162                    "read_pet": False,
163                    "read_snow": False,
164                    "read_qobs": False,
165                    "read_temp": False,
166                }
167            )
168        if read_data is True:
169            setup.update(
170                {
171                    "read_prcp": True,
172                    "read_pet": True,
173                    "read_snow": True,
174                    "read_qobs": True,
175                    "read_temp": True,
176                }
177            )
178
179        self.mysmashmodel.smash = self._model(setup=setup, mesh=mesh)
180
181        if read_data is True:
182            self._gathering_atmosdata()
183
184        self._gathering_parameters(self.mysmashmodel.smash)

Smash model object creation. This function wrap smash.Model(). Setup and mesh argument are optional since these dictionnary are hosted by the smashbox object.

Parameters

setup: dict | None The Smash setup (optionnal), if None the smashbox setup will be used mesh: dict | None The Smash mesh (optionnal), if None the smashbox mesh will be used

Examples

>>> es=smashbox.SmashBox()
>>> sb.newmodel("RealCollobrier")
>>> sb.RealCollobrier.generate_mesh(run=False)
>>> sb.RealCollobrier.model()
def model_warmup(*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)

Smash model warmup function. This function warm the curent model by creating a new on with attribute warmup_model.smash. The final states of warmup_model.smash are copied to the initial states of mysmashmodel.smash.

Parameters

warmup: None | int a integer of the number of days used for warming the model.

def validate(*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)

Smash model validation function. This function run the smash model on a other period where start_time and end_time may differ from the setup. Currently, it won't work with the atmos_data_connector wich only apply to the main model 'mysmashmodel'.

Parameters

Parameters
  • start_time: The start time of the optimization, format "YYYY-mm-dd HH: MM", defaults to None
  • end_time: The end time of the optimization, format "YYYY-mm-dd HH: MM", defaults to None
  • warmup: a integer of the number of days used for warming the model.
def optimize( self, start_time: None | str = None, end_time: None | str = None, mapping: str = 'uniform', optimizer: None | str = None, optimize_options: None | str = None, cost_options: None | str = None, common_options: None | str = None, return_options: None | str = None, callback=None):
468    def optimize(
469        self,
470        start_time: None | str = None,
471        end_time: None | str = None,
472        mapping: str = "uniform",
473        optimizer: None | str = None,
474        optimize_options: None | str = None,
475        cost_options: None | str = None,
476        common_options: None | str = None,
477        return_options: None | str = None,
478        callback=None,
479    ):
480        """
481        Optimize the current model (with the current setup and mesh),
482        store the model in the attribute optimize_model.smash and set the calibrated
483        parameters to the model behind the attribute mysmashmodel.smash.
484        Start_time and end_time can be specified here to change the period of the
485        calibration compare to the current setup. All other arguments are
486        equivalent to the smash.model.optimize function
487        (see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize).
488         One difference from Smash is that gauges with no data are
489         automatically removed from the optimization without raising an error.
490         This provide a convient way to calibrate quickly the parameters using the current
491         mesh which may include gauges with data for calibration
492         and location gauge for discharges computation.
493
494        :param start_time: The start time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
495        :type start_time: None | str, optional
496        :param end_time: The end time of the optimization, format "YYYY-mm-dd HH:MM", defaults to None
497        :type end_time: None | str, optional
498        :param mapping: Type of mapping, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to "uniform"
499        :type mapping: str, optional
500        :param optimizer: Name of optimizer, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
501        :type optimizer: None | str, optional
502        :param optimize_options: Dictionary containing optimization options for fine-tuning the optimization process, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
503        :type optimize_options: None | str, optional
504        :param cost_options: Dictionary containing computation cost options for simulated and observed responses. see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
505        :type cost_options: None | str, optional
506        :param common_options: Dictionary containing common options with two elements: ncpu (int, default 1) and verbose (bool, default is False), defaults to None
507        :type common_options: None | str, optional
508        :param return_options: Dictionary containing return options to save additional simulation results, see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
509        :type return_options: None | str, optional
510        :param callback: A callable called after each iteration with the signature callback(iopt: Optimize), see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
511        :type callback: TYPE, optional
512
513        """
514        o_setup = copy.deepcopy(self.mysetup.setup)
515
516        if start_time is not None and end_time is not None:
517            o_setup["start_time"] = start_time
518            o_setup["end_time"] = end_time
519
520        o_setup["read_qobs"] = True
521        o_setup["read_prcp"] = True
522        o_setup["read_pet"] = True
523
524        if self.optimize_model.smash is None:
525            self.optimize_model.smash = self._model(
526                setup=o_setup, mesh=self.mymesh.mesh
527            )
528            print(
529                f"</> Getting parameters from the default model `mysmashmodel.smash` ..."
530            )
531
532            for key in list(self.optimize_model.smash.rr_parameters.keys):
533                pos = list(self.optimize_model.smash.rr_parameters.keys).index(
534                    key
535                )
536                pos_w = list(self.mysmashmodel.smash.rr_parameters.keys).index(
537                    key
538                )
539                self.optimize_model.smash.rr_parameters.values[:, :, pos] = (
540                    self.mysmashmodel.smash.rr_parameters.values[:, :, pos_w]
541                )
542
543        if self.warmup_model.smash is not None:
544            for key in list(self.optimize_model.smash.rr_initial_states.keys):
545                pos = list(
546                    self.optimize_model.smash.rr_initial_states.keys
547                ).index(key)
548                pos_w = list(
549                    self.warmup_model.smash.rr_initial_states.keys
550                ).index(key)
551                self.optimize_model.smash.rr_initial_states.values[
552                    :, :, pos
553                ] = self.warmup_model.smash.rr_final_states.values[:, :, pos_w]
554
555        default_cost_option = {
556            "end_warmup": o_setup["start_time"],
557            "gauge": "all",
558        }
559        if cost_options is not None:
560            default_cost_option.update(cost_options)
561
562        # auto remove gauge if no observation
563        if isinstance(default_cost_option["gauge"], str):
564            if default_cost_option["gauge"] == "dws":
565                gauge = np.empty(shape=0)
566
567                for i, pos in enumerate(
568                    self.optimize_model.smash.mesh.gauge_pos
569                ):
570                    if (
571                        self.optimize_model.smash.mesh.flwdst[tuple(pos)]
572                        == 0.0
573                    ):
574                        gauge = np.append(
575                            gauge, self.optimize_model.smash.mesh.code[i]
576                        )
577
578            elif default_cost_option["gauge"] == "all":
579                gauge = np.array(self.optimize_model.smash.mesh.code, ndmin=1)
580            else:
581                gauge = np.array(default_cost_option["gauge"], ndmin=1)
582        elif isinstance(default_cost_option["gauge"], list):
583            gauge = np.array(default_cost_option["gauge"], ndmin=1)
584
585        st = pd.Timestamp(self.optimize_model.smash.setup.start_time)
586        et = pd.Timestamp(self.optimize_model.smash.setup.end_time)
587        ew = pd.Timestamp(default_cost_option["end_warmup"])
588        start_slice = int(
589            (ew - st).total_seconds() / self.optimize_model.smash.setup.dt
590        )
591        # end_slice = start_slice+int((et - ew).total_seconds() / self.optimize_model.smash.setup.dt)
592        end_slice = -1
593        time_slice = slice(start_slice, end_slice)
594
595        del_gauge = []
596        for i in range(len(gauge)):
597            pos = np.where(self.optimize_model.smash.mesh.code == gauge[i])[0][
598                0
599            ]
600            # print(i, pos)
601            if np.all(
602                self.optimize_model.smash.response_data.q[pos, time_slice] < 0
603            ):
604                del_gauge.append(i)
605
606                print(
607                    f"No observed discharge available at gauge '{gauge[i]}' for the selected "
608                    f"optimization period ['{ew}', '{et}']. This gauge is removed "
609                    f"from the optimization."
610                )
611
612        gauge = np.delete(gauge, del_gauge)
613        default_cost_option.update({"gauge": gauge})
614
615        self.optimize_model.smash.optimize(
616            mapping,
617            optimizer,
618            optimize_options,
619            default_cost_option,
620            common_options,
621            return_options,
622            callback,
623        )
624
625        if (
626            hasattr(self, "mysmashmodel")
627            and self.mysmashmodel.smash is not None
628        ):
629            self.mysmashmodel.smash.rr_parameters = (
630                self.optimize_model.smash.rr_parameters.copy()
631            )

Optimize the current model (with the current setup and mesh), store the model in the attribute optimize_model.smash and set the calibrated parameters to the model behind the attribute mysmashmodel.smash. Start_time and end_time can be specified here to change the period of the calibration compare to the current setup. All other arguments are equivalent to the smash.model.optimize function (see https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize). One difference from Smash is that gauges with no data are automatically removed from the optimization without raising an error. This provide a convient way to calibrate quickly the parameters using the current mesh which may include gauges with data for calibration and location gauge for discharges computation.

Parameters
  • start_time: The start time of the optimization, format "YYYY-mm-dd HH: MM", defaults to None
  • end_time: The end time of the optimization, format "YYYY-mm-dd HH: MM", defaults to None
  • mapping: Type of mapping, see https: //smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to "uniform"
  • optimizer: Name of optimizer, see https: //smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
  • optimize_options: Dictionary containing optimization options for fine-tuning the optimization process, see https: //smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
  • cost_options: Dictionary containing computation cost options for simulated and observed responses. see https: //smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
  • common_options: Dictionary containing common options with two elements: ncpu (int, default 1) and verbose (bool, default is False), defaults to None
  • return_options: Dictionary containing return options to save additional simulation results, see https: //smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
  • callback: A callable called after each iteration with the signature callback(iopt: Optimize), see https: //smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.optimize.html#smash.optimize, defaults to None
def forward_run(*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)

Smash model forward run.This function wrap smash.model.forward_run().

Parameters

warmup: None | int a integer of the number of days used for warming the model. invert_states : bool = False invert states of the model, so that the final states are used for the initial states. cost_options : dict | None, Dictionary containing computation cost options for simulated and observed responses (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html). common_options: dict| None, Dictionary containing common options with two elements, ncpu and verbose (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html) return_options : dict | None, Dictionary containing return options to save additional simulation results. (https://smash.recover.inrae.fr/api_reference/principal_methods/smash/smash.Model.forward_run.html)

Examples

>>> es=smashbox.SmashBox()
>>> sb.newmodel("RealCollobrier")
>>> sb.RealCollobrier.generate_mesh(run=False)
>>> sb.RealCollobrier.model()
>>> sb.RealCollobrier.forward_run()
def atmos_data_connector(*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)

Generate a connector for the external atmos_data comming from other model such as Graffas (spatial rainfall generator).

Parameters

input_prcp : np.ndarray | None = None An np.ndarray with shape (nrow, ncol, npdt) wich stores the spatial rainfall which will be used by Smash.Ideally, the extend of this array match exactlly the extend of the Smash domain. input_pet : np.ndarray | None = None An np.ndarray with shape (nrow, ncol, npdt) wich stores the spatial evapotranspiration which will be used by Smash. input_dt : float = 3600. Time step in seconds of the input precipitation input_res : tuple | list = (1000., 1000.) The resolution of the input precipitation input_start_time : str = "2050-01-01 01:00" The date of the start_time input_bbox : dict | None = None The extend of the domain using a bbox. The convention used here is a dictionary like bbox={"left":xmin, "top": ymax, "right": xmax, "bottom": ymin}. If not provided, the extend of the smash domain will be used starting from xmin and ymin. input_epsg : int = 2154 The epsg code of the coordinate system. If not provided, the coordinate system used in Smash will be used. resampling_method: str The method to use to resample and crop the input array. Default is 'home_made_with_scipy_zoom' Choice are: ['rasterio_1', 'rasterio_2', 'home_made_with_scipy_zoom']. 'home_made_with_scipy_zoom' is the fastest method. 'rasterio_1' is the slowest method. However, 'rasterio' method use much tested and reliable method to resample and crop the array.

Examples

>>> prcp_array=np.zeros(200,200,20)+1.
>>> es=smashbox.SmashBox()
>>> sb.newmodel("RealCollobrier")
>>> sb.RealCollobrier.generate_mesh()
>>> sb.RealCollobrier.atmos_data_connector(input_prcp=prcp_array)
def import_parameters(self, model=None):
877    def import_parameters(self, model=None):
878        """
879        Import Geotiff parameter in Smash. This function wrap
880        smash.io.read_grid_parameters(). Path to the parameters is defined in
881        self._myparam.param.smash_parameters.
882
883        Parameter
884        ---------
885
886        model: smash.Model object
887            A Smash model object. If None, the Smash model of smashbox will be used
888
889        Examples
890        --------
891
892        >>> es=smashbox.SmashBox()
893        >>> sb.newmodel("RealCollobrier")
894        >>> sb.RealCollobrier.generate_mesh(run=False)
895        >>> sb.RealCollobrier.model()
896        >>> sb.RealCollobrier.import_parameters()
897        >>> sb.RealCollobrier.forward_run()
898        """
899        if self._myparam.param.smash_parameters is None:
900            print(
901                "</> Warning: no calibrated Smash parameters is used, leaving it to"
902                "  default."
903            )
904            return
905
906        if model is None:
907            model = self.mysmashmodel.smash
908
909        smash.io.read_grid_parameters(
910            model=model,
911            path=self._myparam.param.smash_parameters,
912        )

Import Geotiff parameter in Smash. This function wrap smash.io.read_grid_parameters(). Path to the parameters is defined in self._myparam.param.smash_parameters.

Parameter

model: smash.Model object A Smash model object. If None, the Smash model of smashbox will be used

Examples

>>> es=smashbox.SmashBox()
>>> sb.newmodel("RealCollobrier")
>>> sb.RealCollobrier.generate_mesh(run=False)
>>> sb.RealCollobrier.model()
>>> sb.RealCollobrier.import_parameters()
>>> sb.RealCollobrier.forward_run()
def export_parameters(self, path: os.PathLike = './output_smash_param'):
914    def export_parameters(self, path: os.PathLike = "./output_smash_param"):
915        """
916        Export Geotiff Smash parameter as Geotiff. This function wrap
917        smash.io.save_grid_parameters().
918
919        Parameters
920        ----------
921
922        path : os.PathLike = "./output_smash_param"
923            path to a directory where the parameter will be saved.
924
925        Examples
926        --------
927
928        >>> es=smashbox.SmashBox()
929        >>> sb.newmodel("RealCollobrier")
930        >>> sb.RealCollobrier.generate_mesh(run=False)
931        >>> sb.RealCollobrier.model()
932        >>> sb.RealCollobrier.import_parameters()
933        >>> sb.RealCollobrier.export_parameters()
934        """
935        smash.io.export_parameters(self.mysmashmodel.smash, path)

Export Geotiff Smash parameter as Geotiff. This function wrap smash.io.save_grid_parameters().

Parameters

path : os.PathLike = "./output_smash_param" path to a directory where the parameter will be saved.

Examples

>>> es=smashbox.SmashBox()
>>> sb.newmodel("RealCollobrier")
>>> sb.RealCollobrier.generate_mesh(run=False)
>>> sb.RealCollobrier.model()
>>> sb.RealCollobrier.import_parameters()
>>> sb.RealCollobrier.export_parameters()
def save_model_container_hdf5(self, path_to_hdf5: str | None = None, save_smash_model: bool = True):
 937    def save_model_container_hdf5(
 938        self, path_to_hdf5: str | None = None, save_smash_model: bool = True
 939    ):
 940        """
 941
 942        :param path_to_hdf5: Path to the hdf5 file, defaults to None. If None,
 943        the fucntion will return a dictionnary containing all data of the self object.
 944        :type path_to_hdf5: str | None, optional
 945        :param save_smash_model: Savec the smash models objects or no, defaults
 946        to True. If False the Smash models objects are not saved.
 947        :type save_smash_model: str, optional
 948        :return: if a path_to_hdf5 is None, a dictionary containing all
 949        attribute of the input object self is returned.
 950        :rtype: dict | None
 951
 952        """
 953
 954        if path_to_hdf5 is not None:
 955
 956            structure = (
 957                pyhdf5_handler.src.object_handler.generate_object_structure(
 958                    self, include_method=False
 959                )
 960            )
 961
 962            if not save_smash_model:
 963                del structure["warmup_model"]["smash"]
 964                del structure["mysmashmodel"]["smash"]
 965                del structure["optimize_model"]["smash"]
 966                del structure["validation_model"]["smash"]
 967
 968            pyhdf5_handler.save_object_to_hdf5file(
 969                path_to_hdf5=path_to_hdf5,
 970                instance=self,
 971                keys_data=structure,
 972                location=f"./{self._model_name}/",
 973            )
 974
 975            # Add missing data
 976            structure = (
 977                pyhdf5_handler.src.object_handler.generate_object_structure(
 978                    self._myparam, include_method=False
 979                )
 980            )
 981
 982            pyhdf5_handler.save_object_to_hdf5file(
 983                path_to_hdf5=path_to_hdf5,
 984                instance=self._myparam,
 985                keys_data=structure,
 986                location=f"./{self._model_name}/",
 987            )
 988            pyhdf5_handler.save_dict_to_hdf5file(
 989                path_to_hdf5=path_to_hdf5,
 990                dictionary={"istates": self._istates},
 991                location=f"./{self._model_name}/",
 992            )
 993            pyhdf5_handler.save_dict_to_hdf5file(
 994                path_to_hdf5=path_to_hdf5,
 995                dictionary={"fstates": self._fstates},
 996                location=f"./{self._model_name}/",
 997            )
 998
 999        else:
1000            dict_results = (
1001                pyhdf5_handler.src.object_handler.read_object_as_dict(self)
1002            )
1003
1004            if not save_smash_model:
1005                del dict_results["warmup_model"]["smash"]
1006                del dict_results["mysmashmodel"]["smash"]
1007                del structure["optimize_model"]["smash"]
1008                del structure["validation_model"]["smash"]
1009
1010            return dict_results
Parameters
  • path_to_hdf5: Path to the hdf5 file, defaults to None. If None, the fucntion will return a dictionnary containing all data of the self object.
  • save_smash_model: Savec the smash models objects or no, defaults to True. If False the Smash models objects are not saved.
Returns

if a path_to_hdf5 is None, a dictionary containing all attribute of the input object self is returned.

def save_model_container(self, path: str | None = None, save_full_model=False):
1012    def save_model_container(
1013        self,
1014        path: str | None = None,
1015        save_full_model=False,
1016    ):
1017        """
1018
1019        :param path: Path to the hdf5 file, defaults to None. If None,
1020        the fucntion will return a dictionnary containing all data of the self object.
1021        :type path: str | None, optional
1022
1023        """
1024
1025        if path is None:
1026            raise ValueError("Argument `path` is None.")
1027            return
1028
1029        if save_full_model is True:
1030            save_func = getattr(smash.io, "save_model")
1031        else:
1032            save_func = getattr(smash.io, "save_model_ddt")
1033
1034        if not os.path.exists(os.path.join(path, self._model_name)):
1035            os.makedirs(os.path.join(path, self._model_name))
1036
1037        structure = (
1038            pyhdf5_handler.src.object_handler.generate_object_structure(
1039                self, include_method=False
1040            )
1041        )
1042
1043        if "warmup_model" in structure:
1044            del structure["warmup_model"]["smash"]
1045        if "mysmashmodel" in structure:
1046            del structure["mysmashmodel"]["smash"]
1047        if "optimize_model" in structure:
1048            del structure["optimize_model"]["smash"]
1049        if "validation_model" in structure:
1050            del structure["validation_model"]["smash"]
1051
1052        pyhdf5_handler.save_object_to_hdf5file(
1053            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1054            instance=self,
1055            keys_data=structure,
1056            location=f"./",
1057        )
1058
1059        structure = (
1060            pyhdf5_handler.src.object_handler.generate_object_structure(
1061                self._myparam, include_method=False
1062            )
1063        )
1064        pyhdf5_handler.save_object_to_hdf5file(
1065            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1066            instance=self._myparam,
1067            keys_data=structure,
1068            location=f"./",
1069        )
1070
1071        pyhdf5_handler.save_dict_to_hdf5file(
1072            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1073            dictionary={"istates": self._istates},
1074            location=f"./",
1075        )
1076        pyhdf5_handler.save_dict_to_hdf5file(
1077            path_to_hdf5=os.path.join(path, self._model_name, "smashbox.hdf5"),
1078            dictionary={"fstates": self._fstates},
1079            location=f"./",
1080        )
1081
1082        if self.mysmashmodel.smash is not None:
1083            save_func(
1084                self.mysmashmodel.smash,
1085                os.path.join(path, self._model_name, "mysmashmodel.hdf5"),
1086            )
1087        if self.warmup_model.smash is not None:
1088            save_func(
1089                self.warmup_model.smash,
1090                os.path.join(path, self._model_name, "warmup_model.hdf5"),
1091            )
1092        if self.optimize_model.smash is not None:
1093            save_func(
1094                self.optimize_model.smash,
1095                os.path.join(path, self._model_name, "optimize_model.hdf5"),
1096            )
1097        if self.validation_model.smash is not None:
1098            save_func(
1099                self.validation_model.smash,
1100                os.path.join(path, self._model_name, "validation_model.hdf5"),
1101            )
Parameters
  • path: Path to the hdf5 file, defaults to None. If None, the fucntion will return a dictionnary containing all data of the self object.