# 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 only`r_cap(y, x)`

: installed resource <-> storage/carrier_in conversion capacity`e_cap(y, x)`

: installed storage <-> grid conversion capacity (gross)`r2_cap(y, x)`

: installed secondary resource conversion capacity`r_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/carrier_in (+ 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 device`export(y, x, t)`

: carrier_out -> export

### Costs¶

`cost(y, x, k)`

: total costs`cost_fixed(y, x, k)`

: fixed operation costs`cost_var(y, x, k, t)`

: variable operation costs

### Binary/Integer variables¶

`units(y, x)`

: Number of integer installed technologies`purchased(y, x)`

: Binary switch indicating whether a technology has been installed`operating_units(y, x, t)`

: Binary switch indicating whether a technology that has been installed is operating

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

### Unit commitment¶

Provided by: `calliope.constraints.base.unit_commitment()`

Defines constraint `c_unit_commitment`

:

Note

This constraint only applies to technology-location sets which have `units.max`

, `units.min`

, or `units.equals`

set in their constraints.

### Node energy balance¶

Provided by: `calliope.constraints.base.node_energy_balance()`

Defines nine constraints, which are discussed in turn:

`c_balance_transmission`

: energy balance for`transmission`

technologies`c_balance_conversion`

: energy balance for`conversion`

technologies`c_balance_conversion_plus`

: energy balance for`conversion_plus`

technologies`c_balance_conversion_plus_secondary_out`

: energy balance for`conversion_plus`

technologies which have a secondary output carriers`c_balance_conversion_plus_tertiary_out`

: energy balance for`conversion_plus`

technologies which have a tertiary output carriers`c_balance_conversion_plus_secondary_in`

: energy balance for`conversion_plus`

technologies which have a secondary input carriers`c_balance_conversion_plus_tertiary_in`

: energy balance for`conversion_plus`

technologies which have a tertiary input carriers`c_balance_supply_plus`

: energy balance for`supply_plus`

technologies`c_balance_storage`

: energy balance for`storage`

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.

If the technology is constrained with integer constraints `units.max/min/equals`

then the built storage capacity becomes:

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

If the technology is constrained with integer constraints `units.max/min/equals`

then the carrier conversion capacity becomes:

If the technology is not constrained with integer constraints `units.max/min/equals`

, but does define a `purchase`

cost then the carrier conversion capacity becomes:

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

If the technology is constrained with integer constraints `units.max/min/equals`

then the carrier conversion capacity for storage technologies becomes:

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

`c_units`

¶

This manages the maximum number of integer units by:

If `y.constraints.units.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.

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

If the technology is constrained with integer constraints `units.max/min/equals`

then c_prod(c, y, x, t) constraints become:

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

If the technology is constrained with integer constraints `units.max/min/equals`

then c_con(c, y, x, t) constraints become:

`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

If the technology is constrained with integer constraints `units.max/min/equals`

then export(y, x, t) constraint becomes:

### 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`

.

Note

purchase costs occur twice, but will only be applied once, depending on whether the technology constraints trigger an integer decision variable (`units(y, x)`

) or a binary decision variable (`purchased(y, x)`

).

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