Macro Constraint Library
Currently, Macro includes the following constraints:
Balance constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::BalanceConstraint, v::AbstractVertex, model::Model)
Add a balance constraint to the vertex v
.
- If
v
is aNode
, a demand balance constraint is added. - If
v
is aTransformation
, this constraint ensures that the stoichiometric equations linking the input and output flows are correctly balanced.
\[\begin{aligned} \sum_{\substack{i\ \in \ \text{balance\_eqs\_ids(v)}, \\ t\ \in \ \text{time\_interval(v)}} } \text{balance\_eq(v, i, t)} = 0.0 \end{aligned}\]
Capacity constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::CapacityConstraint, e::Edge, model::Model)
Add a capacity constraint to the edge e
. If the edge is unidirectional, the functional form of the constraint is:
\[\begin{aligned} \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]
If the edge is bidirectional, the constraint is:
\[\begin{aligned} i \times \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
and each i
in {0, 1}
. The function availability
returns the time series of the capacity factor of the edge at time t
.
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::CapacityConstraint, e::EdgeWithUC, model::Model)
Add a capacity constraint to the edge e
with unit commitment. If the edge is unidirectional, the functional form of the constraint is:
\[\begin{aligned} \sum_{t \in \text{time\_interval(e)}} \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \times \text{ucommit(e, t)} \end{aligned}\]
If the edge is bidirectional, the constraint is:
\[\begin{aligned} i \times \text{flow(e, t)} \leq \text{availability(e, t)} \times \text{capacity(e)} \times \text{ucommit(e, t)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
and each i
in [-1, 1]
. The function availability
returns the time series of the availability of the edge at time t
.
CO2 capacity constraint
The CO2 capacity constraint is used to limit the amount of CO2 that can be emitted by a single CO2 node.
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::CO2CapConstraint, n::Node{CO2}, model::Model)
Constraint the CO2 emissions of CO2 on a CO2 node n
to be less than or equal to the value of the rhs_policy
for the CO2CapConstraint
constraint type. If the price_unmet_policy
is also specified, then a slack variable is added to the constraint to allow for the CO2 emissions to exceed the value of the rhs_policy
by the amount specified in the price_unmet_policy
for the CO2CapConstraint
constraint type. Please check the example case in the ExampleSystems
folder of Macro, or the Macro Input Data section of the documentation for more information on how to specify the rhs_policy
and price_unmet_policy
for the CO2CapConstraint
constraint type.
Therefore, the functional form of the constraint is:
\[\begin{aligned} \sum_{t \in \text{time\_interval(n)}} \text{emissions(n, t)} - \text{price\_unmet\_policy(n)} \times \text{slack(n)} \leq \text{rhs\_policy(n)} \end{aligned}\]
"Emissions" in the above equation is the net balance of CO2 flows into and out of the CO2 node n
.
Long-duration storage constraints
These additional constraints (and variables) can be used to ensure that storage levels of long-duration storage systems do not exceed installed capacity over non-representative subperiods.
For a complete description of the constraints, see the paper: "Improved formulation for long-duration storage in capacity expansion models using representative periods", Federico Parolin, Paolo Colbertaldo, Ruaridh Macdonald, 2024, https://doi.org/10.48550/arXiv.2409.19079.
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::LongDurationStorageImplicitMinMaxConstraint, g::LongDurationStorage, model::Model)
Adds constraints to ensure that the storage levels of long-duration storage systems do not exceed installed capacity over non-representative subperiods.
The functional form of the two constraints are:
\[\begin{aligned} \text{storage\_balance(p)} + \text{max\_storage\_level(r)} - \text{storage\_level(tstart(p))} &\leq \text{capacity(g)} \\ \text{storage\_balance(p)} + \ \text{min\_storage\_level(r)} - \text{storage\_level(tstart(p))} &\geq 0 \end{aligned}\]
where:
p
is a non-representative subperiod.r
is the representative subperiod used to modelp
.tstart(p)
is the first timestep of the representative subperiodr
used to model the non-representative subperiodp
.storage_balance(p)
is the balance of the storage resource at the non-representative subperiodp
and is defined as
\[\begin{aligned} \text{storage\_balance(p)} = (1 - \text{loss\_fraction}) \times \text{storage\_initial(p)} + \frac{\text{flow(discharge\_edge, tstart(p))}}{\text{efficiency(discharge\_edge)}} - \text{efficiency(charge\_edge)} \times \text{flow(charge\_edge, tstart(p))} \end{aligned}\]
max_storage_level(r)
andmin_storage_level(r)
are the maximum and minimum storage levels for the representative subperiodr
, respectively. These are used to constrain the storage levels as follows:
\[\begin{aligned} \text{min\_storage\_level(t')} \leq \text{storage\_level(t)} \leq \text{max\_storage\_level(t')} \end{aligned}\]
for each time t
in the time interval of the storage resource g
. t'
is the corresponding time in the representative subperiod r
used to model the time interval of the storage resource g
.
This constraint only applies to long duration energy storage resources. To model a storage technology as long duration energy storage, the user must set long_duration = true
in the Storage
component of the asset in the .json
file. Check the the file hydropower.json
in the ExampleSystems/eastern_us_three_zones
folder for an example of how to model a long duration energy storage resource.
Maximum capacity constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MaxCapacityConstraint, y::Union{AbstractEdge,AbstractStorage}, model::Model)
Add a max capacity constraint to the edge or storage y
. The functional form of the constraint is:
\[\begin{aligned} \text{capacity(y)} \leq \text{max\_capacity(y)} \end{aligned}\]
Maximum non-served demand constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MaxNonServedDemandConstraint, n::Node, model::Model)
Add a max non-served demand constraint to the node n
. The functional form of the constraint is:
\[\begin{aligned} \sum_{s\ \in\ \text{segments\_nsd(n)}} \text{non\_served\_demand(n, s, t)} \leq \text{demand(n, t)} \end{aligned}\]
for each time t
in time_interval(n)
for the node n
.
Maximum non-served demand per segment constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(
ct::MaxNonServedDemandPerSegmentConstraint,
n::Node,
model::Model,
)
Add a max non-served demand per segment constraint to the node n
. The functional form of the constraint is:
\[\begin{aligned} \text{non\_served\_demand(n, s, t)} \leq \text{max\_non\_served\_demand(n, s)} \times \text{demand(n, t)} \end{aligned}\]
for each segment s
in segments_non_served_demand(n)
and each time t
in time_interval(n)
for the node n
. The function segments_non_served_demand
returns the segments of the non-served demand of the node n
as defined in the input data nodes.json
.
Maximum storage level constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MaxStorageLevelConstraint, g::AbstractStorage, model::Model)
Add a max storage level constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{storage\_level(g, t)} \leq \text{max\_storage\_level(g)} \times \text{capacity(g)} \end{aligned}\]
for each time t
in time_interval(g)
for the storage g
.
Minimum capacity constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinCapacityConstraint, y::Union{AbstractEdge,AbstractStorage}, model::Model)
Add a min capacity constraint to the edge or storage y
. The functional form of the constraint is:
\[\begin{aligned} \text{capacity(y)} \geq \text{min\_capacity(y)} \end{aligned}\]
Minimum flow constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinFlowConstraint, e::Edge, model::Model)
Add a min flow constraint to the edge e
. The functional form of the constraint is:
\[\begin{aligned} \text{flow(e, t)} \geq \text{min\_flow\_fraction(e)} \times \text{capacity(e)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
.
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinFlowConstraint, e::EdgeWithUC, model::Model)
Add a min flow constraint to the edge e
with unit commitment. The functional form of the constraint is:
\[\begin{aligned} \text{flow(e, t)} \geq \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ucommit(e, t)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
.
Minimum storage level constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinStorageLevelConstraint, g::AbstractStorage, model::Model)
Add a min storage level constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{storage\_level(g, t)} \geq \text{min\_storage\_level(g)} \times \text{capacity(g)} \end{aligned}\]
for each time t
in time_interval(g)
for the storage g
.
Minimum storage outflow constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinStorageOutflowConstraint, g::AbstractStorage, model::Model)
Add a min storage outflow constraint to the storage g
part of a HydroRes
asset. The functional form of the constraint is:
\[\begin{aligned} \text{flow(spillage\_edge, t)} + \text{flow(discharge\_edge, t)} \geq \text{min\_outflow\_fraction(g)} \times \text{capacity(discharge\_edge)} \end{aligned}\]
for each time t
in time_interval(g)
for the storage g
.
Minimum up and down time constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinUpTimeConstraint, e::EdgeWithUC, model::Model)
Add a min up time constraint to the edge e
with unit commitment. The functional form of the constraint is:
\[\begin{aligned} \text{ucommit(e, t)} \geq \sum_{h=0}^{\text{min\_up\_time(e)}-1} \text{ustart(e, t-h)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
. The function timestepbefore
is used to perform the time wrapping within the subperiods and get the correct time step before t
.
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MinDownTimeConstraint, e::EdgeWithUC, model::Model)
Add a min down time constraint to the edge e
with unit commitment. The functional form of the constraint is:
\[\begin{aligned} \frac{\text{capacity(e)}}{\text{capacity\_size(e)}} - \text{ucommit(e, t)} \geq \sum_{h=0}^{\text{min\_down\_time(e)}-1} \text{ushut(e, t-h)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
. The function timestepbefore
is used to perform the time wrapping within the subperiods and get the correct time step before t
.
Must-run constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::MustRunConstraint, e::Edge, model::Model)
Add a must run constraint to the edge e
. The functional form of the constraint is:
\[\begin{aligned} \text{flow(e, t)} = \text{availability(e, t)} \times \text{capacity(e)} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
.
Ramping limits constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::RampingLimitConstraint, e::Edge, model::Model)
Add a ramping limit constraint to the edge e
. The functional form of the ramping up limit constraint is:
\[\begin{aligned} \text{flow(e, t)} - \text{flow(e, t-1)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_up\_fraction(e)} \times \text{capacity(e)} \leq 0 \end{aligned}\]
On the other hand, the ramping down limit constraint is:
\[\begin{aligned} \text{flow(e, t-1)} - \text{flow(e, t)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_down\_fraction(e)} \times \text{capacity(e)} \leq 0 \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
. The function timestepbefore
is used to perform the time wrapping within the subperiods and get the correct time step before t
.
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::RampingLimitConstraint, e::EdgeWithUC, model::Model)
Add a ramping limit constraint to the edge e
with unit commitment. The functional form of the ramping up limit constraint is:
\[\begin{aligned} \text{flow(e, t)} - \text{flow(e, t-1)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_up\_fraction(e)} \times \text{capacity\_size(e)} \times (\text{ucommit(e, t)} - \text{ustart(e, t)}) + \text{min(availability(e, t), max(min\_flow\_fraction(e), ramp\_up\_fraction(e)))} \times \text{capacity\_size(e)} \times \text{ustart(e, t)} - \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ushut(e, t)} \leq 0 \end{aligned}\]
On the other hand, the ramping down limit constraint is:
\[\begin{aligned} \text{flow(e, t-1)} - \text{flow(e, t)} + \text{regulation\_term(e, t)} + \text{reserves\_term(e, t)} - \text{ramp\_down\_fraction(e)} \times \text{capacity\_size(e)} \times (\text{ucommit(e, t)} - \text{ustart(e, t)}) - \text{min\_flow\_fraction(e)} \times \text{capacity\_size(e)} \times \text{ustart(e, t)} + \text{min(availability(e, t), max(min\_flow\_fraction(e), ramp\_down\_fraction(e)))} \times \text{capacity\_size(e)} \times \text{ushut(e, t)} \leq 0 \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
. The function timestepbefore
is used to perform the time wrapping within the subperiods and get the correct time step before t
.
Storage capacity constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::StorageCapacityConstraint, g::AbstractStorage, model::Model)
Add a storage capacity constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{storage\_level(g, t)} \leq \text{capacity(g)} \end{aligned}\]
for each time t
in time_interval(g)
for the storage g
.
Storage discharge limit constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::StorageDischargeLimitConstraint, e::Edge, model::Model)
Add a storage discharge limit constraint to the edge e
if the start vertex of the edge is a storage. The functional form of the constraint is:
\[\begin{aligned} \frac{\text{flow(e, t)}}{\text{efficiency(e)}} \leq \text{storage\_level(start\_vertex(e), timestepbefore(t, 1, subperiods(e)))} \end{aligned}\]
for each time t
in time_interval(e)
for the edge e
. The function timestepbefore
is used to perform the time wrapping within the subperiods and get the correct time step before t
.
Storage symmetric capacity constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(
ct::StorageSymmetricCapacityConstraint,
g::AbstractStorage,
model::Model,
)
Add a storage symmetric capacity constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{flow(e\_discharge, t)} + \text{flow(e\_charge, t)} \leq \text{capacity(e\_discharge)} \end{aligned}\]
Storage charge discharge ratio constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(
ct::StorageChargeDischargeRatioConstraint,
g::AbstractStorage,
model::Model,
)
Add a storage charge discharge ratio constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{charge\_discharge\_ratio(g)} \times \text{capacity(g.discharge\_edge)} = \text{capacity(g.charge\_edge)} \end{aligned}\]
Storage max duration constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::StorageMaxDurationConstraint, g::AbstractStorage, model::Model)
Add a storage max duration constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{capacity(g)} \leq \text{max\_duration(g)} \times \text{capacity(discharge\_edge(g))} \end{aligned}\]
Storage min duration constraint
MacroEnergy.add_model_constraint!
— Methodadd_model_constraint!(ct::StorageMinDurationConstraint, g::AbstractStorage, model::Model)
Add a storage min duration constraint to the storage g
. The functional form of the constraint is:
\[\begin{aligned} \text{capacity(g)} \geq \text{min\_duration(g)} \times \text{capacity(discharge\_edge(g))} \end{aligned}\]