HydPy-Manager-LWC (low water control management model)

The application model manager_lwc detects severe low flow conditions at a specific point in the river network (called the “target node” or “target location”) and tries increase the flow by requesting additional water releases by controlled lakes or dams lying upstream (called “source elements” or just “sources”).

Warning

We already tested manager_lwc in quite complex scenarios and achieved good results. However, there is still room for further improvements. Hence, be aware that we might change some aspects of manager_lwc in the future.

Integration tests

Note

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

We prepare a simulation period of two months:

>>> from hydpy import pub
>>> pub.timegrids = "2000-01-01", "2000-03-01", "1d"

We initialise a manager_lwc instance and “activate” it by setting its commission date to the start of the simulation period:

>>> from hydpy.models.manager_lwc import *
>>> parameterstep()
>>> commission("2000-01-01")

The following scalar parameters determine how manager_lwc estimates the demand for additional water releases based on memorised discharge at the target location and additional releases from the sources:

>>> dischargethreshold(1.0)
>>> dischargetolerance(0.0)
>>> timedelay(0)
>>> timewindow(1)

The scalar parameter Sources tells manager_lwc the names of those elements which handle the dam models we will initialise below:

>>> sources("d_1", "d_1a", "d_2", "d_2a", "d_2b", "d_2b1")

The following 1-dimensional parameters allow for specific configurations for handling individual sources. However, we simplify the following test cases by defining identical settings for all of them:

>>> releasemax(0.5)
>>> volumethreshold(0.0)
>>> volumetolerance(0.0)
>>> active(True)

Next, we define a network structure containing elements with the previously defined names. We try to keep things simple by omitting routing processes, so the following tests are more educational than a showcase of the capabilities of manager_lwc in real-world projects.

We start with discharge nodes to be placed between (q_1a_1, q_2a_2, q_2b1_2b, and q_2b_2) and below the most downstream (n and t) source elements:

>>> from hydpy import Element, FusedVariable, Node, Nodes
>>> n, q_1a_1, q_2a_2, q_2b1_2b, q_2b_2, t = Nodes(
...     "n", "q_1a_1", "q_2a_2", "q_2b1_2b", "q_2b_2", "t"
... )

manager_lwc sends its requests for additional water releases via the sender sequence Request, which some models of the HydPy-DAM model family can receive via the observer sequence A:

>>> from hydpy.aliases import dam_observers_A, manager_senders_Request
>>> R = FusedVariable("Request", dam_observers_A, manager_senders_Request)
>>> r_1, r_1a, r_2, r_2a, r_2b, r_2b1 = Nodes(
...     "r_1", "r_1a", "r_2", "r_2a", "r_2b", "r_2b1", defaultvariable=R
... )

Additionally, manager_lwc needs to know the current water volume of the instructible sources. All HydPy-Dam models can provide this information via the state sequence WaterVolume, and manager_lwc queries it via its receiver sequence WaterVolume:

>>> from hydpy.aliases import dam_states_WaterVolume, manager_receivers_WaterVolume
>>> V = FusedVariable("V", dam_states_WaterVolume, manager_receivers_WaterVolume)
>>> v_1, v_1a, v_2, v_2a, v_2b, v_2b1 = Nodes(
...     "v_1", "v_1a", "v_2", "v_2a", "v_2b", "v_2b1", defaultvariable=V
... )

Now we initialise the source elements and connect them with the previously defined nodes so that d_1 and d_2 are directly upstream of the target node t, while all other source elements are further upstream behind one (d_1a, d_2a, and d_2b) or two (d_2b1) source elements:

>>> d_1 = Element("d_1", inlets=q_1a_1, outlets=t, observers=r_1, outputs=v_1)
>>> d_1a = Element("d_1a", outlets=q_1a_1, observers=r_1a, outputs=v_1a)
>>> d_2 = Element("d_2", inlets=(q_2a_2, q_2b_2), outlets=t, observers=r_2, outputs=v_2)
>>> d_2a = Element("d_2a", outlets=q_2a_2, observers=r_2a, outputs=v_2a)
>>> d_2b = Element("d_2b", inlets=q_2b1_2b, outlets=q_2b_2, observers=r_2b, outputs=v_2b)
>>> d_2b1 = Element("d_2b1", outlets=q_2b1_2b, observers=r_2b1, outputs=v_2b1)
>>> dams = d_1, d_1a, d_2, d_2a, d_2b, d_2b1

The manager element needs connections to the single target node and the volume and request nodes that are already connected to source elements:

>>> lwc = Element(
...     "lwc",
...     receivers=[t, v_1, v_1a, v_2, v_2a, v_2b, v_2b1],
...     senders=[r_1, r_1a, r_2, r_2a, r_2b, r_2b1],
... )
>>> lwc.model = model

Additionally, we define an element that adds natural inflow to the target node:

>>> i = Element("i", inlets=n, outlets=t)
>>> from hydpy import prepare_model
>>> i.model = prepare_model("dummy_node2node")

All source elements get identically configured model instances of dam_llake:

>>> from hydpy import PPoly
>>> for dam in dams:
...     dam.model = prepare_model("dam_llake")
...     control = dam.model.parameters.control
...     control.catchmentarea(1.0)
...     control.surfacearea(1.0)
...     control.correctionprecipitation(1.0)
...     control.correctionevaporation(1.0)
...     control.weightevaporation(1.0)
...     control.thresholdevaporation(0.0)
...     control.dischargetolerance(0.0)
...     control.toleranceevaporation(0.0)
...     control.allowedwaterleveldrop(inf)
...     control.watervolume2waterlevel(PPoly.from_data(xs=[0.0, 1.0], ys=[0.0, 1.0]))
...     control.waterlevel2flooddischarge(PPoly.from_data(xs=[0.0, 1.0], ys=[0.0, 0.1]))
...     control.commission("2000-01-01")
...     control.pars.update()

Finally, we create our test object, prepare the initial conditions, and define the natural inflow time series:

>>> from hydpy.core.testtools import IntegrationTest
>>> test = IntegrationTest(lwc)
>>> test.plotting_options.axis1 = (fluxes.freedischarge, fluxes.request)
>>> inits = [
...     (logs.loggeddischarge, 1.5),
...     (logs.loggedrequest, 0.0),
...     (logs.loggedwatervolume, 0.5),
... ]
>>> for dam in dams:
...     inits.append((dam.model.sequences.states.watervolume, 0.5))
...     inits.append((dam.model.sequences.logs.loggedadjustedevaporation, 0.0))
>>> test.inits = inits
>>> import numpy
>>> n.sequences.sim.series[:10] = numpy.linspace(1.2, 0.0, 10)
>>> n.sequences.sim.series[10:] = 0.0

base scenario

Initially, the “free discharge” (the sum of the “normal” water releases of the sources d_1 and d_2 and the additional inflow of node n) is above the discharge threshold, so there is no demand for any extra water release. On January 5th, the threshold is first undercut and manager_lwc is “alerted”. It then continually calculates increasing demands and sends them to the source elements. The actual requests to the individual sources are first split equally due to the equal additional release limit. During the further course of the simulation, starting with d_1a, the most “upstream leaf” of the smaller “source branch”, the source elements run dry. At the end of January, d_1 is empty. As d_2 is not allowed to add more than 0.5 m³/s to its “normal” discharge, the actual discharge at the target location is then only around 0.55 m³/s and slowly decreases further, until, finally, also d_2 runs dry and the target location’s discharge falls to zero:

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

Extrapolation

In the last example, during the linear decrease of the natural inflow, there is a long period during which the actual discharge is significantly below the defined threshold of 1 m³/s. This deficit is due to manager_lwc using the last logged discharge and request values for predicting the next free discharge. To improve the situation, we can increase the considered time window for memorising discharges and requests. manager_lwc then uses the linear extrapolation approach of method Calc_FreeDischarge_V1 to estimate the free discharge. The larger the window, the safer the estimate (with a greater risk of more pronounced overestimation):

>>> timewindow(2)
>>> test("manager_lwc_extrapolation")
Click to see the table
Click to see the graph

Smooth transitions

Parameters like DischargeTolerance help smooth otherwise sharp transitions and may reduce small fluctuations around the target discharge:

>>> dischargetolerance(0.5)
>>> test("manager_lwc_smooth_transitions")
Click to see the table
Click to see the graph

Commissioning

You can use the Commission parameter to let manager_lwc only send requests during the end part of the simulation period:

>>> commission("2000-02-01")
>>> test("manager_lwc_commissioning")
Click to see the table
Click to see the graph
class hydpy.models.manager_lwc.Model[source]

Bases: AdHocModel

HydPy-Manager-LWC (low water control management model).

manager_lwc relies on a name-based mechanism to build connections to the relevant nodes of the instructable source elements. See the integration test setup for a realistic setting. Here, we focus on testing and demonstrating the handling of potentially problematic specifications:

>>> from hydpy.models.manager_lwc import *
>>> parameterstep()
>>> sources("d1")
>>> from hydpy import Element, FusedVariable, Node, Nodes
>>> from hydpy.aliases import (
...     dam_observers_A,
...     dam_states_WaterVolume,
...     manager_senders_Request,
...     manager_receivers_WaterVolume,
... )
>>> lwc = Element("lwc")
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There is no source element named `d1` available.
>>> from hydpy.aliases import manager_receivers_WaterVolume
>>> v1 = Node("v1", variable=manager_receivers_WaterVolume)
>>> d1 = Element("d1", outlets="q1", outputs=v1)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that informs about the current water volume of the source element `d1`, but no such nodes are available.
>>> lwc.receivers.add_device(v1, force=True)
>>> sources("d1", "d2")
>>> d2 = Element("d2", outlets="q2", outputs="v2")
>>> lwc.receivers.add_device("v2", force=True)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that informs about the current water volume of the source element `d2`, but no such nodes are available.
>>> lwc.receivers.remove_device("v2", force=True)
>>> v2a, v2b = Nodes("v2a", "v2b", defaultvariable=manager_receivers_WaterVolume)
>>> d2.outputs.add_device(v2a, force=True)
>>> d2.outputs.add_device(v2b, force=True)
>>> lwc.receivers.add_device(v2a, force=True)
>>> lwc.receivers.add_device(v2b, force=True)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that informs about the current water volume of the source element `d2`, but two such nodes are available (`v2a` and `v2b`).
>>> sources("d1")
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that reports the current discharge at the target location, but no such nodes are available.
>>> t = Node("t")
>>> lwc.receivers.add_device(t, force=True)
>>> lwc.receivers.add_device("target", force=True)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that reports the current discharge at the target location, but two such nodes are available (`t` and `target`).
>>> lwc.receivers.remove_device("target", force=True)
>>> from hydpy.aliases import manager_senders_Request
>>> r1 = Node("r1", variable=manager_senders_Request)
>>> d1.observers.add_device(r1, force=True)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `sender` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that informs the source element `d1` about the request for additional water release, but no such nodes are available.
>>> lwc.senders.add_device(r1, force=True)
>>> sources("d1", "d3")
>>> d3 = Element("d3", outlets="q3", observers="r3")
>>> lwc.receivers.add_device("r3", force=True)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `receiver` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that informs about the current water volume of the source element `d3`, but no such nodes are available.
>>> lwc.receivers.remove_device("r3", force=True)
>>> from hydpy.aliases import manager_senders_Request
>>> r3a, r3b = Nodes("r3a", "r3b", defaultvariable=manager_senders_Request)
>>> d3.outputs.add_device(v2a, force=True)
>>> d3.observers.add_device(r3a, force=True)
>>> d3.observers.add_device(r3b, force=True)
>>> lwc.senders.add_device(r3a, force=True)
>>> lwc.senders.add_device(r3b, force=True)
>>> lwc.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to build the node connection of the `sender` sequences of the model handled by element `lwc`, the following error occurred: There must be exactly one node that informs the source element `d3` about the request for additional water release, but two such nodes are available (`r3a` and `r3b`).
>>> sources("d1", "d4")
>>> v4 = Node("v4", variable=manager_receivers_WaterVolume)
>>> r4 = Node("r4", variable=manager_senders_Request)
>>> d4 = Element("d4", outputs=v4, observers=r4)
>>> lwc.receivers.add_device(v4, force=True)
>>> lwc.senders.add_device(r4, force=True)
>>> lwc.model = model
>>> fluxes.request = 1.0, 2.0
>>> model.update_senders()
>>> r1.sequences.sim
sim(1.0)
>>> r4.sequences.sim
sim(2.0)
>>> t.sequences.sim = 1.0
>>> derived.memorylength(1)
>>> v1.sequences.sim = 2.0
>>> v4.sequences.sim = 3.0
>>> model.update_receivers(0)
>>> logs.loggeddischarge
loggeddischarge(1.0)
>>> logs.loggedwatervolume
loggedwatervolume(d1=2.0, d4=3.0)
The following “receiver update methods” are called in the given sequence before performing a simulation step:
The following “run methods” are called in the given sequence during each simulation step:
The following “sender update methods” are called in the given sequence after performing a simulation step:
  • Pass_Request_V1 Pass the additional water release requests to the relevant sender sequences.

DOCNAME = ('Manager-LWC', 'low water control management model')
REUSABLE_METHODS = ()
class hydpy.models.manager_lwc.ControlParameters(master: Parameters, cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)

Bases: SubParameters

Control parameters of model manager_lwc.

The following classes are selected:
  • Commission() Commission date [-].

  • DischargeThreshold() Discharge threshold for estimating release requests [m³/s].

  • DischargeTolerance() Discharge tolerance for estimating release requests [m³/s].

  • TimeDelay() Time delay (in terms of simulation steps) between the release of additional water and its effect on the target cross-section [-].

  • TimeWindow() Time window (in terms of simulation steps) used for calculating multiple “free discharge” estimates [-].

  • Sources() The sources (e.g. dams) that are instructible to release additional water [-].

  • VolumeThreshold() Water volume below which the sources do not fulfil additional water release requests [million m³].

  • VolumeTolerance() Water volume tolerance for determining the water demand of the individual sources [million m³].

  • ReleaseMax() Maximum additional release of the individual sources [m³/s].

  • Active() Flag to activate/deactivate sending requests to individual sources [-].

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

Bases: SubParameters

Derived parameters of model manager_lwc.

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

Bases: FactorSequences

Factor sequences of model manager_lwc.

The following classes are selected:
  • Alertness() The current need for low water control [-].

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

Bases: FluxSequences

Flux sequences of model manager_lwc.

The following classes are selected:
  • DemandTarget() The demand for additional water release at the target location [m³/s].

  • FreeDischarge() The discharge at the target location that would have occurred without requesting additional water releases [m³/s].

  • DemandSources() The demand for additional water release at the individual sources [m³/s].

  • PossibleRelease() The possible additional water releases of the individual sources [m³/s].

  • Request() The actual additional water release requested from the individual sources [m³/s].

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

Bases: LogSequences

Log sequences of model manager_lwc.

The following classes are selected:
  • LoggedDischarge() Logged discharge values of the target location [m³/s].

  • LoggedRequest() Logged sums of the additional release requests of all sources directly neighbouring the target location [m³/s].

  • LoggedWaterVolume() Logged water volumes of the individual sources [million m³].

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

Bases: ReceiverSequences

Receiver sequences of model manager_lwc.

The following classes are selected:
  • Q() Discharge at the target location [m³/s].

  • WaterVolume() The current water volume of the individual sources [million m³].

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

Bases: SenderSequences

Sender sequences of model manager_lwc.

The following classes are selected:
  • Request() The actual additional water release requested from the individual sources [m³/s].