Model formulation¶
This section details the mathematical formulation of the different components. For each component, a link to the actual implementing function in the Calliope code is given.
Time-varying vs. constant model parameters¶
Some model parameters which are defined over the set of time steps t
can either given as time series or as constant values. If given as constant values, the same value is used for each time step t
. For details on how to define a parameter as time-varying and how to load time series data into it, see the time series description in the model configuration section.
Decision variables¶
Capacity¶
s_cap(y, x)
: installed storage capacity. Supply plus/Storage onlyr_cap(y, x)
: installed resource <-> storage conversion capacitye_cap(y, x)
: installed storage <-> grid conversion capacity (gross)r2_cap(y, x)
: installed secondary resource conversion capacityr_area(y, x)
: resource collector area
Unit Commitment¶
r(y, x, t)
: resource <-> storage/carrier_in (+ production, - consumption)r2(y, x, t)
: secondary resource -> storage (+ production)c_prod(c, y, x, t)
: resource/storage/carrier_in -> carrier_out (+ production)c_con(c, y, x, t)
: resource/storage/carrier_in <- carrier_out (- consumption)s(y, x, t)
: total energy stored in deviceexport(y, x, t)
: carrier_out -> export
Costs¶
cost(y, x, k)
: total costscost_fixed(y, x, k)
: fixed operation costscost_var(y, x, k, t)
: variable operation costs
Objective function (cost minimization)¶
Provided by: calliope.constraints.objective.objective_cost_minimization()
The default objective function minimizes cost:
where \(k_{m}\) is the monetary cost class.
Alternative objective functions can be used by setting the objective
in the model configuration (see Model-wide settings).
weight(y) is 1 by default, but can be adjusted to change the relative weighting of costs of different technologies in the objective, by setting weight
on any technology (see Technology).
Basic constraints¶
Node resource¶
Provided by: calliope.constraints.base.node_resource()
Defines constraint c_r_available:
Which limits the resource flow to supply
and supply_plus
technologies, or from demand
technologies.
For supply
:
If the option constraints.force_r
is set to true, then
If that option is not set:
For demand
:
If the option constraints.force_r
is set to true, then
If that option is not set:
For supply_plus
:
If the option constraints.force_r
is set to true, then
If that option is not set:
Note
For all other technology types, defining a resource is irrelevant, so they are not constrained here.
Node energy balance¶
Provided by: calliope.constraints.base.node_energy_balance()
Defines nine constraints, which are discussed in turn:
c_balance_transmission
: energy balance fortransmission
technologiesc_balance_conversion
: energy balance forconversion
technologiesc_balance_conversion_plus
: energy balance forconversion_plus
technologiesc_balance_conversion_plus_secondary_out
: energy balance forconversion_plus
technologies which have a secondary output carriersc_balance_conversion_plus_tertiary_out
: energy balance forconversion_plus
technologies which have a tertiary output carriersc_balance_conversion_plus_secondary_in
: energy balance forconversion_plus
technologies which have a secondary input carriersc_balance_conversion_plus_tertiary_in
: energy balance forconversion_plus
technologies which have a tertiary input carriersc_balance_supply_plus
: energy balance forsupply_plus
technologiesc_balance_storage
: energy balance forstorage
technologies
Transmission balance¶
Transmission technologies are internally expanded into two technologies per transmission link, of the form technology_name:destination
.
For example, if the technology hvdc
is defined and connects region_1
to region_2
, the framework will internally create a technology called hvdc:region_2
which exists in region_1
to connect it to region_2
, and a technology called hvdc:region_1
which exists in region_2
to connect it to region_1
.
The balancing for transmission technologies is given by
Here, \(x_{remote}, y_{remote}\) are x and y at the remote end of the transmission technology. For example, for (y, x) = ('hvdc:region_2', 'region_1')
, the remotes would be ('hvdc:region_1', 'region_2')
.
\(c_{prod}(c, y, x, t)\) for c='power', y='hvdc:region_2', x='region_1'
would be the import of power from region_2
to region_1
, via a hvdc
connection, at time t
.
This also shows that transmission technologies can have both a static or time-dependent efficiency (line loss), \(e_{eff}(y, x, t)\), and a distance-dependent efficiency, \(e_{eff,perdistance}(y, x)\).
For more detail on distance-dependent configuration see Model configuration.
Conversion balance¶
The conversion balance is given by
The principle is similar to that of the transmission balance. The production of carrier \(c_{out}\) (the carrier_out
option set for the conversion technology) is driven by the consumption of carrier \(c_{in}\) (the carrier_in
option set for the conversion technology).
Conversion_plus balance¶
Conversion plus technologies can have several carriers in and several carriers out, leading to a more complex production/consumption balance.
For the primary carrier(s), the balance is:
Where c_{out_1}
and c_{in_1}
are the sets of primary production and consumption carriers, respectively and carrier_{fraction}
is the relative contribution of these carriers, as defined in ??.
The remaining constraints (c_balance_conversion_plus_secondary_out
, c_balance_conversion_plus_tertiary_out
, c_balance_conversion_plus_secondary_in
, c_balance_conversion_plus_tertiary_in
) link the input/output of the technology secondary and tertiary carriers to the primary consumption/production.
For production:
For consumption:
Where x
is either 2 (secondary carriers) or 3 (tertiary carriers).
Warning
The conversion_plus
technology is still experimental and may not cover all edge cases as intended. Please raise an issue on GitHub if you see unexpected behavior. It is also possible to use a combination of several regular conversion
technologies to achieve some of the behaviors covered by conversion_plus
, but at the expense of model complexity.
Supply_plus balance¶
Supply_plus
technologies are supply
technologies with more control over resource flow. You can have multiple resources, a resource capacity, and storage of resource before it is converted to the primary carrier_out.
If storage is possible:
Otherwise:
Where:
\(c_{prod}\) is defined as \(\frac{c_{prod}(c, y, x, t)}{total_{eff}}\).
\(total_{eff}(y, x, t)\) is defined as \(e_{eff}(y, x, t) + p_{eff}(y, x, t)\), the plant efficiency including parasitic losses
\(r_{2}(y, x, t)\) is the secondary resource and is always set to zero unless the technology explicitly defines a secondary resource.
\(s(y, x, t)\) is the storage level at time \(t\).
\(s_{minusone}\) describes the state of storage at the previous timestep. \(s_{minusone} = s_{init}(y, x)\) at time \(t=0\). Else,
Note
In operation mode, s_init
is carried over from the previous optimization period.
Storage balance¶
Storage technologies balance energy charge, energy discharge, and energy stored:
Where:
\(c_{prod}\) is defined as \(\frac{c_{prod}(c, y, x, t)}{total_{eff}}\) if \(total_{eff} > 0\), otherwise \(c_{prod} = 0\)
\(c_{con}\) is defined as \(c_{con}(c, y, x, t) \times total_{eff}\)
\(total_{eff}(y, x, t)\) is defined as \(e_{eff}(y, x, t) + p_{eff}(y, x, t)\), the plant efficiency including parasitic losses
\(s(y, x, t)\) is the storage level at time \(t\).
\(s_{minusone}\) describes the state of storage at the previous timestep. \(s_{minusone} = s_{init}(y, x)\) at time \(t=0\). Else,
Note
In operation mode, s_init
is carried over from the previous optimization period.
Node build constraints¶
Provided by: calliope.constraints.base.node_constraints_build()
Built capacity is managed by six constraints.
c_s_cap
¶
This constrains the built storage capacity by:
If y.constraints.s_cap.equals
is set for location x
or the model is running in operational mode, the inequality in the equation above is turned into an equality constraint.
If both \(e_{cap,max}(y, x)\) and \(charge\_rate\) are not given, \(s_{cap}(y, x)\) is automatically set to zero.
If y.constraints.s_time.max
is true at location x
, then y.constraints.s_time.max
and y.constraints.e_cap.max
are used to to compute s_cap.max
. The minimum value of s_cap.max
is taken, based on analysis of all possible time sets which meet the s_time.max value. This allows time-varying efficiency, \(e_{eff}(y, x, t)\) to be accounted for.
c_r_cap
¶
This constrains the built resource conversion capacity by:
If the model is running in operational mode, the inequality in the equation above is turned into an equality constraint.
c_r_area
¶
This constrains the resource conversion area by:
By default, y.constraints.r_area.max
is set to false, and in that case, \(r_{area}(y, x)\) is forced to \(1.0\). If the model is running in operational mode, the inequality in the equation above is turned into an equality constraint. Finally, if y.constraints.r_area_per_e_cap
is given, then the equation \(r_{area}(y, x) = e_{cap}(y, x) * r\_area\_per\_cap\) applies instead.
c_e_cap
¶
This constrains the carrier conversion capacity by:
If a technology y
is not allowed at a location x
, \(e_{cap}(y, x) = 0\) is forced.
y.constraints.e_cap_scale
defaults to 1.0 but can be set on a per-technology, per-location basis if necessary.
If y.constraints.e_cap.equals
is set for location x
or the model is running in operational mode, the inequality in the equation above is turned into an equality constraint.
c_e_cap_storage
¶
This constrains the carrier conversion capacity for storage technologies by:
Where \(e_{cap,max} = s_{cap}(y, x) * charge\_rate * e\_cap\_scale\)
y.constraints.e_cap_scale
defaults to 1.0 but can be set on a per-technology, per-location basis if necessary.
c_r2_cap
¶
This manages the secondary resource conversion capacity by:
If y.constraints.r2_cap.equals
is set for location x
or the model is running in operational mode, the inequality in the equation above is turned into an equality constraint.
There is an additional relevant option, y.constraints.r2_cap_follows
, which can be overridden on a per-location basis. It can be set either to r_cap
or e_cap
, and if set, sets c_r2_cap
to track one of these, ie, \(r2_{cap,max} = r_{cap}(y, x)\) (analogously for e_cap
), and also turns the constraint into an equality constraint.
Node operational constraints¶
Provided by: calliope.constraints.base.node_constraints_operational()
This component ensures that nodes remain within their operational limits, by constraining r
, c_prod
, c_con
, s
, r2
, and export
.
r
¶
\(r(y, x, t)\) is constrained to remain within \(r_{cap}(y, x)\), with the constraint c_r_max_upper
:
c_prod
¶
\(c_prod(c, y, x, t)\) is constrained by c_prod_max
and c_prod_min
:
if c
is the carrier_out
of y
, else \(c_{prod}(c, y, x, y) = 0\).
If e_cap_min_use
is defined, the minimum output is constrained by:
These contraints are skipped for conversion_plus
technologies if c
is not the primary carrier.
c_con
¶
For technologies which are not supply
or supply_plus
, \(c_con(c, y, x, t)\) is non-zero. Thus \(c_con(c, y, x, t)\) is constrainted by c_con_max
:
and \(c_{con}(c, y, x, t) = 0\) otherwise.
This constraint is skipped for a conversion_plus
and conversion
technologies If c
is a possible consumption carrier (primary, secondary, or tertiary).
s
¶
The constraint c_s_max
ensures that storage cannot exceed its maximum size by
r2
¶
c_r2_max
constrains the secondary resource by
There is an additional check if y.constraints.r2_startup_only
is true. In this case, \(r2(y, x, t) = 0\) unless the current timestep is still within the startup time set in the startup_time_bounds
model-wide setting. This can be useful to prevent undesired edge effects from occurring in the model.
export
¶
c_export_max
constrains the export of a produced carrier by
Transmission constraints¶
Provided by: calliope.constraints.base.node_constraints_transmission()
This component provides a single constraint, c_transmission_capacity
, which forces \(e_{cap}\) to be symmetric for transmission nodes. For example, for for a given transmission line between \(x_1\) and \(x_2\), using the technology hvdc
:
Node costs¶
Provided by: calliope.constraints.base.node_costs()
These equations compute costs per node.
Weights are adjusted for individual timesteps depending on the timestep reduction methods applied (see Time resolution adjustment), and are given by \(W(t)\) when computing costs.
The depreciation rate for each cost class k
is calculated as
if the interest rate \(i\) is \(0\), else
Costs are split into fixed and variable costs. The total costs are computed in c_cost
by
The fixed costs include construction costs, annual operation and maintenance (O&M) costs, and O&M costs which are a fraction of the construction costs.
The total fixed costs are computed in c_cost_fixed
by
Where
The costs are as defined in the model definition, e.g. \(cost_{r\_cap}(y, k)\) corresponds to y.costs.k.r_cap
.
For transmission technologies, \(cost_{e\_cap}(y, k)\) is computed differently, to include the per-distance costs:
This implies that for transmission technologies, the cost of construction is split equally across the two locations connected by the technology.
The variable costs are O&M costs applied at each time step:
Where:
If \(cost_{om\_fuel}(k, y, x, t)\) is given for a supply
technology and \(e_{eff}(y, x) > 0\) for that technology, then:
c
is the technology primary carrier_out
in all cases.
Model balancing constraints¶
Provided by: calliope.constraints.base.model_constraints()
Model-wide balancing constraints are constructed for nodes that have children:
\(i\) are the level 0 locations, and \(X_{i}\) is the set of level 1 locations (\(x\)) within the given level 0 location, together with that location itself.
There is also the need to ensure that technologies cannot export more energy than they produce:
Planning constraints¶
These constraints are loaded automatically, but only when running in planning mode.
System margin¶
Provided by: calliope.constraints.planning.system_margin()
This is a simplified capacity margin constraint, requiring the capacity to supply a given carrier in the time step with the highest demand for that carrier to be above the demand in that timestep by at least the given fraction:
where \(y_{c}\) is the subset of y
that delivers the carrier c
and \(m_{c}\) is the system margin for that carrier.
For each carrier (with the name carrier_name
), Calliope attempts to read the model-wide option system_margin.carrier_name
, only applying this constraint if a setting exists.
System-wide capacity¶
Provided by: calliope.constraints.planning.node_constraints_build_total()
This constraint sets a maximum for capacity, e_cap
, across all locations for any given technology:
If \(e_{cap,total\_equals}\) is given instead, this becomes \(\sum_x e_{cap}(x, y) \leq e_{cap,total\_max}(y)\).
where \(y_{c}\) is the subset of y
that delivers the carrier c
and \(m_{c}\) is the system margin for that carrier.
For each carrier (with the name carrier_name
), Calliope attempts to read the model-wide option system_margin.carrier_name
, only applying this constraint if a setting exists.
Optional constraints¶
Optional constraints are included with Calliope but not loaded by default (see the configuration section for instructions on how to load them in a model).
These optional constraints can be used both in planning and operational modes.
Ramping¶
Provided by: calliope.constraints.optional.ramping_rate()
Constrains the rate at which plants can adjust their output, for technologies that define constraints.e_ramping
:
Group fractions¶
Provided by: calliope.constraints.optional.group_fraction()
This component provides the ability to constrain groups of technologies to provide a certain fraction of total output, a certain fraction of total capacity, or a certain fraction of peak power demand. See Parents and groups in the configuration section for further details on how to set up groups of technologies.
The settings for the group fraction constraints are read from the model-wide configuration, in a group_fraction
setting, as follows:
group_fraction:
capacity:
renewables: ['>=', 0.8]
This is a minimal example that forces at least 80% of the installed capacity to be renewables. To activate the output group constraint, the output
setting underneath group_fraction
can be set in the same way, or demand_power_peak
to activate the fraction of peak power demand group constraint.
For the above example, the c_group_fraction_capacity
constraint sets up an equation of the form
Here, \(y^*\) is the subset of \(y\) given by the specified group, in this example, renewables
. \(fraction\) is the fraction specified, in this example, \(0.8\). The relation between the right-hand side and the left-hand side, \(\geq\), is determined by the setting given, >=
, which can be ==
, <=
, or >=
.
If more than one group is listed under capacity
, several analogous constraints are set up.
Similarly, c_group_fraction_output
sets up constraints in the form of
Finally, c_group_fraction_demand_power_peak
sets up constraints in the form of
This assumes the existence of a technology, demand_power
, which defines a demand (negative resource). \(y_d\) is demand_power
. \(m_{c}\) is the capacity margin defined for the carrier c
in the model-wide settings (see System margin). \(t_{peak}\) is the timestep where \(r(y_d, x, t)\) is maximal.
Whether any of these equations are equalities, greater-or-equal-than inequalities, or lesser-or-equal-than inequalities, is determined by whether >=
, <=
, or ==
is given in their respective settings.
Available area¶
Provided by: calliope.constraints.optional.max_r_area_per_loc()
Where several technologies require space to acquire resource (e.g. solar collecting technologies) at a given location, this constraint provides the ability to limit the total area available at a location:
Where xi
is the set of locations within the family tree, descending from and including x
.
Previous: Tutorials | Next: Model configuration