From a251423b424843bd31197b953cfa7b0512e29eae Mon Sep 17 00:00:00 2001 From: Daafip Date: Fri, 5 Apr 2024 12:35:50 +0200 Subject: [PATCH 1/6] adding snow --- README.md | 4 +++- pyproject.toml | 2 +- src/HBV/HBV_bmi.py | 56 +++++++++++++++++++++++++++++++++------------- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index bccef90..da4bf38 100644 --- a/README.md +++ b/README.md @@ -53,4 +53,6 @@ Be aware of the non-intuitive [BMI](https://github.com/eWaterCycle/grpc4bmi) imp ## v1.3.1 - Fix bug in time indexing ## v1.3.2 -- typo in update updating_dict_var_obj: was getting values wrong \ No newline at end of file +- typo in update updating_dict_var_obj: was getting values wrong +## V1.4.0 +- adding snow reservoir \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d5cf85f..63828f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "HBV" description = "Dev version for a HBV hydrological model using BMI for eWaterCycle." readme = "README.md" license = "Apache-2.0" -version = "1.3.2" +version = "1.4.0" authors = [ { name = "David Haasnoot", email = "davidhaasnoot@gmail.com" }, ] diff --git a/src/HBV/HBV_bmi.py b/src/HBV/HBV_bmi.py index eb372e6..430266b 100644 --- a/src/HBV/HBV_bmi.py +++ b/src/HBV/HBV_bmi.py @@ -13,10 +13,13 @@ "Tlag": "d", "Kf": "-", "Ks": "-", + "FM": "mm/deg/d", "Si": "mm", "Su": "mm", "Sf": "mm", "Ss": "mm", + "Sp": "mm", + "M_dt": "mm/d", "Ei_dt": "mm/d", "Ea_dt": "mm/d", "Qs_dt": "mm/d", @@ -44,17 +47,20 @@ def initialize(self, config_file: str) -> None: # store forcing & obs self.P = utils.load_var(self.config["precipitation_file"], "pr") - # add Tas, Tmin and Tmax support for snow component ??! self.EP = utils.load_var(self.config["potential_evaporation_file"], "pev") + self.Tmin = utils.load_var(self.config["minimum_temperature_file"], "tasmin") + self.Tmax = utils.load_var(self.config["maximum_temperature_file"], "tasmax") + self.Tmean = (self.Tmax + self.Tmin) / 2 + # set up times - self.Ts = self.P['time'].astype("datetime64[s]") - self.end_timestep = len(self.Ts.values) + self.time = self.P['time'].astype("datetime64[s]") + self.end_timestep = len(self.time.values) self.current_timestep = 0 # time step size in seconds (to be able to do unit conversions) - change here to days self.dt = ( - self.Ts.values[1] - self.Ts.values[0] + self.time.values[1] - self.time.values[0] ) / np.timedelta64(1, "s") / 24 / 3600 # define parameters @@ -67,7 +73,8 @@ def initialize(self, config_file: str) -> None: s_in = np.array(self.config['initial_storage'].split(','), dtype=np.float64) self.set_storage(s_in) - # set other flows for initial step + # set other flows for initial step + self.M_dt = 0 # snow melt self.Ei_dt = 0 # interception evaporation self.Ea_dt = 0 # actual evaportation self.Qs_dt = 0 # slow flow @@ -75,7 +82,8 @@ def initialize(self, config_file: str) -> None: self.Q_tot_dt = 0 # total flow self.Q = 0 # Final model prediction - # stores corresponding objects for variables + # other constant for Snow: + self.Tt = -0.5 # Threshold temperature set for now. Can be between -1 to 1 def set_pars(self, par) -> None: self.I_max = par[0] # maximum interception @@ -86,12 +94,14 @@ def set_pars(self, par) -> None: self.T_lag = self.set_tlag(par[5]) # used in triangular transfer function self.Kf = par[6] # Qf=kf*sf self.Ks = par[7] # Qs=Ks* + self.FM = par[8] # degree day factor: Melt FM * (Tmean-Tt) def set_storage(self, stor) -> None: self.Si = stor[0] # Interception storage self.Su = stor[1] # Unsaturated Rootzone Storage self.Sf = stor[2] # Fastflow storage self.Ss = stor[3] # Groundwater storage + self.Sp = stor[4] # SnowPack storage def update(self) -> None: """ Updates model one timestep @@ -107,10 +117,23 @@ def update(self) -> None: self.P_dt = self.P.isel(time=self.current_timestep).to_numpy() * self.dt self.Ep_dt = self.EP.isel(time=self.current_timestep).to_numpy() * self.dt + # split P into rain and snow: + if self.Tmean < self.Tt: + self.Pr = 0 # if snowing, no rainfall + self.Ps = self.P_dt # all precip goes into snow + self.M_dt = 0 # too cold to meld + self.Sp += self.Ps + else: + self.Pr = self.P_dt # if not snowing, all rainfall + self.Ps = 0 # No snow + self.M_dt = min(self.Sp / self.dt, self.FM * (self.Tmean - self.Tt)) # melt factor * diff in temp + self.Sp -= self.M_dt # remove melt from snowpack content + self.Pr += self.M_dt # add it to `rainfall`: snow melt can also be intercepted + # Interception Reservoir - if self.P_dt > 0: + if self.Pr > 0: # if there is rain, no evap - self.Si = self.Si + self.P_dt # increase the storage + self.Si = self.Si + self.Pr # increase the storage self.Pe_dt = max((self.Si - self.I_max) / self.dt, 0) self.Si = self.Si - self.Pe_dt self.Ei_dt = 0 # if rainfall, evaporation = 0 as too moist @@ -169,10 +192,13 @@ def updating_dict_var_obj(self) -> None: "Tlag": self.T_lag, "Kf": self.Kf, "Ks": self.Ks, + "FM": self.FM, "Si": self.Si, "Su": self.Su, "Sf": self.Sf, "Ss": self.Ss, + "Sp": self.Sp, + "M_dt": self.M_dt, "Ei_dt": self.Ei_dt, "Ea_dt": self.Ea_dt, "Qs_dt": self.Qs_dt, @@ -189,7 +215,7 @@ def updating_obj_from_dict_var(self) -> None: self.set_storage([self.dict_var_obj[stor] for stor in stor_names]) def weight_function(self): - """Generates weights for convolution""" + """Generates weights for convolution using generates a weibull weight function""" n_max = int(np.ceil(self.T_lag)) if n_max == 1: @@ -230,7 +256,7 @@ def add_time_lag(self) -> None: self.memory_vector_lag[-1] = 0 def set_empty_memory_vector_lag(self): - self.weights = self.weight_function() # generates weights using a weibull weight function + self.weights = self.weight_function() return np.zeros(self.T_lag) def get_component_name(self) -> str: @@ -319,16 +345,16 @@ def get_output_var_names(self) -> Tuple[str]: # The BMI has to have some time-related functionality: def get_start_time(self) -> float: """Return end time in seconds since 1 january 1970.""" - return get_unixtime(self.Ts.isel(time=0).values) # type: ignore + return get_unixtime(self.time.isel(time=0).values) # type: ignore def get_end_time(self) -> float: """Return end time in seconds since 1 january 1970.""" - return get_unixtime(self.Ts.isel(time=-1).values) # type: ignore + return get_unixtime(self.time.isel(time=-1).values) # type: ignore def get_current_time(self) -> float: """Return current time in seconds since 1 january 1970.""" # we get the timestep from the data, but the stopping condition requires it to go one beyond. - return get_unixtime(self.Ts.isel(time=self.current_timestep).values) # type: ignore + return get_unixtime(self.time.isel(time=self.current_timestep).values) # type: ignore def set_tlag(self, T_lag_in) -> int: """Ensures T_lag is an integer of at minimum 1""" @@ -336,8 +362,8 @@ def set_tlag(self, T_lag_in) -> int: return T_lag def get_time_step(self) -> float: - if len(self.Ts) > 1: - return float((self.Ts.values[1] - self.Ts.values[0]) / np.timedelta64(1, "s")) + if len(self.time) > 1: + return float((self.time.values[1] - self.time.values[0]) / np.timedelta64(1, "s")) else: message = "No time series defined" warnings.warn(message=message, category=ImportWarning) From e9e8b112d272837b51460e0e52896b2cd360b02b Mon Sep 17 00:00:00 2001 From: Daafip Date: Fri, 5 Apr 2024 12:40:08 +0200 Subject: [PATCH 2/6] adding snow - updating_obj_from_dict_var list forgot sp ref --- src/HBV/HBV_bmi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/HBV/HBV_bmi.py b/src/HBV/HBV_bmi.py index 430266b..de1a0fa 100644 --- a/src/HBV/HBV_bmi.py +++ b/src/HBV/HBV_bmi.py @@ -108,9 +108,9 @@ def update(self) -> None: Old documentation: Function to run the update part of one timestep of the HBV model - par: array/list containing 8 parameters: Imax, Ce, Sumax, beta, Pmax, T_lag, Kf, Ks (floats) - s_in: array/list containing 4 storage terms which are input to the timestep: Si, Su, Sf, Ss (floats) - storage_terms: list of arrays which store: Si, Su, Sf, Ss, Ei_dt, Ea_dt, Qs_dt_lst, Qf_dt_lst, Q_tot_dt + par: array/list containing 9 parameters: Imax, Ce, Sumax, beta, Pmax, T_lag, Kf, Ks, Fm (floats) + s_in: array/list containing 5 storage terms which are input to the timestep: Si, Su, Sf, Ss,Sp (floats) + storage_terms: list of arrays which store: Si, Su, Sf, Ss,Sp, Ei_dt, Ea_dt, Qs_dt_lst, Qf_dt_lst, Q_tot_dt step_n - nth step which formard model takes: used to determin which Precipitaion & evaporation to use """ if self.current_timestep < self.end_timestep: @@ -209,8 +209,8 @@ def updating_dict_var_obj(self) -> None: def updating_obj_from_dict_var(self) -> None: """Function which inverts the dictionary above & sets objects correctly""" - param_names = ["Imax", "Ce", "Sumax", "Beta", "Pmax", "Tlag", "Kf", "Ks"] - stor_names = ["Si", "Su", "Sf", "Ss"] + param_names = ["Imax", "Ce", "Sumax", "Beta", "Pmax", "Tlag", "Kf", "Ks","FM"] + stor_names = ["Si", "Su", "Sf", "Ss","SP"] self.set_pars([self.dict_var_obj[par] for par in param_names]) self.set_storage([self.dict_var_obj[stor] for stor in stor_names]) From 43c0b2e57c4d1c1390227d88895a32df40ed91c1 Mon Sep 17 00:00:00 2001 From: Daafip Date: Fri, 5 Apr 2024 15:00:00 +0200 Subject: [PATCH 3/6] Change so that model only takes a reference temp/mean temp --- src/HBV/HBV_bmi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/HBV/HBV_bmi.py b/src/HBV/HBV_bmi.py index de1a0fa..2ee8615 100644 --- a/src/HBV/HBV_bmi.py +++ b/src/HBV/HBV_bmi.py @@ -49,9 +49,7 @@ def initialize(self, config_file: str) -> None: self.EP = utils.load_var(self.config["potential_evaporation_file"], "pev") - self.Tmin = utils.load_var(self.config["minimum_temperature_file"], "tasmin") - self.Tmax = utils.load_var(self.config["maximum_temperature_file"], "tasmax") - self.Tmean = (self.Tmax + self.Tmin) / 2 + self.Tmean = utils.load_var(self.config["mean_temperature_file"], "tasmean") # set up times self.time = self.P['time'].astype("datetime64[s]") From f9756455ac52fe8c1d1a75db4d86d93528936cfb Mon Sep 17 00:00:00 2001 From: Daafip Date: Fri, 5 Apr 2024 16:22:49 +0200 Subject: [PATCH 4/6] oversight bug fix in new temperature array --- src/HBV/HBV_bmi.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/HBV/HBV_bmi.py b/src/HBV/HBV_bmi.py index 2ee8615..f767032 100644 --- a/src/HBV/HBV_bmi.py +++ b/src/HBV/HBV_bmi.py @@ -114,9 +114,10 @@ def update(self) -> None: if self.current_timestep < self.end_timestep: self.P_dt = self.P.isel(time=self.current_timestep).to_numpy() * self.dt self.Ep_dt = self.EP.isel(time=self.current_timestep).to_numpy() * self.dt + self.Tmean_i = self.Tmean.isel(time=self.current_timestep).to_numpy() * self.dt # split P into rain and snow: - if self.Tmean < self.Tt: + if self.Tmean_i < self.Tt: self.Pr = 0 # if snowing, no rainfall self.Ps = self.P_dt # all precip goes into snow self.M_dt = 0 # too cold to meld From 4bb296ae171f5e6b4b4e6472a1cca88ec24ac731 Mon Sep 17 00:00:00 2001 From: Daafip Date: Fri, 5 Apr 2024 16:25:20 +0200 Subject: [PATCH 5/6] 2nd oversight bug fix in new temperature array --- src/HBV/HBV_bmi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HBV/HBV_bmi.py b/src/HBV/HBV_bmi.py index f767032..fa03a9c 100644 --- a/src/HBV/HBV_bmi.py +++ b/src/HBV/HBV_bmi.py @@ -125,7 +125,7 @@ def update(self) -> None: else: self.Pr = self.P_dt # if not snowing, all rainfall self.Ps = 0 # No snow - self.M_dt = min(self.Sp / self.dt, self.FM * (self.Tmean - self.Tt)) # melt factor * diff in temp + self.M_dt = min(self.Sp / self.dt, self.FM * (self.Tmean_i - self.Tt)) # melt factor * diff in temp self.Sp -= self.M_dt # remove melt from snowpack content self.Pr += self.M_dt # add it to `rainfall`: snow melt can also be intercepted From 720700b22abd1a02eeaba5247dc2a2226cb5d270 Mon Sep 17 00:00:00 2001 From: Daafip Date: Mon, 8 Apr 2024 09:48:32 +0200 Subject: [PATCH 6/6] small refactor of formatting, still needs work --- src/HBV/HBV_bmi.py | 216 +++++++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 107 deletions(-) diff --git a/src/HBV/HBV_bmi.py b/src/HBV/HBV_bmi.py index fa03a9c..a55eb67 100644 --- a/src/HBV/HBV_bmi.py +++ b/src/HBV/HBV_bmi.py @@ -4,28 +4,27 @@ import numpy as np import warnings - -DICT_VAR_UNITS = {"Imax":"mm", - "Ce": "-", - "Sumax": "mm", - "Beta": "-", - "Pmax": "mm", - "Tlag": "d", - "Kf": "-", - "Ks": "-", - "FM": "mm/deg/d", - "Si": "mm", - "Su": "mm", - "Sf": "mm", - "Ss": "mm", - "Sp": "mm", - "M_dt": "mm/d", - "Ei_dt": "mm/d", - "Ea_dt": "mm/d", - "Qs_dt": "mm/d", - "Qf_dt": "mm/d", - "Q_tot_dt": "mm/d", - "Q": "mm/d"} +DICT_VAR_UNITS = {"Imax": "mm", + "Ce": "-", + "Sumax": "mm", + "Beta": "-", + "Pmax": "mm", + "Tlag": "d", + "Kf": "-", + "Ks": "-", + "FM": "mm/deg/d", + "Si": "mm", + "Su": "mm", + "Sf": "mm", + "Ss": "mm", + "Sp": "mm", + "M_dt": "mm/d", + "Ei_dt": "mm/d", + "Ea_dt": "mm/d", + "Qs_dt": "mm/d", + "Qf_dt": "mm/d", + "Q_tot_dt": "mm/d", + "Q": "mm/d"} class HBV(Bmi): @@ -58,8 +57,8 @@ def initialize(self, config_file: str) -> None: # time step size in seconds (to be able to do unit conversions) - change here to days self.dt = ( - self.time.values[1] - self.time.values[0] - ) / np.timedelta64(1, "s") / 24 / 3600 + self.time.values[1] - self.time.values[0] + ) / np.timedelta64(1, "s") / 24 / 3600 # define parameters self.set_pars(np.array(self.config['parameters'].split(','), dtype=np.float64)) @@ -72,34 +71,34 @@ def initialize(self, config_file: str) -> None: self.set_storage(s_in) # set other flows for initial step - self.M_dt = 0 # snow melt - self.Ei_dt = 0 # interception evaporation - self.Ea_dt = 0 # actual evaportation - self.Qs_dt = 0 # slow flow - self.Qf_dt = 0 # fast flow - self.Q_tot_dt = 0 # total flow - self.Q = 0 # Final model prediction + self.M_dt = 0 # snow melt + self.Ei_dt = 0 # interception evaporation + self.Ea_dt = 0 # actual evaportation + self.Qs_dt = 0 # slow flow + self.Qf_dt = 0 # fast flow + self.Q_tot_dt = 0 # total flow + self.Q = 0 # Final model prediction # other constant for Snow: - self.Tt = -0.5 # Threshold temperature set for now. Can be between -1 to 1 + self.Tt = -0.5 # Threshold temperature set for now. Can be between -1 to 1 def set_pars(self, par) -> None: - self.I_max = par[0] # maximum interception - self.Ce = par[1] # Ea = Su / (sumax * Ce) * Ep - self.Su_max = par[2] # '' - self.beta = par[3] # Cr = (su/sumax)**beta - self.P_max = par[4] # Qus = Pmax * (Su/Sumax) - self.T_lag = self.set_tlag(par[5]) # used in triangular transfer function - self.Kf = par[6] # Qf=kf*sf - self.Ks = par[7] # Qs=Ks* - self.FM = par[8] # degree day factor: Melt FM * (Tmean-Tt) + self.I_max = par[0] # maximum interception + self.Ce = par[1] # Ea = Su / (sumax * Ce) * Ep + self.Su_max = par[2] # '' + self.beta = par[3] # Cr = (su/sumax)**beta + self.P_max = par[4] # Qus = Pmax * (Su/Sumax) + self.T_lag = self.set_tlag(par[5]) # used in triangular transfer function + self.Kf = par[6] # Qf=kf*sf + self.Ks = par[7] # Qs=Ks* + self.FM = par[8] # degree day factor: Melt FM * (Tmean-Tt) def set_storage(self, stor) -> None: - self.Si = stor[0] # Interception storage - self.Su = stor[1] # Unsaturated Rootzone Storage - self.Sf = stor[2] # Fastflow storage - self.Ss = stor[3] # Groundwater storage - self.Sp = stor[4] # SnowPack storage + self.Si = stor[0] # Interception storage + self.Su = stor[1] # Unsaturated Rootzone Storage + self.Sf = stor[2] # Fastflow storage + self.Ss = stor[3] # Groundwater storage + self.Sp = stor[4] # SnowPack storage def update(self) -> None: """ Updates model one timestep @@ -112,54 +111,54 @@ def update(self) -> None: step_n - nth step which formard model takes: used to determin which Precipitaion & evaporation to use """ if self.current_timestep < self.end_timestep: - self.P_dt = self.P.isel(time=self.current_timestep).to_numpy() * self.dt + self.P_dt = self.P.isel(time=self.current_timestep).to_numpy() * self.dt self.Ep_dt = self.EP.isel(time=self.current_timestep).to_numpy() * self.dt self.Tmean_i = self.Tmean.isel(time=self.current_timestep).to_numpy() * self.dt # split P into rain and snow: if self.Tmean_i < self.Tt: - self.Pr = 0 # if snowing, no rainfall - self.Ps = self.P_dt # all precip goes into snow - self.M_dt = 0 # too cold to meld + self.Pr = 0 # if snowing, no rainfall + self.Ps = self.P_dt # all precip goes into snow + self.M_dt = 0 # too cold to meld self.Sp += self.Ps else: - self.Pr = self.P_dt # if not snowing, all rainfall - self.Ps = 0 # No snow - self.M_dt = min(self.Sp / self.dt, self.FM * (self.Tmean_i - self.Tt)) # melt factor * diff in temp - self.Sp -= self.M_dt # remove melt from snowpack content - self.Pr += self.M_dt # add it to `rainfall`: snow melt can also be intercepted + self.Pr = self.P_dt # if not snowing, all rainfall + self.Ps = 0 # No snow + self.M_dt = min(self.Sp / self.dt, self.FM * (self.Tmean_i - self.Tt)) # melt factor * diff in temp + self.Sp -= self.M_dt # remove melt from snowpack content + self.Pr += self.M_dt # add it to `rainfall`: snow melt can also be intercepted # Interception Reservoir if self.Pr > 0: # if there is rain, no evap - self.Si = self.Si + self.Pr # increase the storage + self.Si = self.Si + self.Pr # increase the storage self.Pe_dt = max((self.Si - self.I_max) / self.dt, 0) - self.Si = self.Si - self.Pe_dt - self.Ei_dt = 0 # if rainfall, evaporation = 0 as too moist + self.Si = self.Si - self.Pe_dt + self.Ei_dt = 0 # if rainfall, evaporation = 0 as too moist else: # Evaporation only when there is no rainfall - self.Pe_dt = 0 # nothing flows in so must be 0 - self.Ei_dt = min(self.Ep_dt, self.Si / self.dt) # evaporation limited by storage - self.Si = self.Si - self.Ei_dt + self.Pe_dt = 0 # nothing flows in so must be 0 + self.Ei_dt = min(self.Ep_dt, self.Si / self.dt) # evaporation limited by storage + self.Si = self.Si - self.Ei_dt # split flow into Unsaturated Reservoir and Fast flow if self.Pe_dt > 0: - cr = (self.Su / self.Su_max)**self.beta - Qiu_dt = (1 - cr ) * self.Pe_dt # flux from Ir to Ur - self.Su = self.Su + Qiu_dt - Quf_dt = cr * self.Pe_dt # flux from Su to Sf + cr = (self.Su / self.Su_max) ** self.beta + Qiu_dt = (1 - cr) * self.Pe_dt # flux from Ir to Ur + self.Su = self.Su + Qiu_dt + Quf_dt = cr * self.Pe_dt # flux from Su to Sf else: Quf_dt = 0 # Transpiration - self.Ep_dt = max(0, self.Ep_dt - self.Ei_dt) # Transpiration + self.Ep_dt = max(0, self.Ep_dt - self.Ei_dt) # Transpiration self.Ea_dt = self.Ep_dt * (self.Su / (self.Su_max * self.Ce)) - self.Ea_dt = min(self.Su, self.Ea_dt) # limited by water in soil - self.Su = self.Su - self.Ea_dt + self.Ea_dt = min(self.Su, self.Ea_dt) # limited by water in soil + self.Su = self.Su - self.Ea_dt # Percolation - Qus_dt = self.P_max * (self.Su / self.Su_max) * self.dt # Flux from Su to Ss - self.Su = self.Su - Qus_dt + Qus_dt = self.P_max * (self.Su / self.Su_max) * self.dt # Flux from Su to Ss + self.Su = self.Su - Qus_dt # Fast Reservoir self.Sf = self.Sf + Quf_dt @@ -183,33 +182,33 @@ def update(self) -> None: def updating_dict_var_obj(self) -> None: """Function which makes getting the objects more readable- but adds more boiler plate..""" self.dict_var_obj = { - "Imax": self.I_max, - "Ce": self.Ce, - "Sumax": self.Su_max, - "Beta": self.beta, - "Pmax": self.P_max, - "Tlag": self.T_lag, - "Kf": self.Kf, - "Ks": self.Ks, - "FM": self.FM, - "Si": self.Si, - "Su": self.Su, - "Sf": self.Sf, - "Ss": self.Ss, - "Sp": self.Sp, - "M_dt": self.M_dt, - "Ei_dt": self.Ei_dt, - "Ea_dt": self.Ea_dt, - "Qs_dt": self.Qs_dt, - "Qf_dt": self.Qf_dt, - "Q_tot_dt": self.Q_tot_dt, - "Q": self.Q, - } + "Imax": self.I_max, + "Ce": self.Ce, + "Sumax": self.Su_max, + "Beta": self.beta, + "Pmax": self.P_max, + "Tlag": self.T_lag, + "Kf": self.Kf, + "Ks": self.Ks, + "FM": self.FM, + "Si": self.Si, + "Su": self.Su, + "Sf": self.Sf, + "Ss": self.Ss, + "Sp": self.Sp, + "M_dt": self.M_dt, + "Ei_dt": self.Ei_dt, + "Ea_dt": self.Ea_dt, + "Qs_dt": self.Qs_dt, + "Qf_dt": self.Qf_dt, + "Q_tot_dt": self.Q_tot_dt, + "Q": self.Q, + } def updating_obj_from_dict_var(self) -> None: """Function which inverts the dictionary above & sets objects correctly""" - param_names = ["Imax", "Ce", "Sumax", "Beta", "Pmax", "Tlag", "Kf", "Ks","FM"] - stor_names = ["Si", "Su", "Sf", "Ss","SP"] + param_names = ["Imax", "Ce", "Sumax", "Beta", "Pmax", "Tlag", "Kf", "Ks", "FM"] + stor_names = ["Si", "Su", "Sf", "Ss", "SP"] self.set_pars([self.dict_var_obj[par] for par in param_names]) self.set_storage([self.dict_var_obj[stor] for stor in stor_names]) @@ -221,18 +220,19 @@ def weight_function(self): weights = float(1) else: weights = np.zeros(n_max) - th = self.T_lag/2 + th = self.T_lag / 2 nh = int(np.floor(th)) for i in range(0, nh): - weights[i] = (float(i+1)-0.5)/th + weights[i] = (float(i + 1) - 0.5) / th i = nh - weights[i] = (1 + (float(i+1) - 1) / th) * (th - int(np.floor(th)))/2 + (1 + (self.T_lag - float(i + 1)) / th) * (int(np.floor(th)) + 1 - th) / 2 - for i in range(nh+1, int(np.floor(self.T_lag))): + weights[i] = (1 + (float(i + 1) - 1) / th) * (th - int(np.floor(th))) / 2 + ( + 1 + (self.T_lag - float(i + 1)) / th) * (int(np.floor(th)) + 1 - th) / 2 + for i in range(nh + 1, int(np.floor(self.T_lag))): weights[i] = (self.T_lag - float(i + 1) + 0.5) / th if self.T_lag > int(np.floor(self.T_lag)): - weights[int(np.floor(self.T_lag))] = (self.T_lag - int(np.floor(self.T_lag)))**2 / (2*th) + weights[int(np.floor(self.T_lag))] = (self.T_lag - int(np.floor(self.T_lag))) ** 2 / (2 * th) weights = weights / sum(weights) @@ -275,7 +275,8 @@ def get_value(self, var_name: str, dest: np.ndarray) -> np.ndarray: if mem_index < len(self.memory_vector_lag): dest[:] = self.memory_vector_lag[mem_index] else: - raise IndexError(f'{mem_index} is out of range for memory vector size {len(self.memory_vector_lag)}') + raise IndexError( + f'{mem_index} is out of range for memory vector size {len(self.memory_vector_lag)}') return dest # otherwise return the variable from the dictionary @@ -325,7 +326,8 @@ def set_value(self, var_name: str, src: np.ndarray) -> None: if mem_index < len(self.memory_vector_lag): self.memory_vector_lag[mem_index] = src[0] else: - raise IndexError(f'{mem_index} is out of range for memory vector size {len(self.memory_vector_lag)}') + raise IndexError( + f'{mem_index} is out of range for memory vector size {len(self.memory_vector_lag)}') # all other values can be set here elif var_name in self.dict_var_obj: @@ -344,16 +346,16 @@ def get_output_var_names(self) -> Tuple[str]: # The BMI has to have some time-related functionality: def get_start_time(self) -> float: """Return end time in seconds since 1 january 1970.""" - return get_unixtime(self.time.isel(time=0).values) # type: ignore + return get_unixtime(self.time.isel(time=0).values) # type: ignore def get_end_time(self) -> float: """Return end time in seconds since 1 january 1970.""" - return get_unixtime(self.time.isel(time=-1).values) # type: ignore + return get_unixtime(self.time.isel(time=-1).values) # type: ignore def get_current_time(self) -> float: """Return current time in seconds since 1 january 1970.""" # we get the timestep from the data, but the stopping condition requires it to go one beyond. - return get_unixtime(self.time.isel(time=self.current_timestep).values) # type: ignore + return get_unixtime(self.time.isel(time=self.current_timestep).values) # type: ignore def set_tlag(self, T_lag_in) -> int: """Ensures T_lag is an integer of at minimum 1""" @@ -373,7 +375,7 @@ def get_time_units(self) -> str: # TODO implement setting different timestep? def get_value_at_indices( - self, name: str, dest: np.ndarray, inds: np.ndarray) -> np.ndarray: + self, name: str, dest: np.ndarray, inds: np.ndarray) -> np.ndarray: raise NotImplementedError() # TODO implement @@ -469,10 +471,10 @@ def get_grid_face_nodes(self, grid: int, face_nodes: np.ndarray) -> np.ndarray: raise NotImplementedError() def get_grid_nodes_per_face( - self, grid: int, nodes_per_face: np.ndarray) -> np.ndarray: + self, grid: int, nodes_per_face: np.ndarray) -> np.ndarray: raise NotImplementedError() def get_unixtime(Ts: np.datetime64) -> int: """Get unix timestamp (seconds since 1 january 1970) from a np.datetime64.""" - return np.datetime64(Ts).astype("datetime64[s]").astype("int") \ No newline at end of file + return np.datetime64(Ts).astype("datetime64[s]").astype("int")