HydPy-Dam-L-Res (reservoir model adopted from LARSIM)

dam_lreservoir is a relatively simple reservoir model, similar to the “TALS” model of LARSIM. It combines the features of dam_llake (“controlled lake”) and dam_lretention (“retention basin”). Additionally, it allows controlling the stored water volume via defining target values that can vary seasonally.

Like dam_lretention, dam_lreservoir allows for combining controlled, “harmless outflow” (via parameter AllowedRelease) and uncontrolled, “spillway outflow” (via parameter WaterLevel2FloodDischarge), and like dam_llake, it allows restricting the speed of the water level decrease during periods with little inflow via parameter AllowedWaterLevelDrop (only through reducing the controlled outflow, of course). Before continuing, please first read the documentation on these two application models.

The additional feature of dam_lreservoir is its ability to track seasonal target volumes. We define these target volumes via parameter TargetVolume. The parameters VolumeTolerance, TargetRangeAbsolute, and TargetRangeRelative serve to yield smoother and more realistic reservoir responses for slight deviations from the given target values. Setting TargetRangeRelative to 0.2 and both other parameters to zero corresponds to selecting the “TALSPERRE SOLLRANGE” option in LARSIM. Please see the following examples and the documentation on method Calc_ActualRelease_V3 for more information on setting and combining the individual parameter values for different use cases.

Like dam_llake, dam_lreservoir supports optional water transfers via submodels that follow the TransferModel_V1 interface, like exch_water_transfer_io. Such a submodel withdraws water from, or supplies water to, the reservoir based on, for example, the reservoir’s current water volume and passes it to its own outlet node. Withdrawals take priority over the controlled outflow (except for the required minimum release) and should comply with the allowed water level drop. At its target volume, dam_lreservoir releases its inflow minus the water transfer, so that the water volume remains constant, and it releases more (less) if the water volume is above (below) the target volume (see the water transfer example).

Integration tests

Note

When new to HydPy, consider reading section Integration Tests first.

We prepare a test set similar to those for application models dam_llake and dam_lretention, including an identical inflow series and an identical relationship between stage and volume:

>>> from hydpy import Element, IntegrationTest, Node, pub
>>> pub.timegrids = "01.01.2000", "21.01.2000", "1d"
>>> from hydpy.models.dam_lreservoir import *
>>> parameterstep("1d")
>>> with model.add_transfermodel_v1("exch_water_transfer_io") as transfermodel:
...     voltransferrules(PPoly(xs=[0.0], ys=[0.0]))
...     minvol(0.0)
...     maxvol(inf)
...     voltol(0.0)
...     qtol(0.0)
...     keepwaterbalance(True)
>>> inflow = Node("inflow", variable="Q")
>>> outflow = Node("outflow", variable="Q")
>>> transfer = Node("transfer", variable="ActualTransfer")
>>> request = Node("request", variable="A")
>>> element = Element(
...     "element", inlets=inflow, outlets=(outflow, transfer), observers=request
... )
>>> element.model = model
>>> test = IntegrationTest(element)
>>> test.dateformat = "%d.%m."
>>> test.plotting_options.axis1 = fluxes.inflow, fluxes.outflow
>>> test.plotting_options.axis2 = states.watervolume
>>> test.inits = [
...     (states.watervolume, 0.0),
...     (logs.loggedadjustedevaporation, 0.0)]
>>> test.reset_inits()
>>> conditions = model.conditions
>>> watervolume2waterlevel(PPoly(xs=[0.0, 1.0], ys=[0.0, 1.0]))
>>> surfacearea(1.44)
>>> catchmentarea(86.4)
>>> correctionprecipitation(1.2)
>>> correctionevaporation(1.2)
>>> weightevaporation(0.8)
>>> thresholdevaporation(0.0)
>>> toleranceevaporation(0.001)
>>> commission("1900-01-01")
>>> 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]
>>> 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]
>>> request.sequences.sim.series = 0.0
>>> transfermodel.sequences.inputs.requestedtransfer.series = inf

base scenario

First, we again use the linear relation between discharge and stage used throughout the integration tests of dam_llake and in the base example of dam_lretention:

>>> waterlevel2flooddischarge(PPoly(xs=[0.0, 1.0], ys=[0.0, 10.0]))

Additionally, we set some of the remaining parameter values extremely high or low to ensure the reservoir stores all water except the one activating the spillway, which becomes “flood discharge”:

>>> targetvolume(100.0)
>>> neardischargeminimumthreshold.shape = 1
>>> neardischargeminimumthreshold.values = -100.0
>>> targetrangeabsolute(0.1)
>>> targetrangerelative(0.2)
>>> watervolumeminimumthreshold(0.0)
>>> volumetolerance(0.1)
>>> dischargetolerance(0.1)
>>> allowedrelease(100.0)
>>> allowedwaterleveldrop(100.0)

Due to the same interpolation configuration, the results are identical to those of the base example of dam_llake and the base example of dam_lretention:

>>> test("dam_lreservoir_base_scenario")
Click to see the table

There is no indication of an error in the water balance:

>>> from hydpy import round_
>>> round_(model.check_waterbalance(conditions))
0.0

spillway

When we reuse the more realistic relationship between flood discharge and stage of the spillway example on dam_lretention, we again get the same flood discharge time series:

>>> waterlevel2flooddischarge(ANN(weights_input=10.0, weights_output=50.0,
...                               intercepts_hidden=-20.0, intercepts_output=0.0))
>>> test("dam_lreservoir_spillway")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

target volume

During dry periods, application model dam_lretention generally releases all its water until the basin runs dry, as long as AllowedRelease is larger than zero. Application model dam_lreservoir instead allows for defining target volumes. dam_lreservoir tries to control its outflow so that the actual volume approximately equals the (potentially seasonally varying) target volume. However, it cannot release arbitrary amounts of water to fulfil this task due to its priority to release a predefined minimum amount of water (for ecological reasons) and its second priority to not release too much water (for flood protection). In this example, we activate these mechanisms by changing some corresponding parameter values (see the documentation on method Calc_ActualRelease_V3 for more detailed examples, including the numerous corner cases):

>>> targetvolume(0.5)
>>> neardischargeminimumthreshold(0.1)
>>> allowedrelease(4.0)
>>> allowedwaterleveldrop(1.0)

Compared with the allowed release results of dam_lretention, dam_lreservoir dampens the given flood event less efficiently. dam_lretention releases all initial inflow, while dam_lreservoir stores most of it until it reaches the target volume of 0.5 million m³. After peak flow, dam_lreservoir first releases its water as fast as allowed but then tries to meet the target volume again. The slow negative trend away from the target value at the end of the simulation period results from the lack of inflow while still needing to release at least 0.1 m³/s:

>>> test("dam_lreservoir_target_volume")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

sharp transitions

Due to smoothing, the above results deviate from those one would expect from LARSIM simulations to some degree. However, if we set both “target range” parameters to zero (like one does not select the LARSIM option “TALSPERRE SOLLRANGE”) and both “tolerance” parameters to zero (to turn off any smoothing), we get more similar results:

>>> targetrangeabsolute(0.0)
>>> targetrangerelative(0.0)
>>> volumetolerance(0.0)
>>> dischargetolerance(0.0)
>>> test("dam_lreservoir_sharp_transitions")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

higher accuracy

The first water volume calculated in the sharp transitions example is negative, resulting from the limited numerical accuracy of the underlying integration algorithm. We can decrease such errors by defining smaller error tolerances but at the risk of relevant increases in computation times (especially in case one applies zero smoothing values):

>>> solver.abserrormax(1e-6)
>>> test("dam_lreservoir_higher_accuracy")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

target range

In the last example, the reservoir behaviour changes abruptly when the actual volume transcends the target volume. According to its documentation, LARSIM predicts unrealistic jumps in discharge in such cases. To solve this issue, LARSIM offers the “TALSPERRE SOLLRANGE” option, which ensures smoother transitions between 80 % and 120 % of the target volume, accomplished by linear interpolation. dam_lreservoir should never output similar jumps, as it controls the correctness of its results. As a drawback, correcting these jumps (which still occur “unseen” and possibly multiple times within each affected simulation time step) costs computation time. Hence, at least for small smoothing parameter values, dam_lreservoir can also benefit from this approach. You can define the interpolation range freely via TargetRangeAbsolute and TargetRangeRelative, depending on your specific needs. Setting the latter to 0.2 corresponds to the original “TALSPERRE SOLLRANGE”-configuration:

>>> targetrangerelative(0.2)
>>> test("dam_lreservoir_target_range")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

minimum volume

In all the examples above, the dam would run dry entirely after a certain time to fulfil the downstream demand defined by parameter NearDischargeMinimumThreshold. Usually, this is neither desired nor technically possible. The following example shows that the parameter WaterVolumeMinimumThreshold allows setting a minimum amount of water below which no release occurs:

>>> watervolumeminimumthreshold(0.45)
>>> test("dam_lreservoir_minimum_volume")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

evaporation

This example builds on the evaporation example of application model dam_llake. The reservoir can no longer maintain the target water level at the end of the simulation period due to missing precipitation or inflow to compensate evaporation:

>>> 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_lreservoir_evaporation")
Click to see the table

There is no indication of an error in the water balance:

>>> round_(model.check_waterbalance(conditions))
0.0

request

This example demonstrates that dam_lreservoir respects externally requested additional water releases as discussed for dam_llake in this example:

>>> request.sequences.sim.series = 10 * [0.0] + 5 * [1.0] + 5 * [0.0]
>>> test("dam_lreservoir_request")
Click to see the table
>>> round_(model.check_waterbalance(conditions))
0.0

commissioning

This example extends the previous one with the commissioning mechanism shown and discussed in the analogue example of application model dam_llake:

>>> commission("2000-01-04")
>>> pemodel.sequences.inputs.referenceevapotranspiration.series = 50.0
>>> test("dam_lreservoir_commissioning")
Click to see the table
>>> round_(model.check_waterbalance(conditions))
0.0

water transfer

As in the water transfer example of dam_llake, we activate the exch_water_transfer_io submodel, but only restrict withdrawals (MaxVol is inf) and decrease the allowed water level drop to 0.1 m per day:

>>> control = transfermodel.parameters.control
>>> control.voltransferrules(PPoly(xs=[0.0, 1.0], ys=[0.0, 0.5]))
>>> control.minvol(0.8)
>>> control.voltol(0.01)
>>> control.qtol(0.1)
>>> transfermodel.sequences.inputs.requestedtransfer.series = (
...     5 * [inf] + 5 * [1.0] + 5 * [2.0] + 5 * [-1.0]
... )
>>> test.plotting_options.axis1 = (
...     fluxes.inflow, fluxes.outflow, fluxes.effectivetransfer, fluxes.actualrelease, fluxes.flooddischarge
... )
>>> commission("1900-01-01")
>>> pemodel.sequences.inputs.referenceevapotranspiration.series = 0.0
>>> request.sequences.sim.series = 0.0
>>> allowedwaterleveldrop(0.1)

As in the water transfer example, the transfer takes priority over the outflow when AllowedWaterLevelDrop limits the total water loss. Hence, the withdrawal of 1 m³/s reduces the controlled release (ActualRelease) from January 7 on (see methods Calc_AllowedDischarge_V4 and Update_AllowedDischarge_V2). From January 11 to 13, exch_water_transfer_io reduces the requested withdrawal of 2 m³/s to about 1.06 m³/s, which is the allowed discharge (about 1.16 m³/s) minus the required minimum release (NearDischargeMinimumThreshold) of 0.1 m³/s, so that the water level drops by 0.1 m per day. On January 14, the water volume falls below MinVol (0.8 million m³), the withdrawal fades out, and the controlled release takes over the allowed discharge. During the last five days, the reservoir passes on the requested supply of 1 m³/s (and some of its stored water) via its controlled release until the water volume approaches its target volume (still 0.5 million m³ from a previous example):

>>> test("dam_lreservoir_water_transfer")
Click to see the table
>>> round_(model.check_waterbalance(conditions))
0.0

Method features

class hydpy.models.dam_lreservoir.Model[source]

Bases: ELSIEModel, Main_PrecipModel_V2, Main_PEModel_V1, Main_TransferModel_V1

HydPy-Dam-L-Res (reservoir model adopted from LARSIM).

The following “inlet update methods” are called in the given sequence at the beginning of each simulation step:
The following “observer 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):
  • Calc_AdjustedPrecipitation_V1 Adjust the given precipitation.

  • Pick_Inflow_V1 Update the inlet sequence Inflow.

  • Calc_WaterLevel_V1 Determine the water level based on an interpolation approach approximating the relationship between water volume and water level.

  • Calc_ActualEvaporation_V3 Calculate the actual evaporation before and after the commission date.

  • Calc_SurfaceArea_V1 Determine the surface area based on an interpolation approach approximating the relationship between the water level and the surface area.

  • Calc_AllowedDischarge_V4 Calculate the maximum discharge not leading to exceedance of the allowed water level drop (when neglecting precipitation and evaporation).

  • Calc_EffectiveTransfer_V2 If available, let a submodel that complies with the TransferModel_V1 interface determine the effective water transfer while considering the current allowed discharge minus the required minimum release.

  • Update_AllowedDischarge_V2 Subtract the effective water transfer from the allowed discharge and restrict the result to the allowed release.

  • Calc_ActualRelease_V3 Calculate an actual water release that tries to change the water storage in the direction of the actual target volume without violating the required minimum and the allowed maximum flow.

  • 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_Outflow_V7 Calculate the total outflow of the dam before and after the commission date.

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 methods are called in the given sequence after solving the ODE system of each simulation step (e.g. for passing the final flux values to submodels):
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.

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.

  • TransferModel_V1 Interface for calculating water transfers based on the main model’s current water volume and allowed discharge.

DOCNAME = ('Dam-L-Res', 'reservoir model adopted from LARSIM')
precipmodel

Optional submodel that complies with the following interface: PrecipModel_V2.

pemodel

Optional submodel that complies with the following interface: PETModel_V1.

transfermodel

Optional submodel that complies with the following interface: TransferModel_V1.

check_waterbalance(initial_conditions: dict[str, dict[str, dict[str, float | NDArray[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:

\[\begin{split}Error = Seconds \cdot 10^{-6} \cdot (\Sigma In - \Sigma Out) - \Delta Vol \\ \\ \Sigma In = \sum_{t=t_0}^{t_1} AdjustedPrecipitation_t + Inflow_t \\ \Sigma Out = \sum_{t=t_0}^{t_1} ActualEvaporation_t + Outflow_t + EffectiveTransfer_t \\ \Delta Vol = WaterVolume_{t_1} - WaterVolume_{t_0}\end{split}\]

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_lreservoir for some examples.

REUSABLE_METHODS = ()

Parameter features

class hydpy.models.dam_lreservoir.ControlParameters(master: Parameters[TM_co], cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)

Bases: ControlParameters

Control parameters of model dam_lreservoir.

The following classes are selected:
class hydpy.models.dam_lreservoir.DerivedParameters(master: Parameters[TM_co], cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)

Bases: DerivedParameters

Derived parameters of model dam_lreservoir.

The following classes are selected:
class hydpy.models.dam_lreservoir.SolverParameters(master: Parameters[TM_co], cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)

Bases: SolverParameters

Solver parameters of model dam_lreservoir.

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 [-].

  • MaxEval Maximum number of function evaluations before stopping the Explicit Labatto Sequence [-].

  • MaxCFL Maximum Caurant-Friedrichs-Lewy number for using the Explicit Labatto Sequence [-].

Sequence features

class hydpy.models.dam_lreservoir.FactorSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: FactorSequences

Factor sequences of model dam_lreservoir.

The following classes are selected:
class hydpy.models.dam_lreservoir.FluxSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: FluxSequences

Flux sequences of model dam_lreservoir.

The following classes are selected:
class hydpy.models.dam_lreservoir.StateSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: StateSequences

State sequences of model dam_lreservoir.

The following classes are selected:
class hydpy.models.dam_lreservoir.LogSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: LogSequences

Log sequences of model dam_lreservoir.

The following classes are selected:
class hydpy.models.dam_lreservoir.InletSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: InletSequences

Inlet sequences of model dam_lreservoir.

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

class hydpy.models.dam_lreservoir.OutletSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: OutletSequences

Outlet sequences of model dam_lreservoir.

The following classes are selected:
  • Q Outflow [m³/s].

class hydpy.models.dam_lreservoir.ObserverSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: ObserverSequences

Observer sequences of model dam_lreservoir.

The following classes are selected:
  • A Externally requested additional water release [m³/s].

class hydpy.models.dam_lreservoir.AideSequences(master: Sequences[TM_co], cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: AideSequences

Aide sequences of model dam_lreservoir.

The following classes are selected: