Skip to content

Storage

At a glance…

Storage nodes represents lakes, weirs, dams, or other reservoirs. Upstream flows accumulate in the storage and flows are passed downstream either via a spillway or regulated outlet. Each storage has 1 spillway, which overflows according to a level-flow relationship when the level of water in the storage exceeds the spillway crest. Regulated outlets allow for regulated downstream releases. Water in storages may be subject to evaporation, rainfall, and seepage.

[node.my_storage_node]
type = storage
loc = 20, 30
rain = data.climate_data_csv.by_name.rainfall
evap = data.climate_data_csv.by_name.mpot
seep = 0.82
dimensions = Level [m], Volume [ML], Area [km2], Spill [ML],
             80.0,      0,           0,          0,
             90.0,      1000,        2,          0,
             90.1,      1010,        2,          1e8,
             90.2,      1020,        2,          1e8
ds_1 = my_other_node

Node properties

Property Description
[node.?] (compulsory) Start of node declaration. This says we are creating a node, and also defines the name of the node. Node naming conventions are discussed at . Example: [node.my_inflow_node]
type (compulsory) The node type, which is “storage” in this case. type = storage
loc (compulsory) The location of the node in cartesian coordinates. Example: loc = 20, 30
rain (optional) Rainfall data [mm]. Default is zero. Example: rain = data.rex_rain_csv.by_name.value
evap (optional) Potential evapotranspiration data [mm]. Default is zero. Example: evap = data.rex_mpot_csv.by_name.value
seep (optional) Seepage rate [mm]. Default is zero. Example: seep = 0.82
initial_volume (optional) Initial volume [ML]. Default is zero. Example: initial_volume = 14000
order_through (optional) Optional boolean flag changing the ordering behaviour of the node such that downstream orders are propagated upstream verbatim. This functionality is explained in detail further down this page. Default is false. Example: order_through = true
target_level (optional) Optional target level [m] which causes this storage node to generate orders to operate the storage near the target level. This functionality is explained in detail below. This feature cannot be used in combination with order_through. Example: target_level = if(sim.month>8, 8.0, 9.5)
pond_demand (optional) Optional on-pond demand [ML]. Example: pond_demand = 15.0 * data.patterns_csv.by_name.amenities
dimensions (compulsory) A tabulated list of values: level (m), volume (ML), area (km2), spill (ML/timestep). See Table parameters to find out more about how table parameter types work in Kalix. Example: dimensions = 90, 0, 0, 0, 91, 100, 1, 0, 91.1, 101, 1, 1e8, 92, 102, 1, 1e8
ds_1_outlet, ds_2_outlet, ds_3_outlet, ds_4_outlet (optional) The outlet capacity on the corresponding link (ds_1, ds_2, etc) as a function of level. Three forms. A minimum operating level (MOL) alone: ds_1_outlet = 81.1 — capacity is zero at or below 81.1 m and unlimited above it. A MOL and a capacity [ML/timestep]: ds_1_outlet = 81.1, 120 — zero at or below, 120 above. A rating table of level, capacity pairs: ds_2_outlet = 80, 0, 81, 0, 81.5, 100, 82, 100 — capacity interpolates linearly in level between points and holds flat beyond the ends; a repeated level is a step. Levels must lie within the dimensions table's level range, and capacities must be non-decreasing. Notes: (1) Orders on ds_1 may be partly or fully met by unregulated spills. (2) There are no spills on the other links. (3) An undefined outlet is unlimited.
ds_1 (optional) Name of the downstream node. This property defines a downstream link. Inflow nodes may only have 1 downstream link. Example: ds_1 = my_other_node
ds_2, ds_3, ds_4 (optional) Additional link used to represent regulated flow pathways separate to the main downstream link. ds_2 = tws_pipline

Results associated with this node

Result Description
dsflow Downstream flow [ML]
usflow Upstream flow [ML]
rain Input rainfall [mm]
evap Input evapotranspiration [mm]
seep Input seepage [mm]
rain_vol Rainfall volume [ML]
evap_vol Evaporation volume [ML]
seep_vol Seepage volume [ML]
pond_demand On-pond demand [ML].
pond_diversion Diversion associated with the pond demand [ML]
ds_1, ds_2, etc Downstream flow [ML] on link ds_1 (outlet + spill), ds_2 (outlet),
ds_1_order Order on link ds_1 [ML] (also available for other links)
ds_1_spill Flow over the spillway to ds_1 [ML] (also available for other links, but = 0)
ds_1_outlet Flow through the outlet to ds_1 [ML] (also available for other links)
volume Volume of water in the storage at the end of the timestep [ML]
level Level of water in the storage at the end of the timestep [m]
area Area of the water surface at the end of the timestep [km2]
initial_volume The declared initial_volume value, static for the whole run. See Static Node Properties.

How the node works

Solver

Storages are simulated with the Backward Euler method: every flux over the timestep is a function of the end-of-timestep state. The solver finds the volume Vi whose fluxes are consistent with Vi itself — with ai and li the area and level at that volume:

  • spill → si = s(li)

  • pond evaporation → ei = Mlake,i × ai

  • pond rainfall → ri = Pi × ai

  • seepage → wi = Wi × ai

  • inflow → qin,i = previously calculated

  • outlet releases → qout,i = see below

Mass balance over the timestep:

Vi = Vi−1 + ri − ei − wi + qin,i − si − qout,i

Outlets are capacity curves. Every ds_N_outlet form defines the same thing — the outlet's maximum release as a function of level, evaluated at the end-of-timestep level, exactly as spill is. A bare MOL is a step from zero to unlimited; a MOL with capacity, a step from zero to that capacity; a rating table, any non-decreasing piecewise-linear curve. Each outlet releases min(order, capacity(li)). Outlets interact only through the solved level: joint demand pulls the level down, and each outlet's release tapers or cuts as the level passes its curve.

Solution. The mass balance is one equation in Vi, and it is monotone: outflow never falls as volume rises (this is why rating capacities must be non-decreasing). The solver brackets the dimension-table segment holding the solution — a binary row search, warm-started from the previous timestep — then solves in closed form: within a segment every term is piecewise linear, so each candidate branch is a linear equation, not an iteration. A step capacity makes the outflow jump; when the balance lands inside a jump, the volume parks exactly on the step's level and the stepping outlets share the residual in priority order ds_1 → ds_4. A demand exceeding the water available drains the storage exactly to empty. In every case the releases and the end-of-timestep volume reconcile to machine precision.

Orders near the spill level. The ds_1 pathway carries both the uncontrolled spill and controlled releases, so its outflow term is max(spill(V), order). That function has a kink at the volume where the interpolated spill curve crosses the order. The solver detects when the equilibrium falls on a dimension-table segment containing this crossing and solves the correct branch exactly: below the crossing the release equals the order (spill passes within it); above it the spill governs and exceeds the order. This keeps the end-of-timestep volume exactly consistent with the released flow — interpolating straight across the kink would destroy water on days when a storage sits just above full supply level with orders comparable to the spill.

Storages ordering upstream

The default behaviour of storages is to NOT propagate any orders upstream. However, storages can be configured to order upstream in either of two ways: (1) using the target_level, or (2) using the order_through property.

Target level

A storage configured with a target_level will operate to satisfy downstream orders to the best of its ability (same as without a target level), but while doing so will also generate its own orders requesting water from upstream as needed to bring its level up to the specified target. In practice, operating targets help operators ensure that sufficient water is available where needed for smooth operation. Target_level serves the same purpose in the model.

Ordering to meet a target_level is imperfect. In the example above, ‘kings_weir’ may not be able to precisely achieve a defined target level because:

  • lag on ‘evolution_ck’ could mean water ordered from ‘muir_lake’ does not arrive immediately, and

  • the target level may have changed in that time,

  • rainfall, evaporation, and seepage may have happened in that time,

  • user1’ and ‘user2’ may have placed orders to ‘kings_weir’ which should be released immediately.

Storages using the target_level property calculate orders as follows:

Oupstream=max(0,Vtarget−Vestimated future)

The term Vtarget is evaluated each timestep and is what we are trying to achieve by sending the current order. This is the target we are hoping to achieve when our order arrives, i.e. after n_timesteps = upstream order travel time. The term Vestimated future is an estimate of the future storage volume after n_timesteps if there were no inflows. Kalix uses a simple and conservative estimate of this:

Vestimated future=Vnow−qout known+qin expected=Vnow−Ods today+Oupstream enroute

Order through

When a storage is set with order_through = true, the storage will pass orders from the downstream link(s) to the upstream link(s) verbatim. Doing this means that downstream orders are really being satisfied from the upstream storage. This may change the ordering travel time perceived by regulated users.

The storage with order_through = true operates to release orders for downstream users as they come through. The volume in such a storage depends on the balance of inflows and outflows, and proper functioning may may depend on unregulated flows to cover losses and delivery inefficiencies.


See also

Inverted Pyramid Storage Tables

References

None.