Creating a lumped and a semi distributed model in CMF can be handled relatively similar. The main difference is that a lumped model contains only one cell, while a semi distributed model contains several cells (one for every subatchment). This leads to a more difficult construction and parametrization of the semi distributed model. The most straightforward way to adress this, is to split the code in severall classes. The main model class calls all the other classes and has the interface to Spotpy (or handles the manual calibration). The second class is a kind of template for a cell. In this class all CMF elements which are the same for all subcatchments are defined. This class inherits then to the classes of the several subcatchments. In those classes for the subcatchments all CMF elements are added which are different to the other subcatchments. This is also a good place to add the weather data for the subcatchments.
Important: In lumped models with a cell area other than 1000 m² and especially in semi distributed models, where every cell has another size, some parameters need to be adjusted for the size of the cell. Examples would be V0 in the PowerLaw or ETV1 in the evapotranspiration.
Following is a example how one might construct a semi distributed model. The example allows the user to choose the amount of cells in the model. All cells here use the same data and parameters. This would have to be changed for other usage.
The model itself and its driving data are attached to this site.
"""
Created on Jan 09 10:39 2018
@author(s): Florian U. Jehn
"""
import datetime
import cmf
import spotpy
from spotpy.parameter import Uniform
import os
import numpy as np
import pandas as pd
class ScalingTester:
"""
A class to determine how CMF handles different amounts of cells. To test
this the same model structure is run as a 1, 2, 4 and 8 cell layout. Each
cell has the same structure and parameters.
"""
area = 562.41
V0_L1 = Uniform(10, 300)
V0_L2 = Uniform(10, 300)
fETV1 = Uniform(0.01, 1, doc='if V<fETV1*V0, water uptake stress for '
'plants starts [%]')
fETV0 = Uniform(0, 0.9, doc='if V<fETV0*fETV1*V0, plants die of drought '
'[%]')
tr_L1_out = Uniform(0.1, 200, doc='Residence time of water in storage '
'when '
'V=V0 [days]')
tr_L1_L2 = Uniform(0.1, 200)
tr_L2_out = Uniform(0.1, 200)
beta_L1_out = Uniform(0.5, 4, doc="Exponent for scaling the outflow[]")
beta_L1_L2 = Uniform(0.5, 4)
beta_L2_out = Uniform(0.4, 4)
snow_meltrate = Uniform(3, 10, doc="Meltrate of the snow [(mm*degC)/day]") snow_melt_temp = Uniform(0, 3.5, doc="Temperature at which the snow [degC]"
"melts")
LAI = Uniform(1, 12, doc="Leaf Area Index")
CanopyClosure = Uniform(0.1, 0.9, doc="Closure of the Canopy [%]")
def __init__(self, begin=None, end=None, num_cells=None):
"""
Initializes the model.
:param begin: Start year for the calibration
:param end: Stop year
:param num_cells: Number of cells used for this layout
:return: None
"""
self.dbname = "scaling_tester_num_cells_" + str(num_cells)
self.data = DataProvider("fulda_kaemmerzell_climate_79_89.csv")
self.project, self.outlet = self.create_project()
self.num_cells = num_cells
self.cells = self.create_cells()
self.data.add_stations(self.project)
self.begin = begin or self.data.begin
self.end = end or self.data.end
self.setparameters()
def create_project(self):
"""
Creates and CMF project with an outlet and other basic stuff and
returns it.
:return: cmf project and cmf outlet
"""
cmf.set_parallel_threads(1)
outlet = p.NewOutlet("outlet", 10, 0, 0)
return p, outlet
def create_cells(self):
"""
Creates a 'num_cells' amount of cells for the project
:return:
"""
area = self.area / self.num_cells
cells = []
for num in range(self.num_cells):
cells.append(CellTemplate(self.project, self.outlet, area,
num))
return cells
def setparameters(self, par=None):
"""
Sets the parameters for all cells seperately
:return:
"""
par = par or spotpy.parameter.create_set(self)
for cell in self.cells:
cell.set_parameters(par)
def runmodel(self):
"""
Runs the models and saves the results.
:return: Simulated discharge
"""
solver = cmf.CVodeKrylov(self.project, 1e-9)
res_q = cmf.timeseries(self.begin, cmf.day)
try:
for t in solver.run(self.data.begin, self.end, cmf.day):
res_q.add(self.outlet.waterbalance(t))
except RuntimeError:
return np.array(self.data.Q[
self.data.begin:self.data.end + datetime.timedelta(
days=1)])*np.nan
return res_q
def simulation(self, vector=None):
"""
Sets the parameters of the model and starts a run
:return: np.array with runoff in mm/day
"""
self.setparameters(vector)
result_q = self.runmodel()
result_q /= 86400
return np.array(result_q[self.begin:self.end])
def evaluation(self):
"""Returns the evaluation data"""
return np.array(self.data.Q[self.begin:self.end])
def objectivefunction(self, simulation, evaluation):
"""Calculates the objective function"""
return spotpy.objectivefunctions.nashsutcliffe(evaluation, simulation)
class CellTemplate:
"""
Template, which provides
"""
def __init__(self, project, outlet, area, cell_num):
self.project = project
self.outlet = outlet
self.area = area
self.cell = self.project.NewCell(cell_num, 0, 0, area * 1e6)
self.basic_set_up()
def basic_set_up(self):
"""
Creates the basic storages, that are to be connected in set_parameters.
:return:
"""
self.cell.add_layer(2.0)
self.cell.add_layer(4.0)
cmf.HargreaveET(self.cell.layers[0], self.cell.transpiration)
self.cell.add_storage("Snow", "S")
cmf.Snowfall(self.cell.snow, self.cell)
self.cell.add_storage("Canopy", "C")
def set_parameters(self, par):
"""
Sets the parameters for a cell instance
:param par: Object with all parameters
:return: None
"""
c = self.cell
out = self.outlet
V0_L1 = (par.V0_L1 / 1000) * self.area * 1e6
V0_L2 = (par.V0_L2 / 1000) * self.area * 1e6
ETV1 = par.fETV1 * V0_L1
ETV0 = par.fETV0 * ETV1
c.set_uptakestress(cmf.VolumeStress(ETV1, ETV0))
cmf.PowerLawConnection(c.layers[0], out, Q0=V0_L1 / par.tr_L1_out,
V0=V0_L1,
beta=par.beta_L1_out)
cmf.PowerLawConnection(c.layers[0], c.layers[1], Q0=(V0_L1 /
par.tr_L1_L2),
V0=V0_L1, beta=par.beta_L1_L2)
cmf.PowerLawConnection(c.layers[1], out, Q0=V0_L2 / par.tr_L2_out,
V0=V0_L2,
beta=par.beta_L2_out)
cmf.TempIndexSnowMelt(c.snow, c.layers[0], c,
rate=par.snow_meltrate)
cmf.Weather.set_snow_threshold(par.snow_melt_temp)
cmf.Rainfall(c.canopy, c, False, True)
cmf.Rainfall(c.surfacewater, c, True, False)
cmf.RutterInterception(c.canopy, c.surfacewater, c)
cmf.CanopyStorageEvaporation(c.canopy, c.evaporation, c)
c.vegetation.LAI = par.LAI
c.vegetation.CanopyClosure = par.CanopyClosure
class DataProvider:
"""
Holds the forcing and calibration data
"""
def __init__(self, file_name):
data = pd.read_csv(file_name, encoding="ISO-8859-1", sep=";")
data = data.iloc[1:]
data = data.dropna(axis=0)
def bstr2date(bs):
"""
Helper function to convert date byte string to datetime object
"""
return datetime.datetime.strptime(bs, '%d.%m.%Y')
self.begin = bstr2date(data["date"].iloc[0]) self.step = bstr2date(data["date"]].iloc[1) - self.begin
self.end = bstr2date(data["date"].iloc[-1])
self.P = cmf.timeseries.from_sequence(self.begin, self.step,
data["Prec"])
self.T = cmf.timeseries.from_sequence(self.begin, self.step,
data["tmean"])
self.Tmin = cmf.timeseries.from_sequence(self.begin, self.step,
data["tmin"])
self.Tmax = cmf.timeseries.from_sequence(self.begin, self.step,
data["tmax"])
self.Q = cmf.timeseries.from_sequence(self.begin, self.step,
data["Q"])
def add_stations(self, project):
"""
Creates a rainstation and a meteo station for the cmf project
:param project: A cmf.project
:return: rainstation, meteo
"""
rainstation = project.rainfall_stations.add('Kaemmerzell avg', self.P,
(0, 0, 0))
project.use_nearest_rainfall()
meteo = project.meteo_stations.add_station('Kaemmerzell avg',
(0, 0, 0))
meteo.T = self.T
meteo.Tmin = self.Tmin
meteo.Tmax = self.Tmax
project.use_nearest_meteo()
return rainstation, meteo
if __name__ == '__main__':
from spotpy.algorithms import lhs as Sampler
parallel = 'mpi' if 'OMPI_COMM_WORLD_SIZE' in os.environ else 'seq'
runs = 100
num_cells = [1, 2, 4, 8]
results = {}
for num in num_cells:
model = ScalingTester(num_cells=num)
print(cmf.describe(model.project))
sampler = Sampler(model, parallel=parallel, dbname=model.dbname,
dbformat='csv', save_sim=False)
sampler.sample(runs)
results[str(num)] = sampler.status.objectivefunction
for key, value in results.items():
print("The model with {} cell(s) has a best NS of {}".format(
key,
value))
The study area, holding all cells, outlets and streams.
Definition project.h:53