def model(self, request): """Return model class.""" models = list(set(request.inputs.keys()).intersection(self.tuple_inputs.keys())) if len(models) > 1: raise NotImplementedError("Multi-model simulations are not supported. ") name = models.pop() params = self.parse_tuple(request.inputs.pop(name)[0]) model = get_model(name)(workdir=self.workdir) model.config.update("params", params) return model
def perform_forecasting_step(rvc, model, workdir=None, **kwds): """ Function that might be useful eventually to do a forecast from a model setup. """ # kwds includes 'ts', the forecast timeseries data # Setup the model m = get_model(model)(workdir=workdir) # Force the initial conditions m.resume(rvc) # Set the parameters, start dates, etc. required to run the model and run m(overwrite=True, **kwds) return m.q_sim
def param(model): """Return a parameter coordinate. Parameters ---------- model : str Model name. """ model_cls = models.get_model(model) return xr.IndexVariable( "param", data=np.array([f.name for f in fields(model_cls.Params)]), attrs={ "standard_name": "parameter", "long_name": f"{model} model parameter name", }, )
def param(model): """Return a parameter coordinate. Parameters ---------- model : str Model name. """ model = models.get_model(model) return xr.IndexVariable( "param", data=np.array(model.params._fields), attrs={ "standard_name": "parameter", "long_name": "{} model parameter name".format(model), }, )
def get_raven_states(model, workdir=None, **kwds): """Get the RAVEN states file (.rvc file) after a model run. Parameters ---------- model : {'HMETS', 'GR4JCN', 'MOHYSE', 'HBVEC'} Model name. kwds : {} Model configuration parameters, including the forcing files (ts). Returns ------- rvc : {} Raven model forcing file """ # Run the model and get the rvc file for future hotstart. m = get_model(model)(workdir=workdir) m(overwrite=True, **kwds) rvc = m.outputs["solution"] return rvc
def regionalize( method, model, nash, params=None, props=None, target_props=None, size=5, min_NSE=0.6, **kwds, ): """Perform regionalization for catchment whose outlet is defined by coordinates. Parameters ---------- method : {'MLR', 'SP', 'PS', 'SP_IDW', 'PS_IDW', 'SP_IDW_RA', 'PS_IDW_RA'} Name of the regionalization method to use. model : {'HMETS', 'GR4JCN', 'MOHYSE'} Model name. nash : pd.Series NSE values for the parameters of gauged catchments. params : pd.DataFrame Model parameters of gauged catchments. Needed for all but MRL method. props : pd.DataFrame Properties of gauged catchments to be analyzed for the regionalization. Needed for MLR and RA methods. target_props : pd.Series or dict Properties of ungauged catchment. Needed for MLR and RA methods. size : int Number of catchments to use in the regionalization. min_NSE : float Minimum calibration NSE value required to be considered as a donor. kwds : {} Model configuration parameters, including the forcing files (ts). Returns ------- (qsim, ensemble) qsim : DataArray (time, ) Multi-donor averaged predicted streamflow. ensemble : Dataset q_sim : DataArray (realization, time) Ensemble of members based on number of donors. parameter : DataArray (realization, param) Parameters used to run the model. """ # TODO: Include list of available properties in docstring. # TODO: Add error checking for source, target stuff wrt method chosen. # Select properties based on those available in the ungauged properties DataFrame. if isinstance(target_props, dict): ungauged_properties = pd.Series(target_props) elif isinstance(target_props, pd.Series): ungauged_properties = target_props elif isinstance(target_props, pd.DataFrame): ungauged_properties = target_props.to_series() else: raise ValueError cr = coords.realization(1 if method == "MLR" else size) cp = coords.param(model) # Filter on NSE valid = nash > min_NSE filtered_params = params.where(valid).dropna() filtered_prop = props.where(valid).dropna() # Check to see if we have enough data, otherwise raise error if len(filtered_prop) < size and method != "MLR": raise ValueError("Hydrological_model and minimum NSE threshold \ combination is too strict for the number of donor \ basins. Please reduce the number of donor basins OR \ reduce the minimum NSE threshold.") # Rank the matrix according to the similarity or distance. if method in ["PS", "PS_IDW", "PS_IDW_RA"]: # Physical similarity dist = similarity(filtered_prop, ungauged_properties) else: # Geographical distance. dist = distance(filtered_prop, ungauged_properties) # Series of distances for the first `size` best donors sdist = dist.sort_values().iloc[:size] # Pick the donors' model parameters and catchment properties sparams = filtered_params.loc[sdist.index] sprop = filtered_prop.loc[sdist.index] # Get the list of parameters to run reg_params = regionalization_params(method, sparams, sprop, ungauged_properties, filtered_params, filtered_prop) # Run the model over all parameters and create ensemble DataArray m = models.get_model(model)() qsims = list() for i, params in enumerate(reg_params): kwds["params"] = params kwds["run_name"] = f"reg_{i}" m(**kwds) qsims.append(m.q_sim.copy(deep=True)) qsims = xr.concat(qsims, dim=cr) # 3. Aggregate runs into a single result -> dataset if method in [ "MLR", "SP", "PS", ]: # Average (one realization for MLR, so no effect). qsim = qsims.mean(dim="realization", keep_attrs=True) elif ( "IDW" in method ): # Here we are replacing the mean by the IDW average, keeping attributes and dimensions. qsim = IDW(qsims, sdist) else: raise ValueError("No matching algorithm for {}".format(method)) # Metadata handling # TODO: Store the basin_name # Create a DataArray for the parameters used in the regionalization param_da = xr.DataArray( reg_params, dims=("realization", "param"), coords={ "param": cp, "realization": cr }, attrs={"long_name": "Model parameters used in the regionalization."}, ) ens = xr.Dataset( data_vars={ "q_sim": qsims, "parameter": param_da }, attrs={ "title": "Regionalization ensemble", "institution": "", "source": "RAVEN V.{} - {}".format(m.version, model), "history": "Created by raven regionalize.", "references": "", "comment": "Regionalization method: {}".format(method), }, ) # TODO: Add global attributes (model name, date, version, etc) return qsim, ens
def perform_climatology_esp(model_name, forecast_date, forecast_duration, workdir=None, **kwds): """ This function takes the model setup and name as well as forecast data and duration and returns an ESP forecast netcdf. The data comes from the climatology data and thus there is a mechanism to get the correct data from the time series and exclude the current year. Parameters ---------- model_name : {'HMETS', 'MOHYSE', 'GR4JCN', 'HBVEC'} Model name to instantiate Raven model. forecast_date : datetime.datetime Date of the forecast issue. forecast_duration : int Number of days of forecast, forward looking. kwds : dict Raven model configuration parameters. Returns ------- qsims Array of streamflow values from the ESP method along with list of member years """ # Get the timeseries tsnc = xr.open_dataset(kwds["ts"]) # Prepare model instance m = get_model(model_name)(workdir=workdir) # Now find the periods of time for warm-up and forecast and add to the model keywords as the defaults are failing # (nanoseconds datetimes do not like the year 0001...) start_date = pd.to_datetime(tsnc["time"][0].values) start_date = start_date.to_pydatetime() kwds["start_date"] = start_date # Forecasting from Feb 29th is not ideal, we will replace with Feb 28th. # Should not change much in a climatological forecast. if forecast_date.month == 2 and forecast_date.day == 29: forecast_date.replace(day=28) # Check to make sure forecast date is not in the first year as we need model warm-up. # We cannot use timedelta because if the dataset happens to start on a leap # year, then the timedelta=365 days will not be robust. (and we cannot use timedelta(years=1)...) dateLimit = start_date.replace(year=start_date.year + 1) if dateLimit > forecast_date: msg = ( "Forecast date is within the warm-up period. Select another forecast date." ) warnings.warn(msg) # initialize the array of forecast variables qsims = [] # list of unique years in the dataset: avail_years = list(np.unique(tsnc["time.year"].data)) # Take a copy of the forecast initial date before overwriting in the forecast step. forecast_date_main = forecast_date # Remove the year that we are forecasting. Or else it's cheating! avail_years.remove(forecast_date.year) # Update the forecast end-date, which will be the day prior to the forecast date. # So forecasts warm-up will be from day 1 in the dataset to the forecast date. kwds["end_date"] = forecast_date - dt.timedelta(days=1) # Get RVC file if it exists, else compute it. if len(kwds['rvc']) > 0: rvc = kwds.pop('rvc') else: # Run model to get rvc file after warm-up using base meteo rvc = get_raven_states(model_name, workdir=workdir, **kwds) # We need to check which years are long enough (ex: wrapping years, 365-day forecast starting in # September 2015 will need data up to August 2016 at least) for years in avail_years: if forecast_date.replace(year=years) + dt.timedelta( days=forecast_duration - 1) > pd.to_datetime( tsnc["time"][-1].values): avail_years.remove(years) msg = ( f"Year {years} has been removed because it is the last year in the dataset and does not cover the " f"forecast duration.") warnings.warn(msg) # We will iterate this for all forecast years for years in avail_years: # Replace the forecast period start and end dates with the climatological ESP dates for the # current member (year) forecast_date = forecast_date.replace(year=years) kwds["start_date"] = forecast_date kwds["end_date"] = forecast_date + dt.timedelta( days=forecast_duration - 1) # Setup the initial states from the warm-up and run the model. # Note that info on start/end dates and timeseries are in the kwds. m.resume(rvc) m(run_name=f"run_{years}", **kwds) # Add member to the ensemble and retag the dates to the real forecast dates # (or else we will get dates from the climate dataset that cover all years) new_member = m.q_sim.copy(deep=True) new_member["time"] = pd.date_range(forecast_date_main, periods=forecast_duration) qsims.append(new_member) # Concatenate the members through a new dimension for the members and remove unused dims. qsims = xr.concat(qsims, dim="member") qsims = qsims.squeeze() # Add the number of the forecast year as member ID qsims["member"] = (["member"], avail_years) return qsims