HydPy-Exch-Weir-HBV96 (weir model adopted from IHMS-HBV96)¶
exch_weir_hbv96
implements the general weir formula. We implemented it on behalf of
the German Federal Institute of Hydrology (BfG) to connect different dam_llake
instances (lake models), enabling them to exchange water in both directions based on
water level differences. This specific combination models some huge, connected
(sub)lakes of the Rhine basin similar to HBV96 (Lindström et al., 1997).
Combinations with other models providing (something like) water level information and
allowing for an additional inflow that can be positive and negative are possible.
Integration tests¶
Note
When new to HydPy, consider reading section Integration Tests first.
We perform all integration tests over a month with a simulation step of one day:
>>> from hydpy import Element, FusedVariable, Nodes, PPoly, prepare_model, pub
>>> pub.timegrids = "2000-01-01", "2000-02-01", "1d"
The following examples demonstrate how exch_weir_hbv96
interacts with lake models
like dam_llake
. Therefore, we must set up one exch_weir_hbv96
instance and two
dam_llake
instances.
First, we define the eight required Node
objects:
inflow1 and inflow2 pass the inflow into the first and the second lake.
outflow1 and outflow2 receive the lakes’ outflows.
overflow1 and overflow2 exchange water between the lakes.
waterlevel1 and waterlevel2 inform the exchange model about the lakes’ current water levels.
We define the variable
type of all nodes explicitly. For the inflow and outflow
nodes, we stick to the default by using the string literal “Q”, telling dam_llake
to
connect these nodes to the inlet sequence Q
and outlet sequence
Q
, respectively:
>>> inflow1, inflow2 = Nodes("inflow1", "inflow2", defaultvariable="Q")
>>> outflow1, outflow2 = Nodes("outflow1", "outflow2", defaultvariable="Q")
The overflow nodes do not connect both lakes directly but the lakes with the exchange
model. Therefore, we define a FusedVariable
that combines the aliases of the outlet
sequence Exchange
of exch_weir_hbv96
and the inlet sequence
E
of dam_llake
:
>>> from hydpy.aliases import exch_outlets_Exchange, dam_inlets_E
>>> Exchange = FusedVariable("Exchange", exch_outlets_Exchange, dam_inlets_E)
>>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable=Exchange)
Next, we define a FusedVariable
that combines the aliases of the receiver sequence
WaterLevels
of exch_weir_hbv96
and the output sequence
WaterLevel
of dam_llake
:
>>> from hydpy.aliases import exch_receivers_WaterLevels, dam_factors_WaterLevel
>>> WaterLevel = FusedVariable("WaterLevel", exch_receivers_WaterLevels, dam_factors_WaterLevel)
>>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel)
Now, we prepare the two Element
objects holding the dam_llake
instances. The
configuration is similar to the one in the documentation on dam_llake
, except in
connecting waterlevel1 and waterlevel2 as additional output nodes:
>>> lake1 = Element("lake1",
... inlets=(inflow1, overflow1),
... outlets=outflow1,
... outputs=waterlevel1)
>>> lake2 = Element("lake2",
... inlets=(inflow2, overflow2),
... outlets=outflow2,
... outputs=waterlevel2)
From the perspective of the exchange element, waterlevel1 and waterlevel2 are
receiver nodes, while overflow1 and overflow2 are outlet nodes. At the beginning
of each simulation step, exch_weir_hbv96
receives water level information from both
lakes. Then, it calculates the correct exchange and sends it to both lakes via the
overflow nodes, but with different signs. If the first lake’s water level is higher,
it passes a negative value to overflow1 (the first lake loses water) and a positive
value to overflow2 (the second lake gains water), and vice versa:
>>> exchange = Element("exchange",
... receivers=(waterlevel1, waterlevel2),
... outlets=(overflow1, overflow2))
In our test configuration, the nodes’ names and the order in which we pass them to the
constructor of class Element
agree with the nodes’ target lakes. This practice seems
advisable for keeping clarity, but it is not a technical requirement. The
documentation on class Model
explains the internal sorting mechanisms
and plausibility checks underlying the connection-related functionalities of
exch_weir_hbv96
.
We parameterise both lake models identically. All of the following values stem from the
documentation on dam_llake
. We will use them in all examples:
>>> lake1.model = prepare_model("dam_llake")
>>> lake2.model = prepare_model("dam_llake")
>>> from numpy import inf
>>> for model_ in (lake1.model, lake2.model):
... control = model_.parameters.control
... control.catchmentarea(86.4)
... control.surfacearea(1.44)
... control.correctionprecipitation(1.2)
... control.correctionevaporation(1.2)
... control.weightevaporation(0.8)
... control.thresholdevaporation(0.0)
... control.dischargetolerance(0.1)
... control.toleranceevaporation(0.001)
... control.allowedwaterleveldrop(inf)
... control.watervolume2waterlevel(PPoly.from_data(xs=[0.0, 1.0], ys=[0.0, 1.0]))
... control.pars.update()
Now, we prepare the exchange model. We will use common values for the flow coefficient and exponent throughout the following examples:
>>> from hydpy.models.exch_weir_hbv96 import *
>>> parameterstep("1d")
>>> flowcoefficient(0.62)
>>> flowexponent(1.5)
>>> exchange.model = model
An IntegrationTest
object will help us to perform the individual examples:
>>> from hydpy.core.testtools import IntegrationTest
>>> test = IntegrationTest(exchange)
>>> test.plotting_options.axis1 = (factors.waterlevels,)
>>> test.plotting_options.axis2 = (fluxes.potentialexchange, fluxes.actualexchange)
We set both lakes’ inflow to zero for simplicity:
>>> inflow1.sequences.sim.series = 0.0
>>> inflow2.sequences.sim.series = 0.0
The only difference between both lakes is their initial state. The first lake starts
empty, and the second starts with a water volume of 1 million m³. Note that
exch_weir_hbv96
requires the same information. We must give it to the log sequence
LoggedWaterLevels
:
>>> test.inits = [(lake1.model.sequences.states.watervolume, 0.0),
... (lake1.model.sequences.logs.loggedadjustedevaporation, 0.0),
... (lake2.model.sequences.states.watervolume, 1.0),
... (lake2.model.sequences.logs.loggedadjustedevaporation, 0.0),
... (logs.loggedwaterlevels, (0.0, 1.0))]
base scenario¶
Our base scenario defines a small crest width of 0.2 meters, enabling only limited water exchange:
>>> crestwidth(0.2)
The crest height of 0.0 m and the allowed exchange of 5.0 m³/s are so low and high, respectively, that they do not affect the following results:
>>> crestheight(0.0)
>>> allowedexchange(5.0)
We define a linear relationship between the water level and the outflow for both lakes:
>>> for model_ in (lake1.model, lake2.model):
... model_.parameters.control.waterlevel2flooddischarge(
... PPoly.from_data(xs=[0.0, 1.0], ys=[0.0, 2.0]))
The following results show that the first lake’s water level drops fast due to the release of water to the second lake and its outlet. The second lake receives this overflow throughout the simulation period but with a decreasing tendency. Hence, the water level rises initially but finally falls again because of the lake’s outflow:
>>> test("exch_weir_hbv96_base_scenario")
Click to see the table
Click to see the graph
crest height¶
In this example, we increase the crest’s width and, more importantly, set its height to 0.5 m, matching a water volume of 0.5 million m³:
>>> crestwidth(20.0)
>>> crestheight(0.5)
The crest height now influences the lakes’ interaction significantly. For clarification, we disallow both lakes to release any water:
>>> for model_ in (lake1.model, lake2.model):
... model_.parameters.control.waterlevel2flooddischarge(
... PPoly.from_data(xs=[0.0], ys=[0.0]))
Due to the identical parameter values of both models and the symmetrical initial conditions of 0.0 and 1.0 million m³, both water levels move towards the defined crest height asymptotically:
>>> test("exch_weir_hbv96_crest_height")
Click to see the table
Click to see the graph
numerical accuracy¶
exch_weir_hbv96
s a flexible tool that requires users to apply wisely. One crucial
aspect is numerical accuracy. One can expect sufficiently accurate results only if the
simulation step size is relatively short compared to water level dynamics. In this
example, we illustrate what happens if there is too much exchange due to a large crest
width:
>>> crestwidth(200.0)
We see substantial numerical oscillations in the results. Due to the stiffness of the underlying differential equations, a further increase of the crest width would even result in a numerical overflow error that might be hard to trace back in a real-world application:
>>> test("exch_weir_hbv96_numerical_accuracy")
Click to see the table
Click to see the graph
allowed exchange¶
Sometimes, there might be hydrological reasons to limit the water exchange. Still,
we use the related parameter AllowedExchange
only as a stop-gap for stabilising
simulation results affected by numerical instability by setting its value to 2.0 m³/s:
>>> allowedexchange(2.0)
The results are far from perfect (the initial water levels change too slowly and still oscillate for a few days) but are at least stable and not overly wrong:
>>> test("exch_weir_hbv96_allowed_exchange")
Click to see the table
Click to see the graph
- class hydpy.models.exch_weir_hbv96.Model[source]¶
Bases:
AdHocModel
HydPy-Exch-Weir-HBV96 (weir model adopted from IHMS-HBV96).
Before continuing, please read the general documentation on application model
exch_weir_hbv96
.To work correctly, each
exch_weir_hbv96
must know which water level node and which overflow node belong to the same lake model. The following examples might provide insight into how we deal with this issue but are merely there for testing that we handle all expected cases well.We recreate the configuration of the
Node
andElement
objects of the main documentation, neglecting the lakes’ inflow and outflow nodes, which are not relevant for connectingexch_weir_hbv96
:>>> from hydpy.models.exch_weir_hbv96 import * >>> parameterstep() >>> from hydpy import Element, FusedVariable, Node, Nodes >>> from hydpy.aliases import exch_outlets_Exchange, dam_factors_WaterLevel, exch_receivers_WaterLevels >>> WaterLevel = FusedVariable("WaterLevel", exch_receivers_WaterLevels, dam_factors_WaterLevel)
>>> Element.clear_all() >>> Node.clear_all()
>>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable=exch_outlets_Exchange) >>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel) >>> lake1 = Element("lake1", inlets=overflow1, outputs=waterlevel1) >>> lake2 = Element("lake2", inlets=overflow2, outputs=waterlevel2) >>> exchange = Element("exchange", ... receivers=(waterlevel1, waterlevel2), ... outlets=(overflow1, overflow2)) >>> exchange.model = model
The water levels of lake1 and lake2 are available via the first and the second entry of the receiver sequence
WaterLevels
, respectively:>>> waterlevel1.sequences.sim = 1.0 >>> waterlevel2.sequences.sim = 2.0 >>> receivers.waterlevels waterlevels(1.0, 2.0)
Likewise, the first and the second entry of the outlet sequence
Exchange
are available to the overflow nodes lake1 and lake2, respectively:>>> outlets.exchange = 3.0, 4.0 >>> overflow1.sequences.sim sim(3.0) >>> overflow2.sequences.sim sim(4.0)
We recreate this configuration multiple times, each time changing one aspect (marked by exclamation marks). First, we connect node waterlevel2 with èlement lake1 and node waterlevel1 with element lake2:
>>> Element.clear_all() >>> Node.clear_all() >>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable="E") >>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel) >>> lake1 = Element("lake1", inlets=overflow1, outputs=waterlevel2) # !!! >>> lake2 = Element("lake2", inlets=overflow2, outputs=waterlevel1) # !!! >>> exchange = Element("exchange", ... receivers=(waterlevel1, waterlevel2), ... outlets=(overflow1, overflow2)) >>> exchange.model = model
Due to this swap, the first of the outlet sequence
Exchange
connects to node overflow2 and the second one to node overflow1:>>> waterlevel1.sequences.sim = 1.0 >>> waterlevel2.sequences.sim = 2.0 >>> receivers.waterlevels waterlevels(1.0, 2.0)
>>> outlets.exchange = 3.0, 4.0 >>> overflow1.sequences.sim sim(4.0) >>> overflow2.sequences.sim sim(3.0)
Swapping the nodes overflow1 and overflow2 instead of waterlevel1 and waterlevel2 leads to the same results (we arbitrarily decided to ground the internal sorting on the alphabetical order of the receiver nodes’ names):
>>> Element.clear_all() >>> Node.clear_all() >>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable="E") >>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel) >>> lake1 = Element("lake1", inlets=overflow2, outputs=waterlevel1) # !!! >>> lake2 = Element("lake2", inlets=overflow1, outputs=waterlevel2) # !!! >>> exchange = Element("exchange", ... receivers=(waterlevel1, waterlevel2), ... outlets=(overflow1, overflow2)) >>> exchange.model = model
>>> waterlevel1.sequences.sim = 1.0 >>> waterlevel2.sequences.sim = 2.0 >>> receivers.waterlevels waterlevels(1.0, 2.0)
>>> outlets.exchange = 3.0, 4.0 >>> overflow1.sequences.sim sim(4.0) >>> overflow2.sequences.sim sim(3.0)
Now, we (accidentally) connect node waterlevel2 to both lakes. Therefore,
exch_weir_hbv96
cannot find a water level node connected to the same lake model as outlet node overflow1:>>> Element.clear_all() >>> Node.clear_all() >>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable="E") >>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel) >>> lake1 = Element("lake1", inlets=overflow1, outputs=waterlevel2) # !!! >>> lake2 = Element("lake2", inlets=overflow2, outputs=waterlevel2) >>> exchange = Element("exchange", ... receivers=(waterlevel1, waterlevel2), ... outlets=(overflow1, overflow2)) >>> exchange.model = model Traceback (most recent call last): ... RuntimeError: While trying to build the node connection of the `outlet` sequences of the model handled by element `exchange`, the following error occurred: Outlet node `overflow1` does not correspond to any available receiver node.
exch_weir_hbv96
raises the following error if not precisely two water level nodes are available:>>> Element.clear_all() >>> Node.clear_all() >>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable="E") >>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel) >>> lake1 = Element("lake1", inlets=overflow1, outputs=waterlevel1) >>> lake2 = Element("lake2", inlets=overflow2, outputs=waterlevel2) >>> exchange = Element("exchange", ... receivers=waterlevel1, # !!! ... outlets=(overflow1, overflow2)) >>> exchange.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 `exchange`, the following error occurred: There must be exactly 2 outlet receiver but the following `1` receiver nodes are defined: waterlevel1.
Correspondingly,
exch_weir_hbv96
raises the following error if there are not precisely two overflow nodes available:>>> Element.clear_all() >>> Node.clear_all() >>> overflow1, overflow2 = Nodes("overflow1", "overflow2", defaultvariable="E") >>> waterlevel1, waterlevel2 = Nodes("waterlevel1", "waterlevel2", defaultvariable=WaterLevel) >>> lake1 = Element("lake1", inlets=overflow1, outputs=waterlevel1) >>> lake2 = Element("lake2", inlets=overflow2, outputs=waterlevel2) >>> exchange = Element("exchange", ... receivers=(waterlevel1, waterlevel2), ... outlets=(overflow1, overflow2, waterlevel2)) # !!! >>> exchange.model = model Traceback (most recent call last): ... RuntimeError: While trying to build the node connection of the `outlet` sequences of the model handled by element `exchange`, the following error occurred: There must be exactly 2 outlet nodes but the following `3` outlet nodes are defined: overflow1, overflow2, and waterlevel2.
- The following “receiver update methods” are called in the given sequence before performing a simulation step:
Pic_LoggedWaterLevels_V1
Pic the logged water levels from two receiver nodes.
- The following “run methods” are called in the given sequence during each simulation step:
Update_WaterLevels_V1
Update the factor sequenceWaterLevels
.Calc_DeltaWaterLevel_V1
Calculate the effective difference of both water levels.Calc_PotentialExchange_V1
Calculate the potential exchange that strictly follows the weir formula without taking any other limitations into account.Calc_ActualExchange_V1
Calculate the actual exchange.
- The following “outlet update methods” are called in the given sequence at the end of each simulation step:
Pass_ActualExchange_V1
Pass the actual exchange to an outlet node.
- REUSABLE_METHODS: ClassVar[tuple[type[ReusableMethod], ...]] = ()¶
- class hydpy.models.exch_weir_hbv96.ControlParameters(master: Parameters, cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
SubParameters
Control parameters of model exch_weir_hbv96.
- The following classes are selected:
CrestHeight()
Crest height [m].CrestWidth()
Crest width [m].FlowCoefficient()
Flow coefficient [-].FlowExponent()
Flow exponent [-].AllowedExchange()
The highest water exchange allowed [m³/s].
- class hydpy.models.exch_weir_hbv96.FactorSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
FactorSequences
Factor sequences of model exch_weir_hbv96.
- The following classes are selected:
WaterLevels()
The water level at two locations [m].DeltaWaterLevel()
Effective difference of the two water levels [m].
- class hydpy.models.exch_weir_hbv96.FluxSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
FluxSequences
Flux sequences of model exch_weir_hbv96.
- The following classes are selected:
PotentialExchange()
The potential bidirectional water exchange [m³/s].ActualExchange()
The actual bidirectional water exchange [m³/s].
- class hydpy.models.exch_weir_hbv96.LogSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
LogSequences
Log sequences of model exch_weir_hbv96.
- The following classes are selected:
LoggedWaterLevels()
Logged water levels [m].
- class hydpy.models.exch_weir_hbv96.OutletSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
OutletSequences
Outlet sequences of model exch_weir_hbv96.
- The following classes are selected:
Exchange()
Bidirectional water exchange [m³/s].
- class hydpy.models.exch_weir_hbv96.ReceiverSequences(master: Sequences, cls_fastaccess: type[TypeFastAccess_co] | None = None, cymodel: CyModelProtocol | None = None)¶
Bases:
ReceiverSequences
Receiver sequences of model exch_weir_hbv96.
- The following classes are selected:
WaterLevels()
The water level at multiple remote locations [m].