def pastas_hook(obj): for key, value in obj.items(): if key in ["tmin", "tmax", "date_modified", "date_created"]: val = pd.Timestamp(value) if val is pd.NaT: val = None obj[key] = val elif key == "series": try: obj[key] = pd.read_json(value, typ='series', orient="split") except: try: obj[key] = ps.TimeSeries(**value) except: obj[key] = value elif key == "time_offset": obj[key] = pd.Timedelta(value) elif key == "parameters": # Necessary to maintain order when using the JSON format! value = json.loads(value, object_pairs_hook=OrderedDict) param = pd.DataFrame(data=value, columns=value.keys()).T obj[key] = param.apply(pd.to_numeric, errors="ignore") else: try: obj[key] = json.loads(value, object_hook=pastas_hook) except: obj[key] = value return obj
def _parse_stressmodel_dict(self, d: Dict, onam: Optional[str] = None) -> Dict: """Internal method to parse StressModel dictionary. Note: supports 'nearest' or 'nearest <kind>' as input to 'stress', which will automatically select nearest stress with kind=<kind>. Requires "x" and "y" locations to be present in both oseries and stresses metadata. Parameters ---------- d : dict dictionary containing WellModel information onam : str, optional name of oseries used when 'nearest <kind>' is provided as stress, by default None Returns ------- d : dict dictionary that can be read by ps.io.base.load(), containing stresses obtained from PastaStore, and setting defaults if they were not already provided. """ # get stress snam = d.pop("stress") # if list, obtain first and only entry if isinstance(snam, list): snam = snam[0] # if str, either name of single series or 'nearest <kind>' if snam.startswith("nearest"): if len(snam.split()) > 1: kind = snam.split()[-1] else: kind = None logger.info(f" | using nearest stress with kind='{kind}'") if kind == "oseries": snam = self.pstore.get_nearest_oseries(onam).iloc[0, 0] else: snam = self.pstore.get_nearest_stresses( onam, kind=kind).iloc[0, 0] s, smeta = self.pstore.get_stresses(snam, return_metadata=True) s = ps.TimeSeries(s, snam, settings=d.pop("settings", None), metadata=smeta) d["stress"] = [s.to_dict()] # use stress name if not provided if "name" not in d: d["name"] = snam # rfunc if "rfunc" not in d: logger.info(" | no 'rfunc' provided, using 'Gamma'") d["rfunc"] = "Gamma" return d
def add_recharge(self, ml: ps.Model, rfunc=ps.Gamma, recharge=ps.rch.Linear(), recharge_name: str = "recharge") -> None: """Add recharge to a pastas model. Uses closest precipitation and evaporation timeseries in database. These are assumed to be labeled with kind = 'prec' or 'evap'. Parameters ---------- ml : pastas.Model pastas.Model object rfunc : pastas.rfunc, optional response function to use for recharge in model, by default ps.Gamma (for different response functions, see pastas documentation) recharge : ps.RechargeModel recharge model to use, default is ps.rch.Linear() recharge_name : str name of the RechargeModel """ # get nearest prec and evap stns names = [] for var in ("prec", "evap"): try: name = self.get_nearest_stresses(ml.oseries.name, kind=var).iloc[0, 0] except AttributeError: msg = "No precipitation or evaporation timeseries found!" raise Exception(msg) if isinstance(name, float): if np.isnan(name): raise ValueError(f"Unable to find nearest '{var}' stress! " "Check X and Y coordinates.") else: names.append(name) if len(names) == 0: msg = "No precipitation or evaporation timeseries found!" raise Exception(msg) # get data tsdict = self.conn.get_stresses(names) stresses = [] for (k, s), setting in zip(tsdict.items(), ("prec", "evap")): metadata = self.conn.get_metadata("stresses", k, as_frame=False) stresses.append( ps.TimeSeries(s, name=k, settings=setting, metadata=metadata)) # add recharge to model rch = ps.RechargeModel(stresses[0], stresses[1], rfunc, name=recharge_name, recharge=recharge, settings=("prec", "evap"), metadata=[i.metadata for i in stresses]) ml.add_stressmodel(rch)
def test_add_pastas_timeseries(request, conn): o1 = pd.DataFrame(data=1.0, columns=["test_df"], index=pd.date_range("2000", periods=10, freq="D")) o1.index.name = "test_idx" ts = ps.TimeSeries(o1, metadata={"x": 100000., "y": 400000.}) conn.add_oseries(ts, "test_pastas_ts", metadata=None) conn.add_stress(ts, "test_pastas_ts", kind="test", metadata={"x": 200000., "y": 500000.}) conn.del_oseries("test_pastas_ts") conn.del_stress("test_pastas_ts") return
def add_series(self, series, name=None, kind=None, metadata=None, settings=None, **kwargs): """Method to add series to the oseries or stresses database. Parameters ---------- series: pandas.Series / pastas.TimeSeries Series object. name: str String with the name of the series that will be maintained in the database. kind: str The kind of series that is added. When oseries are added it is necessary to state "oseries" here. metadata: dict Dictionary with any metadata that will be passed to the TimeSeries object that is created internally. settings: dict or str Dictionary with any settings that will be passed to the TimeSeries object that is created internally. Returns ------- """ if name is None: name = series.name if kind == "oseries": data = self.oseries else: data = self.stresses if name in data.index: logger.error("Time series with name %s is already present in the \ database. Please provide a different name." % name) try: ts = ps.TimeSeries(series=series, name=name, settings=settings, metadata=metadata, **kwargs) except: logger.warning("Time series %s is ommitted from the database." % name) return data.at[name, "name"] = name data.at[name, "series"] = ts # Do not add as first! data.at[name, "kind"] = kind # Transfer x, y and z to dataframe as well to increase speed. for i in ts.metadata.keys(): value = ts.metadata[i] data.at[name, i] = value
def create_model(self, name: str, modelname: str = None, add_recharge: bool = True, recharge_name: str = "recharge") -> ps.Model: """Create a pastas Model. Parameters ---------- name : str name of the oseries to create a model for modelname : str, optional name of the model, default is None, which uses oseries name add_recharge : bool, optional add recharge to the model by looking for the closest precipitation and evaporation timeseries in the stresses library, by default True recharge_name : str name of the RechargeModel Returns ------- pastas.Model model for the oseries Raises ------ KeyError if data is stored as dataframe and no column is provided ValueError if timeseries is empty """ # get oseries metadata meta = self.conn.get_metadata("oseries", name, as_frame=False) ts = self.conn.get_oseries(name) # convert to Timeseries and create model if not ts.dropna().empty: ts = ps.TimeSeries(ts, name=name, settings="oseries", metadata=meta) if modelname is None: modelname = name ml = ps.Model(ts, name=modelname, metadata=meta) if add_recharge: self.add_recharge(ml, recharge_name=recharge_name) return ml else: raise ValueError("Empty timeseries!")
def create_pastas_project(oc, pr=None, project_name='', obs_column='stand_m_tov_nap', kind='oseries', add_metadata=True, verbose=False): """add observations to a new or existing pastas project Parameters ---------- oc : observation.ObsCollection collection of observations pr : pastas.project, optional Existing pastas project, if None a new project is created project_name : str, optional Name of the pastas project only used if pr is None obs_column : str, optional Name of the column in the Obs dataframe to be used kind : str, optional The kind of series that is added to the pastas project add_metadata : boolean, optional If True metadata from the observations added to the project. verbose : boolean, optional Print additional information to the screen (default is False). Returns ------- pr : pastas.project the pastas project with the series from the ObsCollection """ if pr is None: pr = ps.Project(project_name) for o in oc.obs.values: if verbose: print('add to pastas project -> {}'.format(o.name)) if add_metadata: meta = _get_metadata_from_obs(o, verbose=verbose) else: meta = dict() series = ps.TimeSeries(o[obs_column], name=o.name, metadata=meta) pr.add_series(series, kind=kind) return pr
def add_recharge(self, ml: ps.Model, rfunc=ps.Gamma) -> None: """Add recharge to a pastas model. Uses closest precipitation and evaporation timeseries in database. These are assumed to be labeled with kind = 'prec' or 'evap'. Parameters ---------- ml : pastas.Model pastas.Model object rfunc : pastas.rfunc, optional response function to use for recharge in model, by default ps.Gamma (for different response functions, see pastas documentation) """ # get nearest prec and evap stns names = [] for var in ("prec", "evap"): try: name = self.get_nearest_stresses( ml.oseries.name, kind=var).iloc[0, 0] except AttributeError: msg = "No precipitation or evaporation timeseries found!" raise Exception(msg) names.append(name) if len(names) == 0: msg = "No precipitation or evaporation timeseries found!" raise Exception(msg) # get data tsdict = self.conn.get_stresses(names) stresses = [] for k, s in tsdict.items(): metadata = self.conn.get_metadata("stresses", k, as_frame=False) s = self.conn._get_dataframe_values("stresses", k, s, metadata=metadata) stresses.append(ps.TimeSeries(s, name=k, metadata=metadata)) # add recharge to model rch = ps.StressModel2(stresses, rfunc, name="recharge", metadata=[i.metadata for i in stresses], settings=("prec", "evap")) ml.add_stressmodel(rch)
def create_model(self, name: str, add_recharge: bool = True) -> ps.Model: """Create a new pastas Model. Parameters ---------- name : str name of the oseries to create a model for add_recharge : bool, optional add recharge to the model by looking for the closest precipitation and evaporation timeseries in the stresses library, by default True Returns ------- pastas.Model model for the oseries Raises ------ KeyError if data is stored as dataframe and no column is provided ValueError if timeseries is empty """ # get oseries metadata meta = self.conn.get_metadata("oseries", name, as_frame=False) ts = self.conn.get_oseries(name) # get right column of data ts = self.conn._get_dataframe_values( "oseries", name, ts, metadata=meta) # convert to Timeseries and create model if not ts.dropna().empty: ts = ps.TimeSeries(ts, name=name, settings="oseries", metadata=meta) ml = ps.Model(ts, name=name, metadata=meta) if add_recharge: self.add_recharge(ml) return ml else: raise ValueError("Empty timeseries!")
H = meny.H['Obsevation well'] ml = ps.Model(H['values']) # Add precipitation IN = meny.IN['Precipitation']['values'] IN.index = IN.index.round("D") IN.name = 'Precipitation' IN2 = meny.IN['Evaporation']['values'] IN2.index = IN2.index.round("D") IN2.name = 'Evaporation' sm = ps.RechargeModel(IN, IN2, ps.Gamma, 'Recharge') ml.add_stressmodel(sm) # Add well extraction 2 IN = meny.IN['Extraction 2'] well = ps.TimeSeries(IN["values"], settings="well") # extraction amount counts for the previous month sm1 = ps.StressModel(well, ps.Hantush, 'Extraction_2', up=False) # Add well extraction 3 IN = meny.IN['Extraction 3'] well = ps.TimeSeries(IN["values"], settings="well") # extraction amount counts for the previous month sm2 = ps.StressModel(well, ps.Hantush, 'Extraction_3', up=False) # add_stressmodels also allows addings multiple stressmodels at once ml.add_stressmodel([sm1, sm2]) # Solve ml.solve(tmax="1995")
def load_model(data): # Create model oseries = ps.TimeSeries(**data["oseries"]) if "constant" in data.keys(): constant = data["constant"] else: constant = False if "settings" in data.keys(): settings = data["settings"] else: settings = dict() if "metadata" in data.keys(): metadata = data["metadata"] else: metadata = dict(name="Model") # Make sure there is a name if "name" in data.keys(): name = data["name"] else: name = metadata["name"] if "noisemodel" in data.keys(): noise = True else: noise = False ml = ps.Model(oseries, constant=constant, noisemodel=noise, name=name, metadata=metadata, settings=settings) if "file_info" in data.keys(): ml.file_info.update(data["file_info"]) # Add stressmodels for name, ts in data["stressmodels"].items(): stressmodel = getattr(ps.stressmodels, ts["stressmodel"]) ts.pop("stressmodel") ts["rfunc"] = getattr(ps.rfunc, ts["rfunc"]) for i, stress in enumerate(ts["stress"]): ts["stress"][i] = ps.TimeSeries(**stress) stressmodel = stressmodel(**ts) ml.add_stressmodel(stressmodel) # Add transform if "transform" in data.keys(): transform = getattr(ps.transform, data["transform"]["transform"]) data["transform"].pop("transform") transform = transform(**data["transform"]) ml.add_transform(transform) # Add noisemodel if present if "noisemodel" in data.keys(): n = getattr(ps.noisemodels, data["noisemodel"]["type"])() ml.add_noisemodel(n) # Add parameters, use update to maintain correct order ml.parameters = ml.get_init_parameters(noise=ml.settings["noise"]) ml.parameters.update(data["parameters"]) ml.parameters = ml.parameters.apply(pd.to_numeric, errors="ignore") return ml
def _load_model(data): """Internal method to create a model from a dictionary.""" # Create model _remove_keyword(data["oseries"]) oseries = ps.TimeSeries(**data["oseries"]) if "constant" in data.keys(): constant = data["constant"] else: constant = False if "metadata" in data.keys(): metadata = data["metadata"] else: metadata = None if "name" in data.keys(): name = data["name"] else: name = None if "noisemodel" in data.keys(): noise = True else: noise = False ml = ps.Model(oseries, constant=constant, noisemodel=noise, name=name, metadata=metadata) if "settings" in data.keys(): ml.settings.update(data["settings"]) if "file_info" in data.keys(): ml.file_info.update(data["file_info"]) # Add stressmodels for name, ts in data["stressmodels"].items(): stressmodel = getattr(ps.stressmodels, ts["stressmodel"]) ts.pop("stressmodel") if "rfunc" in ts.keys(): ts["rfunc"] = getattr(ps.rfunc, ts["rfunc"]) if "recharge" in ts.keys(): ts["recharge"] = getattr(ps.recharge, ts["recharge"])() if "stress" in ts.keys(): for i, stress in enumerate(ts["stress"]): _remove_keyword(stress) ts["stress"][i] = ps.TimeSeries(**stress) if "prec" in ts.keys(): _remove_keyword(ts["prec"]) ts["prec"] = ps.TimeSeries(**ts["prec"]) if "evap" in ts.keys(): _remove_keyword(ts["evap"]) ts["evap"] = ps.TimeSeries(**ts["evap"]) if "temp" in ts.keys() and ts["temp"] is not None: ts["temp"] = ps.TimeSeries(**ts["temp"]) stressmodel = stressmodel(**ts) ml.add_stressmodel(stressmodel) # Add transform if "transform" in data.keys(): transform = getattr(ps.transform, data["transform"]["transform"]) data["transform"].pop("transform") transform = transform(**data["transform"]) ml.add_transform(transform) # Add noisemodel if present if "noisemodel" in data.keys(): n = getattr(ps.noisemodels, data["noisemodel"]["type"])() ml.add_noisemodel(n) # Add fit object to the model if "fit" in data.keys(): fit = getattr(ps.solver, data["fit"]["name"]) data["fit"].pop("name") ml.fit = fit(ml=ml, **data["fit"]) # Add parameters, use update to maintain correct order ml.parameters = ml.get_init_parameters(noise=ml.settings["noise"]) ml.parameters.update(data["parameters"]) ml.parameters = ml.parameters.apply(to_numeric, errors="ignore") # When initial values changed for param, value in ml.parameters.loc[:, "initial"].iteritems(): ml.set_parameter(name=param, initial=value) collect() return ml
def load_model(data): # Create model oseries = ps.TimeSeries(**data["oseries"]) if "constant" in data.keys(): constant = data["constant"] else: constant = False if "metadata" in data.keys(): metadata = data["metadata"] else: metadata = None if "name" in data.keys(): name = data["name"] else: name = None if "noisemodel" in data.keys(): noise = True else: noise = False ml = ps.Model(oseries, constant=constant, noisemodel=noise, name=name, metadata=metadata) if "settings" in data.keys(): ml.settings.update(data["settings"]) if "file_info" in data.keys(): ml.file_info.update(data["file_info"]) ml.file_info["version"] = ps.__version__ # Add stressmodels for name, ts in data["stressmodels"].items(): stressmodel = getattr(ps.stressmodels, ts["stressmodel"]) ts.pop("stressmodel") if "rfunc" in ts.keys(): ts["rfunc"] = getattr(ps.rfunc, ts["rfunc"]) if "stress" in ts.keys(): for i, stress in enumerate(ts["stress"]): ts["stress"][i] = ps.TimeSeries(**stress) stressmodel = stressmodel(**ts) ml.add_stressmodel(stressmodel) # Add transform if "transform" in data.keys(): transform = getattr(ps.transform, data["transform"]["transform"]) data["transform"].pop("transform") transform = transform(**data["transform"]) ml.add_transform(transform) # Add noisemodel if present if "noisemodel" in data.keys(): n = getattr(ps.noisemodels, data["noisemodel"]["type"])() ml.add_noisemodel(n) # Add parameters, use update to maintain correct order ml.parameters = ml.get_init_parameters(noise=ml.settings["noise"]) ml.parameters.update(data["parameters"]) ml.parameters = ml.parameters.apply(to_numeric, errors="ignore") # When initial values changed for param, value in ml.parameters.loc[:, "initial"].iteritems(): ml.set_initial(name=param, value=value) gc.collect() return ml
In this example a daily simulation is conducted from 9:00 until 9:00 (dutch standard time) This is the time at which precipitation is logged in dutch KNMI-stations. """ import pastas as ps import pandas as pd # read observations obs = ps.read_dino('data/B58C0698001_1.csv') # Create the time series model ml = ps.Model(obs) # read weather data knmi = ps.read.knmi.KnmiStation.fromfile('data/neerslaggeg_HEIBLOEM-L_967-2.txt') rain = ps.TimeSeries(knmi.data['RD'],settings='prec') evap = ps.read_knmi('data/etmgeg_380.txt', variables='EV24') if True: # also add 9 hours to the evaporation s = evap.series_original s.index = s.index+pd.to_timedelta(9,'h') evap.series_original = s # Create stress sm = ps.StressModel2(stress=[rain, evap], rfunc=ps.Exponential, name='recharge') ml.add_stressmodel(sm) # set the time-offset of the model. This should be done automatically in the future. ml._set_time_offset()
def load(self, fyaml: str) -> List[ps.Model]: """Load Pastas YAML file. Note: currently supports RechargeModel, StressModel and WellModel. Parameters ---------- fyaml : str path to file Returns ------- models : list list containing pastas model(s) Raises ------ ValueError if insufficient information is provided to construct pastas model NotImplementedError if unsupported stressmodel is encountered """ with open(fyaml, "r") as f: yml = yaml.load(f, Loader=yaml.CFullLoader) models = [] for mlnam in yml.keys(): mlyml = yml[mlnam] # get oseries + metadata if isinstance(mlyml["oseries"], dict): onam = str(mlyml["oseries"]["name"]) settings = mlyml["oseries"].pop("settings", "oseries") _ = mlyml.pop("oseries") else: onam = str(mlyml.pop("oseries")) settings = "oseries" logger.info(f"Building model '{mlnam}' for oseries '{onam}'") o, ometa = self.pstore.get_oseries( onam, return_metadata=True) # create model to obtain default model settings o_ts = ps.TimeSeries(o, metadata=ometa, settings=settings) ml = ps.Model(o_ts, name=mlnam, metadata=ometa) mldict = ml.to_dict(series=True) # update with stored model settings if "settings" in mlyml: mldict["settings"].update(mlyml["settings"]) # stressmodels for smnam, smyml in mlyml["stressmodels"].items(): # set name if not provided if smyml is not None: name = smyml.get("name", smnam) else: name = smnam logger.info(f"| parsing stressmodel: '{name}'") # check whether smtyp is defined if smyml is not None: if "stressmodel" in smyml: smtyp = True else: smtyp = False else: smtyp = False # check if RechargeModel based on name if smtyp not defined if (smnam.lower() in ["rch", "rech", "recharge", "rechargemodel"]) and not smtyp: logger.info( "| assuming RechargeModel based on stressmodel name.") # check if stressmodel dictionary is empty, create (nearly # empty) dict so defaults are used if smyml is None: mlyml["stressmodels"][smnam] = {"name": "recharge"} smyml = mlyml["stressmodels"][smnam] if "name" not in smyml: smyml["name"] = smnam smtyp = smyml.get("stressmodel", "RechargeModel") else: # if no info is provided, raise error, # cannot make any assumptions for non-RechargeModels if smyml is None: raise ValueError("Insufficient information " f"for stressmodel '{name}'!") # get stressmodel type, with default StressModel if "stressmodel" in smyml: smtyp = smyml["stressmodel"] else: logger.info( "| no 'stressmodel' type provided, " "using 'StressModel'") smtyp = "StressModel" # parse dictionary based on smtyp if smtyp in ["RechargeModel", "TarsoModel"]: # parse RechargeModel sm = self._parse_rechargemodel_dict(smyml, onam=onam) # turn off constant for TarsoModel if smtyp == "TarsoModel": mldict["constant"] = False elif smtyp == "StressModel": # parse StressModel sm = self._parse_stressmodel_dict(smyml, onam=onam) elif smtyp == "WellModel": # parse WellModel sm = self._parse_wellmodel_dict(smyml, onam=onam) else: raise NotImplementedError( "PastaStore.yaml interface does " f"not (yet) support '{smtyp}'!") # add to list smyml.update(sm) # update model dict w/ default settings with loaded data mldict.update(mlyml) # add name to dictionary if not already present if "name" not in mldict: mldict["name"] = mlnam # convert warmup and time_offset to panads.Timedelta if "warmup" in mldict["settings"]: mldict["settings"]["warmup"] = pd.Timedelta( mldict["settings"]["warmup"]) if "time_offset" in mldict["settings"]: mldict["settings"]["time_offset"] = pd.Timedelta( mldict["settings"]["time_offset"]) # load model ml = ps.io.base._load_model(mldict) models.append(ml) return models
def _parse_wellmodel_dict(self, d: Dict, onam: Optional[str] = None) -> Dict: """Internal method to parse WellModel dictionary. Note: supports 'nearest' or 'nearest <number> <kind>' as input to 'stress', which will automatically select nearest or <number> of nearest stress(es) with kind=<kind>. Requires "x" and "y" locations to be present in both oseries and stresses metadata. Parameters ---------- d : dict dictionary containing WellModel information onam : str, optional name of oseries used when 'nearest' is provided as stress, by default None Returns ------- d : dict dictionary that can be read by ps.io.base.load(), containing stresses obtained from PastaStore, and setting defaults if they were not already provided. """ # parse stress snames = d.pop("stress") # if str, either name of single series or 'nearest <n> <kind>' if isinstance(snames, str): if snames.startswith("nearest"): if len(snames.split()) == 3: n = int(snames.split()[1]) kind = snames.split()[2] elif len(snames.split()) == 2: try: n = int(snames.split()[1]) except ValueError: raise ValueError( f"Could not parse: '{snames}'! " "When using option 'nearest' for WellModel, " "use 'nearest <n>' or 'nearest <n> <kind>'!") kind = "well" elif len(snames.split()) == 1: n = 1 kind = "well" snames = self.pstore.get_nearest_stresses( onam, kind=kind, n=n).iloc[0].values logger.info( f" | using {n} nearest stress(es) with kind='{kind}': " f"{snames}") else: snames = [snames] # get timeseries slist = [] for snam in snames: s, smeta = self.pstore.get_stresses(snam, return_metadata=True) slist.append( ps.TimeSeries(s, snam, settings="well", metadata=smeta).to_dict() ) d["stress"] = slist # get distances if "distances" not in d: d["distances"] = self.pstore.get_distances( oseries=onam, stresses=snames).values.squeeze() # use default name if not provided if "name" not in d: d["name"] = "wells" # rfunc if "rfunc" not in d: logger.info(" | no 'rfunc' provided, using 'HantushWellModel'") d["rfunc"] = "HantushWellModel" if "up" not in d: logger.info(" | no 'up' provided, set to 'False', " "(i.e. pumping rate is positive for extraction).") d["up"] = False return d
H = meny.H['Obsevation well'] ml = ps.Model(H['values']) # Add precipitation IN = meny.IN['Precipitation']['values'] IN.index = IN.index.round("D") IN.name = 'Precipitation' IN2 = meny.IN['Evaporation']['values'] IN2.index = IN2.index.round("D") IN2.name = 'Evaporation' sm = ps.StressModel2([IN, IN2], ps.Gamma, 'Recharge') ml.add_stressmodel(sm) # Add well extraction 2 IN = meny.IN['Extraction 2'] well = ps.TimeSeries(IN["values"], freq_original="M", settings="well") # extraction amount counts for the previous month sm1 = ps.StressModel(well, ps.Hantush, 'Extraction_2', up=False) # Add well extraction 3 IN = meny.IN['Extraction 3'] well = ps.TimeSeries(IN["values"], freq_original="M", settings="well") # extraction amount counts for the previous month sm2 = ps.StressModel(well, ps.Hantush, 'Extraction_3', up=False) # add_stressmodels also allows addings multiple stressmodels at once ml.add_stressmodel(sm1, sm2) # Solve ml.solve(tmax="1995")
def _parse_rechargemodel_dict(self, d: Dict, onam: Optional[str] = None) -> Dict: """Internal method to parse RechargeModel dictionary. Note: supports 'nearest' as input to 'prec' and 'evap', which will automatically select nearest stress with kind="prec" or "evap". Requires "x" and "y" locations to be present in both oseries and stresses metadata. Parameters ---------- d : dict dictionary containing RechargeModel information onam : str, optional name of oseries used when 'nearest' is provided as prec or evap, by default None Returns ------- d : dict dictionary that can be read by ps.io.base.load(), containing stresses obtained from PastaStore, and setting defaults if they were not already provided. """ # precipitation prec_val = d.get("prec", "nearest") if isinstance(prec_val, dict): pnam = prec_val["name"] prec = self.pstore.get_stresses(pnam) prec = ps.TimeSeries(prec, **prec_val) elif prec_val.startswith("nearest"): if onam is None: raise ValueError("Provide oseries name when using nearest!") if len(prec_val.split()) > 1: kind = prec_val.split()[-1] else: kind = "prec" pnam = self.pstore.get_nearest_stresses( onam, kind=kind).iloc[0, 0] logger.info( f" | using nearest timeseries with kind='{kind}': '{pnam}'") prec, pmeta = self.pstore.get_stresses(pnam, return_metadata=True) prec = ps.TimeSeries(prec, pnam, settings="prec", metadata=pmeta) elif isinstance(prec_val, str): pnam = d["prec"] prec, pmeta = self.pstore.get_stresses(pnam, return_metadata=True) prec = ps.TimeSeries(prec, pnam, settings="prec", metadata=pmeta) else: raise NotImplementedError( f"Could not parse prec value: '{prec_val}'") d["prec"] = prec.to_dict() # evaporation evap_val = d.get("evap", "nearest") if isinstance(evap_val, dict): enam = evap_val["name"] evap = self.pstore.get_stresses(enam) evap = ps.TimeSeries(evap, **evap_val) elif evap_val.startswith("nearest"): if onam is None: raise ValueError("Provide oseries name when using nearest!") if len(evap_val.split()) > 1: kind = evap_val.split()[-1] else: kind = "evap" enam = self.pstore.get_nearest_stresses( onam, kind=kind).iloc[0, 0] logger.info( f" | using nearest timeseries with kind='{kind}': '{enam}'") evap, emeta = self.pstore.get_stresses(enam, return_metadata=True) evap = ps.TimeSeries(evap, enam, metadata=emeta, settings="evap") elif isinstance(evap_val, str): enam = d["evap"] evap, emeta = self.pstore.get_stresses(enam, return_metadata=True) evap = ps.TimeSeries(evap, enam, metadata=emeta, settings="evap") else: raise NotImplementedError( f"Could not parse evap value: '{evap_val}'") d["evap"] = evap.to_dict() # rfunc if "rfunc" not in d: logger.info(" | no 'rfunc' provided, using 'Exponential'") # stressmodel if "stressmodel" not in d: d["stressmodel"] = "RechargeModel" # recharge type (i.e. Linear, FlexModel, etc.) if ("recharge" not in d) and (d["stressmodel"] == "RechargeModel"): logger.info(" | no 'recharge' type provided, using 'Linear'") # tarsomodel logic if d["stressmodel"] == "TarsoModel": dmin = d.get("dmin", None) dmax = d.get("dmin", None) oseries = d.get("oseries", None) if ((dmin is None) or (dmax is None)) and (oseries is None): logger.info(" | no 'dmin/dmax' or 'oseries' provided," f" filling in 'oseries': '{onam}'") d["oseries"] = onam if "oseries" in d: onam = d["oseries"] if isinstance(onam, str): o = self.pstore.get_oseries(onam) d["oseries"] = o return d