HydPy-Exch-Branch-Rules (branching based on piecewise linear or polynomial interpolation rules)

exch_branch_rules allows branching the summed input from some inlet nodes to an arbitrary number of outlet nodes. It calculates the output of each branch based on seasonally varying, piecewise polynomial interpolation rules (see parameter Rules). Branching discharge is the typical use case, but exch_branch_rules is suitable for splitting arbitrary variables. These can be negative (e.g. representing reverse flow).

Integration tests

Note

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

We perform the following examples over a simulation period of 11 days:

>>> from hydpy import pub, Nodes, Element
>>> pub.timegrids = "2000-01-01", "2000-01-12", "1d"

exch_branch_rules has no parameter whose values depend on the simulation time step, so we do not need to define the parameter time step size here:

>>> from hydpy.models.exch_branch_rules import *
>>> parameterstep()

In the following setup, exch_branch_rules queries input from two inlet nodes and passes the branched output to three outlet nodes:

>>> nodes = Nodes("input1", "input2", "output1", "output2", "output3")
>>> branch = Element("branch",
...                  inlets=["input1", "input2"],
...                  outlets=["output1", "output2", "output3"])

The variable types of the respective Node instances do not matter. Instead, exch_branch_rules treats all inlet nodes as input providers and builds separate connections to its outlet nodes, relying on the outlet nodes’ names. Hence, these names must be used as keyword arguments when passing the respective interpolation rules to parameter Rules:

>>> rules(
...     PPolys(
...         output1=PPoly.from_data(xs=[0.0, 2.0], ys=[0.0, 1.0]),
...         output2=PPoly.from_data(xs=[0.0, 2.0, 4.0, 6.0], ys=[0.0, 1.0, 0.0, 0.0]),
...         output3=PPoly.from_data(xs=[0.0, 2.0, 4.0, 6.0], ys=[0.0, 0.0, 2.0, 6.0]),
...     )
... )

Now, as exch_branch_rules knows which interpolation rule applies to which outlet node, we can safely connect the model to these nodes:

>>> branch.model = model

We do not have to define any initial values in the test settings because exch_branch_rules has no memory:

>>> from hydpy.core.testtools import IntegrationTest
>>> test = IntegrationTest(branch)
>>> test.dateformat = "%d Jan."

The values of the inlet nodes input1 and input2 define no realistic input series. Instead, they show exch_branch_rules’s behaviour inside and outside the ranges covered by the interpolation rules’ supporting points:

>>> import numpy
>>> nodes.input1.sequences.sim.series = numpy.arange(-2.0, 9.0)
>>> nodes.input2.sequences.sim.series = 0.0
>>> nodes.input2.sequences.sim.series[0] = 1.0

independent rules

In the first example, we configure the interpolation rules of all outlet nodes independently. output1 shows linear continuations below and above the range of its supporting points. output2 points out that inverse relationships are allowed. output3 shows that exch_branch_rules does not enforce equality between the total sum of input and output values (for example, the sum of all outputs is 14 instead of 8 for 11 Jan):

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

coordinated rules

Often, maintaining the balance between input and outputs is essential. Instead of carefully synchronising the interpolation rules of all outlet nodes, you can use the REST option for one of them. This node then receives everything not distributed to the other nodes. In the following example, node output2 starts receiving input when it exceeds 6. Node output3 provides a constant output of -1 for input values up to 0, which then changes linearly to zero for an input of 4 (demonstrating that negative outputs are allowed, which might, for example, represent supplies). Finally, node output1 receives the rest:

>>> rules(
...     PPolys(
...         output1=PPolys.REST,
...         output2=PPoly(xs=[0.0, 6.0, 7.0], ys=[0.0, 0.0, 1.0]),
...         output3=PPoly(xs=[-1.0, 0.0, 4.0, 6.0], ys=[-1.0, -1.0, 0.0, 0.0]),
...     )
... )

Because we use REST, the sum of all outputs equals the input at each time step (note that output1 receives more than the total input when output3 is negative):

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

seasonal rules

Parameter Rules is a SeasonalInterpolator, so that you can define different interpolation rules for different times of the year. In this example, we define two rules: the first passes half of the positive input to node output2 and passes nothing to node output3. The second rule does the opposite. Node output1 receives the rest in both cases:

>>> rule1 = PPolys(
...     output1=PPolys.REST,
...     output2=PPoly(xs=[-1.0, 0.0, 1.0], ys=[0.0, 0.0, 0.5]),
...     output3=PPoly(xs=[0.0, 1.0], ys=[0.0, 0.0]),
... )
>>> rule2 = PPolys(
...     output1=PPolys.REST,
...     output2=PPoly(xs=[0.0, 1.0], ys=[0.0, 0.0]),
...     output3=PPoly(xs=[-1.0, 0.0, 1.0], ys=[0.0, 0.0, 0.5]),
... )

We apply both rules twice, letting each control its respective time span without interference from the other:

>>> rules(
...     toy_01_01_12=rule1, toy_01_05_12=rule1, toy_01_10_12=rule2, toy_12_30_12=rule2,
... )

Between the two separate time spans of rule one (1 Jan to 5 Jan) and rule two (10 Jan to 31 Dec), exch_branch_rules linearly interpolates the results of both rules:

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

input-modifying rules

exch_branch_rules replaced the deprecated model exch_branch_hbv96, which only supported piecewise linear interpolation. However, exch_branch_hbv96 has additional parameters Delta and Minimum. Delta adds or subtracts seasonally varying values from the input, and Minimum resets the result to an eventually violated threshold value. We decided against adding such parameters to exch_branch_rules, because one can easily construct the interpolation rules to achieve the same behaviour.

To demonstrate this, we take the only integration test of exch_branch_hbv96 as an example. It defines a Delta of -1 and a Minimum of -1. For the XPoints [0.0, 2.0, 4.0, 6.0], the YPoints are [0.0, 1.0, 2.0, 3.0], [0.0, 1.0, 0.0, 0.0], and [0.0, 0.0, 2.0, 6.0] for nodes output1, output2, and output3, respectively. This translates into:

>>> rules(
...     PPolys(
...         output1=PPoly(xs=[-1.0, 0.0, 1.0], ys=[-0.5, -0.5, 0.0]),
...         output2=PPoly(xs=[-1.0, 0.0, 3.0, 5.0, 7.0], ys=[-0.5, -0.5, 1.0, 0.0, 0.0]),
...         output3=PPoly(xs=[1.0, 3.0, 5.0, 7.0], ys=[0.0, 0.0, 2.0, 6.0]),
...     )
... )

As expected, the following results are identical to those of the exch_branch_hbv96 example:

>>> test("exch_branch_rules_input_modifying_rules")
Click to see the table
class hydpy.models.exch_branch_rules.Model[source]

Bases: AdHocModel

HydPy-Exch-Branch-Rules (branching based on piecewise linear or polynomial interpolation rules).

The following “inlet update methods” are called in the given sequence at the beginning of each simulation step:
The following “outlet update methods” are called in the given sequence at the end of each simulation step:
  • Pass_Branched_V1 Branch the current input by applying the (seasonally varying) interpolation rules.

DOCNAME = ('Exch-Branch-Rules', 'branching based on piecewise linear or polynomial interpolation rules')
property targetnames: tuple[str, ...]

Names of the target nodes.

The names are sorted alphabetically, which agrees with the order of the outputs of parameter Rules.

connect() → None[source]

Connect all available inlet nodes to the inlet sequence Total and all target outlet nodes to the outlet sequence Branched.

The following test configuration involves two inlet nodes (inflow1 and inflow2) and two outlet nodes (diversion and river):

>>> from hydpy import Element, Nodes, pub
>>> pub.timegrids = "2000-01-01", "2000-01-03", "1d"
>>> diversion, inflow1, inflow2, river = Nodes(
...     "diversion", "inflow1", "inflow2", "river"
... )
>>> branch = Element(
...     "branch", inlets=[inflow1, inflow2], outlets=[diversion, river]
... )

exch_branch_rules takes the target node names from parameter Rules. Hence, you cannot build connections before defining the interpolation rules:

>>> from hydpy.models.exch_branch_rules import *
>>> parameterstep()
>>> branch.model = model
Traceback (most recent call last):
...
RuntimeError: While trying to connect model `exch_branch_rules` with element `branch`, the following error occurred: The names of the target nodes are still unknown.  Please define them via parameter `rules` first.

The target node names must agree with the outlet nodes’ names:

>>> rules(
...     PPolys(
...         channel=PPolys.REST,
...         diversion=PPoly(xs=[0.0, 3.0], ys=[0.0, 2.0]),
...     )
... )
>>> model.connect()
Traceback (most recent call last):
...
RuntimeError: While trying to connect model `exch_branch_rules` with element `branch`, the following error occurred: The target node names `channel and diversion` do not agree with the available outlet nodes `diversion and river`.

With consistent definitions, the branched flows reach the nodes diversion and river as intended:

>>> rules(
...     PPolys(
...         river=PPolys.REST,
...         diversion=PPoly(xs=[0.0, 3.0], ys=[0.0, 2.0]),
...     )
... )
>>> model.connect()
>>> inflow1.sequences.sim = 1.0
>>> inflow2.sequences.sim = 2.0
>>> derived.toy.update()
>>> model.update_inlets()
>>> model.update_outlets()
>>> river.sequences.sim
sim(1.0)
>>> diversion.sequences.sim
sim(2.0)
REUSABLE_METHODS = ()
class hydpy.models.exch_branch_rules.ControlParameters(master: Parameters[TM_co], cls_fastaccess: type[FastAccessParameter] | None = None, cymodel: CyModelProtocol | None = None)

Bases: ControlParameters

Control parameters of model exch_branch_rules.

The following classes are selected:
  • Rules() Seasonally varying interpolation rules for branching the input [-].

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

Bases: DerivedParameters

Derived parameters of model exch_branch_rules.

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

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

Bases: FluxSequences

Flux sequences of model exch_branch_rules.

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

Bases: InletSequences

Inlet sequences of model exch_branch_rules.

The following classes are selected:
  • Total() Total input [e.g. m³/s].

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

Bases: OutletSequences

Outlet sequences of model exch_branch_rules.

The following classes are selected: