Constraints

Balance constraint

Nodal balance

In SpineOpt, node is the place where an energy balance is enforced. As universal aggregators, they are the glue that brings all components of the energy system together. An energy balance is created for each node for all node_stochastic_time_indices, unless the balance_type parameter of the node takes the value balance_type_none or if the node in question is a member of a node group, for which the balance_type is balance_type_group. The parameter nodal_balance_sense defaults to equality, but can be changed to allow overproduction (nodal_balance_sense >=) or underproduction (nodal_balance_sense <=). The energy balance is enforced by the following constraint:

\[\begin{aligned} & v_{node\_injection}(n,s,t) \\ & + \sum_{\substack{(conn,n',d_{in},s,t) \in connection\_flow\_indices: \\ d_{out} == :to\_node}} v_{connection\_flow}(conn,n',d_{in},s,t)\\ & - \sum_{\substack{(conn,n',d_{out},s,t) \in connection\_flow\_indices: \\ d_{out} == :from\_node}} v_{connection\_flow}(conn,n',d_{out},s,t)\\ & + v_{node\_slack\_pos}(n,s,t) \\ & - v_{node\_slack\_neg}(n,s,t) \\ & \{>=,==,<=\} \\ & 0 \\ & \forall (n,s,t) \in node\_stochastic\_time\_indices: \\ & p_{balance\_type}(n) != balance\_type\_none \\ & \nexists ng \in groups(n) : balance\_type\_group \\ \end{aligned}\]

The constraint consists of the node injections, the net connection_flows and node slack variables.

Node injection

The node injection itself represents all local production and consumption, represented by the sum of all connected unit flows and the nodal demand. The node injection is created for each node in the network (unless the node is only used for parameter aggregation purposes, see Introduction to groups of objects).

\[\begin{aligned} & v_{node\_injection}(n,s,t) \\ & == \\ & + \sum_{\substack{(u,n',d_{in},s,t) \in unit\_flow\_indices: \\ d_{out} == :to\_node}} v_{unit\_flow}(u,n',d_{in},s,t)\\ & - \sum_{\substack{(u,n',d_{out},s,t) \in unit\_flow\_indices: \\ d_{out} == :from\_node}} v_{unit\_flow}(u,n',d_{out},s,t)\\ & - p_{demand}(n,s,t)\\ & \forall (n,s,t) \in node\_stochastic\_time\_indices \end{aligned}\]

Node injection with storage capability

If a node corresponds to a storage node, the parameter has_state should be set to true for this node. In this case the nodal injection will translate to the following constraint:

\[\begin{aligned} & v_{node\_injection}(n,s,t) \\ & == \\ & (v_{node\_state}(n, s, t\_before)\\ & - v_{node\_state}(n, s, t) \cdot p_{state\_coeff}(n,s,t)) \\ & / \Delta t_{after} \\ & - v_{node\_state}(n, s, t) \cdot p_{frac\_state\_loss}(n,s,t) \\ & + \sum_{\substack{(n2,s,t) \in node\_state\_indices: \\ \exists diff\_coeff(n2,n)}} v_{node\_state}(n2,s,t)\\ & - \sum_{\substack{(n2,s,t) \in node\_state\_indices: \\ \exists diff\_coeff(n,n2)}} v_{node\_state}(n2,s,t)\\ & + \sum_{\substack{(u,n',d_{in},s,t) \in unit\_flow\_indices: \\ d_{out} == :to\_node}} v_{unit\_flow}(u,n',d_{in},s,t)\\ & - \sum_{\substack{(u,n',d_{out},s,t) \in unit\_flow\_indices: \\ d_{out} == :from\_node}} v_{unit\_flow}(u,n',d_{out},s,t)\\ & - demand(n,s,t)\\ & \forall (n,t) \in node\_time\_indices : p_{has\_state}(n)\\ & \forall s \in stochastic\_scenario\_path \\ & t_{before} \in t\_before\_t(t\_after=t)\\ \end{aligned}\]

Note that for simplicity, the stochastic path is assumed to be known. In the constraint constraint_node_injection.jl the active stochastic paths of all involved variables is retrieved beforehand.

Node state capacity

To limit the storage content, the $v_{node\_state}$ variable needs be constrained by the following equation:

\[\begin{aligned} & v_{node\_state}(n, s, t)\\ & <= p_{node\_state\_cap}(n, s, t)\\ & \forall (n,s,t) \in node\_stochastic\_time\_indices : \\ & p_{has\_state}(n)\\ \end{aligned}\]

The discharging and charging behavior of storage nodes can be described through unit(s), representing the link between the storage node and the supply node. Note that the dis-/charging efficiencies and capacities are properties of these units. See the capacity constraint and the unit flow ratio constraints

Cyclic condition on node state variable

To ensure that the node state at the end of the optimization is at least the same value as the initial value at the beginning of the optimization (or higher), the cyclic node state constraint can be used by setting the cyclic_condition of a node__temporal_block to true. This triggers the following cyclic constraint:

\[\begin{aligned} & v_{node\_state}(n, s, t)\\ & >= v_{node\_state}(n, s, t)\\ & \forall (n,tb) \in p_{cyclic\_condition}(n,tb) : \\ & \{p_{cyclic\_condition}(n,tb) == true,\\ & p_{has\_state}(n) \}\\ & \forall (n',t_{initial}) \in node\_time\_indices : \\ & \{n' == n, \\ & t_{initial} == t\_before\_t(t\_after=first(t \in tb)),\\ & \forall (n',t_{last}) \in node\_time\_indices : \\ & n' == n, \\ & t_{last} == last(t \in tb))\\ & \forall s \in stochastic\_path\\ \end{aligned}\]

Unit operation

In the following, the operational constraints on the variables associated with units will be elaborated on. The static constraints, in contrast to the dynamic constraints, are addressing constraints without sequential time-coupling. It should however be noted that static constraints can still perform temporal aggregation.

Static constraints

The fundamental static constraints for units within SpineOpt relate to the relationships between commodity flows from and to units and to limits on the unit flow capacity.

Conversion constraint / limiting flow shares inprocess / relationship in process

A unit can have different commodity flows associated with it. The most simple relationship between these flows is a linear relationship between input and/or output nodes/node groups. SpineOpt holds constraints for each combination of flows and also for the type of relationship, i.e. whether it is a maximum, minimum or fixed ratio between commodity flows. Note that node groups can be used in order to aggregate flows, i.e. to give a ratio between a combination of units flows.

Ratios between output and input flows of a unit

By defining the parameters fix_ratio_out_in_unit_flow, max_ratio_out_in_unit_flow or min_ratio_out_in_unit_flow, a ratio can be set between outgoing and incoming flows from and to a unit. Whenever there is only a single input node and a single output node, this relationship relates to the notion of an efficiency. Also, the ratio equation can for instance be used to relate emissions to input primary fuel flows. In the most general form of the equation, two node groups are defined (an input node group $ng_{in}$ and an output node group $ng_{out}$), and a linear relationship is expressed between both node groups. Note that whenever the relationship is specified between groups of multiple nodes, there remains a degree of freedom regarding the composition of the input node flows within group $ng_{in}$ and the output node flows within group $ng_{out}$.

The constraint given below enforces a fixed, maximum or minimum ratio between outgoing and incoming unit_flow. Note that the potential node groups, that the parameters fix_ratio_out_in_unit_flow, max_ratio_out_in_unit_flow and min_ratio_out_in_unit_flow defined on, are getting internally expanded to the members of the node group within the unit_flow_indices.

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t_{out}) \in unit\_flow\_indices: \\ (u,n,d,s,t_{out}) \, \in \, (u,ng_{out},:to\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{out}) \cdot \Delta t_{out} \\ & \{ \\ & == p_{fix\_ratio\_out\_in\_unit\_flow}(u,ng_{out},ng_{in},s,t), \\ & <= p_{max\_ratio\_out\_in\_unit\_flow}(u,ng_{out},ng_{in},s,t), \\ & >= p_{min\_ratio\_out\_in\_unit\_flow}(u,ng_{out},ng_{in},s,t)\\ & \} \\ & \cdot \sum_{\substack{(u,n,d,s,t_{in}) \in unit\_flow\_indices:\\ (u,n,d,s,t_{in}) \in (u,ng_{in},:from\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{in}) \cdot \Delta t_{in} \\ & + p_{\{fix,max,min\}\_units\_on\_coefficient\_out\_in}(u,ng_{out},ng_{in},s,t) \\ & \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,s,t_{units\_on}) \in (u,s,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(\Delta t_{units\_on},\Delta t) \\ & \forall (u, ng_{out}, ng_{in}) \in ind(p_{\{fix,max,min\}\_ratio\_out\_in\_unit\_flow}), \\ & \forall t \in time\_slices, \forall s \in stochastic\_path \end{aligned}\]

Note that a right-hand side constant coefficient associated with the variable units_on can optionally be included, triggered by the existence of the fix_units_on_coefficient_out_in, max_units_on_coefficient_out_in, min_units_on_coefficient_out_in, respectively.

Ratios between input and output flows of a unit

Similarly to the ratio between outgoing and incoming unit flows, a ratio can also be defined in reverse between incoming and outgoing flows.

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t_{in}) \in unit\_flow\_indices: \\ (u,n,d,s,t_{in}) \, \in \, (u,ng_{in},:from\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{in}) \cdot \Delta t_{in} \\ & \{ \\ & == p_{fix\_ratio\_in\_out\_unit\_flow}(u,ng_{in},ng_{out},s,t), \\ & <= p_{max\_ratio\_in\_out\_unit\_flow}(u,ng_{in},ng_{out},s,t), \\ & >= p_{min\_ratio\_in\_out\_unit\_flow}(u,ng_{in},ng_{out},s,t)\\ & \} \\ & \cdot \sum_{\substack{(u,n,d,s,t_{out}) \in unit\_flow\_indices:\\ (u,n,d,s,t_{in}) \in (u,ng_{in},:to\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{out}) \cdot \Delta t_{out} \\ & + p_{\{fix,max,min\}\_units\_on\_coefficient\_in\_out}(u,ng_{in},ng_{out},s,t) \\ & \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,s,t_{units\_on}) \in (u,s,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(\Delta t_{units\_on},\Delta t) \\ & \forall (u, ng_{in}, ng_{out}) \in ind(p_{\{fix,max,min\}\_ratio\_in\_out\_unit\_flow}), \\ & \forall t \in time\_slices, \forall s \in stochastic\_path \end{aligned}\]

Note that a right-hand side constant coefficient associated with the variable units_on can optionally be included, triggered by the existence of the fix_units_on_coefficient_in_out, max_units_on_coefficient_in_out, min_units_on_coefficient_in_out, respectively.

Ratios between input and input flows of a unit

Similarly to the ratio between outgoing and incoming units flows, one can also define a fixed, maximum or minimum ratio between incoming flows of a units.

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t_{in1}) \in unit\_flow\_indices: \\ (u,n,d,s,t_{in1}) \, \in \, (u,ng_{in1},:from\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{in1}) \cdot \Delta t_{in1} \\ & \{ \\ & == p_{fix\_ratio\_in\_in\_unit\_flow}(u,ng_{in1},ng_{in2},s,t), \\ & <= p_{max\_ratio\_in\_in\_unit\_flow}(u,ng_{in1},ng_{in2},s,t), \\ & >= p_{min\_ratio\_in\_in\_unit\_flow}(u,ng_{in1},ng_{in2},s,t)\\ & \} \\ & \cdot \sum_{\substack{(u,n,d,s,t_{in2}) \in unit\_flow\_indices:\\ (u,n,d,s,t_{in2}) \in (u,ng_{in2},:from\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{in2}) \cdot \Delta t_{in2} \\ & + p_{\{fix,max,min\}\_units\_on\_coefficient\_in\_in}(u,ng_{in1},ng_{in2},s,t) \\ & \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,s,t_{units\_on}) \in (u,s,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(\Delta t_{units\_on},\Delta t) \\ & \forall (u, ng_{in1}, ng_{in2}) \in ind(p_{\{fix,max,min\}\_ratio\_in\_in\_unit\_flow}), \\ & \forall t \in time\_slices, \forall s \in stochastic\_path \end{aligned}\]

Note that a right-hand side constant coefficient associated with the variable units_on can optionally be included, triggered by the existence of the fix_units_on_coefficient_in_in, max_units_on_coefficient_in_in, min_units_on_coefficient_in_in, respectively.

Ratios between output and output flows of a unit

Similarly to the ratio between outgoing and incoming units flows, one can also define a fixed, maximum or minimum ratio between outgoing flows of a units.

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t_{out1}) \in unit\_flow\_indices: \\ (u,n,d,s,t_{out1}) \, \in \, (u,ng_{out1},:to\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{out1}) \cdot \Delta t_{out1} \\ & \{ \\ & == p_{fix\_ratio\_out\_out\_unit\_flow}(u,ng_{out1},ng_{out2},s,t), \\ & <= p_{max\_ratio\_out\_out\_unit\_flow}(u,ng_{out1},ng_{out2},s,t), \\ & >= p_{min\_ratio\_out\_out\_unit\_flow}(u,ng_{out1},ng_{out2},s,t)\\ & \} \\ & \cdot \sum_{\substack{(u,n,d,s,t_{out2}) \in unit\_flow\_indices:\\ (u,n,d,s,t_{out2}) \in (u,ng_{out2},:to\_node,s,t)}} v_{unit\_flow}(u,n,d,s,t_{out2}) \cdot \Delta t_{out2} \\ & + p_{\{fix,max,min\}\_units\_on\_coefficient\_out\_out}(u,ng_{out1},ng_{out2},s,t) \\ & \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,s,t_{units\_on}) \in (u,s,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(\Delta t_{units\_on},\Delta t) \\ & \forall (u, ng_{out1}, ng_{out2}) \in ind(p_{\{fix,max,min\}\_ratio\_out\_out\_unit\_flow}), \\ & \forall t \in time\_slices, \forall s \in stochastic\_path \end{aligned}\]

Note that a right-hand side constant coefficient associated with the variable units_on can optionally be included, triggered by the existence of the fix_units_on_coefficient_out_out, max_units_on_coefficient_out_out, min_units_on_coefficient_out_out, respectively.

Bounds on the unit capacity

In a multi-commodity setting, there can be different commodities entering/leaving a certain technology/unit. These can be energy-related commodities (e.g., electricity, natural gas, etc.), emissions, or other commodities (e.g., water, steel). The unit_capacity be specified for at least one unit__to_node or unit__from_node relationship, in order to trigger a constraint on the maximum commodity flows to this location in each time step. When desirable, the capacity can be specified for a group of nodes (e.g. combined capacity for multiple products).

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t') \in unit\_flow\_indices: \\ (u,n,d,s,t') \, \in \, (u,ng,d,s,t)}} v_{unit\_flow}(u,n,d,s,t') \cdot \Delta t' \\ & <= p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{unit\_conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \cdot \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,\Delta t_{units\_on} \in (u,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(t_{units\_on},\Delta t) \\ & \forall (u,ng,d) \in ind(p_{unit\_capacity}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Note that the conversion factor unit_conv_cap_to_flow has a default value of 1, but can be adjusted in case the unit of measurement for the capacity is different to the unit flows unit of measurement.

When the unit also provides non-spinning reserves to a reserve node, the corresponding flows are excluded from the capacity constraint and the unit capacity constraint translates to the following inequality:

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t') \in unit\_flow\_indices: \\ (u,n,d,s,t') \, \in \, (u,ng,d,s,t)} \\ n !\in is\_non\_spinning} v_{unit\_flow}(u,n,d,s,t') \cdot \Delta t' \\ & <= p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{unit\_conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \cdot \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,\Delta t_{units\_on} \in (u,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(t_{units\_on},\Delta t) \\ & \forall (u,ng,d) \in ind(p_{unit\_capacity}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Dynamic constraints

Commitment constraints

For modeling certain technologies/units, it is important to not only have unit_flow variables of different commodities, but also model the online ("commitment") status of the unit/technology at every time step. Therefore, an additional variable units_on is introduced. This variable represents the number of online units of that technology (for a normal unit commitment model, this variable might be a binary, for investment planning purposes, this might also be an integer or even a continuous variable). To define the type of a commitment variable, see online_variable_type. Commitment variables will be introduced by the following constraints (with corresponding parameters):

  • constraint on units_on
  • constraint on units_available
  • constraint on the unit state transition
  • constraint on the minimum operating point
  • constraint on minimum down time
  • constraint on minimum up time
  • constraint on ramp rates
Bound on online units

The number of online units need to be restricted to the number of available units:

\[\begin{aligned} & v_{units\_on}(u,s,t) \\ & <= v_{units\_available}(u,s,t) \\ & \forall (u,s,t) \in units\_on\_indices \end{aligned}\]

Bound on available units

The number of available units itself is constrained by the parameters unit_availability_factor and number_of_units, and the variable number of invested units units_invested_available:

\[\begin{aligned} & v_{units\_available}(u,s,t) \\ & == p_{unit\_availability\_factor}(u,s,t) \\ & \cdot (p_{number\_of\_units}(u,s,t) \\ & + \sum_{(u,s,t) \in units\_invested\_available\_indices} v_{units\_invested\_available}(u,s,t) ) \\ & \forall (u,s,t) \in units\_on\_indices \end{aligned}\]

The investment formulation is described in chapter Investments.

Unit state transition

The units on status is constrained by shutting down and starting up actions. This transition is defined as follows:

\[\begin{aligned} & v_{units\_on}(u,s,t_{after}) \\ & - v_{units\_started\_up}(u,s,t_{after}) \\ & + v_{units\_shut\_down}(u,s,t_{after}) \\ & == v_{units\_on}(u,s,t_{before}) \\ & \forall (u,s,t_{after}) \in units\_on\_indices, \\ & \forall t_{before} \in t\_before\_t(t\_after=t_{after}) : t_{before} \in units\_on\_indices\\ \end{aligned}\]

Constraint on minimum operating point

The minimum operating point of a unit can be based on the unit_flows of input or output nodes/node groups ng:

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t') \in unit\_flow\_indices: \\ (u,n,d,t') \, \in \, (u,ng,d,t)}} v_{unit\_flow}(u,n,d,s,t') \cdot \Delta t' \\ & >= p_{minimum\_operating\_point}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \cdot \sum_{\substack{(u,s,t_{units\_on}) \in units\_on\_indices:\\ (u,\Delta t_{units\_on} \in (u,t)}} v_{units\_on}(u,s,t_{units\_on}) \\ & \cdot \min(\Delta t_{units\_on},\Delta t) \\ & \forall (u,ng,d) \in ind(p_{minimum\_operating\_point}), \\ & \forall t \in t\_lowest\_resolution(node\_\_temporal\_block(node=members(ng))),\\ & \forall s \in stochastic\_path \end{aligned}\]

Note that this constraint is always generated for the lowest resolution of all involved members of the node group ng, i.e. the lowest resolution of the involved units flows. This is also why the term $\min(\Delta t_{units\_on},\Delta t)$ is added for the units on variable, in order to dis-/aggregate the units on resolution to the resolution of the unit flows.

Minimum down time (basic version)

In order to impose a minimum offline time of a unit, before it can be started up again, the min_down_time parameter needs to be defined, which triggers the generation of the following constraint:

\[\begin{aligned} & v_{units\_available}(u,s,t) \\ & - v_{units\_on}(u,s,t) \\ & >= \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ t' >=t-p_{min\_down\_time}(u,s,t) \quad t' <= t}} v_{units\_shut\_down}(u,s,t') \\ & \forall (u,s,t) \in units\_on\_indices\\ \end{aligned}\]

Note that for the use reserves the generated minimum down time constraint will include startups for non-spinning reserves.

Minimum up time (basic version)

Similarly to the minimum down time constraint, a minimum time that a unit needs to remain online after a startup can be imposed by defining the min_up_time parameter. This will trigger the generation of the following constraint:

\[\begin{aligned} & v_{units\_on}(u,s,t) \\ & >= \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ t' >=t-p_{min\_up\_time}(u,s,t), \\ t' <= t}} v_{units\_started\_up}(u,s,t') \\ & \forall (u,s,t) \in units\_on\_indices\\ \end{aligned}\]

This constraint can be extended to the use of nonspinning reserves. See also.

Ramping and reserve constraints

To include ramping and reserve constraints, it is a pre requisite that minimum operating points and maximum capacity constraints are enforced as described.

For dispatchable units, additional ramping constraints can be introduced. For setting up ramping characteristics of units see Ramping and Reserves. First, the unit flows are split into their online, start-up, shut-down and non-spinning ramping contributions.

Splitting unit flows into ramps

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t_{after}) \in unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})\\ !p_{is\_reserve}(n)}} v_{unit\_flow}(u,n,d,s,t_{after}) \\ & + \sum_{\substack{(u,n,d,s,t_{after}) \in unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})\\ p_{is\_reserve(n)} \\ p_{upward\_reserve}(n)}} v_{unit\_flow}(u,n,d,s,t_{after}) \\ & - \sum_{\substack{(u,n,d,s,t_{after}) \in unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})\\ p_{is\_reserve(n)} \\ p_{downward\_reserve}(n)}} v_{unit\_flow}(u,n,d,s,t_{after}) \\ & - \sum_{\substack{(u,n,d,s,t_{before}) \in unit\_flow\_indices: \\ (u,n,d,t_{before}) \, \in \, (u,n,d,t_{before})\\ !p_{is\_reserve}(n)}} v_{unit\_flow}(u,n,d,s,t_{before}) \\ & == \\ & + \sum_{\substack{(u,n,d,s,t_{after}) \in ramp\_up\_unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})}} v_{ramp\_up\_unit\_flow}(u,n,d,s,t_{after}) \\ & + \sum_{\substack{(u,n,d,s,t_{after}) \in start\_up\_unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})}} v_{start\_up\_unit\_flow}(u,n,d,s,t_{after}) \\ & + \sum_{\substack{(u,n,d,s,t_{after}) \in nonspin\_ramp\_up\_unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})}} v_{nonspin\_ramp\_up\_unit\_flow}(u,n,d,s,t_{after}) \\ & - \sum_{\substack{(u,n,d,s,t_{after}) \in ramp\_down\_unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})}} v_{ramp\_down\_unit\_flow}(u,n,d,s,t_{after}) \\ & - \sum_{\substack{(u,n,d,s,t_{after}) \in shut\_down\_unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})}} v_{shut\_down\_unit\_flow}(u,n,d,s,t_{after}) \\ & - \sum_{\substack{(u,n,d,s,t_{after}) \in nonspin\_ramp\_down\_unit\_flow\_indices: \\ (u,n,d,t_{after}) \, \in \, (u,n,d,t_{after})}} v_{nonspin\_ramp\_down\_unit\_flow}(u,n,d,s,t_{after}) \\ & \forall (u,n,d,s,t_{after}) \in (\\ & ramp\_up\_unit\_flow\_indices,\\ & start\_up\_unit\_flow\_indices,\\ & nonspin\_ramp\_up\_unit\_flow\_indices, \\ & ramp\_down\_unit\_flow\_indices,\\ & shut\_down\_unit\_flow\_indices,\\ & nonspin\_ramp\_down\_unit\_flow\_indices) \\ & \forall t_{before} \in t\_before\_t(t\_after=t_{after}) : t_{before} \in unit\_flow\_indices \\ \end{aligned}\]

Note that each individual tuple of the unit_flow_indices is split into its ramping contributions, if any of the ramping variables exist for this tuple. How to set-up ramps for units is described in Ramping and Reserves.

Constraint on spinning upwards ramp_up

The maximum online ramp up ability of a unit can be constraint by the ramp_up_limit, expressed as a share of the unit_capacity. With this constraint, online (i.e. spinning) ramps can be applied to groups of commodities (e.g. electricity + balancing capacity). Moreover, balancing product might have specific ramping requirements, which can herewith also be enforced.

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in ramp\_up\_unit\_flow\_indices: \\ (u,n,d) \, \in \, (u,ng,d)}} v_{ramp\_up\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ (u,s) \in (u,s) \\ t'\in t\_overlap\_t(t)}} (v_{units\_on}(u,s,t') - v_{units\_started\_up}(u,s,t')) \\ & \min(\Delta t',\Delta t) \\ & \cdot p_{ramp\_up\_limit}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{ramp\_up\_limit})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Note that only online units that are not started up during this timestep are considered.

Constraint on minimum upward start up ramp_up

To enforce a lower bound on the ramp of a unit during start-up, the min_startup_ramp given as a share of the unit_capacity needs to be defined, which triggers the constraint below. Usually, only non-reserve commodities can have a start-up ramp. However, it is possible to include them, by adding them to the ramp defining node ng.

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in start\_up\_unit\_flow\_indices: \\ (u,n,d) \, \in \, (u,ng,d)}} v_{start\_up\_unit\_flow}(u,n,d,s,t) \\ & >= \\ & + \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ (u,s) \in (u,s) \\ t'\in t\_overlap\_t(t)}} v_{units_started\_up}(u,s,t) \\ & \cdot p_{min\_startup\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{min\_startup\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Constraint on maximum upward start up ramp_up

This constraint enforces a upper limit on the unit ramp during startup process, triggered by the existence of the max_startup_ramp, which should be given as a share of the unit_capacity. Typically, only ramp flows to non-reserve nodes are considered during the start-up process. However, it is possible to include them, by adding them to the ramp defining node ng.

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in start\_up\_unit\_flow\_indices: \\ (u,n,d) \, \in \, (u,ng,d)}} v_{start\_up\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ (u,s) \in (u,s) \\ t'\in t\_overlap\_t(t)}} v_{units_started\_up}(u,s,t) \\ & \cdot p_{max\_startup\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{max\_startup\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Constraint on upward non-spinning start ups

For non-spinning reserve provision, offline units can be scheduled to provide nonspinning reserves, if they have recovered their minimum down time. If nonspinning reserves are used for a unit, the minimum down-time constraint takes the following form:

\[\begin{aligned} & v_{units\_available}(u,s,t) \\ & - v_{units\_on}(u,s,t) \\ & >= \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ t' >t-p_{min\_down\_time}(u,s,t) \\ t' <= t}} v_{units\_shut\_down}(u,s,t') \\ & + \sum_{\substack{(u',n',s',t') \in nonspin\_units\_started\_up\_indices:\\ (u',s',t') \in (u,s,t)}} v_{nonspin\_units\_started\_up}(u',n',s',t') \\ & \forall (u,s,t) \in units\_on\_indices:\\ & (u,n,s,t) \in nonspin\_units\_started\_up\_indices \end{aligned}\]

Minimum nonspinning ramp up

The nonspinning ramp flows of a units nonspin_ramp_up_unit_flow are dependent on the units holding available for nonspinning reserve provision, i.e. nonspin_units_started_up. A lower bound on these nonspinning reserves can be enforced by defining the min_res_startup_ramp parameter (given as a fraction of the unit_capacity).

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in nonspin\_ramp\_up\_unit\_flow\_indices: \\ (u,n,d) \in (u,ng,d)}} v_{nonspin\_ramp\_up\_unit\_flow}(u,n,d,s,t) \\ & >= \\ & + \sum_{\substack{(u,n,s,t) \in nonspin\_units\_started\_up\_indices: \\ (u,n) \in (u,ng}} v_{nonspin\_units\_started\_up}(u,n,s,t) \\ & \cdot p_{min\_res\_startup\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{min\_res\_startup\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Maximum nonspinning ramp up

The nonspinning ramp flows of a units nonspin_ramp_up_unit_flow are dependent on the units holding available for nonspinning reserve provision, i.e. nonspin_units_started_up. An upper bound on these nonspinning reserves can be enforced by defining the max_res_startup_ramp parameter (given as a fraction of the unit_capacity).

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in nonspin\_ramp\_up\_unit\_flow\_indices: \\ (u,n,d) \in (u,ng,d)}} v_{nonspin\_ramp\_up\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,n,s,t) \in nonspin\_units\_started\_up\_indices: \\ (u,n) \in (u,ng}} v_{nonspin\_units\_started\_up}(u,n,s,t) \\ & \cdot p_{max\_res\_startup\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{max\_res\_startup\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Constraint on spinning downward ramps

Similarly to the online ramp up capbility of a unit, it is also possible to impose an upper bound on the online ramp down ability of unit by defining a ramp_down_limit, expressed as a share of the unit_capacity.

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in ramp\_down\_unit\_flow\_indices: \\ (u,n,d) \, \in \, (u,ng,d)}} v_{ramp\_down\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ (u,s) \in (u,s) \\ t'\in t\_overlap\_t(t)}} (v_{units\_on}(u,s,t') - v_{units\_started\_up}(u,s,t')) \\ & \cdot p_{ramp\_down\_limit}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{ramp\_down\_limit})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Lower bound on downward shut-down ramps

This constraint enforces a lower bound on the unit ramp during shutdown process. Usually, units will only provide shutdown ramps to non-reserve nodes. However, it is possible to include them, by adding them to the ramp defining node ng. The constraint is triggered by the existence of the min_shutdown_ramp parameter.

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in shut\_down\_unit\_flow\_indices: \\ (u,n,d) \, \in \, (u,ng,d)}} v_{shut\_down\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ (u,s) \in (u,s) \\ t'\in t\_overlap\_t(t)}} v_{units\_shut\_down}(u,s,t') \\ & \cdot p_{min\_shutdown\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{min\_shutdown\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Upper bound on downward shut-down ramps

This constraint enforces an upper bound on the unit ramp during shutdown process. Usually, units will only provide shutdown ramps to non-reserve nodes. However, it is possible to include them, by adding them to the ramp defining node ng. The constraint is triggered by the existence of the max_shutdown_ramp parameter.

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in shut\_down\_unit\_flow\_indices: \\ (u,n,d) \, \in \, (u,ng,d)}} v_{shut\_down\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ (u,s) \in (u,s) \\ t'\in t\_overlap\_t(t)}} v_{units\_shut\_down}(u,s,t') \\ & \cdot p_{max\_shutdown\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{max\_shutdown\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Constraint on upward non-spinning shut-downs

For non-spinning downward reserves, online units can be scheduled for reserve provision through shut down if they have recovered their minimum up time. If nonspinning reserves are used the minimum up-time constraint becomes:

\[\begin{aligned} & v_{units\_on}(u,s,t) \\ & >= \sum_{\substack{(u,s,t') \in units\_on\_indices: \\ t' >t-p_{min\_up\_time}(u,s,t) \quad t' <= t}} v_{units\_started\_up}(u,s,t') \\ & + \sum_{\substack{(u',n',s',t') \in nonspin\_units\_shut\_down\_indices: \\ (u',s',t') \in (u,s,t)}} v_{nonspin\_units\_shut\_down}(u',n',s',t') \\ & \forall (u,s,t) \in units\_on\_indices:\\ & u \in nonspin\_units\_started\_up\_indices \end{aligned}\]

Lower bound on the nonspinning downward reserve provision

A lower bound on the nonspinning reserve provision of a unit can be imposed by defining the min_res_shutdown_ramp parameter, which leads to the creation of the following constraint in the model:

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in nonspin\_ramp\_down\_unit\_flow\_indices: \\ (u,n,d,s,t) \in (u,n,d,s,t)}} v_{nonspin\_ramp\_down\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,n,s,t) \in nonspin\_units\_shut\_down\_indices: \\ (u,n,s,t) \in (u,n,s,t)}} v_{nonspin\_units\_shut\_down}(u,n,s,t) \\ & \cdot p_{min\_res\_shutdown\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{min\_res\_shutdown\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Upper bound on the nonspinning downward reserve provision

An upper limit on the nonspinning reserve provision of a unit can be imposed by defining the max_res_shutdown_ramp parameter, which leads to the creation of the following constraint in the model:

\[\begin{aligned} & + \sum_{\substack{(u,n,d,s,t) \in nonspin\_ramp\_down\_unit\_flow\_indices: \\ (u,n,d,s,t) \in (u,n,d,s,t)}} v_{nonspin\_ramp\_down\_unit\_flow}(u,n,d,s,t) \\ & <= \\ & + \sum_{\substack{(u,n,s,t) \in nonspin\_units\_shut\_down\_indices: \\ (u,n,s,t) \in (u,n,s,t)}} v_{nonspin\_units\_shut\_down}(u,n,s,t) \\ & \cdot p_{max\_res\_shutdown\_ramp}(u,ng,d,s,t) \\ & \cdot p_{unit\_capacity}(u,ng,d,s,t) \\ & \cdot p_{conv\_cap\_to\_flow}(u,ng,d,s,t) \\ & \forall (u,ng,d) \in ind(p_{max\_res\_shutdown\_ramp})\\ & \forall s \in stochastic\_path, \forall t \in time\_slice \end{aligned}\]

Constraint on minimum node state for reserve provision

Storage nodes can also contribute to the provision of reserves. The amount of balancing contributions is limited by the ramps of the storage unit (see above) and by the node state:

\[\begin{aligned} & v_{node\_state}(n_{stor}, s, t)\\ & >= p_{node\_state\_min}(n_{stor}, s, t) \\ & + \sum_{\substack{(u,n_{res},d,s,t) \in unit\_flow\_indices: \\ u \in unit\_flow\_indices;n=n_{stor}) \\ p_{is\_reserve\_node}(n_{res}) }} v_{unit\_flow}(u,n_{res},d,s,t) \\ & \cdot p_{minimum\_reserve\_activation\_time}(n_{res}) \\ & \forall (n_{stor},s,t) \in node\_stochastic\_time\_indices : p_{has\_state}(n)\\ \end{aligned}\]

Bounds on the unit capacity including ramping constraints

(Comment 2021-04-29: Currently under development)

Operating segments

Operating segments of units

The unit_flow_op operating segment variable is bounded by the difference between successive operating_points adjusted for unit_capacity

\[\begin{aligned} & v_{unit\_flow\_op}(u, n, op, s, t) \\ & <= p_{unit\_capacity}(u, n, d, s, t) \\ & \cdot v_{units\_available}(u, s, t) \\ & \cdot p_{unit\_conv\_cap\_to\_flow}(u, n, d, s, t) \\ \cdot \bigg( & p_{operating\_points}(u, n, op, s, t) \\ & - \begin{cases} 0 & \text{if op = 1}\\ p_{operating\_points}(u, n, op-1, s, t) & \text{otherwise}\\ \end{cases} \bigg) \\ & \forall (u,n,d,s,t) \in unit\_flow\_op\_indices \\ \end{aligned}\]

Bounding unit flows by summing over operating segments

unit_flow is constrained to be the sum of all operating segment variables, unit_flow_op

\[\begin{aligned} & v_{unit\_flow}(u, n, s, t) \\ & = \sum_{op} v_{unit\_flow\_op}(u, n, op, s, t) \\ & \forall (u,n,d) \in operating\_point\_indices \\ & \forall (u,n,d,s,t) \in unit\_flow\_op\_indices \\ \end{aligned}\]

Unit piecewise incremental heat rate

\[\begin{aligned} & v_{unit\_flow}(u, n_{in}, d, s, t) \\ & = \sum_{op} \bigg( v_{unit\_flow\_op}(u, n_{out}, d, op, s, t) \\ & \qquad \cdot p_{unit\_incremental\_heat\_rate}(u, n_{in}, n_{out}, op, s, t) \bigg) \\ & + v_{units\_on}(u, s, t) \cdot p_{unit\_idle\_heat\_rate}(u, n_{in}, n_{out}, s, t) \\ & + v_{units\_started\_up}(u, s, t) \cdot p_{unit\_start\_flow}(u, n_{in}, n_{out}, s, t) \\ & \forall (u,n_{in},n_{out},s,t) \in unit\_pw\_heat\_rate\_indices \\ \end{aligned}\]

Bounds on commodity flows

Upper bound on cumulated unit flows

To impose a limit on the cumulative amount of certain commodity flows, a cumulative bound can be set by defining the parameter max_cum_in_unit_flow_bound for entire optimization window:

\[\begin{aligned} & \sum_{\substack{(u,n,d,s,t') \in unit\_flow\_indices: \\ (u,n,d,t') \, \in \, (ug,ng,d)}} v_{unit\_flow}(u,n,d,s,t') \cdot \Delta t' \\ & <= p_{max\_cum\_unit\_flow\_bound}(ug,ng,d,s,t) \\ & \forall (ug,ng,d) \in ind(p_{max\_cum\_unit\_flow\_bound}) \end{aligned}\]

(Comment 2021-04-29: Currently under development)

Network constraints

Static constraints

Capacity constraint on connections

In a multi-commodity setting, there can be different commodities entering/leaving a certain connection. These can be energy-related commodities (e.g., electricity, natural gas, etc.), emissions, or other commodities (e.g., water, steel). The connection_capacity should be specified for at least one connection__to_node or connection__from_node relationship, in order to trigger a constraint on the maximum commodity flows to this location in each time step. When desirable, the capacity can be specified for a group of nodes (e.g. combined capacity for multiple products). Note that the conversion factor connection_conv_cap_to_flow has a default value of 1, but can be adjusted in case the unit of measurement for the capacity is different to the connection flows unit of measurement.

\[\begin{aligned} & \sum_{\substack{(conn,n,d,s,t') \in connection\_flow\_indices: \\ (conn,n,d,s,t') \, \in \, (conn,ng,d,s,t)}} v_{connection\_flow}(conn,n,d,s,t') \cdot \Delta t' \\ & - \sum_{\substack{(conn,n,d_{reverse},s,t') \in connection\_flow\_indices: \\ (conn,n,s,t') \, \in \, (conn,ng,s,t) \\ d_{reverse} != d}} v_{connection\_flow}(conn,n,d_{reverse},s,t') \cdot \Delta t' \\ & <= p_{connection\_capacity}(conn,ng,d,s,t) \\ & \cdot p_{connection\_availability\_factor}(conn,s,t) \\ & \cdot p_{connection\_conv\_cap\_to\_flow}(conn,ng,d,s,t) \Delta t\\ & \forall (conn,ng,d) \in ind(p_{connection\_capacity}): \\ & \nexists p_{candidate\_connections}(conn)\\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

If the connection is a candidate_connections, i.e. can be invested in, the connection capacity constraint translates to:

\[\begin{aligned} & \sum_{\substack{(conn,n,d,s,t') \in connection\_flow\_indices: \\ (conn,n,d,s,t') \, \in \, (conn,ng,d,s,t)}} v_{connection\_flow}(conn,n,d,s,t') \cdot \Delta t' \\ & - \sum_{\substack{(conn,n,d_{reverse},s,t') \in connection\_flow\_indices: \\ (conn,n,s,t') \, \in \, (conn,ng,s,t) \\ d_{reverse} != d}} v_{connection\_flow}(conn,n,d_{reverse},s,t') \cdot \Delta t' \\ & <= p_{connection\_capacity}(conn,ng,d,s,t) \\ & \cdot p_{connection\_availability\_factor}(conn,s,t) \\ & \cdot p_{connection\_conv\_cap\_to\_flow}(conn,ng,d,s,t) \Delta t\\ & \cdot \sum_{\substack{(conn,s,t') \in connections\_invested\_available\_indices: \\ (conn,s,t') \, \in \, (conn,s,t\_in\_t(t_{short})}} v_{connections\_invest\_available(conn, s, t)} & \forall (conn,ng,d) \in ind(p_{connection\_capacity}): \\ & \exists p_{candidate\_connections}(conn)\\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Fixed ratio between outgoing and incoming flows of a connection

By defining the parameters fix_ratio_out_in_connection_flow, max_ratio_out_in_connection_flow or min_ratio_out_in_connection_flow, a ratio can be set between outgoing and incoming flows from and to a connection.

In the most general form of the equation, two node groups are defined (an input node group $ng_{in}$ and an output node group $ng_{out}$), and a linear relationship is expressed between both node groups. Note that whenever the relationship is specified between groups of multiple nodes, there remains a degree of freedom regarding the composition of the input node flows within group $ng_{in}$ and the output node flows within group $ng_{out}$.

The constraint given below enforces a fixed, maximum or minimum ratio between outgoing and incoming connection_flow. Note that the potential node groups, that the parameters fix_ratio_out_in_connection_flow, max_ratio_out_in_connection_flow and min_ratio_out_in_connection_flow are defined on, are getting internally expanded to the members of the node group within the connection_flow_indices.

\[\begin{aligned} & \sum_{\substack{(conn,n,d,s,t_{out}) \in connection\_flow\_indices: \\ (conn,n,d,s,t_{out}) \, \in \, (conn,ng_{out},:to\_node,s,t)}} v_{connection\_flow}(conn,n,d,s,t_{out}) \cdot \Delta t_{out} \\ & \{ \\ & == p_{fix\_ratio\_out\_in\_connection\_flow}(conn,ng_{out},ng_{in},s,t), \\ & <= p_{max\_ratio\_out\_in\_connection\_flow}(conn,ng_{out},ng_{in},s,t), \\ & >= p_{min\_ratio\_out\_in\_connection\_flow}(conn,ng_{out},ng_{in},s,t)\\ & \} \\ & \cdot \sum_{\substack{(conn,n,d,s,t_{in}) \in connection\_flow\_indices:\\ (conn,n,d,s,t_{in}) \in (conn,ng_{in},:from\_node,s,t)}} v_{connection\_flow}(conn,n,d,s,t_{in}) \cdot \Delta t_{in} \\ & \forall (conn, ng_{out}, ng_{in}) \in ind(p_{\{fix,max,min\}\_ratio\_out\_in\_connection\_flow}), \\ & \forall t \in time\_slices, \forall s \in stochastic\_path \end{aligned}\]

Specific network representation

In the following, the different specific network representations are introduced. While the Static constraints find application in any of the different networks, the following equations are specific to the discussed use cases. Currently, SpineOpt incorporated equations for pressure driven gas networks, nodal lossless DC power flows and PTDF based lossless DC power flow.

Pressure driven gas transfer

For gas pipelines it can be relevant a pressure driven gas transfer can be modelled, i.e. to account for linepack flexibility. Generally speaking, the main challenges related to pressure driven gas transfers are the non-convexities associated with the Weymouth equation. In SpineOpt, a convexified MILP representation has been implemented, which as been presented in Schwele - Coordination of Power and Natural Gas Systems: Convexification Approaches for Linepack Modeling. The approximation approach is based on the Taylor series expansion around fixed pressure points.

In addition to the already known variables, such as connection_flow and node_state, the start and end points of a gas pipeline connection are associated with the variable node_pressure. The variable is triggered by the has_pressure parameter. For more details on how to set up a gas pipeline, see also the advanced concept section on pressure driven gas transfer.

Maximum node pressure

In order to impose an upper limit on the maximum pressure at a node the maximum node pressure constraint can be included, by defining the parameter max_node_pressure which triggers the following constraint:

\[\begin{aligned} & \sum_{\substack{(n,s,t') \in node\_pressure\_indices: \\ (n,s,t') \, \in \, (n,s,t)}} v_{node\_pressure}(n,s,t') \cdot \Delta t' \\ & <= p_{max\_node\_pressure}(ng,s,t) \cdot \Delta t \\ & \forall (ng) \in ind(p_{max\_node\_pressure}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

As indicated in the equation, the parameter max_node_pressure can also be defined on a node group, in order to impose an upper limit on the aggregated node_pressure within one node group.

Minimum node pressure

In order to impose a lower limit on the pressure at a node the maximum node pressure constraint can be included, by defining the parameter min_node_pressure which triggers the following constraint:

\[\begin{aligned} & \sum_{\substack{(n,s,t') \in node\_pressure\_indices: \\ (n,s,t') \, \in \, (n,s,t)}} v_{node\_pressure}(n,s,t') \cdot \Delta t' \\ & >= p_{min\_node\_pressure}(ng,s,t) \cdot \Delta t \\ & \forall (ng) \in ind(p_{min\_node\_pressure}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

As indicated in the equation, the parameter min_node_pressure can also be defined on a node group, in order to impose a lower limit on the aggregated node_pressure within one node group.

Constraint on the pressure ratio between two nodes

If a compression station is located in between two nodes, the connection is considered to be active and a compression ratio between the two nodes can be imposed. The parameter compression_factor needs to be defined on a connection__node__node relationship, where the first node corresponds the origin node, before the compression, while the second node corresponds to the destination node, after compression. The existence of this parameter will trigger the following constraint:

\[\begin{aligned} & \sum_{\substack{(n,s,t') \in node\_pressure\_indices: \\ (n,s,t') \, \in \, (ng2,s,t)}} v_{node\_pressure}(n,s,t') \cdot \Delta t' \\ & <= p_{compression\_factor}(conn,ng1,ng2,s,t) \\ & \sum_{\substack{(n,s,t') \in node\_pressure\_indices: \\ (n,s,t') \, \in \, (ng1,s,t)}} v_{node\_pressure}(n,s,t') \cdot \Delta t' \\ & \forall (conn,ng1,ng2) \in ind(p_{compression\_factor}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Outer approximation through fixed pressure points

The Weymouth relates the average flows through a connection to the difference between the adjacent squared node pressures.

\[\begin{aligned} & ((v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2 \\ & - (v_{connection\_flow}(conn, n_{dest},:from\_node,s,t) + v_{connection\_flow}(conn, n_{orig},:to\_node,s,t))/2)\\ & \cdot\\ & |((v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2\\ & - (v_{connection\_flow}(conn, n_{dest},:from\_node,s,t) + v_{connection\_flow}(conn, n_{orig},:to\_node,s,t))/2 |) \\ & = K(conn) \cdot (v_{node\_pressure}(n_{orig},s,t)^2 - v_{node\_pressure}(n_{dest},s,t)^2) \\ \end{aligned}\]

Which can be rewritten as

\[\begin{aligned} & ((v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2 \\ & - (v_{connection\_flow}(conn, n_{dest},:from\_node,s,t) + v_{connection\_flow}(conn, n_{orig},:to\_node,s,t))/2)\\ & = \sqrt{K(conn) \cdot (v_{node\_pressure}(n_{orig},s,t)^2 - v_{node\_pressure}(n_{dest},s,t)^2)} \\ & \forall (v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2 > 0 \end{aligned}\]

and

\[ \begin{aligned} & ((v_{connection\_flow}(conn, n_{dest},:from\_node,s,t) + v_{connection\_flow}(conn, n_{orig},:to\_node,s,t))/2\\ & - (v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2) \\ & = \sqrt{K(conn) \cdot (v_{node\_pressure}(n_{dest},s,t)^2 - v_{node\_pressure}(n_{orig},s,t)^2)} \\ & \forall (v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2 < 0 \end{aligned}\]

where K corresponds to the natural gas flow constant.

The cone described by the Weymouth equation can be outer approximated by a number of tangent planes, using a set of fixed pressure points, as illustrated in Schwele - Integration of Electricity, Natural Gas and Heat Systems With Market-based Coordination. The bigM method is used to replace the sign function.

The linearized version of the Weymouth equation implemented in SpineOpt is given as follows:

\[\begin{aligned} & ((v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2 \\ & <= p_{fixed\_pressure\_constant\_1}(conn,n_{orig},n_{dest},j,s,t) \cdot v_{node\_pressure}(n_{orig},s,t) \\ & - p_{fixed\_pressure\_constant\_0}(conn,n_{orig},n_{dest},j,s,t) \cdot v_{node\_pressure}(n_{dest},s,t) \\ & + p_{big\_m} \cdot (1 - v_{binary\_gas\_connection\_flow}(conn, n_{dest}, :to\_node, s, t)) \\ & \forall (conn, n_{orig}, n_{dest}) \in ind(p_{fixed\_pressure\_constant\_1}) \\ & \forall j \in 1:n(p_{fixed\_pressure\_constant\_1(connection=conn, node1=n_{orig}, node2=n_dest)}): \\ & p_{fixed\_pressure\_constant\_1}(conn, n_{orig}, n_{dest}, i=j) != 0 \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

The parameters fixed_pressure_constant_1 and fixed_pressure_constant_0 should be defined in the database. For each considered fixed pressure point, they can be calculated as follows:

\[\begin{aligned} & p_{fixed\_pressure\_constant\_1}(conn,n_{orig},n_{dest},j) \\ & = K(conn) \cdot p_{fixed\_pressure}(n_{orig},j)/ \sqrt{p_{fixed\_pressure}(n_{orig},j)^2 - p_{fixed\_pressure}(n_{dest},j)^2}\\ & p_{fixed\_pressure\_constant\_0}(conn,n_{orig},n_{dest},j) \\ & = K(conn) \cdot p_{fixed\_pressure}(n_{dest},j)/ \sqrt{p_{fixed\_pressure}(n_{orig},j)^2 - p_{fixed\_pressure}(n_{dest},j)^2}\\ \end{aligned}\]

where K corrsponds to the natural gas flow constant.

The big_m parameter combined with the variable binary_gas_connection_flow together with the equations on unitary gas flow and on the maximum gas flow ensure that the bound on the average flow through the fixed pressure points becomes active, if the flow is in a positive direction for the observed set of connection, node1 and node2.

Enforcing unidirectional flow

As stated above, the flow through a connection can only be in one direction at at time. Whether a flow is active in a certain direction is indicated by the binary_gas_connection_flow variable, which takes a value of 1 if the direction of flow is positive. To ensure that the binary_gas_connection_flow in the opposite direction then takes the value 0, the following constraint is enforced:

\[\begin{aligned} & v_{binary\_gas\_connection\_flow}(conn, n_{orig}, :to\_node, s, t)) \\ & = (1 - v_{binary\_gas\_connection\_flow}(conn, n_{dest}, :to\_node, s, t)) \\ & \forall (n,d,s,t) \in binary\_gas\_connection\_flow\_indices\\ \end{aligned}\]

Gas connection flow capacity

To enforce that the averge flow of a connection is only in one direction, the flow in the opposite direction is forced to be 0 by the following euqation. For the connection flow in the direction of flow the parameter big_m should be chosen large enough to not become binding.

\[\begin{aligned} & ((v_{connection\_flow}(conn, n_{orig},:from\_node,s,t) + v_{connection\_flow}(conn, n_{dest},:to\_node,s,t))/2 \\ & <= p_{big\_m} \cdot v_{binary\_gas\_connection\_flow}(conn, n_{dest}, :to\_node, s, t) \\ & \forall (conn, n_{orig}, n_{dest}) \in ind(p_{fixed\_pressure\_constant\_1}) \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Linepack storage flexibility

In order to account for linepack flexibility, i.e. storage capability of a connection, the linepack storage is linked to the average pressure of the adjacent nodes by the following equation, triggered by the parameter connection_linepack_constant:

\[\begin{aligned} & v_{node\_state}(n_{stor},s,t) \Delta t \\ & = p_{connection\_linepack\_constant}(conn,n_{stor},n_{ngroup}) /2 \sum_{\substack{(n,s,t') \in node\_pressure\_indices: \\ (n,s,t') \, \in \, (ng,s,t)}} v_{node\_pressure}(n,s,t') \cdot \Delta t' \\ & \forall (conn, n_{stor}, n_{ngroup}) \in ind(p_{connection\_linepack\_constant}) \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Note that the parameter connection_linepack_constant should be defined on a connection__node__node relationship, where the first node corresponds to the linepack storage node, whereas the second node corresponds to the node group of both start and end nodes of the pipeline.

Nodebased lossless DC power flow

For the implementation of the nodebased loss DC powerflow model, a new variable node_voltage_angle is introduced. See also has_voltage_angle. For further explanation on setting up a database for nodal lossless DC power flow, see the advanced concept chapter on Lossless nodal DC power flows.

Maximum node voltage angle

In order to impose an upper limit on the maximum voltage angle at a node the maximum node voltage angle constraint can be included, by defining the parameter max_voltage_angle which triggers the following constraint:

\[\begin{aligned} & \sum_{\substack{(n,s,t') \in node\_voltage\_angle\_indices: \\ (n,s,t') \, \in \, (n,s,t)}} v_{node\_voltage\_angle}(n,s,t') \cdot \Delta t' \\ & <= p_{max\_voltage\_angle}(ng,s,t) \\ & \cdot \Delta t \\ & \forall (ng) \in ind(p_{max\_voltage\_angle}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

As indicated in the equation, the parameter max_voltage_angle can also be defined on a node group, in order to impose an upper limit on the aggregated node_voltage_angle within one node group.

Minimum node voltage angle

In order to impose a lower limit on the voltage angle at a node the maximum node voltage angle constraint can be included, by defining the parameter min_voltage_angle which triggers the following constraint:

\[\begin{aligned} & \sum_{\substack{(n,s,t') \in node\_voltage\_angle\_indices: \\ (n,s,t') \, \in \, (n,s,t)}} v_{node\_voltage\_angle}(n,s,t') \cdot \Delta t' \\ & >= p_{min\_voltage\_angle}(ng,s,t) \\ & \cdot \Delta t \\ & \forall (ng) \in ind(p_{min\_voltage\_angle}), \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

As indicated in the equation, the parameter min_voltage_angle can also be defined on a node group, in order to impose a lower limit on the aggregated node_voltage_angle within one node group.

Voltage angle to connection flows

To link the flow over a connection to the voltage angles of the adjacent nodes, the following constraint is imposed. Note that this constraint is only generated if the parameter connection_reactance is defined for a connection__node__node relationship and if a fix_ratio_out_in_connection_flow is defined for the corresponding connection, node, node tuples.

\[\begin{aligned} & + \sum_{\substack{(conn,n',d,s,t) \in connection\_flow\_indices: \\ d_{from} == :from\_node \\ n' \in n_{from}}} v_{connection\_flow}(conn,n',d,s,t)\\ & - \sum_{\substack{(conn,n',d,s,t) \in connection\_flow\_indices: \\ d_{from} == :from\_node \\ n' \in n_{to}}} v_{connection\_flow}(conn,n',s,t)\\ & = \\ & 1/p_{connection\_reactance}(conn) \cdot p_{connection\_reactance\_base}(conn)\\ & \cdot (\sum_{\substack{(n,s,t') \in node\_voltage\_angle\_indices: \\ (n,s,t') \, \in \, (n_{from},s,t)}} v_{node\_voltage\_angle}(n,s,t') \cdot \Delta t' \\ & - \sum_{\substack{(n,s,t') \in node\_voltage\_angle\_indices: \\ (n,s,t') \, \in \, (n_{to},s,t)}} v_{node\_voltage\_angle}(n,s,t') \cdot \Delta t' \\ & (conn, n_{to}, n_{from}) \in indices(p_{fix_ratio_out_in_connection_flow})\\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

PTDF based DC lossless powerflow

Connection intact flow PTDF

The power transfer distribution factors are a property of the network reactances. ptdf(n, c) represents the fraction of an injection at node n that will flow on connection c. The flow on connection c is then the sum over all nodes of ptdf(n, c)*net_injection(c). connection_intact_flow represents the flow on each line of the network will all candidate connections with PTDF-based flow present in the network.

\[\begin{aligned} & + v_{connection\_intact\_flow}(c, n_{to}, d_{to}, s, t) \\ & - v_{connection\_intact\_flow}(c, n_{to}, d_{from}, s, t) \\ & == \sum_{n_{inj}} \Big( v_{node\_injection}(n_{inj}, s, t) \cdot p_{ptdf}(c, n_{inj}) \Big) \\ & \forall (c,n_{to},s,t) \in connection\_ptdf\_flow\_indices \\ \end{aligned}\]

N-1 post contingency connection flow limits

The N-1 security constraint for the post-contingency flow on monitored connection, c_mon, upon the outage of contingency connection, c_conn, is formed using line outage distribution factors (lodf). lodf(c_con, c_mon) represents the fraction of the pre-contingency flow on connection c_conn that will flow on c_mon if c_conn is disconnected. If connection c_conn is disconnected, the post-contingency flow on monitored connection connection c_mon is the pre-contingency connection_flow on c_mon plus the line outage distribution factor (lodf) times the pre-contingency connection_flow on c_conn. This post-contingency flow should be less than the connection_emergency_capacity of c_mon.

\[\begin{aligned} & + v_{connection\_flow}(c_{mon}, n_{mon\_to}, d_{to}, s, t) \\ & - v_{connection\_flow}(c_{mon}, n_{mon\_to}, d_{from}, s, t) \\ & + p_{lodf}(c_{conn}, c_{mon}) \cdot \big( \\ & \quad + v_{connection\_flow}(c_{conn}, n_{conn\_to}, d_{to}, s, t) \\ & \quad - v_{connection\_flow}(c_{conn}, n_{conn\_to}, d_{from}, s, t) \big) \\ & < min( p_{connection\_emergency\_capacity}(c_{mon}, n_{conn\_to}, d_{to}, s, t), p_{connection\_emergency\_capacity}(c_{mon}, n_{conn\_to}, d_{from},s ,t)) \\ & \forall (c_{mon}, c_{conn}, s, t) \in constraint\_connection\_flow\_lodf\_indices \\ \end{aligned}\]

Investments

Investments in units

Economic lifetime of a unit

Enforces the minimum duration of a unit's investment decision. Once a unit has been invested-in, it must remain invested-in for unit_investment_lifetime.

\[\begin{aligned} & v_{units\_invested\_available}(u,s,t) \\ & >= \sum_{\substack{(u,s,t') \in units\_invested\_available\_indices: \\ t' >=t-p_{unit\_investment\_lifetime}(u,s,t), \\ t' <= t}} v_{units\_invested}(u,s,t') \\ & \forall (u,s,t) \in unit\_investment\_lifetime\_indices\\ \end{aligned}\]

Technical lifetime of a unit

(Comment 2021-04-29: Currently under development)

Available Investment Units

The number of available invested-in units at any point in time is less than the number of investment candidate units.

\[\begin{aligned} & v_{units\_invested\_available}(u,s,t) \\ & < p_{candidate\_units}(u,s,t) \\ & \forall u \in candidate\_units\_indices, \\ & \forall (u,s,t) \in units\_invested\_available\_indices\\ \end{aligned}\]

Investment transfer

units_invested represents the point-in-time decision to invest in a unit or not while units_invested_available represents the invested-in units that are available in a specific timeslice. This constraint enforces the relationship between units_invested, units_invested_available and units_mothballed in adjacent timeslices.

\[\begin{aligned} & v_{units\_invested\_available}(u,s,t_{after}) \\ & - v_{units\_invested}(u,s,t_{after}) \\ & + v_{units\_monthballed}(u,s,t_{after}) \\ & == v_{units\_invested\_available}(u,s,t_{before}) \\ & \forall (u,s,t_{after}) \in units\_invested\_available\_indices, \\ & \forall t_{before} \in t\_before\_t(t\_after=t_{after}) : t_{before} \in units\_invested\_available\_indices\\ \end{aligned}\]

Investments in connections

Available invested-in connections

The number of available invested-in connections at any point in time is less than the number of investment candidate connections.

\[\begin{aligned} & v_{connections\_invested\_available}(c,s,t) \\ & < p_{candidate\_connections}(c,s,t) \\ & \forall c \in candidate\_connections\_indices, \\ & \forall (c,s,t) \in connections\_invested\_available\_indices\\ \end{aligned}\]

Transfer of previous investments

connections_invested represents the point-in-time decision to invest in a connection or not while connections_invested_available represents the invested-in connections that are available in a specific timeslice. This constraint enforces the relationship between connections_invested, connections_invested_available and connections_decommissioned in adjacent timeslices.

\[\begin{aligned} & v_{connections\_invested\_available}(c,s,t_{after}) \\ & - v_{connections\_invested}(c,s,t_{after}) \\ & + v_{connections\_decommissioned}(c,s,t_{after}) \\ & == v_{connections\_invested\_available}(c,s,t_{before}) \\ & \forall (c,s,t_{after}) \in connections\_invested\_available\_indices, \\ & \forall t_{before} \in t\_before\_t(t\_after=t_{after}) : t_{before} \in connections\_invested\_available\_indices\\ \end{aligned}\]

Intact network ptdf-based flows on connections

Enforces the relationship between connection_intact_flow (flow with all investments assumed in force) and connection_flow connection_intact_flow is the flow on all lines with all investments assumed in place. This constraint ensures that the connection_flow is connection_intact_flow plus additional flow contributions from investment connections that are not invested in.

\[\begin{aligned} & + v_{connection\_flow}(c, n_{to}, d_{from}, s, t) \\ & - v_{connection\_flow}(c, n_{to}, d_{to}, s, t) \\ & - v_{connection\_intact\_flow}(c, n_{to}, d_{from}, s, t) \\ & + v_{connection\_intact\_flow}(c, n_{to}, d_{to}, s, t) \\ & ==\\ & \sum_{c_{candidate}, n_{to_candidate}} p_{lodf}(c_{candidate}, c) \cdot \Big( \\ & \qquad + v_{connection\_flow}(c_{candidate}, n_{to_candidate}, d_{from}, s, t) \\ & \qquad - v_{connection\_flow}(c_{candidate}, n_{to_candidate}, d_{to}, s, t) \\ & \qquad - v_{connection\_intact\_flow}(c_{candidate}, n_{to_candidate}, d_{from}, s, t) \\ & \qquad + v_{connection\_intact\_flow}(c_{candidate}, n_{to_candidate}, d_{to}, s, t) \Big) \\ & \forall (c,n_{to},s,t) \in connection\_flow\_intact\_flow\_indices \\ \end{aligned}\]

Intact connection flows capacity

Similarly to constraint_connection_flow_capacity, limits connection_intact_flow according to connection_capacity

\[\begin{aligned} & \sum_{\substack{(conn,n,d,s,t') \in connection\_intact\_flow\_indices: \\ (conn,n,d,s,t') \, \in \, (conn,ng,d,s,t)}} v_{connection\_intact\_flow}(conn,n,d,s,t') \cdot \Delta t' \\ & - \sum_{\substack{(conn,n,d_{reverse},s,t') \in connection\_intact\_flow\_indices: \\ (conn,n,s,t') \, \in \, (conn,ng,s,t) \\ d_{reverse} != d}} v_{connection\_intact\_flow}(conn,n,d_{reverse},s,t') \cdot \Delta t' \\ & <= p_{connection\_capacity}(conn,ng,d,s,t) \\ & \cdot p_{connection\_availability\_factor}(conn,s,t) \\ & \cdot p_{connection\_conv\_cap\_to\_flow}(conn,ng,d,s,t) \Delta t\\ & \forall (conn,ng,d) \in ind(p_{connection\_capacity}): \\ & \forall t \in time\_slices, \\ & \forall s \in stochastic\_path \end{aligned}\]

Fixed ratio between outgoing and incoming intact flows of a connection

For ptdf-based lossless DC power flow, ensures that the output flow to the to_node equals the input flow from the from_node.

\[\begin{aligned} & + v_{connection\_intact\_flow}(c, n_{out}, d_{to}, s, t) \\ & ==\\ & + v_{connection\_intact\_flow}(c, n_{in}, d_{from}, s, t) \\ & \forall (c,n_{in},n_{out},s,t) \in connection\_intact\_flow\_indices \\ \end{aligned}\]

Lower bound on candidate connection flow

For candidate connections with PTDF-based poweflow, together with constraint_candidate_connection_flow_ub, this constraint ensures that connection_flow is zero if the candidate connection is not invested-in and equals connection_intact_flow otherwise.

\[\begin{aligned} & + v_{connection\_flow}(c, n, d, s, t) \\ & >=\\ & + v_{connection\_intact\_flow}(c, n, d, s, t) \\ & - p_{connection\_capacity}(c, n, d, s, t) \cdot (p_{candidate\_connections}(c, s, t) - v_{connections\_invested\_available}(c, s, t)) \\ & \forall (c,n,d,s,t) \in constraint\_candidate\_connection\_flow\_lb\_indices \\ \end{aligned}\]

Upper bound on candidate connection flow

For candidate connections with PTDF-based poweflow, together with constraint_candidate_connection_flow_lb, this constraint ensures that connection_flow is zero if the candidate connection is not invested-in and equals connection_intact_flow otherwise.

\[\begin{aligned} & + v_{connection\_flow}(c, n, d, s, t) \\ & <=\\ & + v_{connection\_intact\_flow}(c, n, d, s, t) \\ \\ & \forall (c,n,d,s,t) \in constraint\_candidate\_connection\_flow\_ub\_indices \\ \end{aligned}\]

Economic lifetime of a connection

Enforces the minimum duration of a connection's investment decision. Once a connection has been invested-in, it must remain invested-in for connection_investment_lifetime.

\[\begin{aligned} & v_{connections\_invested\_available}(c,s,t) \\ & >= \sum_{\substack{(c,s,t') \in connections\_invested\_available\_indices: \\ t' >=t-p_{connection\_investment\_lifetime}(c,s,t), \\ t' <= t}} v_{connections\_invested}(c,s,t') \\ & \forall (c,s,t) \in connection\_investment\_lifetime\_indices\\ \end{aligned}\]

Technical lifetime of a connection

(Comment 2021-04-29: Currently under development)

Investments in storages

Note: can we actually invest in nodes that are not storages? (e.g. new location)

Available invested storages

The number of available invested-in storages at node n at any point in time is less than the number of investment candidate storages at that node.

\[\begin{aligned} & v_{storages\_invested\_available}(n,s,t) \\ & < p_{candidate\_storages}(n,s,t) \\ & \forall (n) \in candidate\_storages\_indices, \\ & \forall (n,s,t) \in storages\_invested\_available\_indices\\ \end{aligned}\]

Storage capacity transfer?

storages_invested represents the point-in-time decision to invest in storage at a node, n or not while storages_invested_available represents the invested-in storages that are available at a node in a specific timeslice. This constraint enforces the relationship between storages_invested, storages_invested_available and storages_decommissioned in adjacent timeslices.

\[\begin{aligned} & v_{storages\_invested\_available}(n,s,t_{after}) \\ & - v_{storages\_invested}(n,s,t_{after}) \\ & + v_{storages\_decommissioned}(n,s,t_{after}) \\ & == v_{storages\_invested\_available}(n,s,t_{before}) \\ & \forall (n,s,t_{after}) \in storages\_invested\_available\_indices, \\ & \forall t_{before} \in t\_before\_t(t\_after=t_{after}) : t_{before} \in storages\_invested\_available\_indices\\ \end{aligned}\]

Economic lifetime of a storage

Enforces the minimum duration of a storage investment decision at node n. Once a storage has been invested-in, it must remain invested-in for storage_investment_lifetime.

\[\begin{aligned} & v_{storages\_invested\_available}(n,s,t) \\ & >= \sum_{\substack{(n,s,t') \in storages\_invested\_available\_indices: \\ t' >=t-p_{storage\_investment\_lifetime}(n,s,t), \\ t' <= t}} v_{storages\_invested}(n,s,t') \\ & \forall (n,s,t) \in storage\_investment\_lifetime\_indices\\ \end{aligned}\]

Technical lifetime of a storage

(Comment 2021-04-29: Currently under development)

Capacity transfer

(Comment 2021-04-29: Currently under development)

Early retirement of capacity

(Comment 2021-04-29: Currently under development)

Benders decomposition

This section describes the high-level formulation of the benders-decomposed problem.

Taking the simple example of minimising capacity and operating cost for a fleet of units with a linear cost coefficient $`operational\_cost_u`$ :

\[\begin{aligned} minimise:& \\ &+ \sum_u p_{unit\_investment\_cost}(u) \cdot v_{units\_invested}(u)\\ &+ \sum_{u, n, t} p_{operational\_cost} \cdot v_{unit\_flow}(u, n, t)\\ subject\ to:& \\ &flow_{u,n,t} \le p_{unit\_capacity}(u, n, t) \cdot (v_{units\_available} + v_{units\_invested\_available}(u, n, t))\\ &\sum_{u,n,t} v_{unit\_flow}(u,t) = p_{demand}(n, t) \end{aligned}\]

So this is a single problem that can't be decoupled over t because the investment variables units_invested_available couple the timesteps together. If units_invested_available were a constant in the problem, then all t's could be solved individually. This is the basic idea in Benders decomposition. We decompose the problem into a master problem and sub problems with the master problem optimising the coupling investment variables which are treated as constants in the sub problems.

The master problem in the initial benders iteration is simply to minimise total investment costs:

\[\begin{aligned} minimise &Z: \\ &Z \ge \sum_u p_{unit\_investment\_cost}(u) \cdot v_{units\_invested}(u)\\ \end{aligned} \]

The solution to this problem yields values for the investment variables which are fixed as $`\overline{units\_invested_u}`$ in the sub problem and will be zero in the first iteration.

The sub problem for benders iteration b then becomes :

\[\begin{aligned} minimise:& \\ obj_b = &+ \sum_{u,n,t} p_{operational\_cost}(u) \cdot v_{unit\_flow}(u,n,t)\\ subject\ to:& \\ &v_{unit_flow}(u,n,t) \le p_{unit\_capacity}(u) \cdot (v_{units\_available}(u,t) + p_{units\_invested\_available}(u, t)) \qquad \mu_{b,u,t} \\ &\sum_{u,n,t} v_{unit_flow}(u, n, t) = p_{demand}(n, t) \\ \\ \end{aligned}\]

This sub problem can be solved individually for each t. This is pretty trivial in this small example but if we consider a single t to be a single rolling horizon instead, decoupling the investment variables means that each rolling horizon can be solved individually rather than having to solve the entire model horizon as a single problem.

\[`\mu_{u,t}`\]

is the marginal value of the capacity constraint and can be interpreted as the decrease in the objective function at time t for an additional MW of flow from unit u. This information is used to construct a benders cut which represents the reduction in the sub problem objective function which is possible in this benders iteration by adjusting the variable units_investment. This is effectively the decrease in operating costs possible by adding another unit of type u and is expressed as :

\[`obj_{b} + \sum_{u,t}p_{unit\_capacity}(u,n,t) \cdot \mu_{b,u,t} \cdot (v_{units\_invested}(u,t) - p_{units\_invested}(u,b,t))`\]

In the first benders iteration, the value of the investment variables will have been zero so $`p_{units\_invested}(u,b,t)`$ will have the value of zero and thus the expression represents the total reduction in cost from an addition of a new unit of type u. This Benders cut is added to the master problem which then becomes, for each subsequent benders iteration, b:

\[\begin{aligned} minimise &Z: \\ &Z \ge \sum_{u,t} p_{unit\_investment\_cost}(u) \cdot v_{units\_invested}(u,t)\\ subject\ to:& \\ Z \ge& + \sum_u p_{unit\_investment\_cost}(u) \cdot v_{units\_invested}(u,t)\\ & + \sum_{u,t}p_{unit\_capacity}(u,t) \cdot \mu_{b,u,t} \cdot (v_{units\_invested}(u,t) - p_{units\_invested}(u,b,t)) \qquad \forall b \\ \end{aligned}\]

Note the benders cuts are added as inequalities because they represent an upper bound on the value we are going to get from adjusting the master problem variables in that benders iteration. If we consider the example of renewable generation - because it's marginal cost is zero, on the first benders iteration, it could look like there would be a lot of value in increasing the capacity because of the marginal values from the sub problems. However, when the capacity variables are increased accordingly and curtailment occurs in the sub-problems, the marginal values will be zero when curtailment occurs and so, other resources may become optimal in subsequent iterations.

This is a simple example but it illustrates the general strategy. The algorithm pseudo code looks something like this:

  initialise master problem
  initialise sub problem
  solve first master problem
  create master problem variable time series
  solve rolling spine opt model
  save zipped marginal values
  while master problem not converged
      update master problem
      solve master problem
      update master problem variable timeseries for benders iteration b
      rewind sub problem
      update sub problem
      solve rolling spine opt model
      save zipped marginal values
      test for convergence
  end

Benders cuts

The benders cuts for the problem including all investments in candidate connections, storages and units is given below.

\[\begin{aligned} &v_{objective\_lower\_bound}(b)\\ &>=\\ & + \sum_{u,s,t} p_{units\_invested\_available\_mv}(u,t,b) \cdot \lbrack v_{units\_invested\_available}(u,s,t)-p_{units\_invested\_available\_bi}(u,t,b) \rbrack \\ & + \sum_{c,s,t} p_{connections\_invested\_available\_mv}(c,t,b) \cdot \lbrack v_{connections\_invested\_available}(c,s,t)-p_{connections\_invested\_available\_bi}(c,t,b) \rbrack \\ & + \sum_{n,s,t} p_{storages\_invested\_available\_mv}(n,t,b) \cdot \lbrack v_{storages\_invested\_available}(n,s,t)-p_{storages\_invested\_available\_bi}(n,t,b) \rbrack \\ \end{aligned}\]

where

\[`p_{units\_invested\_available\_mv}`\]

is the reduced cost of the units_invested_available fixed sub-problem variable, representing the reduction in operating costs possible from an investment in a unit of this type, $`p_{connections\_invested\_available\_mv}`$ is the reduced cost of the connections_invested_available fixed sub-problem variable, representing the reduction in operating costs possible from an investment in a connection of this type, $`p_{storages\_invested\_available\_mv}`$ is the reduced cost of the storages_invested_available fixed sub-problem variable, representing the reduction in operating costs possible from an investment in a storage of this type, $`p_{units\_invested\_available\_bi}(u,t,b)`$ is the value of the fixed sub problem variable units_invested_available(u,t) in benders iteration b, $`p_{connections\_invested\_available\_bi}(c,t,b)`$ is the value of the fixed sub problem variable connections_invested_available(c,t) in benders iteration b and $`p_{storages\_invested\_available\_bi}(n,t,b)`$ is the value of the fixed sub problem variable storages_invested_available(n,t) in benders iteration b

User constraints

User constraint

The user_constraint is a generic data-driven custom constraint, which allows for defining constraints involving multiple units, nodes, or connections. The constraint_sense parameter changes the sense of the user_constraint, while the right_hand_side parameter allows for defining the constant terms of the constraint.

Coefficients for the different variables appearing in the user_constraint are defined using relationships, like e.g. unit__from_node__user_constraint and connection__to_node__user_constraint for unit_flow and connection_flow variables, or unit__user_constraint and node__user_constraint for units_on, units_started_up, and node_state variables.

For more information, see the dedicated article on User Constraints

\[\begin{aligned} &+\sum_{\substack{u,n \in unit\_\_node\_\_user\_constraint(uc),t,s}} \\ & \begin{cases} \begin{aligned} \sum_{\substack{op}} v_{unit\_flow\_op}(u,n,d,op,s,t) \cdot p_{unit\_flow\_coefficient}(u,n,op,uc,s,t) \qquad &\text{if } \vert operating\_points(u)\vert > 1\\ v_{unit\_flow}(u,n,d,s,t) \cdot p_{unit\_flow\_coefficient}(u,n,uc,s,t) \qquad &\text{otherwise}\\ \end{aligned} \end{cases}\\ &+\sum_{\substack{u \in unit\_\_user\_constraint(uc),t,s}} v_{units\_started\_up}(u,s,t) \cdot p_{units\_started\_up\_coefficient}(u,uc,s,t)\\ &+\sum_{\substack{u \in unit\_\_user\_constraint(uc),t,s}} v_{units\_on}(u,s,t) \cdot p_{units\_on\_coefficient}(u,uc,s,t)\\ &+\sum_{\substack{c,n \in connection\_\_node\_\_user\_constraint(uc),t,s}} v_{connection\_flow}(c,n,d,s,t) \cdot p_{connection\_flow\_coefficient}(c,n,uc,s,t)\\ &+\sum_{\substack{n \in node\_\_user\_constraint(uc),t,s}} v_{node\_state}(n,s,t) \cdot p_{node\_state\_coefficient}(n,uc,s,t)\\ &+\sum_{\substack{n \in node\_\_user\_constraint(uc),t,s}} p_{demand}(n,s,t) \cdot p_{demand\_coefficient}(n,uc,s,t)\\ & \begin{cases} \begin{aligned} == \qquad &\text{if } p_{constraint\_sense}(uc) \text{= "=="}\\ >= \qquad &\text{if } p_{constraint\_sense}(uc) \text{= ">="}\\ <= \qquad &\text{otherwise}\\ \end{aligned} \end{cases}\\ &+p_{right\_hand\_side}(uc,t,s)\\ &\forall uc,t,s \in constraint\_user\_constraint\_indices\\ \end{aligned}\]