Tutorial 1: national scale¶
This example consists of two possible power supply technologies, a power demand at two locations, the possibility for battery storage at one of the locations, and a transmission technology linking the two. The diagram below gives an overview:
Supply-side technologies¶
The example model defines two power supply technologies.
The first is ccgt
(combined-cycle gas turbine), which serves as an example of a simple technology with an infinite resource. Its only constraints are the cost of built capacity (energy_cap
) and a constraint on its maximum built capacity.
The definition of this technology in the example model’s configuration looks as follows:
ccgt:
essentials:
name: 'Combined cycle gas turbine'
color: '#E37A72'
parent: supply
carrier_out: power
constraints:
resource: inf
energy_eff: 0.5
energy_cap_max: 40000 # kW
energy_cap_max_systemwide: 100000 # kW
energy_ramping: 0.8
lifetime: 25
costs:
monetary:
interest_rate: 0.10
energy_cap: 750 # USD per kW
om_con: 0.02 # USD per kWh
There are a few things to note. First, ccgt
defines essential information: a name, a color (given as an HTML color code, for later visualisation), its parent, supply
, and its carrier_out, power
. It has set itself up as a power supply technology. This is followed by the definition of constraints and costs (the only cost class used is monetary, but this is where other “costs”, such as emissions, could be defined).
Note
There are technically no restrictions on the units used in model definitions. Usually, the units will be kW and kWh, alongside a currency like USD for costs. It is the responsibility of the modeler to ensure that units are correct and consistent. Some of the analysis functionality in the analysis
module assumes that kW and kWh are used when drawing figure and axis labels, but apart from that, there is nothing preventing the use of other units.
The second technology is csp
(concentrating solar power), and serves as an example of a complex supply_plus technology making use of:
a finite resource based on time series data
built-in storage
plant-internal losses (
parasitic_eff
)
This definition in the example model’s configuration is more verbose:
csp:
essentials:
name: 'Concentrating solar power'
color: '#F9CF22'
parent: supply_plus
carrier_out: power
constraints:
storage_cap_max: 614033
energy_cap_per_storage_cap_max: 1
storage_loss: 0.002
resource: file=csp_resource.csv
resource_unit: energy_per_area
energy_eff: 0.4
parasitic_eff: 0.9
resource_area_max: inf
energy_cap_max: 10000
lifetime: 25
costs:
monetary:
interest_rate: 0.10
storage_cap: 50
resource_area: 200
resource_cap: 200
energy_cap: 1000
om_prod: 0.002
Again, csp
has the definitions for name, color, parent, and carrier_out. Its constraints are more numerous: it defines a maximum storage capacity (storage_cap_max
), an hourly storage loss rate (storage_loss
), then specifies that its resource should be read from a file (more on that below). It also defines a carrier conversion efficiency of 0.4 and a parasitic efficiency of 0.9 (i.e., an internal loss of 0.1). Finally, the resource collector area and the installed carrier conversion capacity are constrained to a maximum.
The costs are more numerous as well, and include monetary costs for all relevant components along the conversion from resource to carrier (power): storage capacity, resource collector area, resource conversion capacity, energy conversion capacity, and variable operational and maintenance costs. Finally, it also overrides the default value for the monetary interest rate.
Storage technologies¶
The second location allows a limited amount of battery storage to be deployed to better balance the system. This technology is defined as follows:
battery:
essentials:
name: 'Battery storage'
color: '#3B61E3'
parent: storage
carrier: power
constraints:
energy_cap_max: 1000 # kW
storage_cap_max: inf
energy_cap_per_storage_cap_max: 4
energy_eff: 0.95 # 0.95 * 0.95 = 0.9025 round trip efficiency
storage_loss: 0 # No loss over time assumed
lifetime: 25
costs:
monetary:
interest_rate: 0.10
storage_cap: 200 # USD per kWh storage capacity
The contraints give a maximum installed generation capacity for battery storage together with a maximum ratio of energy capacity to storage capacity (energy_cap_per_storage_cap_max
) of 4, which in turn limits the storage capacity. The ratio is the charge/discharge rate / storage capacity (a.k.a the battery reservoir). In the case of a storage technology, energy_eff
applies twice: on charging and discharging. In addition, storage technologies can lose stored energy over time – in this case, we set this loss to zero.
Other technologies¶
Three more technologies are needed for a simple model. First, a definition of power demand:
demand_power:
essentials:
name: 'Power demand'
color: '#072486'
parent: demand
carrier: power
Power demand is a technology like any other. We will associate an actual demand time series with the demand technology later.
What remains to set up is a simple transmission technology. Transmission technologies (like conversion technologies) look different than other nodes, as they link the carrier at one location to the carrier at another (or, in the case of conversion, one carrier to another at the same location):
ac_transmission:
essentials:
name: 'AC power transmission'
color: '#8465A9'
parent: transmission
carrier: power
constraints:
energy_eff: 0.85
lifetime: 25
costs:
monetary:
interest_rate: 0.10
energy_cap: 200
om_prod: 0.002
free_transmission:
essentials:
name: 'Local power transmission'
color: '#6783E3'
parent: transmission
carrier: power
constraints:
energy_cap_max: inf
energy_eff: 1.0
costs:
monetary:
om_prod: 0
ac_transmission
has an efficiency of 0.85, so a loss during transmission of 0.15, as well as some cost definitions.
free_transmission
allows local power transmission from any of the csp facilities to the nearest location. As the name suggests, it applies no cost or efficiency losses to this transmission.
Locations¶
In order to translate the model requirements shown in this section’s introduction into a model definition, five locations are used: region-1
, region-2
, region1-1
, region1-2
, and region1-3
.
The technologies are set up in these locations as follows:
Let’s now look at the first location definition:
region1:
coordinates: {lat: 40, lon: -2}
techs:
demand_power:
constraints:
resource: file=demand-1.csv:demand
ccgt:
constraints:
energy_cap_max: 30000 # increased to ensure no unmet_demand in first timestep
There are several things to note here:
The location specifies a dictionary of technologies that it allows (
techs
), with each key of the dictionary referring to the name of technologies defined in ourtechs.yaml
file. Note that technologies listed here must have been defined elsewhere in the model configuration.It also overrides some options for both
demand_power
andccgt
. For the latter, it simply sets a location-specific maximum capacity constraint. Fordemand_power
, the options set here are related to reading the demand time series from a CSV file. CSV is a simple text-based format that stores tables by comma-separated rows. Note that we did not define anyresource
option in the definition of thedemand_power
technology. Instead, this is done directly via a location-specific override. For this location, the filedemand-1.csv
is loaded and the columndemand
is taken (the text after the colon). If no column is specified, Calliope will assume that the column name matches the location nameregion1-1
. Note that in Calliope, a supply is positive and a demand is negative, so the stored CSV data will be negative.Coordinates are defined by latitude (
lat
) and longitude (lon
), which will be used to calculate distance of transmission lines (unless we specify otherwise later on) and for location-based visualisation.
The remaining location definitions look like this:
region2:
coordinates: {lat: 40, lon: -8}
techs:
demand_power:
constraints:
resource: file=demand-2.csv:demand
battery:
region1-1.coordinates: {lat: 41, lon: -2}
region1-2.coordinates: {lat: 39, lon: -1}
region1-3.coordinates: {lat: 39, lon: -2}
region1-1, region1-2, region1-3:
techs:
csp:
region2
is very similar to region1
, except that it does not allow the ccgt
technology. The three region1-
locations are defined together, except for their location coordinates, i.e. they each get the exact same configuration. They allow only the csp
technology, this allows us to model three possible sites for CSP plants.
For transmission technologies, the model also needs to know which locations can be linked, and this is set up in the model configuration as follows:
region1,region2:
techs:
ac_transmission:
constraints:
energy_cap_max: 10000
region1,region1-1:
techs:
free_transmission:
region1,region1-2:
techs:
free_transmission:
region1,region1-3:
techs:
free_transmission:
We are able to override constraints for transmission technologies at this point, such as the maximum capacity of the specific region1
to region2
link shown here.
Running the model¶
We now take you through running the model in a Jupyter notebook, which is included fully below. To download and run the notebook yourself, you can find it here. You will need to have Calliope installed.
Calliope National Scale Example Model¶
import calliope
# We increase logging verbosity
calliope.set_log_level('INFO')
model = calliope.examples.national_scale()
# Model inputs can be viewed at `model.inputs`.
# Variables are indexed over any combination of `techs`, `locs`, `carriers`, `costs` and `timesteps`,
# although `techs`, `locs`, and `carriers` are often concatenated.
# e.g. `ccgt`, `region1`, `power` -> `region1::ccgt::power`
model.inputs
# Individual data variables can be accessed easily, `to_pandas()` reformats the data to look nicer
model.inputs.resource.to_pandas()
# To reformat the array, deconcatenating loc_techs / loc_tech_carriers, you can use model.get_formatted_array()
# You can then apply loc/tech/carrier only operations, like summing information over locations:
model.get_formatted_array('resource').sum('locs').to_pandas()
# Solve the model. Results are loaded into `model.results`.
# By including logging (see package importing), we can see the timing of parts of the run, as well as the solver's log
model.run()
# Model results are held in the same structure as model inputs.
# The results consist of the optimal values for all decision variables, including capacities and carrier flow
# There are also results, like system capacity factor and levelised costs, which are calculated in postprocessing
# before being added to the results Dataset
model.results
# We can sum power output over all locations and turn the result into a pandas DataFrame
df_power = model.get_formatted_array('carrier_prod').loc[{'carriers':'power'}].sum('locs').to_pandas().T
#The information about the dataframe tells us about the amount of data it holds in the index and in each column
df_power.info()
# Using .head() to see the first few rows of power generation and demand
# Note: power output in ac_transmission:region1 is power received by the high voltage line at any location connected to `r1`
df_power.head()
# We can plot this by using the timeseries plotting functionality.
# The top-left dropdown gives us the chance to scroll through other timeseries data too.
model.plot.timeseries()
# plot.capacities gives a graphical view of the non-timeseries variables, both input and output
model.plot.capacity()
# We can also examine total technology costs
# notice all the NaN values which appear when seperating loc::techs to locs and techs.
# Any NaN value means we never considered that combination of `loc` and `tech` for the variable
costs = model.get_formatted_array('cost').loc[{'costs': 'monetary'}].to_pandas()
costs
# We can examine levelized costs for each location and technology
lcoes = model.results.systemwide_levelised_cost.loc[{'carriers': 'power', 'costs':'monetary'}].to_pandas()
lcoes
# We can just plot this directly using calliope analysis functionality
model.plot.capacity(array='systemwide_levelised_cost')
# Plotting a map of locations and transmission lines is easily possible
# In our example, the system is purely hypothetical and the resulting map
# gives us little useful information...
model.plot.transmission()
# Transmission plots give us a static picture of installed capacity along links.
# If we want timeseries data on energy flows at locations and along links
# we can use energy flow plotting. We only show every 4th hour here, to improve loading speed.
model.plot.flows(timestep_cycle=4)
# `cufflinks` allows for easy plotly plots to be
# produced from a pandas DataFrame
##
# ATTENTION: To run this final part of the tutorial,
# you need to run `pip install cufflinks`
##
import cufflinks
cufflinks.go_offline()
# We might like to plot the costs table from further above,
# which is outside the scope of internal calliope analysis functions
# But by using cufflinks, we can plot directly from our pandas DataFrame
# Note that the colours are not correct for our technologies here,
# as they come from the default colour theme applied by cufflinks
costs.iplot(kind='bar')
See the Calliope documentation for more details on setting up and running a Calliope model.
Calliope National Scale Example Model¶
import calliope
Previous: Tutorials | Next: Tutorial 2: urban scale