HydPy-Dam-L-Lake (controlled lake model adopted from LARSIM)¶
Conceptionally, dam_llake
is similar to the “controlled lake” model of LARSIM
(selectable via the “SEEG” option) in its way to simulate flood retention processes.
However, in contrast to the “SEEG” option, it can include precipitation, evaporation,
and a (positive or negative) water exchange into the lake’s water balance.
One can regard dam_llake
as controlled in two ways. First, it allows for seasonal
modifications of the rating curve via parameter WaterLevel2FloodDischarge
; second, it
supports restricting the speed of the water level decrease during periods with little
inflow via parameter AllowedWaterLevelDrop
.
The (optional) inclusion of precipitation and evaporation requires submodels that
follow the PrecipModel_V2
and PETModel_V1
interfaces. The latter must provide
potential evaporation values. If these reflect, for example, grass reference
evaporation, they usually show a too-high short-term variability. Therefore, the
parameter WeightEvaporation
provides a simple means to damp and delay the given
potential evaporation values by a simple time weighting approach.
The optional water exchange term enables bidirectional coupling of dam_llake
instances and other model objects. Please see the documentation on the exchange model
exch_weir_hbv96
, where we demonstrate how to represent a system of two lakes
connected by a short ditch.
Like all models of the HydPy-Dam family, dam_llake
solves its underlying
continuous ordinary differential equations with an error-adaptive numerical integration
method. Hence, simulation speed, robustness, and accuracy depend on the configuration
of the parameters of the model equations and the underlying solver. We discuss these
topics in more detail in the documentation on the application model dam_v001
. Before
the first usage of any HydPy-Dam model, you should at least read how to set
proper smoothing parameter values and how to configure InterpAlgorithm
objects for
interpolating the relationships between stage and volume (WaterVolume2WaterLevel
) and
between discharge and stage (WaterLevel2FloodDischarge
).
Integration tests¶
Note
When new to HydPy, consider reading section Integration Tests first.
We are going to perform all example calculations over 20 days:
>>> from hydpy import Element, Node, pub
>>> pub.timegrids = "01.01.2000", "21.01.2000", "1d"
Now, we prepare a dam_llake
model instance as usual:
>>> from hydpy.models.dam_llake import *
>>> parameterstep("1d")
Next, we embed this model instance into an Element
connected to one inlet Node
(inflow) and one outlet Node
(outflow):
>>> inflow = Node("inflow", variable="Q")
>>> outflow = Node("outflow", variable="Q")
>>> exchange = Node("exchange", variable="E")
>>> lake = Element("lake", inlets=(inflow, exchange), outlets=outflow)
>>> lake.model = model
To execute the following examples conveniently, we prepare a test function object and change some of its default output settings:
>>> from hydpy import IntegrationTest
>>> test = IntegrationTest(lake)
>>> test.dateformat = "%d.%m."
>>> test.plotting_options.axis1 = fluxes.inflow, fluxes.outflow
>>> test.plotting_options.axis2 = states.watervolume
WaterVolume
is the only state sequence of dam_llake
. The purpose of the only log
sequence LoggedAdjustedEvaporation
is to allow for the mentioned time-weighting of
the external potential evaporation values. We set the initial values of both sequences
to zero for each of the following examples:
>>> test.inits = [(states.watervolume, 0.0),
... (logs.loggedadjustedevaporation, 0.0)]
Using method check_waterbalance()
prove that dam_llake
keeps the water balance
in each example run requires storing the defined (initial) conditions before performing
the first simulation run:
>>> test.reset_inits()
>>> conditions = model.conditions
dam_llake
assumes the relationship between WaterLevel
and WaterVolume
to be
constant over time. For simplicity, we define a linear relationship by using PPoly
:
>>> watervolume2waterlevel(PPoly.from_data(xs=[0.0, 1.0], ys=[0.0, 1.0]))
>>> figure = watervolume2waterlevel.plot(0.0, 1.0)
>>> from hydpy.core.testtools import save_autofig
>>> save_autofig("dam_llake_watervolume2waterlevel.png", figure=figure)
dam_llake
uses parameter WaterLevel2FloodDischarge
(which extends parameter
SeasonalInterpolator
) to allow for annual changes in the relationship between
FloodDischarge
and WaterLevel
. Please read the documentation on class
SeasonalInterpolator
on how to model seasonal patterns. Here, we keep things as
simple as possible and define a single linear relationship that applies for the whole
year:
>>> waterlevel2flooddischarge(PPoly.from_data(xs=[0.0, 1.0], ys=[0.0, 10.0]))
>>> figure = waterlevel2flooddischarge.plot(0.0, 1.0)
>>> figure = save_autofig("dam_llake_waterlevel2flooddischarge.png", figure=figure)
The following group of parameters deal with lake precipitation and evaporation. Note
that, despite dam_llake
’s ability to calculate the water-level dependent surface area
(see aide sequence SurfaceArea
), it always assumes a fixed surface area
(defined by control parameter SurfaceArea
) for converting precipitation
and evaporation heights into volumes. Here, we set this fixed surface area to
1.44 km²:
>>> surfacearea(1.44)
We set the correction factors for precipitation and evaporation to 1.2:
>>> correctionprecipitation(1.2)
>>> correctionevaporation(1.2)
Given the daily simulation time step, we configure moderate damping and delay of the external evaporation values (0.8 is relatively close to 1.0, which would avoid any delay and damping, while 0.0 would result in a complete loss of variability):
>>> weightevaporation(0.8)
dam_llake
uses the parameter ThresholdEvaporation
to define the water level around
which actual evaporation switches from zero to potential evaporation. As usual but not
mandatory, we set this threshold to 0 m:
>>> thresholdevaporation(0.0)
Additionally, we set the values of the related smoothing parameters
DischargeTolerance
and ToleranceEvaporation
to 0.1 m³/s and 1 mm (these are values
we can recommend for many cases – see the documentation on application model
dam_v001
on how to fine-tune such smoothing parameters to your needs):
>>> dischargetolerance(0.1)
>>> toleranceevaporation(0.001)
Finally, we define a precipitation series including only a heavy one-day rainfall event and a corresponding inflowing flood wave, starting and ending with zero discharge:
>>> with model.add_precipmodel_v2("meteo_precip_io") as precipmodel:
... precipitationfactor(1.0)
>>> precipmodel.prepare_inputseries()
>>> precipmodel.sequences.inputs.precipitation.series = [
... 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
... 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>> lake.inlets.inflow.sequences.sim.series = [
... 0.0, 0.0, 6.0, 12.0, 10.0, 6.0, 3.0, 2.0, 1.0, 0.0,
... 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
base scenario¶
For our first example, we set the allowed water level drop to inf
, neglect
potential evaporation, and set the exchange values to zero to ensure they do not affect
the calculated lake outflow:
>>> allowedwaterleveldrop(inf)
>>> exchange.sequences.sim.series = 0.0
The only purpose of parameter CatchmentArea
is to automatically determine reasonable
default values for the parameter AbsErrorMax
, controlling the accuracy of the
numerical integration process:
>>> catchmentarea(86.4)
>>> from hydpy import round_
>>> round_(solver.abserrormax.INIT)
0.0001
>>> parameters.update()
>>> solver.abserrormax
abserrormax(0.0001)
The following test results show the expected storage retention pattern. The sums of inflow and outflow are nearly identical, and the maximum of the outflow graph intersects with the falling limb of the inflow graph:
>>> test("dam_llake_base_scenario")
Click to see the table
Click to see the graph
dam_llake
achieves this sufficiently high accuracy with 174 calls to its underlying
system of differential equations, which averages to less than nine calls per day:
>>> model.numvars.nmb_calls
426
There is no indication of an error in the water balance:
>>> from hydpy import round_
>>> round_(model.check_waterbalance(conditions))
0.0
low accuracy¶
By increasing the numerical tolerance, e.g. setting AbsErrorMax
to 0.1 m³/s, we gain
some additional speedups without relevant deteriorations of the results (dam_llake
usually achieves higher accuracies than indicated by the actual tolerance value):
>>> model.numvars.nmb_calls = 0
>>> solver.abserrormax(0.1)
>>> test("dam_llake_low_accuracy")
Click to see the table
Click to see the graph
>>> model.numvars.nmb_calls
104
There is no indication of an error in the water balance:
>>> round_(model.check_waterbalance(conditions))
0.0
water level drop¶
When setting AllowedWaterLevelDrop
to 0.1 m/d, the resulting outflow hydrograph shows
a plateau in its falling limb. This plateau coincides with little inflow but still
high potential outflow (FloodDischarge
) due to large amounts of stored water. In
agreement with the linear relationship between the water volume and the water level,
there is a constant decrease in the water volume when the allowed “water level drop”
limits the outflow:
>>> allowedwaterleveldrop(0.1)
>>> solver.abserrormax(0.01)
>>> test("dam_llake_water_level_drop")
Click to see the table
Click to see the graph
There is no indication of an error in the water balance:
>>> round_(model.check_waterbalance(conditions))
0.0
evaporation¶
In this example, we add an evap_ret_io
submodel and set its (unadjusted) potential
evaporation to 1 mm/d for the first ten days and 5 mm/d for the last ten days. The
adjusted evaporation follows the given potential evaporation with a short delay. When
the water volume reaches zero, actual evaporation is nearly zero, but due to the
defined smoothing, it is not precisely zero. Hence, slightly negative water volumes
result (which do not cause negative outflow):
>>> with model.add_pemodel_v1("evap_ret_io") as pemodel:
... evapotranspirationfactor(1.0)
>>> pemodel.prepare_inputseries()
>>> pemodel.sequences.inputs.referenceevapotranspiration.series = 10 * [1.0] + 10 * [5.0]
>>> test("dam_llake_evaporation")
Click to see the table
Click to see the graph
There is no indication of an error in the water balance:
>>> round_(model.check_waterbalance(conditions))
0.0
exchange¶
The water exchange functionality of dam_llake
is optional insofar as one does not
need to connect the inlet sequence E
to any nodes. If there is a connection to one
or multiple nodes, they can add and subtract water, indicated by positive or negative
values. This mechanism allows bidirectional water exchange between different
dam_llake
(see the documentation exch_weir_hbv96
for further information).
dam_llake
handles the water exchange strictly as input, meaning it always includes it
in its water balance without any modification. Hence, other models calculating the
water exchange must ensure that it does not bring dam_llake
into an unrealistic
state. For demonstration, we set the water exchange to 0.5 m³/s in the first half of
the simulation period and -0.5 m³/s in the second half, which causes highly negative
water volumes at the end of the simulation period:
>>> exchange.sequences.sim.series = 10 * [0.5] + 10 * [-0.5]
>>> test("dam_llake_exchange")
Click to see the table
Click to see the graph
>>> round_(model.check_waterbalance(conditions))
0.0
- class hydpy.models.dam_llake.Model[source]¶
Bases:
Main_PrecipModel_V2
,Main_PEModel_V1
HydPy-Dam-L-Lake (controlled lake model adopted from LARSIM).
- The following “inlet update methods” are called in the given sequence at the beginning of each simulation step:
Calc_Precipitation_V1
If available, let a submodel that complies with thePrecipModel_V2
interface determine precipitation.Calc_PotentialEvaporation_V1
If available, let a submodel that complies with thePETModel_V1
interface determine potential evaporation.Calc_AdjustedEvaporation_V1
Adjust the given potential evaporation.
- The following methods define the relevant components of a system of ODE equations (e.g. direct runoff):
Calc_AdjustedPrecipitation_V1
Adjust the given precipitation.Pic_Inflow_V1
Update the inlet sequenceInflow
.Pic_Exchange_V1
Update the inlet sequenceExchange
.Calc_WaterLevel_V1
Determine the water level based on an interpolation approach approximating the relationship between water volume and water level.Calc_ActualEvaporation_V1
Calculate the actual evaporation.Calc_SurfaceArea_V1
Determine the surface area based on an interpolation approach approximating the relationship between the water level and the surface area.Calc_FloodDischarge_V1
Calculate the discharge during and after a flood event based on seasonally varying interpolation approaches approximating the relationship(s) between discharge and water stage.Calc_AllowedDischarge_V1
Calculate the maximum discharge not leading to exceedance of the allowed water level drop.Calc_Outflow_V2
Calculate the total outflow of the dam, taking the allowed water discharge into account.
- The following methods define the complete equations of an ODE system (e.g. change in storage of fast water due to effective precipitation and direct runoff):
Update_WaterVolume_V4
Update the actual water volume.
- The following “outlet update methods” are called in the given sequence at the end of each simulation step:
Calc_WaterLevel_V1
Determine the water level based on an interpolation approach approximating the relationship between water volume and water level.Pass_Outflow_V1
Update the outlet link sequenceQ
.
- The following “additional methods” might be called by one or more of the other methods or are meant to be directly called by the user:
Fix_Min1_V1
Apply functionsmooth_min1()
without risking negative results.
- Users can hook submodels into the defined main model if they satisfy one of the following interfaces:
PrecipModel_V2
Simple interface for determining precipitation in one step.PETModel_V1
Simple interface for calculating all potential evapotranspiration values in one step.
- precipmodel: modeltools.SubmodelProperty¶
Optional submodel that complies with the following interface: PrecipModel_V2.
- pemodel: modeltools.SubmodelProperty¶
Optional submodel that complies with the following interface: PETModel_V1.
- check_waterbalance(initial_conditions: dict[str, dict[str, dict[str, float | ndarray[Any, dtype[float64]]]]]) float [source]¶
Determine the water balance error of the previous simulation run in million m³.
Method
check_waterbalance()
calculates the balance error as follows:\(Seconds \cdot 10^{-6} \cdot \sum_{t=t0}^{t1} \big( AdjustedPrecipitation_t - ActualEvaporation_t + Inflow_t - Outflow_t + Exchange_t \big) + \big( WaterVolume_{t0}^k - WaterVolume_{t1}^k \big)\)
The returned error should always be in scale with numerical precision so that it does not affect the simulation results in any relevant manner.
Pick the required initial conditions before starting the simulation run via property
conditions
. See the integration tests of the application modeldam_llake
for some examples.
- REUSABLE_METHODS: ClassVar[tuple[type[ReusableMethod], ...]] = ()¶
- class hydpy.models.dam_llake.AideSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
AideSequences
Aide sequences of model dam_llake.
- The following classes are selected:
SurfaceArea()
Surface area [km²].AllowedDischarge()
Discharge threshold not to be overcut by the actual discharge [m³/s].
- class hydpy.models.dam_llake.ControlParameters(master: Parameters, cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
SubParameters
Control parameters of model dam_llake.
- The following classes are selected:
SurfaceArea()
Average size of the water surface [km²].CatchmentArea()
Size of the catchment draining into the dam [km²].CorrectionPrecipitation()
Precipitation correction factor [-].CorrectionEvaporation()
Evaporation correction factor [-].WeightEvaporation()
Time weighting factor for evaporation [-].ThresholdEvaporation()
The water level at which actual evaporation is 50 % of potential evaporation [m].ToleranceEvaporation()
A tolerance value defining the steepness of the transition of actual evaporation between zero and potential evaporation [m].WaterVolume2WaterLevel()
An interpolation function that describes the relationship between water level and water volume [-].WaterLevel2FloodDischarge()
An interpolation function that describesg the relationship between flood discharge and water volume [-].AllowedWaterLevelDrop()
The highest allowed water level decrease [m/T].DischargeTolerance()
Smoothing parameter for discharge-related smoothing operations [m³/s].
- class hydpy.models.dam_llake.DerivedParameters(master: Parameters, cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
SubParameters
Derived parameters of model dam_llake.
- The following classes are selected:
TOY()
References thetimeofyear
index array provided by the instance of classIndexer
available in modulepub
[-].Seconds()
Length of the actual simulation step size [s].InputFactor()
Factor for converting meteorological input from mm/T to million m³/s.SmoothParEvaporation()
Smoothing parameter to be derived fromToleranceEvaporation
for smoothing kernelsmooth_logistic1()
[m].DischargeSmoothPar()
Smoothing parameter to be derived fromDischargeTolerance
for smoothing kernelssmooth_logistic2()
,smooth_min1()
, andsmooth_max1()
[m³/s].
- class hydpy.models.dam_llake.FactorSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
FactorSequences
Factor sequences of model dam_llake.
- The following classes are selected:
WaterLevel()
Water level [m].
- class hydpy.models.dam_llake.FluxSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
FluxSequences
Flux sequences of model dam_llake.
- The following classes are selected:
Precipitation()
Precipitation [mm].AdjustedPrecipitation()
Adjusted precipitation [m³/s].PotentialEvaporation()
Potential evaporation [mm/T].AdjustedEvaporation()
Adjusted evaporation [m³/s].ActualEvaporation()
Actual evaporation [m³/s].Inflow()
Total inflow [m³/s].Exchange()
Water exchange with another location [m³/s].FloodDischarge()
Water release associated with flood events [m³/s].Outflow()
Total outflow [m³/s].
- class hydpy.models.dam_llake.InletSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
InletSequences
Inlet sequences of model dam_llake.
- class hydpy.models.dam_llake.LogSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
LogSequences
Log sequences of model dam_llake.
- The following classes are selected:
LoggedAdjustedEvaporation()
Logged adjusted evaporation [m³/s].
- class hydpy.models.dam_llake.OutletSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
OutletSequences
Outlet sequences of model dam_llake.
- The following classes are selected:
Q()
Outflow [m³/s].
- class hydpy.models.dam_llake.SolverParameters(master: Parameters, cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
SubParameters
Solver parameters of model dam_llake.
- The following classes are selected:
AbsErrorMax()
Absolute numerical error tolerance [m³/s].RelErrorMax()
Relative numerical error tolerance [1/T].RelDTMin()
Smallest relative integration time step size allowed [-].RelDTMax()
Largest relative integration time step size allowed [-].
- class hydpy.models.dam_llake.StateSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
StateSequences
State sequences of model dam_llake.
- The following classes are selected:
WaterVolume()
Water volume [million m³].