Math components¶
Here, we will briefly introduce each of the math components you will need to build an optimisation problem. A more detailed description of the math YAML syntax is provided on the math syntax page and in the math formulation schema.
Decision variables¶
Decision variables (called variables
in Calliope) are the unknown quantities whose values can be chosen by the optimisation algorithm while optimising for the chosen objective (e.g. cost minimisation) under the bounds set by the constraints.
These include the output capacity of technologies, the per-timestep flow of carriers into and out of technologies or along transmission lines, and storage content in each timestep.
A decision variable in Calliope math looks like this:
variables:
storage_cap:
description: >-
The upper limit on a carrier that can
be stored by a technology in any timestep.
unit: energy
foreach: [nodes, techs]
where: "include_storage=True OR base_tech=storage"
domain: real # optional; defaults to real.
bounds:
min: storage_cap_min
max: storage_cap_max
active: true # optional; defaults to true.
- It needs a unique name (
storage_cap
in the example above). - Ideally, it has a long-form
description
and aunit
added. These are not required, but are useful metadata for later reference. - It can have a top-level
foreach
list andwhere
string. Without aforeach
, it becomes an un-indexed variable. Without awhere
string, all valid members (according to thedefinition_matrix
) based onforeach
will be included in this decision variable. - It can define a domain to turn it into a binary or integer variable (in either of those cases, domain becomes
integer
). - It requires a minimum and maximum bound, which can be:
- a numeric value:
- a reference to an input parameter, where each valid member of the variable (i.e. each value of the variable for a specific combination of indices) will get a different value based on the values of the referenced parameters (see example above). If a value for a valid variable member is undefined in the referenced parameter, the decision variable will be unbounded for this member.
- It can be deactivated so that it does not appear in the built optimisation problem by setting
active: false
.
Global Expressions¶
Global expressions are those combinations of decision variables and input parameters that you want access to in multiple constraints / objectives in the model. You will also receive the result of the global expression as a numeric value in your optimisation results, without having to do any additional post-processing.
For instance, total costs are global expressions as the cost associated with a technology is not a constraint, but rather a linear combination of decision variables and parameters (e.g., storage_cap * cost_storage_cap
).
To not clutter the objective function with all combinations of variables and parameters, we define a separate global expression:
global_expressions:
cost:
description: >-
The total annualised costs of a technology,
including installation and operation costs.
unit: cost
foreach: [nodes, techs, costs]
where: "cost_investment OR cost_var"
equations:
- expression: $cost_investment + $cost_var_sum
sub_expressions:
cost_investment:
- where: "cost_investment"
expression: cost_investment
- where: "NOT cost_investment"
expression: "0"
cost_var_sum:
- where: "cost_var"
expression: sum(cost_var, over=timesteps)
- where: "NOT cost_var"
expression: "0"
active: true # optional; defaults to true.
Global expressions are by no means necessary to include, but can make more complex linear expressions easier to keep track of and can reduce post-processing requirements.
- It needs a unique name (
cost
in the above example). - Ideally, it has a long-form
description
and aunit
added. These are not required, but are useful metadata for later reference. - It can have a top-level
foreach
list andwhere
string. Without aforeach
, it becomes an un-indexed expression. Without awhere
string, all valid members (according to thedefinition_matrix
) based onforeach
will be included in this expression. - It has equations (and, optionally, sub-expressions and slices) with corresponding lists of
where
+expression
dictionaries. The equation expressions do not have comparison operators; those are reserved for constraints - It can be deactivated so that it does not appear in the built optimisation problem by setting
active: false
.
Constraints¶
Decision variables / global expressions need to be constrained or included in the model objective. Constraining these math components is where you introduce the realities of the system you are modelling. This includes limits on things like the maximum area use of tech (there's only so much rooftop available for roof-mounted solar PV), and links between in/outflows such as how much carrier is consumed by a technology to produce each unit of output carrier. Here is an example:
constraints:
set_storage_initial:
description: >-
Fix the relationship between carrier stored in a `storage` technology at
the start and end of the whole model period.
foreach: [nodes, techs]
where: "storage AND storage_initial AND cyclic_storage=True"
equations:
- expression: >-
storage[timesteps=$final_step] * (
(1 - storage_loss) ** timestep_resolution[timesteps=$final_step]
) == storage_initial * storage_cap
slices:
final_step:
- expression: get_val_at_index(timesteps=-1)
active: true # optional; defaults to true.
- It needs a unique name (
set_storage_initial
in the above example). - Ideally, it has a long-form
description
and aunit
added. These are not required, but are useful metadata for later reference. - It can have a top-level
foreach
list andwhere
string. Without aforeach
, it becomes an un-indexed constraint. Without awhere
string, all valid members (according to thedefinition_matrix
) based onforeach
will be included in this constraint. - It has equations (and, optionally, sub-expressions and slices) with corresponding lists of
where
+expression
dictionaries. The equation expressions must have comparison operators. - It can be deactivated so that it does not appear in the built optimisation problem by setting
active: false
.
Objectives¶
With your constrained decision variables and a global expression that binds these variables to costs, you need an objective to minimise/maximise. The default built-in objective is min_cost_optimisation
and looks as follows:
objectives:
min_cost_optimisation:
description: >-
Minimise the total cost of installing and operating
all technologies in the system.
If multiple cost classes are present (e.g., monetary and co2 emissions),
the weighted sum of total costs is minimised.
Cost class weights can be defined in the indexed parameter
`objective_cost_weights`.
equations:
- where: "any(cost, over=[nodes, techs, costs])"
expression: >-
sum(
sum(cost, over=[nodes, techs])
* objective_cost_weights,
over=costs
) + $unmet_demand
- where: "NOT any(cost, over=[nodes, techs, costs])"
expression: $unmet_demand
sub_expressions:
unmet_demand:
- where: "config.ensure_feasibility=True"
expression: >-
sum(
sum(unmet_demand - unused_supply, over=[carriers, nodes])
* timestep_weights,
over=timesteps
) * bigM
- where: "NOT config.ensure_feasibility=True"
expression: "0"
sense: minimise
active: true # optional; defaults to true.
- It needs a unique name.
- Ideally, it has a long-form
description
and aunit
added. These are not required, but are useful metadata for later reference. - It can have a top-level
where
string, but noforeach
(it is a single value you need to minimise/maximise). Without awhere
string, the objective will be activated. - It has equations (and, optionally, sub-expressions and slices) with corresponding lists of
where
+expression
dictionaries. These expressions do not have comparison operators. - It can be deactivated so that it does not appear in the built optimisation problem by setting
active: false
.
Warning
You can only have one objective function activated in your math.
If you have defined multiple objective functions, you can deactivate unwanted ones using active: false
, or you can set your top-level where
string on each that leads to only one being valid for your particular problem.