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_{ conn } v^{connection\_flow}_{(conn,n,to\_node,s,t)} - \sum_{ conn } v^{connection\_flow}_{(conn,n,from\_node,s,t)} \\ & \begin{cases} \ge & \text{if } p^{nodal\_balance\_sense} = ">=" \\ = & \text{if } p^{nodal\_balance\_sense} = "==" \\ \le & \text{if } p^{nodal\_balance\_sense} = "<=" \\ \end{cases} \\ & 0 \\ & \forall n \in node: p^{balance\_type}_{(n)} \ne balance\_type\_none \land \nexists ng \ni n : p^{balance\_type}_{(ng)} = balance\_type\_group \\ & \forall (s,t) \end{aligned}\]

See also balance_type and nodal_balance_sense.

Node injection

The node injection itself represents all local production and consumption, computed as the sum of all connected unit flows and the nodal demand. If a node corresponds to a storage node, the parameter has_state should be set to true for this node. 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)} \\ & = \\ & \left(p^{state\_coeff}_{(n, s, t-1)} \cdot v^{node\_state}_{(n, s, t-1)} - p^{state\_coeff}_{(n, s, t)} \cdot v^{node\_state}_{(n, s, t)}\right) / \Delta t \\ & - p^{frac\_state\_loss}_{(n,s,t)} \cdot v^{node\_state}_{(n, s, t)} \\ & + \sum_{n'} p^{diff\_coeff}_{(n',n,s,t)} \cdot v^{node\_state}_{(n', s, t)} - \sum_{n'} p^{diff\_coeff}_{(n,n',s,t)} \cdot v^{node\_state}_{(n, s, t)} \\ & + \sum_{ u } v^{unit\_flow}_{(u,n,to\_node,s,t)} - \sum_{ u } v^{unit\_flow}_{(u,n,from\_node,s,t)}\\ & - \left(p^{demand}_{(n,s,t)} + \sum_{ng \ni n} p^{fractional\_demand}_{(n,s,t)} \cdot p^{demand}_{(ng,s,t)}\right) \\ & + v^{node\_slack\_pos}_{(n,s,t)} - v^{node\_slack\_neg}_{(n,s,t)} \\ & \forall n \in node: p^{has\_state}_{(n)}\\ & \forall (s, t) \end{aligned}\]

See also state_coeff, frac_state_loss, diff_coeff, node__node, unit__from_node, unit__to_node, demand, fractional_demand, has_state.

Node state capacity

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

\[v^{node\_state}_{(n, s, t)} \leq p^{node\_state\_cap}_{(n, s, t)} \quad \forall n \in node : p^{has\_state}_{(n)}, \, \forall (s,t)\]

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.

See also node_state_cap, has_state.

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 constraint:

\[v^{node\_state}_{(n, s, start(tb))} \geq v^{node\_state}_{(n, s, end(tb))} \qquad \forall (n,tb) \in indices(p^{cyclic\_condition}): p^{cyclic\_condition}_{(n,tb)}\]

See also cyclic_condition.

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 flows of a unit

By specifying the parameters fix_ratio_out_in_unit_flow, fix_ratio_in_out_unit_flow, fix_ratio_in_in_unit_flow, and/or fix_ratio_out_out_unit_flow, a fix ratio can be set between, respectively, outgoing and incoming flows from and to a unit, incoming and outgoing flows to and from a unit, two incoming flows to a unit, and/or two outgoing flows from a unit.

Similary, a minimum ratio between flows can be set by specifying min_ratio_out_in_unit_flow, min_ratio_in_out_unit_flow, min_ratio_in_in_unit_flow, and/or min_ratio_out_out_unit_flow.

Finally, a maximum ratio can be set by specifying max_ratio_out_in_unit_flow, max_ratio_in_out_unit_flow, max_ratio_in_in_unit_flow, and/or max_ratio_out_out_unit_flow.

For example, whenever there is only a single input node and a single output node, fix_ratio_out_in_unit_flow relates to the notion of efficiency. Also, fix_ratio_in_out_unit_flow can for instance be used to relate emissions to input primary fuel flows.

The constraint below is written for fix_ratio_out_in_unit_flow, but equivalent formulations exist for the other 11 cases described above.

\[\begin{aligned} & \sum_{n \in ng_{out}} v^{unit\_flow}_{(u,n,from\_node,s,t)} \\ & = \\ & p^{fix\_ratio\_out\_in\_unit\_flow}_{(u, ng_{out}, ng_{in},s,t)} \cdot \sum_{n \in ng_{in}} v^{unit\_flow}_{(u,n,to\_node,s,t)} \\ & + p^{fix\_units\_on\_coefficient\_out\_in}_{(u,ng_{out},ng_{in},s,t)} \cdot v^{units\_on}_{(u,s,t)} \\ & \forall (u, ng_{out}, ng_{in}) \in indices(p^{fix\_ratio\_out\_in\_unit\_flow}) \\ & \forall (s,t) \end{aligned}\]

Note

If any of the above mentioned ratio parameters is specified for a node group, then the ratio is enforced over the sum of flows from or to that group. In this case, there remains a degree of freedom regarding the composition of flows within the group.

See also fix_ratio_out_in_unit_flow, fix_units_on_coefficient_out_in.

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 must 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_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[ \neg p^{is\_reserve\_node}_{(n)} \right]\\ & + \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[ p^{is\_reserve\_node}_{(n)} \land p^{upward\_reserve}_{(n)} \land \neg p^{is\_non\_spinning}_{(n)} \right]\\ & \le \\ & p^{unit\_capacity}_{(u,ng,d,s,t)} \cdot p^{unit\_availability\_factor}_{(u,s,t)} \cdot p^{unit\_conv\_cap\_to\_flow}_{(u,ng,d,s,t)} \\ & \cdot ( \\ & \qquad v^{units\_on}_{(u,s,t)} \\ & \qquad - \left(1 - p^{shut\_down\_limit}_{(u,ng,d,s,t)}\right) \cdot \left( v^{units\_shut\_down}_{(u,s,t+1)} + \sum_{ n \in ng } v^{nonspin\_units\_shut\_down}_{(u,n,s,t)} \right) \\ & \qquad - \left(1 - p^{start\_up\_limit}_{(u,ng,d,s,t)}\right) \cdot v^{units\_started\_up}_{(u,s,t)} \\ & ) \\ & \forall (u,ng,d) \in indices(p^{unit\_capacity}) \\ & \forall (s,t) \end{aligned}\]

where

\[[p] \vcentcolon = \begin{cases} 1 & \text{if } p \text{ is true;}\\ 0 & \text{otherwise.} \end{cases}\]

Note

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.

Note

The above formulation is valid for time-slices whose duration is greater than the minimum up time of the unit. This ensures that the unit is not online for exactly one time-slice, which might result in an infeasibility if this formulation was used. Instead, for time-slices whose duration is lower or equal than the minimum up time of the unit there is a similar formulation, but the details are omitted for brevity.

Note

The above formulation is valid for flows going from a unit to a node (i.e., output flows). For flows going from a node to a unit (i.e., input flows) the direction of the reserves is switched (downwards becomes upwards, non-spinning units shut-down becomes non-spinning units started-up). The details are omitted for brevity.

See also is_reserve_node, upward_reserve, is_non_spinning, unit_capacity, unit_availability_factor, unit_conv_cap_to_flow, start_up_limit, shut_down_limit.

Constraint on minimum operating point

The minimum operating point of a unit is based on the unit_flows of input or output nodes/node groups.

\[\begin{aligned} & \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[\neg p^{is\_reserve\_node}_{(n)}\right] - \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[p^{is\_reserve\_node}_{(n)} \land p^{downward\_reserve}_{(n)}\right] \\ & \ge p^{minimum\_operating\_point}_{(u,ng,d,s,t)} \cdot p^{unit\_capacity}_{(u,ng,d,s,t)} \cdot p^{unit\_conv\_cap\_to\_flow}_{(u,ng,d,s,t)} \\ & \cdot \left( v^{units\_on}_{(u,s,t)} - \sum_{ n \in ng } v^{nonspin\_units\_shut\_down}_{(u,n,s,t)} \right) \\ & \forall (u,ng,d) \in indices(p^{minimum\_operating\_point}) \\ & \forall (s,t) \end{aligned}\]

where

\[[p] \vcentcolon = \begin{cases} 1 & \text{if } p \text{ is true;}\\ 0 & \text{otherwise.} \end{cases}\]

Note

The above formulation is valid for flows going from a unit to a node (i.e., output flows). For flows going from a node to a unit (i.e., input flows) the direction of the reserves is switched (downwards becomes upwards, non-spinning units shut-down becomes non-spinning units started-up). The details are omitted for brevity.

See also is_reserve_node, downward_reserve, is_non_spinning, minimum_operating_point, unit_capacity, unit_conv_cap_to_flow

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 minimum down time
  • constraint on minimum up time
  • constraint on ramp rates
  • constraint on reserve provision
Bound on available units

The aggregated available units are constrained by the parameter number_of_units , the variable number of invested units units_invested_available less the number of units on outage units_out_of_service:

\[\begin{aligned} & v^{units\_available}_{(u,s,t)} \leq p^{number\_of\_units}_{(u,s,t)} + v^{units\_invested\_available}_{(u,s,t)} + v^{units\_out\_of\_service}_{(u,s,t)}\\ & \forall u \in unit \\ & \forall (s,t) \end{aligned}\]

See also number_of_units.

Bound on online units

The number of online units needs to be restricted to the aggregated available units:

\[v^{units\_on}_{(u,s,t)} \leq v^{units\_available}_{(u,s,t)} \quad \forall u \in unit, \, \forall (s,t)\]

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)} - v^{units\_started\_up}_{(u,s,t)} + v^{units\_shut\_down}_{(u,s,t)} = v^{units\_on}_{(u,s,t-1)} \\ & \forall u \in unit \\ & \forall (s,t) \end{aligned}\]

Minimum down time

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

\[\begin{aligned} & p^{number\_of\_units}_{(u,s,t)} + v^{units\_invested\_available}_{(u,s,t)} - v^{units\_on}_{(u,s,t)} \\ & - \sum_{n} v^{nonspin\_units\_started\_up}_{(u,n,s,t)} \\ & \geq \sum_{t'=t-p^{min\_down\_time}_{(u,s,t)} + 1}^{t} v^{units\_shut\_down}_{(u,s,t')} \\ & \forall u \in indices(p^{min\_down\_time})\\ & \forall (s,t) \end{aligned}\]

See also number_of_units, min_down_time.

Minimum up time

Similarly to the minimum down time constraint, a minimum time that a unit needs to remain online after a start up 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_{n} v^{nonspin\_units\_shut\_down}_{(u,n,s,t)} \\ & \geq \sum_{t'=t-p^{min\_up\_time}_{(u,s,t)} +1 }^{t} v^{units\_started\_up}_{(u,s,t')} \\ & \forall u \in indices(p^{min\_up\_time})\\ & \forall (s,t) \end{aligned}\]

See also min_up_time

Ramping constraints

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

For dispatchable units, additional ramping constraints can be introduced. For setting up ramping characteristics of units see Ramping.

Ramp up limit

Limit the increase of unit_flow over a time period of one duration_unit according to the start_up_limit and ramp_up_limit parameter values.

\[\begin{aligned} & \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[ \neg p^{is\_reserve\_node}_{(n)} \right] \\ & - \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t-1)} \cdot \left[ \neg p^{is\_reserve\_node}_{(n)} \right] \\ & + \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[ p^{is\_reserve\_node}_{(n)} \land p^{upward\_reserve}_{(n)} \right] \\ & \le ( \\ & \qquad \left(p^{start\_up\_limit}_{(u,ng,d,s,t)} - p^{minimum\_operating\_point}_{(u,ng,d,s,t)} - p^{ramp\_up\_limit}_{(u,ng,d,s,t)}\right) \cdot v^{units\_started\_up}_{(u,s,t)} \\ & \qquad + \left(p^{minimum\_operating\_point}_{(u,ng,d,s,t)} + p^{ramp\_up\_limit}_{(u,ng,d,s,t)}\right) \cdot v^{units\_on}_{(u,s,t)} \\ & \qquad - p^{minimum\_operating\_point}_{(u,ng,d,s,t)} \cdot v^{units\_on}_{(u,s,t-1)} \\ & ) \cdot p^{unit\_capacity}_{(u,ng,d,s,t)} \cdot p^{unit\_conv\_cap\_to\_flow}_{(u,ng,d,s,t)} \cdot \Delta t \\ & \forall (u,ng,d) \in indices(p^{ramp\_up\_limit}) \cup indices(p^{start\_up\_limit}) \\ & \forall (s,t) \end{aligned}\]

where

\[[p] \vcentcolon = \begin{cases} 1 & \text{if } p \text{ is true;}\\ 0 & \text{otherwise.} \end{cases}\]

See also is_reserve_node, upward_reserve, unit_capacity, unit_conv_cap_to_flow, ramp_up_limit, start_up_limit, minimum_operating_point.

Ramp down limit

Limit the decrease of unit_flow over a time period of one duration_unit according to the shut_down_limit and ramp_down_limit parameter values.

\[\begin{aligned} & \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t-1)} \cdot \left[ \neg p^{is\_reserve\_node}_{(n)} \right] \\ & - \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[ \neg p^{is\_reserve\_node}_{(n)} \right] \\ & + \sum_{ n \in ng } v^{unit\_flow}_{(u,n,d,s,t)} \cdot \left[ p^{is\_reserve\_node}_{(n)} \land p^{downward\_reserve}_{(n)} \right] \\ & \le ( \\ & \qquad \left(p^{shut\_down\_limit}_{(u,ng,d,s,t)} - p^{minimum\_operating\_point}_{(u,ng,d,s,t)} - p^{ramp\_down\_limit}_{(u,ng,d,s,t)}\right) \cdot v^{units\_shut\_down}_{(u,s,t)} \\ & \qquad + \left(p^{minimum\_operating\_point}_{(u,ng,d,s,t)} + p^{ramp\_down\_limit}_{(u,ng,d,s,t)}\right) \cdot v^{units\_on}_{(u,s,t-1)} \\ & \qquad - p^{minimum\_operating\_point}_{(u,ng,d,s,t)} \cdot v^{units\_on}_{(u,s,t)} \\ & ) \cdot p^{unit\_capacity}_{(u,ng,d,s,t)} \cdot p^{unit\_conv\_cap\_to\_flow}_{(u,ng,d,s,t)} \cdot \Delta t \\ & \forall (u,ng,d) \in indices(p^{ramp\_down\_limit}) \cup indices(p^{shut\_down\_limit}) \\ & \forall (s,t) \end{aligned}\]

where

\[[p] \vcentcolon = \begin{cases} 1 & \text{if } p \text{ is true;}\\ 0 & \text{otherwise.} \end{cases}\]

See also is_reserve_node, downward_reserve, unit_capacity, unit_conv_cap_to_flow, ramp_down_limit, shut_down_limit, minimum_operating_point.

Reserve constraints

Constraint on minimum node state for reserve provision

(Comment 2023-11-20: Currently under development)

Operating segments

Operating segments of units

Limit the maximum number of each activated segment unit_flow_op_active cannot be higher than the number of online units. This constraint is activated only when parameter ordered_unit_flow_op is set true.

\[\begin{aligned} & v^{unit\_flow\_op\_active}_{(u,n,d,op,s,t)} \leq v^{units\_on}_{(u,s,t)} \\ & \forall (u,n,d) \in indices(p^{operating\_points}): p^{ordered\_unit\_flow\_op}_{(u,n,d)} \\ & \forall op \in \{ 1, \ldots, \left\|p^{operating\_points}_{(u,n,d)}\right\| \} \\ & \forall (s,t) \end{aligned}\]

See also operating_points, ordered_unit_flow_op.

Rank operating segments as per the index of operating points

Rank operating segments by enforcing that the variable unit_flow_op_active of operating point i can only be active if previous operating point i-1 is also active. The first segment does not need this constraint.

\[\begin{aligned} & v^{unit\_flow\_op\_active}_{(u,n,d,op,s,t)} \leq v^{unit\_flow\_op\_active}_{(u,n,d,op-1,s,t)} \\ & \forall (u,n,d) \in indices(p^{operating\_points}): p^{ordered\_unit\_flow\_op}_{(u,n,d)} \\ & \forall op \in \{ 2, \ldots, \left\|p^{operating\_points}_{(u,n,d)}\right\| \} \\ & \forall (s,t) \end{aligned}\]

See also operating_points, ordered_unit_flow_op.

Operating segments of units

If the segments of a unit_flow, i.e. unit_flow_op is not ordered according to the rank of the unit_flow's operating_points (parameter ordered_unit_flow_op is false), the operating segment variable unit_flow_op is only bounded by the difference between successive operating_points adjusted for available capacity. If the order is enforced on the segments (parameter ordered_unit_flow_op is true), unit_flow_op can only be active if the segment is active (variable unit_flow_op_active is true) besides being bounded by the segment capacity.

\[\begin{aligned} & v^{unit\_flow\_op}_{(u, n, d, op, s, t)} \\ & \leq p^{unit\_capacity}_{(u, n, d, s, t)} \cdot p^{unit\_conv\_cap\_to\_flow}_{(u, n, d, s, t)} \cdot p^{unit\_availability\_factor}_{(u, s, t)} \\ & \cdot \left( p^{operating\_points}_{(u, n, d, op, s, t)} - \begin{cases} p^{operating\_points}_{(u, n, op-1, s, t)} & \text{if } op > 1\\ 0 & \text{otherwise}\\ \end{cases} \right) \\ & \cdot \begin{cases} v^{unit\_flow\_op\_active}_{(u,n,d,op,s,t)} & \text{if } p^{ordered\_unit\_flow\_op}_{(u,s,t)} \\ v^{units\_on}_{(u,s,t)} & \text{otherwise}\\ \end{cases} \\ & \forall (u,n,d) \in indices(p^{unit\_capacity}) \cup indices(p^{operating\_points}) \\ & \forall op \in \{ 1, \ldots, \left\|p^{operating\_points}_{(u,n,d)}\right\| \} \\ & \forall (s,t) \end{aligned}\]

See also unit_capacity, unit_conv_cap_to_flow, unit_availability_factor, operating_points, ordered_unit_flow_op.

Bounding operating segments to use up its own capacity for activating the next segment

Enforce the operating point flow variable unit_flow_op at operating point i to use its full capacity if the subsequent operating point i+1 is active if parameter ordered_unit_flow_op is set true. The last segment does not need this constraint.

\[\begin{aligned} & v^{unit\_flow\_op}{(u, n, d, op, s, t)} \\ & \geq p^{unit\_capacity}_{(u, n, d, s, t)} \cdot p^{unit\_conv\_cap\_to\_flow}_{(u, n, d, s, t)} \\ & \cdot \left(p^{operating\_points}_{(u, n, op, s, t)} - \begin{cases} p^{operating\_points}_{(u, n, op-1, s, t)} & \text{if } op > 1 \\ 0 & \text{otherwise} \\ \end{cases} \right) \\ & \cdot v^{unit\_flow\_op\_active}_{(u, n, d, op+1, s, t)} \\ & \forall (u,n,d) \in indices(p^{unit\_capacity}) \cup indices(p^{operating\_points}): p^{ordered\_unit\_flow\_op}_{(u,n,d)} \\ & \forall op \in \{ 1, \ldots, \left\|p^{operating\_points}_{(u,n,d)}\right\| - 1\} \\ & \forall (s,t) \end{aligned}\]

See also unit_capacity, unit_conv_cap_to_flow, operating_points, ordered_unit_flow_op.

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, d, s, t)} = \sum_{op=1}^{\left\|p^{operating\_points}_{(u,n,d)}\right\|} v^{unit\_flow\_op}_{(u, n, d, op, s, t)} \\ & \forall (u,n,d) \in indices(p^{operating\_points}) \\ & \forall (s,t) \end{aligned}\]

See also operating_points.

Unit piecewise incremental heat rate

Implements a standard piecewise linear heat-rate function where unit_flow from a (input fuel consumption) node is equal to the sum over operating point segments of unit_flow_op to a (output electricity node) node times the corresponding incremental_heat_rate.

\[\begin{aligned} & v^{unit\_flow}_{(u, n_{in}, d, s, t)} \\ & = \sum_{op=1}^{\left\|p^{operating\_points}_{(u,n,d)}\right\|} p^{unit\_incremental\_heat\_rate}_{(u, n_{in}, n_{out}, op, s, t)} \cdot v^{unit\_flow\_op}_{(u, n_{out}, d, op, s, t)} \\ & + p^{unit\_idle\_heat\_rate}_{(u, n_{in}, n_{out}, s, t)} \cdot v^{units\_on}_{(u, s, t)} \\ & + p^{unit\_start\_flow}_{(u, n_{in}, n_{out}, s, t)} \cdot v^{units\_started\_up}_{(u, s, t)} \\ & \forall (u,n_{in},n_{out}) \in indices(p^{unit\_incremental\_heat\_rate}) \\ & \forall (s,t) \end{aligned}\]

See also unit_incremental_heat_rate, unit_idle_heat_rate, unit_start_flow.

Bounds on commodity flows

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 one of the following parameters:

A maximum cumulated flow restriction can for example be used to limit emissions or consumption of a certain commodity.

\[\begin{aligned} & \sum_{u \in ug, n \in ng} v^{unit\_flow}_{(u,n,d,s,t)} \leq p^{max\_total\_cumulated\_unit\_flow\_from\_node}_{(ug,ng,d)} \\ & \forall (ug,ng,d) \in indices(p^{max\_total\_cumulated\_unit\_flow\_from\_node}), \, \forall s \\ & \sum_{u \in ug, n \in ng} v^{unit\_flow}_{(u,n,d,s,t)} \geq p^{min\_total\_cumulated\_unit\_flow\_from\_node}_{(ug,ng,d)} \\ & \forall (ug,ng,d) \in indices(p^{min\_total\_cumulated\_unit\_flow\_from\_node}), \, \forall s \\ & \sum_{u \in ug, n \in ng} v^{unit\_flow}_{(u,n,d,s,t)} \leq p^{max\_total\_cumulated\_unit\_flow\_to\_node}_{(ug,ng,d)} \\ & \forall (ug,ng,d) \in indices(p^{max\_total\_cumulated\_unit\_flow\_to\_node}), \, \forall s \\ & \sum_{u \in ug, n \in ng} v^{unit\_flow}_{(u,n,d,s,t)} \geq p^{min\_total\_cumulated\_unit\_flow\_to\_node}_{(ug,ng,d)} \\ & \forall (ug,ng,d) \in indices(p^{min\_total\_cumulated\_unit\_flow\_to\_node}), \, \forall s \\ \end{aligned}\]

See also max_total_cumulated_unit_flow_from_node, min_total_cumulated_unit_flow_from_node, max_total_cumulated_unit_flow_to_node, min_total_cumulated_unit_flow_to_node.

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).

\[\begin{aligned} & \sum_{n \in ng} v^{connection\_flow}_{(conn,n,d,s,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)} \\ & \cdot \left( p^{number\_of\_connections}_{(conn,s,t)} + v^{connections\_invested\_available}_{(conn,s,t)} \right)\\ & \forall (conn,ng,d) \in indices(p^{connection\_capacity}) \\ & \forall (s,t) \end{aligned}\]

See also connection_capacity, connection_availability_factor, connection_conv_cap_to_flow, number_of_connections, candidate_connections

Note

For situations where the same connection handles flows to multiple nodes with different temporal resolutions, the constraint is only generated for the lowest resolution, and only the average of the higher resolution flow is constrained. In other words, what gets constrained is the "average power" (e.g. MWh/h) rather than the "instantaneous power" (e.g. MW). If instantaneous power needs to be constrained as well, then connection_capacity needs to be specified separately for each node served by the connection.

Note

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.

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.

The constraint below is written for fix_ratio_out_in_connection_flow, but equivalent formulations exist for the other two cases.

\[\begin{aligned} & \sum_{n \in ng_{out}} v^{connection\_flow}_{(conn,n,from\_node,s,t)} \\ & = \\ & p^{fix\_ratio\_out\_in\_connection\_flow}_{(conn, ng_{out}, ng_{in},s,t)} \cdot \sum_{n \in ng_{in}} v^{connection\_flow}_{(conn,n,to\_node,s,t)} \\ & \forall (conn, ng_{out}, ng_{in}) \in indices(p^{fix\_ratio\_out\_in\_connection\_flow}) \\ & \forall (s,t) \end{aligned}\]

Note

If any of the above mentioned ratio parameters is specified for a node group, then the ratio is enforced over the sum of flows from or to that group. In this case, there remains a degree of freedom regarding the composition of flows within the group.

See also fix_ratio_out_in_connection_flow.

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 parameter max_node_pressure can be specified which triggers the following constraint:

\[\sum_{n \in ng} v^{node\_pressure}_{(n,s,t)} \leq p^{max\_node\_pressure}_{(ng,s,t)} \quad \forall (ng) \in indices(p^{max\_node\_pressure}), \, \forall (s,t)\]

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.

See also max_node_pressure.

Minimum node pressure

In order to impose a lower limit on the pressure at a node the parameter min_node_pressure can be specified which triggers the following constraint:

\[\sum_{n \in ng} v^{node\_pressure}_{(n,s,t)} \geq p^{min\_node\_pressure}_{(ng,s,t)} \quad \forall (ng) \in indices(p^{min\_node\_pressure}), \, \forall (s,t)\]

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.

See also min_node_pressure.

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_{n \in ng2} v^{node\_pressure}_{(n,s,t)} \leq p^{compression\_factor}_{(conn,ng1,ng2,s,t)} \cdot \sum_{n \in ng1} v^{node\_pressure}_{(n,s,t)} \\ & \forall (conn,ng1,ng2) \in indices(p^{compression\_factor}) \\ & \forall (s,t) \end{aligned}\]

See also compression_factor.

Outer approximation through fixed pressure points

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

\[\begin{aligned} & \left( \left( v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)} \right) - \left( v^{connection\_flow}_{(conn, n_{dest},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{orig},to\_node,s,t)} \right) \right) \\ & \cdot \left\| \left( v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)} \right) - \left( v^{connection\_flow}_{(conn, n_{dest},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{orig},to\_node,s,t)} \right) \right\| \\ & = 4 \cdot K_{(conn)} \cdot \left( \left(v^{node\_pressure}_{(n_{orig},s,t)}\right)^2 - \left(v^{node\_pressure}_{(n_{dest},s,t)}\right)^2 \right) \\ \end{aligned}\]

where $K$ corresponds to the natural gas flow constant.

The above can be rewritten as

\[\begin{aligned} & \left( \left(v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)}\right) - \left(v^{connection\_flow}_{(conn, n_{dest},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{orig},to\_node,s,t)}\right) \right)\\ & = 2 \cdot \sqrt{ K_{(conn)} \cdot \left( \left(v^{node\_pressure}_{(n_{orig},s,t)}\right)^2 - \left(v^{node\_pressure}_{(n_{dest},s,t)}\right)^2 \right) } \\ & \text{if } \left( v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)} \right) > 0 \end{aligned}\]

and

\[\begin{aligned} & \left( \left( v^{connection\_flow}_{(conn, n_{dest},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{orig},to\_node,s,t)} \right) - \left( v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)} \right) \right) \\ & = 2 \cdot \sqrt{ K_{(conn)} \cdot \left( \left(v^{node\_pressure}_{(n_{dest},s,t)}\right)^2 - \left(v^{node\_pressure}_{(n_{orig},s,t)}\right)^2 \right) } \\ & \text{if } \left( v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)} \right) < 0 \end{aligned}\]

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 big M method is used to replace the sign function.

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

\[\begin{aligned} & \left. \left(v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)}\right) \middle/2 \right. \\ & \leq 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 \left(1 - v^{binary\_gas\_connection\_flow}_{(conn, n_{dest}, to\_node, s, t)}\right) \\ & \forall (conn, n_{orig}, n_{dest}) \in indices(p^{fixed\_pressure\_constant\_1}) \\ & \forall j \in \left\{1, \ldots, \left\| p^{fixed\_pressure\_constant\_1}_{(conn, n_{orig}, n_{dest})} \right\| \right\}: p^{fixed\_pressure\_constant\_1}_{(conn, n_{orig}, n_{dest}, j)} \neq 0 \\ & \forall (s,t) \end{aligned}\]

The parameters fixed_pressure_constant_1 and fixed_pressure_constant_0 should be defined. 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)} = \left. K_{(conn)} \cdot p^{fixed\_pressure}_{(n_{orig},j)} \middle/ \sqrt{ \left(p^{fixed\_pressure}_{(n_{orig},j)}\right)^2 - \left(p^{fixed\_pressure}_{(n_{dest},j)}\right)^2 }\right. \\ & p^{fixed\_pressure\_constant\_0}_{(conn,n_{orig},n_{dest},j)} = \left. K_{(conn)} \cdot p^{fixed\_pressure}_{(n_{dest},j)} \middle/ \sqrt{ \left(p^{fixed\_pressure}_{(n_{orig},j)}\right)^2 - \left(p^{fixed\_pressure}_{(n_{dest},j)}\right)^2 }\right. \\ \end{aligned}\]

where $p^{fixed\_pressure}_{(n,j)}$ is the fix pressure for node $n$ and point $j$.

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.

See also fixed_pressure_constant_1, fixed_pressure_constant_0, big_m.

Enforcing unidirectional flow

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 (conn, n_{orig}, n_{dest}) \in indices(p^{fixed\_pressure\_constant\_1}) \\ & \forall (s,t) \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 equation. For the connection flow in the direction of flow the parameter big_m should be chosen large enough not to become binding.

\[\begin{aligned} & \left. \left(v^{connection\_flow}_{(conn, n_{orig},from\_node,s,t)} + v^{connection\_flow}_{(conn, n_{dest},to\_node,s,t)}\right) \middle/2 \right. \\ & <= p^{big\_m} \cdot v^{binary\_gas\_connection\_flow}_{(conn, n_{dest}, to\_node, s, t)} \\ & \forall (conn, n_{orig}, n_{dest}) \in indices(p^{fixed\_pressure\_constant\_1}) \\ & \forall (s,t) \end{aligned}\]

See also p^{fixed_pressure_constant_1}, big_m.

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)} = \left( p^{connection\_linepack\_constant}_{(conn,n_{stor},ng)} \middle/ 2 \right) \cdot \sum_{n \in ng} v^{node\_pressure}_{(n,s,t)} \\ & \forall (conn, n_{stor}, ng) \in indices(p^{connection\_linepack\_constant}) \\ & \forall (s,t) \end{aligned}\]

Note

The parameter connection_linepack_constant should be defined on a connection__node__noderelationship, 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.

See also connection_linepack_constant

Node-based 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 parameter max_voltage_angle can be specified which triggers the following constraint:

\[\begin{aligned} & \sum_{n \in ng} v^{node\_voltage\_angle}_{(n,s,t)} \geq p^{max\_voltage\_angle}_{(ng,s,t)} \\ & \forall ng \in indices(p^{max\_voltage\_angle}) \\ & \forall (s,t) \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.

See also max_voltage_angle.

Minimum node voltage angle

In order to impose a lower limit on the voltage angle at a node the parameter min_voltage_angle can be specified which triggers the following constraint:

\[\begin{aligned} & \sum_{n \in ng} v^{node\_voltage\_angle}_{(n,s,t)} \leq p^{min\_voltage\_angle}_{(ng,s,t)} \\ & \forall ng \in indices(p^{min\_voltage\_angle}) \\ & \forall (s,t) \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.

See also min_voltage_angle.

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 object and if a fix_ratio_out_in_connection_flow is defined for a connection__node__node relationship involving that connection.

\[\begin{aligned} & \sum_{n \in ng_{from}} v^{connection\_flow}_{(conn,n,from\_node,s,t)} - \sum_{n \in ng_{to}} v^{connection\_flow}_{(conn,n,from\_node,s,t)}\\ & = \\ & \left(p^{connection\_reactance\_base}_{(conn,s,t)} \middle/ p^{connection\_reactance}_{(conn,s,t)}\right) \\ & \cdot \left(\sum_{n \in ng_{from}} v^{node\_voltage\_angle}_{(n,s,t)} - \sum_{n \in ng_{to}} v^{node\_voltage\_angle}_{(n,s,t)} \right)\\ & \forall (conn, ng_{to}, ng_{from}) \in indices(p^{fix\_ratio\_out\_in\_connection\_flow})\\ & \forall (s,t) \end{aligned}\]

See also connection_reactance, connection_reactance_base, fix_ratio_out_in_connection_flow.

PTDF based DC lossless powerflow

Connection intact flow PTDF

The power transfer distribution factors are a property of the network reactances. $p^{ptdf}_{(c, n)}$ 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 $p^{ptdf}_{(c, n)}$ multiplied by the net injection at that node. connection_intact_flow represents the flow on each line of the network with all candidate connections with PTDF-based flow present in the network.

\[\begin{aligned} & v^{connection\_intact\_flow}_{(c, n_{to}, to\_node, s, t)} - v^{connection\_intact\_flow}_{(c, n_{to}, from\_node, s, t)} \\ & = \sum_{n_{inj}} p^{ptdf}_{(c, n_{inj}, t)} \cdot v^{node\_injection}_{(n_{inj}, s, t)} \cdot \left[p^{node\_opf\_type}_{(n_{inj})} \neq node\_opf\_type\_reference \right] \\ & \forall c \in connection : p^{is\_monitored}_{(c)} \\ & \forall (s,t) \end{aligned}\]

where

\[[p] \vcentcolon = \begin{cases} 1 & \text{if } p \text{ is true;}\\ 0 & \text{otherwise.} \end{cases}\]

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 a contingency connection, $c_{cont}$, is formed using line outage distribution factors (LODF). $p^{lodf}_{(c_{cont}, c_{mon})}$ represents the fraction of the pre-contingency flow on connection $c_{cont}$ that will flow on $c_{mon}$ if the former is disconnected. If connection $c_{cont}$ is disconnected, the post-contingency flow on the monitored connection connection $c_{mon}$ is the pre-contingency connection_flow on $c_{mon}$ plus the LODF times the pre-contingency connection_flow on $c_{cont}$. 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}, to\_node, s, t)} - v^{connection\_flow}_{(c_{mon}, n_{mon\_to}, from\_node, s, t)} \\ & + p^{lodf}_{(c_{cont}, c_{mon})} \cdot \left( v^{connection\_flow}_{(c_{cont}, n_{cont\_to}, to\_node, s, t)} - v^{connection\_flow}_{(c_{cont}, n_{cont\_to}, from\_node, s, t)} \right) \\ & \leq min \left( p^{connection\_emergency\_capacity}_{(c_{mon}, n_{cont\_to}, to\_node, s, t)}, p^{connection\_emergency\_capacity}_{(c_{mon}, n_{cont\_to}, from\_node,s ,t)} \right) \\ & \forall (c_{mon}, c_{cont}) \in connection \times connection : p^{is\_monitored}_{(c_{mon})} \land p^{is\_contingency}_{(c_{cont})} \\ & \forall (s,t) \end{aligned}\]

Investments

Investments in units

Economic lifetime of a unit

(Comment 2023-05-03: Currently under development)

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)} \\ & \forall u \in unit: p^{candidate\_units}_{(u)} \neq 0 \\ & \forall (s,t) \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 at a specific time. 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)} - v^{units\_invested}_{(u,s,t)} + v^{units\_monthballed}_{(u,s,t)} = v^{units\_invested\_available}_{(u,s,t-1)} \\ & \forall u \in unit: p^{candidate\_units}_{(u)} \neq 0 \\ & \forall (s,t) \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)} \\ & \forall c \in connection: p^{candidate\_connections}_{(c)} \neq 0 \\ & \forall (s,t) \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 at a specific time. 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)} - v^{connections\_invested}_{(c,s,t)} + v^{connections\_decommissioned}_{(c,s,t)} \\ & = v^{connections\_invested\_available}_{(c,s,t-1)} \\ & \forall c \in connection: p^{candidate\_connections}_{(c)} \neq 0 \\ & \forall (s,t) \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. 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} & \left(v^{connection\_flow}_{(c, n_{to}, from\_node, s, t)} - v^{connection\_flow}_{(c, n_{to}, to\_node, s, t)} \right) - \left(v^{connection\_intact\_flow}_{(c, n_{to}, from\_node, s, t)} - v^{connection\_intact\_flow}_{(c, n_{to}, to\_node, s, t)} \right) \\ & =\\ & \sum_{c_{cand}} p^{lodf}_{(c_{cand}, c)} \cdot \left[p^{candidate\_connections}_{(c_{cand})} \neq 0 \right] \cdot \Big( \\ & \qquad \left( v^{connection\_flow}_{(c_{cand}, n_{to\_cand}, from\_node, s, t)} - v^{connection\_flow}_{(c_{cand}, n_{to\_cand}, to\_node, s, t)} \right) \\ & \qquad - \left( v^{connection\_intact\_flow}_{(c_{cand}, n_{to\_cand}, from\_node, s, t)} - v^{connection\_intact\_flow}_{(c_{cand}, n_{to\_cand}, to\_node, s, t)} \right) \\ & \Big) \\ & \forall c \in connection : p^{is\_monitored}_{(c)} \land p^{candidate\_connections}_{(c)} = 0 \\ & \forall (s,t) \end{aligned}\]

Intact connection flow capacity

Similarly to this, limits connection_intact_flow according to connection_capacity

\[\begin{aligned} & \sum_{ n \in ng } v^{connection\_intact\_flow}_{(conn,n,d,s,t)} \\ & \leq \\ & 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)} \\ & \cdot \left( p^{number\_of\_connections}_{(conn,s,t)} + p^{candidate\_connections}_{(conn,s,t)} \right) \\ & \forall (conn,ng,d) \in indices(p^{connection\_capacity}) \\ & \forall (s,t) \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 \in connection : p^{is\_monitored}_{(c)} \\ & \forall (s,t) \end{aligned}\]

Lower bound on candidate connection flow

For candidate connections with PTDF-based poweflow, together with this, 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)} \\ & \geq \\ & v^{connection\_intact\_flow}_{(c, n, d, s, t)} - p^{connection\_capacity}_{(c, n, d, s, t)} \cdot \left( p^{candidate\_connections}_{(c, s, t)} - v^{connections\_invested\_available}_{(c, s, t)} \right) \\ & \forall c \in connection : p^{candidate\_connections}_{(c)} \neq 0 \\ & \forall (s,t) \end{aligned}\]

Upper bound on candidate connection flow

For candidate connections with PTDF-based poweflow, together with this, 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)} \leq v^{connection\_intact\_flow}_{(c, n, d, s, t)} \\ & \forall c \in connection : p^{candidate\_connections}_{(c)} \neq 0 \\ & \forall (s,t) \end{aligned}\]

Economic lifetime of a connection

(Comment 2023-05-12: Currently under development)

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)} \leq p^{candidate\_storages}_{(n,s,t)} \\ & \forall n \in node: p^{candidate\_storages}_{(n)} \neq 0 \\ & \forall (s,t) \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 at a specific time. 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)} - v^{storages\_invested}_{(n,s,t)} + v^{storages\_decommissioned}_{(n,s,t)} = v^{storages\_invested\_available}_{(n,s,t-1)} \\ & \forall n \in node: p^{candidate\_storages}_{(n)} \neq 0 \\ & \forall (s,t) \end{aligned}\]

Economic lifetime of a storage

(Comment 2023-05-12: Currently under development)

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)

User constraints

User constraint

This 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 term 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_{u, n} \left\{ \begin{aligned} & \sum_{op=1}^{\left\| p^{operating\_points}_{(u)} \right\|} p^{unit\_flow\_coefficient}_{(u,n,op,uc,s,t)} \cdot v^{unit\_flow\_op}_{(u,n,d,op,s,t)} &\text{if } \left\| p^{operating\_points}_{(u)} \right\| > 1 & \\ & p^{unit\_flow\_coefficient}_{(u,n,uc,s,t)} \cdot v^{unit\_flow}_{(u,n,d,s,t)} &\text{otherwise} & \\ \end{aligned} \right. \\ &+\sum_{u} p^{units\_started\_up\_coefficient}_{(u,uc,s,t)} \cdot v^{units\_started\_up}_{(u,s,t)} \\ &+\sum_{u} p^{units\_on\_coefficient}_{(u,uc,s,t)} \cdot v^{units\_on}_{(u,s,t)} \\ &+\sum_{c} p^{connection\_flow\_coefficient}_{(c,n,uc,s,t)} \cdot v^{connection\_flow}_{(c,n,d,s,t)} \\ &+\sum_{n} p^{node\_state\_coefficient}_{(n,uc,s,t)} \cdot v^{node\_state}_{(n,s,t)} \\ &+\sum_{n} p^{demand\_coefficient}_{(n,uc,s,t)} \cdot p^{demand}_{(n,s,t)} \\ & \begin{cases} = &\text{if } p^{constraint\_sense}_{(uc)} \text{= "=="}\\ \geq &\text{if } p^{constraint\_sense}_{(uc)} \text{= ">="}\\ \leq &\text{if } p^{constraint\_sense}_{(uc)} \text{= "=="}\\ \end{cases}\\ &+p^{right\_hand\_side}_{(uc,t,s)}\\ &\forall uc \in user\_constraint \\ &\forall (s,t) \end{aligned}\]

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 $p^{operational\_cost}$:

\[\begin{aligned} min& \\ & \sum_{u,s,t} \left( p^{unit\_investment\_cost}_{(u,s,t)} \cdot v^{units\_invested}_{(u,s,t)} + \sum_{n,d} p^{operational\_cost}_{(u,n,d,s,t)} \cdot v^{unit\_flow}_{(u, n, d, s, t)} \right) \\ s.t. & \\ & v^{unit\_flow}_{(u,n,d,s,t)} \le p^{unit\_capacity}_{(u, n, d, s, t)} \cdot \left( v^{units\_available}_{(u,s,t)} + v^{units\_invested\_available}_{(u, s, t)} \right) \quad \forall u \in unit, n \in node, s, t \\ & \sum_{u,d} v^{unit\_flow}_{(u,n,d,s,t)} = p^{demand}_{(n, s, t)} \quad \forall n \in node, s,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 is built by replacing the operational costs (which will be minimised in the sub problem) by a new decision variable, $v^{sp\_objective}$:

\[\begin{aligned} min & \\ & \sum_{u,s,t} p^{unit\_investment\_cost}_{(u,s,t)} \cdot v^{units\_invested}_{(u,s,t)} + v^{sp\_objective} \\ s.t. & \\ & v^{sp\_objective} \geq 0 \end{aligned} \]

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

The sub problem for benders iteration $b$ then becomes :

\[\begin{aligned} min& \\ & sp\_obj_b = \sum_{u,n,d,s,t} p^{operational\_cost}_{(u,n,d,s,t)} \cdot v^{unit\_flow}_{(u, n, d, s, t)}\\ s.t.& \\ & v^{unit\_flow}_{(u,n,d,s,t)} \le p^{unit\_capacity}_{(u, n, d, s, t)} \cdot \left( v^{units\_available}_{(u,s,t)} + p^{units\_invested\_available}_{(b, u, s, t)} \right) \\ & \qquad \forall u \in unit, n \in node, s,t \qquad [\mu_{(b,u,s,t)}] \\ & \sum_{u,d} v^{unit\_flow}_{(u,n,d,s,t)} = p^{demand}_{(n, s, t)} \quad \forall n \in node, s,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_{(b,u,s,t)}$ is the marginal value of the capacity constraint for benders iteration $b$ and can be interpreted as the decrease in the objective function for an additional MW of flow from unit $u$ (in scenario $s$ at time $t$). Thus, an upper bound on the sub problem objective function is obtained as follows:

\[sp\_obj_{b} + \sum_{u,n,d,s,t} \mu_{(b,u,s,t)} \cdot p^{unit\_capacity}_{(u,n,d,s,t)} \cdot \left(v^{units\_invested\_available}_{(u,s,t)} - p^{units\_invested\_available}_{(b,u,s,t)}\right)\]

The above is added to the master problem for the next iteration as a new constraint, called a Benders cut, thus becoming:

\[\begin{aligned} min & \\ & \sum_{u,s,t} p^{unit\_investment\_cost}_{(u,s,t)} \cdot v^{units\_invested}_{(u,s,t)} + v^{sp\_objective} \\ s.t. & \\ & v^{sp\_objective} \geq sp\_obj_{b} \\ & \quad + \sum_{u,n,d,s,t} \mu_{(b,u,s,t)} \cdot p^{unit\_capacity}_{(u,n,d,s,t)} \cdot \left(v^{units\_invested\_available}_{(u,s,t)} - p^{units\_invested\_available}_{(b,u,s,t)}\right) \quad \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 for the sub problem objective function by 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^{sp\_objective} \\ & \geq \\ & p^{sp\_obj}_{(b)} + \\ & \sum_{u,s,t} p^{units\_invested\_available\_mv}_{(b,u,s,t)} \cdot \left( v^{units\_invested\_available}_{(u,s,t)} - p^{units\_invested\_available}_{(u,s,t)} \right) \\ & + \sum_{c,s,t} p^{connections\_invested\_available\_mv}_{(b,c,s,t)} \cdot \left( v^{connections\_invested\_available}_{(c,s,t)} - p^{connections\_invested\_available}_{(c,s,t)} \right) \\ & + \sum_{n,s,t} p^{storages\_invested\_available\_mv}_{(b,n,s,t)} \cdot \left( v^{storages\_invested\_available}_{(n,s,t)} - p^{storages\_invested\_available}_{(n,s,t)} \right) \\ \end{aligned}\]

where

  • $p^{sp\_obj}_{(b)}$ is the sub problem objective function value in benders iteration $b$,
  • $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 node of this type,
  • $p^{units\_invested\_available}$ is the value of the fixed sub problem variable units_invested_available in benders iteration $b$,
  • $p^{connections\_invested\_available}$ is the value of the fixed sub problem variable connections_invested_available in benders iteration $b$ and
  • $p^{storages\_invested\_available}$ is the value of the fixed sub problem variable storages_invested_available in benders iteration $b$