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)
_images/dam_llake_watervolume2waterlevel.png

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)
_images/dam_llake_waterlevel2flooddischarge.png

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:
The following methods define the relevant components of a system of ODE equations (e.g. direct runoff):
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):
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 sequence Q.

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 function smooth_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.

DOCNAME: DocName = ('Dam-L-Lake', 'controlled lake model adopted from LARSIM')
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 model dam_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:
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:
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:
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:
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:
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.

The following classes are selected:
  • Q() Inflow [m³/s].

  • E() Bidirectional water exchange [m³/s].

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:
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: