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.
