HydPy-Dam-DB (detention basin model)

dam_detention simulates the flood protection effects of dry detention basins that actively control their release.

dam_detention serves similar purposes as dam_lretention but controls its outflow actively according to “safe discharge” estimates, which are usually derived from discharge values simulated (or measured) at other river segments. Opposed to dam_lretention, dam_detention requires no relationship between water level and (possible) release, as it tries to release the ideal amount of water and neglects any “natural” lake retention effects. Due to this conceptual design, dam_detention requires no numerical solution algorithms (like most other models in the HydPy-Dam family) and is therefore solved analytically.

It is also possible to simulate wet basins by setting the TargetVolume parameter to a value larger than one. Note that even then, natural lake retention effects are neglected, so dam_lretention or a similar model of the HydPy-Dam family might be preferable.

The information for estimating the safe discharge is transmitted via an arbitrary number of observer nodes. Therefore, dam_detention can react as soon as critical situations somewhere else in the river network occur. Please note that an observer node cannot be positioned downstream of the element that handles the observing dam_detention instance.

Integration tests

We perform all example calculations over a period of 20 days:

>>> from hydpy import Element, IntegrationTest, Node, pub
>>> pub.timegrids = "01.01.2000", "21.01.2000", "1d"

We prepare a dam_detention model instance:

>>> from hydpy.models.dam_detention import *
>>> parameterstep("1d")

We begin with the simplest case of a single inlet and a single outlet node and leave the possibility of adding observer nodes for later consideration:

>>> inflow, outflow = Node("inflow"), Node("outflow")
>>> element = Element("element", inlets=inflow, outlets=outflow)
>>> element.model = model

The following test function instance facilitates the convenient execution of all integration tests:

>>> test = IntegrationTest(element)
>>> test.dateformat = "%d.%m."
>>> test.plotting_options.axis1 = fluxes.inflow, fluxes.outflow
>>> test.plotting_options.axis2 = states.watervolume

All integration tests shall start from dry initial conditions:

>>> test.inits = [(states.watervolume, 0.0), (logs.loggedadjustedevaporation, 0.0)]
>>> test.reset_inits()
>>> conditions = model.conditions

The following input time series stems from the integration tests on application model dam_lretention:

>>> inflow.sequences.sim.series = [
...     0.0, 1.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
... ]

The first tests deal with a dry detention basin:

>>> targetvolume(0.0)
>>> maximumvolume(1.5)

As the surface area (which becomes relevant only after adding submodels that include precipitation and evaporation into the water balance) is a fixed property, we set its size to zero, which is the “regular” surface area of dry detention basins:

>>> surfacearea(0.0)

The following three parameters require values even if no corresponding submodels are added (we plan to make this more convenient in the future):

>>> correctionprecipitation(1.0)
>>> correctionevaporation(1.0)
>>> weightevaporation(0.8)

The allowed release, which does not cause any harm directly downstream of the detention basin, is 2 m³/s:

>>> allowedrelease(2.0)

As dry detention basins cannot help much in ensuring a minimum flow (e.g. for ecological reasons), we set the minimum release to zero:

>>> minimumrelease(0.0)

We begin without any restrictions on the emptying speed:

>>> allowedwaterleveldrop(inf)

The following simple relationship between water volume and level does not affect the basin’s behaviour as long as AllowedWaterLevelDrop is set to infinity but enables calculating the water level at the end of the respective simulation steps for informational purposes:

>>> watervolume2waterlevel(PPoly.from_data(xs=[0.0, 1.0, 2.0], ys=[0.0, 1.0, 1.5]))

local protection

The current simple configuration shows the basic flood protection functionality of dam_detention. In this case, the protection is not perfect, but at least it reduces the peak flow from 12 m³/s to less than 7 m³/s:

>>> test("dam_detention_local_protection")
Click to see the table
Click to see the graph

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

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

restricted emptying

Setting the allowed water level drop to 0.15 m/day reduces the water release at the later declining phase of the flood event (due to the higher water level decrease for the same water volume decrease at this time):

>>> allowedwaterleveldrop(0.15)
>>> test("dam_detention_restricted_emptying")
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

remote protection

We now add the observer node gauge, which provides discharge information from somewhere else in the river network:

>>> gauge = Node("gauge")
>>> element.observers = gauge

Next, we add an exch_interp submodel that takes the observer node’s discharge information in converts it into a safe discharge estimate. From 0 to 3 m³/s, this estimate decreases linearly from 3 to 0 m³/s (and remains at 0 m³/s for larger values):

>>> nmbsafereleasemodels(1)
>>> with model.add_safereleasemodel("exch_interp", position=0):
...     observernodes("gauge")
...     x2y(PPoly.from_data(xs=[0.0, 3.0, 6.0], ys=[3.0, 0.0, 0.0]))

Due to the change in the network configuration, we need to recreate the test function object:

>>> element.model.connect()
>>> 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

The inflow time series is initially identical to the one previously used but remains at 1 m³/s instead of decreasing to zero after the event:

>>> inflow.sequences.sim.series = [
...     0.0, 1.0, 6.0, 12.0, 10.0, 6.0, 3.0, 2.0, 1.0, 1.0,
...     1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
... ]

The observed discharge time also includes a runoff event, but this one occurs slightly later in time:

>>> gauge.sequences.sim.series = [
...     1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
...     3.0, 6.0, 4.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0
... ]

During the first days of the simulation period, the detention basin adjusts its release, as in the previous examples, for local flood protection. After that, it reduces its release as much as possible to help decrease the flood at the remote location (which does not work too well because there is still much water left from the first event):

>>> test("dam_detention_remote_protection")
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

target volume

This example demonstrates how dam_detention works for targeted water volumes larger than zero:

>>> targetvolume(1.0)

Specifying a minimum release larger than zero now makes sense, as keeping the desired water volume during wet periods may provide the opportunity to release additional water in subsequent dry periods:

>>> minimumrelease(1.0)

The higher the targeted volume, the more reasonable it is to set the (time-averaged) surface area size to a value larger than one:

>>> surfacearea(1.44)

With a non-zero surface area, adding submodels that introduce precipitation and evaporation can make a relevant difference in the basin’s water balance:

>>> with model.add_precipmodel_v2("meteo_precip_io") as precipmodel:
...     precipitationfactor(1.2)
...     inputs.precipitation.prepare_series()
...     inputs.precipitation.series = 0.0
...     inputs.precipitation.series[1] = 50.0
>>> with model.add_pemodel_v1("evap_ret_io") as pemodel:
...     evapotranspirationfactor(1.0)
...     inputs.referenceevapotranspiration.prepare_series()
...     inputs.referenceevapotranspiration.series = 6.0

We suppress the inflow directly after the event but reactivate it with 1.5 m³/s for the last few simulation steps:

>>> inflow.sequences.sim.series[9:16] = 0.0
>>> inflow.sequences.sim.series[16:] = 1.5

As expected, dam_detention now keeps the targeted volume in flood-free periods as long as the sum of inflow and precipitation exceeds the sum of evaporation and minimum release:

>>> test("dam_detention_target_volume")
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
class hydpy.models.dam_detention.Model[source]

Bases: AdHocModel, MixinSimpleWaterBalance, Main_PrecipModel_V2, Main_PEModel_V1, ExchangeModel_V1

HydPy-Dam-DB (detention basin model).

The following “inlet update methods” are called in the given sequence at the beginning of each simulation step:
The following “run methods” are called in the given sequence during each simulation step:
The following “outlet update methods” are called in the given sequence at the end of each simulation step:
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:
  • Return_WaterLevelError_V1 Calculate and return the difference between the allowed water level and the water level that corresponds to the given water volume.

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.

  • ExchangeModel_V1 Interface for exchanging modified, scalar data of arbitrary type.

The following “submodels” might be called by one or more of the implemented methods or are meant to be directly called by the user:
  • PegasusWaterVolume Pegasus iterator for finding the water volume corresponding to the allowed water level.

DOCNAME: DocName = ('Dam-DB', 'detention basin model')
OBSERVER_METHODS: ClassVar[tuple[type[Method], ...]] = ()
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.

safereleasemodels

Vector of submodels that comply with the following interface: ExchangeModel_V1.

add_safereleasemodel

Initialise the given safereleasemodel that follows the ExchangeModel_V1 interface.

REUSABLE_METHODS: ClassVar[tuple[type[ReusableMethod], ...]] = ()
class hydpy.models.dam_detention.AideSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)

Bases: AideSequences

Aide sequences of model dam_detention.

The following classes are selected:
  • AllowedDischarge() Discharge threshold that should not be overcut by the actual discharge [m³/s].

  • AllowedWaterLevel() The water level at the end of a simulation step that would follow from applying the allowed water level drop [m].

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

Bases: SubParameters

Control parameters of model dam_detention.

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

Bases: SubParameters

Derived parameters of model dam_detention.

The following classes are selected:
  • TOY() References the timeofyear index array provided by the instance of class Indexer available in module pub [-].

  • Seconds() Length of the actual simulation step size [s].

  • InputFactor() Factor for converting meteorological input from mm/T to million m³/s.

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

Bases: FactorSequences

Factor sequences of model dam_detention.

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

Bases: FluxSequences

Flux sequences of model dam_detention.

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

Bases: InletSequences

Inlet sequences of model dam_detention.

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

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

Bases: LogSequences

Log sequences of model dam_detention.

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

Bases: OutletSequences

Outlet sequences of model dam_detention.

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

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

Bases: StateSequences

State sequences of model dam_detention.

The following classes are selected: