Skip to content

Inter-cluster storage math

Pre-defined additional math to apply inter-cluster storage math on top of the base mathematical formulation. This math is only applied if referenced in the config.init.add_math list as storage_inter_cluster.

A guide to math documentation

If a math component's initial conditions are met (the first if statement), it will be applied to a model. For each objective, constraint and global expression, a number of sub-conditions then apply (the subsequent, indented if statements) to decide on the specific expression to apply at a given iteration of the component dimensions.

In the expressions, terms in bold font are decision variables and terms in italic font are parameters. The decision variables and parameters are listed at the end of the page; they also refer back to the global expressions / constraints in which they are used. Those parameters which are defined over time (timesteps) in the expressions can be defined by a user as a single, time invariant value, or as a timeseries that is loaded from file or dataframe.

Note

For every math component in the documentation, we include the YAML snippet that was used to generate the math in a separate tab.

Download the inter-cluster storage math formulation as a YAML file

Subject to

storage_max

REMOVED

balance_supply_with_storage

UPDATED Fix the outflow of a supply technology to its consumption of the available source, with a storage buffer to temporally offset the outflow from source consumption.

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ carrier }\negthickspace \in \negthickspace\text{ carriers, } \text{ timestep }\negthickspace \in \negthickspace\text{ timesteps } \!\!,\\ \text{if } (\exists (\textbf{storage}_\text{node,tech,timestep}) \land \textit{base\_tech}_\text{tech}\mathord{=}\text{supply})\!\!:\\[2em] \quad \text{if } (\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true}))\!\!:\\ \qquad \textbf{storage}_\text{node,tech,timestep} = \textit{storage\_initial}_\text{tech} \times \textbf{storage\_cap}_\text{node,tech} + (\textbf{source\_use}_\text{node,tech,timestep} \times \textit{source\_eff}_\text{tech}) - \textbf{flow\_out\_inc\_eff}_\text{node,tech,carrier,timestep}\\[2em] \quad \text{if } (((\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]} \land \textit{cyclic\_storage}\mathord{=}\text{true}) \lor \neg (\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]})) \land \neg (\exists (\textit{lookup\_cluster\_last\_timestep}_\text{timestep})))\!\!:\\ \qquad \textbf{storage}_\text{node,tech,timestep} = ((1 - \textit{storage\_loss}_\text{tech})^{\textit{timestep\_resolution}_\text{timestep-1}}) \times \textbf{storage}_\text{node,tech,timestep-1} + (\textbf{source\_use}_\text{node,tech,timestep} \times \textit{source\_eff}_\text{tech}) - \textbf{flow\_out\_inc\_eff}_\text{node,tech,carrier,timestep}\\[2em] \quad \text{if } (\exists (\textit{lookup\_cluster\_last\_timestep}_\text{timestep}) \land \neg (\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true})))\!\!:\\ \qquad \textbf{storage}_\text{node,tech,timestep} = (\textbf{source\_use}_\text{node,tech,timestep} \times \textit{source\_eff}_\text{tech}) - \textbf{flow\_out\_inc\_eff}_\text{node,tech,carrier,timestep}\\[2em] \end{array} \]
foreach:
- nodes
- techs
- carriers
- timesteps
where: storage AND base_tech=supply
equations:
- expression: storage == $storage_previous_step + source_use * source_eff - flow_out_inc_eff
sub_expressions:
  storage_previous_step:
  - where: timesteps=get_val_at_index(timesteps=0) AND NOT cyclic_storage=True
    expression: storage_initial * storage_cap
  - where: "(\n  (timesteps=get_val_at_index(timesteps=0) AND cyclic_storage=True)\n\
      \  OR NOT timesteps=get_val_at_index(timesteps=0)\n) AND NOT lookup_cluster_last_timestep"
    expression: (1 - storage_loss) ** roll(timestep_resolution, timesteps=1) * roll(storage,
      timesteps=1)
  - where: lookup_cluster_last_timestep AND NOT (timesteps=get_val_at_index(timesteps=0)
      AND NOT cyclic_storage=True)
    expression: '0'

balance_storage

UPDATED Fix the quantity of carrier stored in a storage technology at the end of each timestep based on the net flow of carrier charged and discharged and the quantity of carrier stored at the start of the timestep.

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ timestep }\negthickspace \in \negthickspace\text{ timesteps } \!\!,\\ \text{if } ((\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage}) \land \neg (\textit{base\_tech}_\text{tech}\mathord{=}\text{supply} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{demand}))\!\!:\\[2em] \quad \text{if } (\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true}))\!\!:\\ \qquad \textbf{storage}_\text{node,tech,timestep} = \textit{storage\_initial}_\text{tech} \times \textbf{storage\_cap}_\text{node,tech} - \sum\limits_{\text{carrier} \in \text{carriers}} (\textbf{flow\_out\_inc\_eff}_\text{node,tech,carrier,timestep}) + \sum\limits_{\text{carrier} \in \text{carriers}} (\textbf{flow\_in\_inc\_eff}_\text{node,tech,carrier,timestep})\\[2em] \quad \text{if } (((\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]} \land \textit{cyclic\_storage}\mathord{=}\text{true}) \lor \neg (\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]})) \land \neg (\exists (\textit{lookup\_cluster\_last\_timestep}_\text{timestep})))\!\!:\\ \qquad \textbf{storage}_\text{node,tech,timestep} = ((1 - \textit{storage\_loss}_\text{tech})^{\textit{timestep\_resolution}_\text{timestep-1}}) \times \textbf{storage}_\text{node,tech,timestep-1} - \sum\limits_{\text{carrier} \in \text{carriers}} (\textbf{flow\_out\_inc\_eff}_\text{node,tech,carrier,timestep}) + \sum\limits_{\text{carrier} \in \text{carriers}} (\textbf{flow\_in\_inc\_eff}_\text{node,tech,carrier,timestep})\\[2em] \quad \text{if } (\exists (\textit{lookup\_cluster\_last\_timestep}_\text{timestep}) \land \neg (\textit{timesteps}_\text{timestep}\mathord{=}\text{timesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true})))\!\!:\\ \qquad \textbf{storage}_\text{node,tech,timestep} = \sum\limits_{\text{carrier} \in \text{carriers}} (\textbf{flow\_out\_inc\_eff}_\text{node,tech,carrier,timestep}) + \sum\limits_{\text{carrier} \in \text{carriers}} (\textbf{flow\_in\_inc\_eff}_\text{node,tech,carrier,timestep})\\[2em] \end{array} \]
foreach:
- nodes
- techs
- timesteps
where: (include_storage=true or base_tech=storage) AND NOT (base_tech=supply OR base_tech=demand)
equations:
- expression: "storage == $storage_previous_step -\n  sum(flow_out_inc_eff, over=carriers)
    + sum(flow_in_inc_eff, over=carriers)"
sub_expressions:
  storage_previous_step:
  - where: timesteps=get_val_at_index(timesteps=0) AND NOT cyclic_storage=True
    expression: storage_initial * storage_cap
  - where: "(\n  (timesteps=get_val_at_index(timesteps=0) AND cyclic_storage=True)\n\
      \  OR NOT timesteps=get_val_at_index(timesteps=0)\n) AND NOT lookup_cluster_last_timestep"
    expression: (1 - storage_loss) ** roll(timestep_resolution, timesteps=1) * roll(storage,
      timesteps=1)
  - where: lookup_cluster_last_timestep AND NOT (timesteps=get_val_at_index(timesteps=0)
      AND NOT cyclic_storage=True)
    expression: '0'

set_storage_initial

UPDATED Fix the relationship between carrier stored in a storage technology at the start and end of the whole model period.

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs } \!\!,\\ \text{if } (\exists (\textbf{storage}_\text{node,tech,timestep}) \land \exists (\textit{storage\_initial}_\text{tech}) \land \textit{cyclic\_storage}\mathord{=}\text{true})\!\!:\\[2em] \quad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep=datesteps[-1]} \times ((1 - \textit{storage\_loss}_\text{tech})^{24}) = \textit{storage\_initial}_\text{tech} \times \textbf{storage\_cap}_\text{node,tech}\\ \end{array} \]
foreach:
- nodes
- techs
where: storage AND storage_initial AND cyclic_storage=True
equations:
- expression: storage_inter_cluster[datesteps=$final_step] * ((1 - storage_loss) **
    24) == storage_initial * storage_cap
slices:
  final_step:
  - expression: get_val_at_index(datesteps=-1)
active: true

storage_intra_max

NEW Set the upper bound of a storage technology's stored carrier within a clustered day

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ timestep }\negthickspace \in \negthickspace\text{ timesteps } \!\!,\\ \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad \textbf{storage}_\text{node,tech,timestep} \leq \textbf{storage\_intra\_cluster\_max}_\text{node,tech,cluster=\(\textit{timestep\_cluster}_\text{timestep}\)}\\ \end{array} \]
foreach:
- nodes
- techs
- timesteps
where: include_storage=True OR base_tech=storage
equations:
- expression: storage <= storage_intra_cluster_max[clusters=$cluster]
slices:
  cluster:
  - expression: timestep_cluster

storage_intra_min

NEW Set the lower bound of a storage technology's stored carrier within a clustered day

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ timestep }\negthickspace \in \negthickspace\text{ timesteps } \!\!,\\ \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad \textbf{storage}_\text{node,tech,timestep} \geq \textbf{storage\_intra\_cluster\_min}_\text{node,tech,cluster=\(\textit{timestep\_cluster}_\text{timestep}\)}\\ \end{array} \]
foreach:
- nodes
- techs
- timesteps
where: include_storage=True OR base_tech=storage
equations:
- expression: storage >= storage_intra_cluster_min[clusters=$cluster]
slices:
  cluster:
  - expression: timestep_cluster

storage_inter_max

NEW Set the upper bound of a storage technology's stored carrier across all days in the timeseries.

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ datestep }\negthickspace \in \negthickspace\text{ datesteps } \!\!,\\ \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep} + \textbf{storage\_intra\_cluster\_max}_\text{node,tech,cluster=\(\textit{lookup\_datestep\_cluster}_\text{datestep}\)} \leq \textbf{storage\_cap}_\text{node,tech}\\ \end{array} \]
foreach:
- nodes
- techs
- datesteps
where: include_storage=True OR base_tech=storage
equations:
- expression: storage_inter_cluster + storage_intra_cluster_max[clusters=$cluster]
    <= storage_cap
slices:
  cluster:
  - expression: lookup_datestep_cluster

storage_inter_min

NEW Set the lower bound of a storage technology's stored carrier across all days in the timeseries.

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ datestep }\negthickspace \in \negthickspace\text{ datesteps } \!\!,\\ \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad (\textbf{storage\_inter\_cluster}_\text{node,tech,datestep} \times ((1 - \textit{storage\_loss}_\text{tech})^{24})) + \textbf{storage\_intra\_cluster\_min}_\text{node,tech,cluster=\(\textit{lookup\_datestep\_cluster}_\text{datestep}\)} \geq 0\\ \end{array} \]
foreach:
- nodes
- techs
- datesteps
where: include_storage=True OR base_tech=storage
equations:
- expression: storage_inter_cluster * ((1 - storage_loss) ** 24) + storage_intra_cluster_min[clusters=$cluster]
    >= 0
slices:
  cluster:
  - expression: lookup_datestep_cluster

balance_storage_inter

NEW Fix the relationship between one day and the next of a storage technology's available stored carrier, according to the previous day's representative storage fluctuations and the excess stored carrier available from all days up to this day.

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ datestep }\negthickspace \in \negthickspace\text{ datesteps } \!\!,\\ \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad \text{if } (\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true}))\land{}(\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true}))\!\!:\\ \qquad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep} = \textit{storage\_initial}_\text{tech}\\[2em] \quad \text{if } (\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true}))\land{}(\neg (\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true})))\!\!:\\ \qquad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep} = \textit{storage\_initial}_\text{tech} + \textbf{storage}_\text{node,tech,timestep=\(\textit{lookup\_datestep\_last\_cluster\_timestep}_\text{datestep-1}\)}\\[2em] \quad \text{if } ((\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \textit{cyclic\_storage}\mathord{=}\text{true}) \lor \neg (\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]}))\land{}(\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true}))\!\!:\\ \qquad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep} = ((1 - \textit{storage\_loss}_\text{tech})^{24}) \times \textbf{storage\_inter\_cluster}_\text{node,tech,datestep-1}\\[2em] \quad \text{if } ((\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \textit{cyclic\_storage}\mathord{=}\text{true}) \lor \neg (\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]}))\land{}(\neg (\textit{datesteps}_\text{datestep}\mathord{=}\text{datesteps[0]} \land \neg (\textit{cyclic\_storage}\mathord{=}\text{true})))\!\!:\\ \qquad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep} = ((1 - \textit{storage\_loss}_\text{tech})^{24}) \times \textbf{storage\_inter\_cluster}_\text{node,tech,datestep-1} + \textbf{storage}_\text{node,tech,timestep=\(\textit{lookup\_datestep\_last\_cluster\_timestep}_\text{datestep-1}\)}\\[2em] \end{array} \]
foreach:
- nodes
- techs
- datesteps
where: include_storage=True OR base_tech=storage
equations:
- expression: storage_inter_cluster == $storage_previous_step + $storage_intra
sub_expressions:
  storage_previous_step:
  - where: datesteps=get_val_at_index(datesteps=0) AND NOT cyclic_storage=True
    expression: storage_initial
  - where: (datesteps=get_val_at_index(datesteps=0) AND cyclic_storage=True) OR NOT
      datesteps=get_val_at_index(datesteps=0)
    expression: ((1 - storage_loss) ** 24) * roll(storage_inter_cluster, datesteps=1)
  storage_intra:
  - where: datesteps=get_val_at_index(datesteps=0) AND NOT cyclic_storage=True
    expression: '0'
  - where: NOT (datesteps=get_val_at_index(datesteps=0) AND NOT cyclic_storage=True)
    expression: storage[timesteps=$final_step]
slices:
  final_step:
  - expression: roll(lookup_datestep_last_cluster_timestep, datesteps=1)

Decision Variables

storage

UPDATED The virtual carrier stored by a supply_plus or storage technology in each timestep of a clustered day. Stored carrier can be negative so long as it does not go below the carrier stored in storage_inter_cluster. Only together with storage_inter_cluster does this variable's values gain physical significance.

Used in:

Unit: energy

Default: 0

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ timestep }\negthickspace \in \negthickspace\text{ timesteps } \!\!,\\ \forall\mathbb{R}\;\!\!:\\[2em] \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad -inf \leq \textbf{storage}_\text{node,tech,timestep}\\ \quad \textbf{storage}_\text{node,tech,timestep} \leq inf\\ \end{array} \]
foreach:
- nodes
- techs
- timesteps
where: include_storage=True OR base_tech=storage
bounds:
  min: -.inf
  max: .inf

storage_inter_cluster

NEW The virtual carrier stored by a supply_plus or storage technology between days of the entire timeseries. Only together with storage does this variable's values gain physical significance.

Used in:

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ datestep }\negthickspace \in \negthickspace\text{ datesteps } \!\!,\\ \forall\mathbb{R}\;\!\!:\\[2em] \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad 0 \leq \textbf{storage\_inter\_cluster}_\text{node,tech,datestep}\\ \quad \textbf{storage\_inter\_cluster}_\text{node,tech,datestep} \leq inf\\ \end{array} \]
foreach:
- nodes
- techs
- datesteps
where: include_storage=True OR base_tech=storage
bounds:
  min: 0
  max: .inf

storage_intra_cluster_max

NEW Virtual variable to limit the maximum value of storage in a given representative day.

Used in:

Unit: energy

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ cluster }\negthickspace \in \negthickspace\text{ clusters } \!\!,\\ \forall\mathbb{R}\;\!\!:\\[2em] \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad -inf \leq \textbf{storage\_intra\_cluster\_max}_\text{node,tech,cluster}\\ \quad \textbf{storage\_intra\_cluster\_max}_\text{node,tech,cluster} \leq inf\\ \end{array} \]
foreach:
- nodes
- techs
- clusters
where: include_storage=True OR base_tech=storage
bounds:
  min: -.inf
  max: .inf

storage_intra_cluster_min

NEW Virtual variable to limit the minimum value of storage in a given representative day.

Used in:

Unit: energy

\[ \begin{array}{l} \forall{} \text{ node }\negthickspace \in \negthickspace\text{ nodes, } \text{ tech }\negthickspace \in \negthickspace\text{ techs, } \text{ cluster }\negthickspace \in \negthickspace\text{ clusters } \!\!,\\ \forall\mathbb{R}\;\!\!:\\[2em] \text{if } (\textit{include\_storage}_\text{tech}\mathord{=}\text{true} \lor \textit{base\_tech}_\text{tech}\mathord{=}\text{storage})\!\!:\\[2em] \quad -inf \leq \textbf{storage\_intra\_cluster\_min}_\text{node,tech,cluster}\\ \quad \textbf{storage\_intra\_cluster\_min}_\text{node,tech,cluster} \leq inf\\ \end{array} \]
foreach:
- nodes
- techs
- clusters
where: include_storage=True OR base_tech=storage
bounds:
  min: -.inf
  max: .inf

Parameters

base_tech

Should be the name of one of the abstract base classes, from which some initial parameter defaults will be derived and with which certain base math will be triggered.

Used in:

include_storage

When true, math will be triggered to allow discontinuous carrier inflow and outflows across timesteps.

Used in:

Default: False

source_eff

Conversion efficiency from the technology from source. Set as value between 1 (no loss) and 0 (all lost).

Used in:

Unit: fraction.

Default: 1.0

storage_initial

Set stored flow in device at the first timestep, as a fraction of total storage capacity.

Used in:

Unit: fraction.

Default: 0

storage_loss

Rate of storage loss per hour, used to calculate lost stored flow as (1 - storage_loss)^hours_per_timestep.

Used in:

Unit: \(\frac{\text{fraction}}{\text{hour}}\).

Default: 0

timestep_resolution

Used in:

timestep_cluster

Used in:

lookup_cluster_last_timestep

Used in:

lookup_datestep_cluster

Used in:

lookup_datestep_last_cluster_timestep

Used in:

cyclic_storage

If true, link storage levels in the last model timestep with the first model timestep. inter_cluster_storage custom math must be included if using time clustering and setting this to true. This must be set to false if using operate mode.

Used in:

Unit: boolean.