Index: doc/wf_DynamicFramework.rst =================================================================== diff -u -r03e05d6d2222c4796f72947abfaaeb6c126db568 -rfb630c9f28a399af8c0a201eafe11e4b26d9a7dd --- doc/wf_DynamicFramework.rst (.../wf_DynamicFramework.rst) (revision 03e05d6d2222c4796f72947abfaaeb6c126db568) +++ doc/wf_DynamicFramework.rst (.../wf_DynamicFramework.rst) (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -116,19 +116,21 @@ --------------------------------------- Most of the time this section is not needed as this will mostly be configured -in the python code by the model developer. Howevere, in some case this section can be used -to for example force the model to read RootingDepth from an external data source. +in the python code by the model developer. However, in some case this section can be used +alter the modell for example force the model to read RootingDepth from an external data source. The format of entries in this section is as follows:: name=stack,type,default # example: - # RootingDepth=monthlyclim/ROOT,monthyclim,100 + # RootingDepth=monthlyclim/ROOT,monthyclim,100,1 + name - Name of the parameter (internal variable, without the self.) + stack - Name of the mapstack (representation on disk or in mem) relative to case + type - Type of parameter (default = static) + default - Default value if map/tbl is not present ++ verbose - If set to 1 (True) the maps a log log message will be generated is a default values is used + instead of a map Possible parameter types (the second option)are: @@ -143,9 +145,9 @@ Example:: [modelparameters] - RootingDepth=monthlyclim/ROOTS,monthlyclim,75 + RootingDepth=monthlyclim/ROOTS,monthlyclim,75,0 # Force the model to read monthly climatology of P - Precipitation=inmaps/P,monthlyclim,0.0 + Precipitation=inmaps/P,monthlyclim,0.0,1 Settings in the summary_* sections Index: doc/wflow_building.rst =================================================================== diff -u -rb7f6c4b703a8f7f7bb1551a10f191d9404b34c1e -rfb630c9f28a399af8c0a201eafe11e4b26d9a7dd --- doc/wflow_building.rst (.../wflow_building.rst) (revision b7f6c4b703a8f7f7bb1551a10f191d9404b34c1e) +++ doc/wflow_building.rst (.../wflow_building.rst) (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -61,6 +61,14 @@ to support data preparation. +.. note:: + + Within the earth2observe project tools are being made to automatically download + and downscale reanalysis date to be used as forcing to the wflow models. See + https://code.google.com/p/e2o-downscaling-tools/ + + + Depending on the formats of the data some converting of data may be needed. The procedure described below assumes you have the main maps available in pcraster format. If that is not the case free tools like Qgis (www.qgis.org) and gdal can be Index: wflow-py/wflow/wf_DynamicFramework.py =================================================================== diff -u -r03e05d6d2222c4796f72947abfaaeb6c126db568 -rfb630c9f28a399af8c0a201eafe11e4b26d9a7dd --- wflow-py/wflow/wf_DynamicFramework.py (.../wf_DynamicFramework.py) (revision 03e05d6d2222c4796f72947abfaaeb6c126db568) +++ wflow-py/wflow/wf_DynamicFramework.py (.../wf_DynamicFramework.py) (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -247,7 +247,7 @@ def __init__(self, userModel, lastTimeStep=0, firstTimestep=1,datetimestart=dt.datetime(1990,01,01),timestepsecs=86400): frameworkBase.FrameworkBase.__init__(self) - self.ParamType = namedtuple("ParamType", "name stack type default") + self.ParamType = namedtuple("ParamType", "name stack type default verbose") self.modelparameters = [] # list of model parameters self.exchnageitems = wf_exchnageVariables() self.setQuiet(True) @@ -327,14 +327,14 @@ if par.type == 'timeseries': if not hasattr(self._userModel(),par.name): self._userModel().logger.info("Adding " + par.name + " to model.") - theparmap = self.wf_readmap(os.path.join(self._userModel().caseName,par.stack), par.default) + theparmap = self.wf_readmap(os.path.join(self._userModel().caseName,par.stack), par.default,verbose=par.verbose) theparmap = cover(theparmap,par.default) setattr(self._userModel(),par.name,theparmap) if par.type == 'monthlyclim': if not hasattr(self._userModel(),par.name): self._userModel().logger.info("Adding " + par.name + " to model.") - theparmap = self.wf_readmapClimatology(os.path.join(self._userModel().caseName,par.stack) ,kind=1, default=par.default, verbose=True) + theparmap = self.wf_readmapClimatology(os.path.join(self._userModel().caseName,par.stack) ,kind=1, default=par.default, verbose=par.verbose) theparmap = cover(theparmap,par.default) setattr(self._userModel(),par.name,theparmap) @@ -347,7 +347,7 @@ if par.type == 'dailyclim': if not hasattr(self._userModel(),par.name): self._userModel().logger.info(par.name + " is not defined yet, adding anyway.") - theparmap = self.wf_readmapClimatology(os.path.join(self._userModel().caseName,par.stack) ,kind=2, default=par.default, verbose=True) + theparmap = self.wf_readmapClimatology(os.path.join(self._userModel().caseName,par.stack) ,kind=2, default=par.default, verbose=par.verbose) setattr(self._userModel(),par.name,theparmap) @@ -549,27 +549,30 @@ # Get model parameters from model object - self.modelparameters = self._userModel().parameters() + if hasattr(self._userModel(),"parameters"): + self.modelparameters = self._userModel().parameters() + else: + self.modelparameters = [] # Read extra model parameters from ini file modpars = configsection(self._userModel().config,"modelparameters") for par in modpars: aline = self._userModel().config.get("modelparameters",par) vals = aline.split(',') - if len(vals) == 3: + if len(vals) == 4: # check if par already present present = par in [xxx[0] for xxx in self.modelparameters] if present: pos = [xxx[0] for xxx in self.modelparameters].index(par) # Check if the existing definition is static, in that case append, otherwise overwrite if 'static' in self.modelparameters[pos].type: self._userModel().logger.debug("Creating extra parameter specification for par: " + par + " (" + str(vals) + ")") - self.modelparameters.append(self.ParamType(name=par,stack=vals[0],type=vals[1],default=float(vals[2]))) + self.modelparameters.append(self.ParamType(name=par,stack=vals[0],type=vals[1],default=float(vals[2])),silent=vals[3]) else: self._userModel().logger.debug("Updating existing parameter specification for par: " + par + " (" + str(vals) + ")") - self.modelparameters[pos] = self.ParamType(name=par,stack=vals[0],type=vals[1],default=float(vals[2])) + self.modelparameters[pos] = self.ParamType(name=par,stack=vals[0],type=vals[1],default=float(vals[2]),silent=vals[3]) else: self._userModel().logger.debug("Creating parameter specification for par: " + par + " (" + str(vals) + ")") - self.modelparameters.append(self.ParamType(name=par,stack=vals[0],type=vals[1],default=float(vals[2]))) + self.modelparameters.append(self.ParamType(name=par,stack=vals[0],type=vals[1],default=float(vals[2])),silent=vals[3]) else: logging.error("Parameter line in ini not valid: " + aline) @@ -1298,16 +1301,27 @@ Output: - current model time (since start of the run) - - .. todo:: + - Get timestep info from from config file - """ seconds_since_epoch = time.mktime(self.currentdatetime.timetuple()) return seconds_since_epoch - + def wf_supplyEpoch(self): + """ + Supplies the time epoch as a CF string + Output: + - current model time (since start of the run) + + + """ + epoch = time.gmtime(0) + + epochstr = 'seconds since %04d-%02d-%02d %02d:%02d:%02d.0 00:00' % (epoch.tm_year, epoch.tm_mon, epoch.tm_mday, epoch.tm_hour,epoch.tm_min, epoch.tm_sec) + return epochstr + + + def wf_supplyRowCol(self,mapname,xcor,ycor): """ returns a tuple (Row,Col) for the given X and y coordinate @@ -1634,7 +1648,6 @@ mapje=readmap(path) return mapje else: - print verbose if verbose: self.logger.debug("Forcing data (" + path + ") for timestep not present, returning " + str(default)) return scalar(default) Index: wflow-py/wflow/wflow_bmi.py =================================================================== diff -u -r817868557aba868707cbd4f13c39e90b6708d4f1 -rfb630c9f28a399af8c0a201eafe11e4b26d9a7dd --- wflow-py/wflow/wflow_bmi.py (.../wflow_bmi.py) (revision 817868557aba868707cbd4f13c39e90b6708d4f1) +++ wflow-py/wflow/wflow_bmi.py (.../wflow_bmi.py) (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -24,6 +24,9 @@ # The current pcraster framework needs a max number of timesteps :-( # This cannot be avoided at the moment (needs a rework) # set to 10000 for now + # .. todo:: + # Get name of module from ini file name + maxNrSteps = 10000 if "wflow_sbm.ini" in configfile: import wflow_sbm as wf @@ -70,6 +73,16 @@ + def get_time_units(self): + """ + + :return: time units as a CF convention string (http://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/cf-conventions.html#time-coordinate) + + """ + + return self.dynModel.wf_supplyEpoch() + + def get_var_count(self): """ Return number of variables Index: wflow-py/wflow/wflow_sbm.py =================================================================== diff -u -r03e05d6d2222c4796f72947abfaaeb6c126db568 -rfb630c9f28a399af8c0a201eafe11e4b26d9a7dd --- wflow-py/wflow/wflow_sbm.py (.../wflow_sbm.py) (revision 03e05d6d2222c4796f72947abfaaeb6c126db568) +++ wflow-py/wflow/wflow_sbm.py (.../wflow_sbm.py) (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -326,10 +326,10 @@ #modelparameters.append(self.ParamType(name="RunoffGeneratingGWPerc",stack="intbl/RunoffGeneratingGWPerc.tbl",type="static",default=0.1)) # Meteo and other forcing - modelparameters.append(self.ParamType(name="Precipitation",stack="inmaps/P",type="timeseries",default=0.0)) - modelparameters.append(self.ParamType(name="PotenEvap",stack="inmaps/PET",type="timeseries",default=0.0)) - modelparameters.append(self.ParamType(name="Temperature",stack="inmaps/TEMP",type="timeseries",default=10.0)) - modelparameters.append(self.ParamType(name="Inflow",stack="inmaps/IF",type="timeseries",default=0.0)) + modelparameters.append(self.ParamType(name="Precipitation",stack="inmaps/P",type="timeseries",default=0.0,verbose=True)) + modelparameters.append(self.ParamType(name="PotenEvap",stack="inmaps/PET",type="timeseries",default=0.0,verbose=True)) + modelparameters.append(self.ParamType(name="Temperature",stack="inmaps/TEMP",type="timeseries",default=10.0,verbose=True)) + modelparameters.append(self.ParamType(name="Inflow",stack="inmaps/IF",type="timeseries",default=0.0,verbose=False)) return modelparameters @@ -650,9 +650,13 @@ max(0.0001, windowaverage(self.Slope, celllength() * 4.0))) ** (-0.1875) * self.N ** (0.375) # Use supplied riverwidth if possible, else calulate self.RiverWidth = ifthenelse(self.RiverWidth <= 0.0, W, self.RiverWidth) + # Only allow rinfiltration in rover cells + self.MaxReinfilt = self.ZeroMap + self.MaxReinfilt = ifthenelse(self.River, self.ZeroMap + 999.0, self.ZeroMap) + # soil thickness based on topographical index (see Environmental modelling: finding simplicity in complexity) # 1: calculate wetness index # 2: Scale the capacity (now actually a max capacity) based on the index, also apply a minmum capacity @@ -952,13 +956,7 @@ self.OrgStorage = self.UStoreDepth + self.FirstZoneDepth + self.LowerZoneStorage self.OldCanopyStorage = self.CanopyStorage self.PotEvap = self.PotenEvap # - #TODO: Snow modelling if enabled _ need to be moved as it breaks the scalar input - """ - .. todo:: - - Snow modelling if enabled _ needs to be moved as it breaks the scalar input - """ if self.modelSnow: self.TSoil = self.TSoil + self.w_soil * (self.Temperature - self.TSoil) # return Snow,SnowWater,SnowMelt,RainFall @@ -1196,8 +1194,12 @@ self.CumSurfaceWater = self.CumSurfaceWater + SurfaceWater # Estimate water that may re-infiltrate + # - Never more that 90% of the available water + # - self.MaxReinFilt: a map with reinfilt locations (usually the river mak) can be supplied) + # - take into account that the river may not cover the whole cell if self.reInfilt: - Reinfilt = max(0, min(SurfaceWater, min(self.InfiltCapSoil, UStoreCapacity))) + Reinfilt = min(self.MaxReinfilt,max(0, min(SurfaceWater * self.RiverWidth/self.reallength * 0.9, + min(self.InfiltCapSoil * (1.0 - self.PathFrac), UStoreCapacity)))) self.CumReinfilt = self.CumReinfilt + Reinfilt self.UStoreDepth = self.UStoreDepth + Reinfilt else: Index: wflow-py/wflow/wflow_sceleton.py =================================================================== diff -u -r03e05d6d2222c4796f72947abfaaeb6c126db568 -rfb630c9f28a399af8c0a201eafe11e4b26d9a7dd --- wflow-py/wflow/wflow_sceleton.py (.../wflow_sceleton.py) (revision 03e05d6d2222c4796f72947abfaaeb6c126db568) +++ wflow-py/wflow/wflow_sceleton.py (.../wflow_sceleton.py) (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -215,7 +215,7 @@ def main(argv=None): """ - *Optional* + *Optional but needed it you want to run the model from the command line* Perform command line execution of the model. This example uses the getopt module to parse the command line options. Index: wflow-py/wflow/wflow_vegetation.py =================================================================== diff -u --- wflow-py/wflow/wflow_vegetation.py (revision 0) +++ wflow-py/wflow/wflow_vegetation.py (revision fb630c9f28a399af8c0a201eafe11e4b26d9a7dd) @@ -0,0 +1,267 @@ +#!/usr/bin/python + +""" +Sceleton of a wflow_vegetation model. +--------------------------------------- + + + +Usage: +wflow_vegatation -C case -R Runid -c inifile + + -C: set the name of the case (directory) to run + + -R: set the name runId within the current case + + -c name of the config file (in the case directory) + +$Author: schelle $ +$Id: wflow_sceleton.py 898 2014-01-09 14:47:06Z schelle $ +$Rev: 898 $ +""" + +import numpy +import os +import os.path +import shutil, glob +import getopt + +from wflow.wf_DynamicFramework import * +from wflow.wflow_adapt import * +#import scipy + + + +def usage(*args): + sys.stdout = sys.stderr + for msg in args: print msg + print __doc__ + sys.exit(0) + +class WflowModel(DynamicModel): + """ + The user defined model class. This is your work! + """ + + def __init__(self, cloneMap,Dir,RunDir,configfile): + """ + *Required* + + The init function **must** contain what is shown below. Other functionality + may be added by you if needed. + + """ + DynamicModel.__init__(self) + setclone(Dir + "/staticmaps/" + cloneMap) + self.runId=RunDir + self.caseName=Dir + self.Dir = Dir + self.configfile = configfile + + + def parameters(self): + """ + List all the parameters (both static and forcing here). Use the wf_updateparameters() + function to update them in the initial section (static) and the dynamic section for + dynamic parameters. + + Possible parameter types are: + + + staticmap: Read at startup from map + + statictbl: Read at startup from tbl, fallback to map (need Landuse, Soil and TopoId (subcatch) maps! + + timeseries: read map for each timestep + + monthlyclim: read a map corresponding to the current month (12 maps in total) + + dailyclim: read a map corresponding to the current day of the year + + hourlyclim: read a map corresponding to the current hour of the day (24 in total) + + + :return: List of modelparameters + """ + modelparameters = [] + + #Static model parameters + modelparameters.append(self.ParamType(name="Altitude",stack="staticmaps/wflow_dem.map",type="staticmap",default=0.0)) + + # Meteo and other forcing + modelparameters.append(self.ParamType(name="Temperature",stack="inmaps/TEMP",type="timeseries",default=10.0)) + + return modelparameters + + def stateVariables(self): + """ + *Required* + + Returns a list of state variables that are essential to the model. + This list is essential for the resume and suspend functions to work. + + This function is specific for each model and **must** be present. This is + where you specify the state variables of you model. If your model is stateless + this function must return and empty array (states = []) + + In the simple example here the TSoil variable is a state + for the model. + + :var TSoil: Temperature of the soil [oC] + """ + states = ['RootingDepth','LAI'] + + return states + + + def supplyCurrentTime(self): + """ + *Optional* + + Supplies the current time in seconds after the start of the run + This function is optional. If it is not set the framework assumes + the model runs with daily timesteps. + + Ouput: + + - time in seconds since the start of the model run + + """ + + return self.currentTimeStep() * int(configget(self.config,'model','timestepsecs','86400')) + + def suspend(self): + """ + *Required* + + Suspends the model to disk. All variables needed to restart the model + are saved to disk as pcraster maps. Use resume() to re-read them + + This function is required. + + """ + + self.logger.info("Saving initial conditions...") + #: It is advised to use the wf_suspend() function + #: here which will suspend the variables that are given by stateVariables + #: function. + self.wf_suspend(self.Dir + "/outstate/") + + + def initial(self): + + """ + *Required* + + Initial part of the model, executed only once. It reads all static model + information (parameters) and sets-up the variables used in modelling. + + This function is required. The contents is free. However, in order to + easily connect to other models it is advised to adhere to the directory + structure used in the other models. + + """ + #: pcraster option to calculate with units or cells. Not really an issue + #: in this model but always good to keep in mind. + setglobaloption("unittrue") + + + self.timestepsecs = int(configget(self.config,'model','timestepsecs','86400')) + self.basetimestep=86400 + self.wf_updateparameters() + self.logger.info("Starting Dynamic run...") + + + def resume(self): + """ + *Required* + + This function is required. Read initial state maps (they are output of a + previous call to suspend()). The implementation showns here is the most basic + setup needed. + + """ + self.logger.info("Reading initial conditions...") + #: It is advised to use the wf_resume() function + #: here which pick up the variable save by a call to wf_suspend() + try: + self.wf_resume(self.Dir + "/instate/") + except: + self.logger.warn("Cannot load initial states, setting to default") + for s in self.stateVariables(): + exec "self." + s + " = cover(1.0)" + + + def default_summarymaps(self): + """ + *Optional* + + Return a default list of variables to report as summary maps in the outsum dir. + The ini file has more option, including average and sum + """ + return ['self.Altitude'] + + def dynamic(self): + """ + *Required* + + This is where all the time dependent functions are executed. Time dependent + output should also be saved here. + """ + + self.wf_updateparameters() # read the temperature map fo each step (see parameters()) + + self.LAI = self.LAI * 0.9 + self.RootingDepth = self.LAI * 0.6 + + # reporting of maps and csv timeseries is done by the framework (see ini file) + + +# The main function is used to run the program from the command line + +def main(argv=None): + """ + *Optional but needed it you want to run the model from the command line* + + Perform command line execution of the model. This example uses the getopt + module to parse the command line options. + + The user can set the caseName, the runDir, the timestep and the configfile. + """ + global multpars + caseName = "default" + runId = "run_default" + configfile="wflow_sceleton.ini" + _lastTimeStep = 10 + _firstTimeStep = 1 + timestepsecs=86400 + wflow_cloneMap = 'wflow_subcatch.map' + + # This allows us to use the model both on the command line and to call + # the model usinge main function from another python script. + + if argv is None: + argv = sys.argv[1:] + if len(argv) == 0: + usage() + return + + opts, args = getopt.getopt(argv, 'C:S:T:c:s:R:') + + for o, a in opts: + if o == '-C': caseName = a + if o == '-R': runId = a + if o == '-c': configfile = a + if o == '-s': timestepsecs = int(a) + if o == '-T': _lastTimeStep=int(a) + if o == '-S': _firstTimeStep=int(a) + + if (len(opts) <=1): + usage() + + myModel = WflowModel(wflow_cloneMap, caseName,runId,configfile) + dynModelFw = wf_DynamicFramework(myModel, _lastTimeStep,firstTimestep=_firstTimeStep) + dynModelFw.createRunId(NoOverWrite=False,level=logging.DEBUG) + dynModelFw._runInitial() + dynModelFw._runResume() + dynModelFw._runDynamic(_firstTimeStep,_lastTimeStep) + dynModelFw._runSuspend() + dynModelFw._wf_shutdown() + + +if __name__ == "__main__": + main()