Creating a model¶
What a basic model needs¶
You need these basic building blocks in place for the most minimal possible model:
- Basic model configuration
- One carrier
- One
supplyand onedemandtech for that carrier - One node
- One timestep
A minimal model that satisfies the above requirements could be written in a single model.yaml file like this:
# Model configuration
config:
solve:
solver: glpk
# Definition of two techs and the carrier they use (power)
techs:
power_supply:
base_tech: supply
carrier_out: power
name: "Power plant"
flow_cap_max: 100 # Maximum generation capacity (`flow_cap`); Unit: MW
cost_flow_cap: # Monetary cost of installing a unit of `flow_cap`
data: 10 # Unit: EUR/MW
index: monetary
dims: costs
power_demand:
name: "Power demand"
base_tech: demand
carrier_in: power
# Definition of a single node
# Here we also define a single timestep with a single value for power demand
nodes:
single_region:
techs:
power_supply:
power_demand:
# `sink_use_equals` forces this technology to act as a sink
# with exactly the amount specified here
sink_use_equals:
data: [50] # Unit: MWh
index: ["2025-01-01 12:00:00"]
dims: timesteps
Without specifying anything further, this model will run in the base math mode, which means a capacity expansion problem where both the technology capacities (time-independent) and their operation (time-dependent) are decision variables.
In the above example, we would find that the power_supply technology, which has a maximum capacity of 100, would get a capacity of 50 in the solution, since that is the demand from power_demand in the single timestep.
Note
Above, you see another feature of YAML: comment strings.
Anything after a # character is ignored when Calliope reads the model files, so we can use this to leave detailed comments and documentation about the model.
For example, we can use these comments to specify units: Calliope does not enforce units, but it is good to keep track of them ourselves.
Anything more - additional nodes, more timesteps, more techs (including storage, conversion, transmission, etc), and more carriers - is an extension of this most minimal case. Since we want to build much larger and more complex models, so let us look at the various building blocks to do so in more detail.
As you will see in the documentation and the examples, there are often different ways of specifying a Calliope model and its data, with their advantages and disadvantages.
For example, in the minimal example above, we are defining the data for power_demand directly within the main model.yaml file.
This works for a single timestep, but for a real-world model with thousands of timesteps, we would want to read the same data from a tabular file.
Model directory layout¶
It makes sense to collect all files belonging to a model inside a single model directory, and to separate different parts of the model into separate files (making use of the import top-level key to combine them).
The layout of a Calliope model directory might look like this:
example_model/
├── data_tables/
│ ├── electricity_demand.csv
│ └── solar_resource.csv
├── model_definition/
│ ├── nodes.yaml
│ └── techs.yaml
├── model.yaml
└── scenarios.yaml
We use the above layout in the example models, where the files model.yaml, nodes.yaml and techs.yaml together are the model definition.
Part of model.yaml is also taken up by the model configuration.
We will first look at that in more detail, before moving on to the model definition.
Model configuration¶
The model configuration specifies the information Calliope needs to initialise, build, and solve the model.
This includes for example the choice of solver with which to actually solve the mathematical optimisation problem.
Configuration split into init, build, and solve, which mirrors the three main stages through which Calliope creates and runs a model.
A more complete example would look like this:
config:
init: # Configuration for initialising the model
name: 'My energy model'
subset.timesteps: ['2005-01-01', '2005-01-05'] # Run for only a subset of all defined timesteps
mode: operate # Run in "operate" mode
build: # Configuration for building the model
backend: pyomo # Use the (default) Pyomo backend to build the model
solve: # Configuration for solving the model
solver: cbc
More details on what is available in the model configuration is in the model configuration documentation.
Techs¶
The model's techs (technologies) are defined under the techs top-level key.
Each technology must specify its base_tech, which defines its basic characteristics (i.e., its decision variables and constraints):
supply: Draws from a source to produce a carrier.demand: Consumes a carrier to supply to an external sink.storage: Stores a carrier.transmission: Transmits a carrier from one node to another.conversion: Converts from one carrier to another.
As explained above, a model must contain at least one supply and demand tech, whereas the other techs are optional.
Technologies generally need to specify, besides their base_tech, at least one carrier, and at least one or two constraints like flow_cap_max (which constraints the maximum nameplate capacity to produce a flow of the given carrier). A more fully-featured technology definition would look like this:
ccgt:
name: 'Combined cycle gas turbine'
color: '#FDC97D' # (1)!
base_tech: supply
carrier_out: power
source_use_max: .inf # (2)!
flow_out_eff: 0.5
flow_cap_max: 40000 # kW
lifetime: 25
cost_interest_rate:
data: 0.10 # (3)!
index: monetary
dims: costs
cost_flow_cap:
data: 750 # USD per kW
index: monetary
dims: costs
cost_flow_in:
data: 0.02 # USD per kWh
index: monetary
dims: costs
- Note that YAML is flexible about dealing with text strings.
You can, but do not need to, enclose them in quotation marks.
However, in this example using quotation marks is important.
Without them, the colour code would be interpreted as a YAML comment due to the
#character! - The period at the start of
.infwill ensure that this is read as a floating-point number (float) type rather than as the text string"inf". - Costs require us to explicitly define data in the data definition syntax format so that we can define their mandatory cost class (in this case:
monetary).
More details on how techs are defined is in the techs documentation.
Nodes¶
The model's nodes are the locations in space where technologies can be placed and are defined under the nodes top-level key.
A model can specify any number of nodes.
These nodes can be linked together by transmission technologies.
By consuming a carrier in one node and outputting it in another, linked node, transmission technologies allow resources to be drawn from the system at a different node from where they are brought into it.
The nodes section specifies each node. A more complete example looks like this:
nodes:
region1:
latitude: 40
longitude: -2
available_area: 100
techs:
demand_power:
ccgt:
flow_cap_max: 30000
Note that:
- Some of the node-level model specification pertains to the node itself, e.g. the
latitude,longitude, andavailable_area. The former two are used in visualisation, the latter can be used in area-related constraints. These are all optional. - The
techskey at the node level specifies which techs are installable (available) at that node. As seen in the example above, each allowed tech must be listed (e.g. the case ofdemand_power), and can optionally specify node-specific parameters (e.g. the case ofccgt).
If given, node-specific parameters supersede any parameters given at the technology level. In the above example, flow_cap_max for the tech ccgt in the node region will supersede any model-wide value for ccgt's flow_cap_max defined in the techs top-level key.
More details on how techs are defined is in the nodes documentation.
Transmission techs¶
A special kind of technology links nodes together so that a carrier can flow from one node to another. These are transmission techs, specified by defining a tech with base_tech: transmission.
Each potential "transmission link" between two nodes is specified as a separate technology with link_from and link_to keys specifying the two nodes it connects:
techs:
ac_transmission:
base_tech: transmission
link_from: region1
link_to: region2
flow_cap_max: 100
...
Data tables¶
We have chosen YAML syntax to define Calliope models as it is human-readable. However, when you have a large dataset this can become cumbersome. For instance, for parameters that vary in time we would have a list of 8760 hourly values and timestamps to put in our YAML file!
Therefore, alongside your YAML model definition, you can load tabular data from CSV files (or from in-memory [pandas.DataFrame][] objects) using the data_tables top-level key.
Recall our simple directory layout above. We have a data_tables directory with two tabular CSV files:
To read such a file into our model, we specify the file we want to load and how exactly we want to treat the rows and columns, for example:
data_tables:
pv_capacity_factor_data:
table: data_tables/solar_resource.csv
rows: timesteps
add_dims:
techs: pv
inputs: source_use_equals
More detail on how to use this powerful feature and how to structure your CSV files is in the data tables documentation.
The examples and tutorials section is also particularly useful to see in small example models how this feature works.
Data definitions¶
Sometimes we want to define data neither via the node-specific or tech-specific ways outlined above, nor read data in as data tables from separate files.
We can also specify model data via data definitions, with the data_definitions top-level key.
This is particularly useful when making use of more advanced functionality such as user-defined custom math, where we may want to introduce custom parameters.
More details on how techs are defined is in the data definitions documentation.
Three kinds of parameters: tech-specific, node-specific and indexed parameters
Above, we describe how you can directly define data for tech-specific parameters within techs and node-specific parameters within nodes.
With data definitions (and of course also with data tables), you can define parameters with any dimensions (including techs/nodes).
The data definition syntax used for this can also be used in the nodes and techs keys, for example to define data with additional dimensions.
Overrides and scenarios¶
Overrides (and the scenarios that reference overrides) can overwrite anything that is defined in the Calliope YAML files: both model configuration and model definition.
For example, you might want to explore several pre-defined capacity expansion plans in a model of the European power grid.
To do so, you first define a base model, then define one override with each alternative grid configuration.
The scenarios can combine several overrides.
For example, you might also want to explore different future cost developments, and define overrides for those.
In your scenarios, you can then combine overrides for a specific realisation of future costs and a specific grid configuration.
For more details on how these are used in practice, refer to the example models, and to the overrides and scenarios documentation.
Creating a new model from a built-in template¶
To get a basic model from which to expand, you can use the calliope new command, which makes a copy of one of the built-in examples models:
This creates a new directory, my_new_model, in the current working directory.
By default, calliope new uses the national-scale example model as a template.
To use a different template, you can specify the example model to use, e.g.: --template=urban_scale.