Calliope National Scale Example Model


In [1]:
import calliope

# We increase logging verbosity
calliope.set_log_level('INFO')

In [2]:
model = calliope.examples.national_scale()


[2018-10-04 16:26:05] INFO: Model: initialising
[2018-10-04 16:26:06] INFO: Model: preprocessing stage 1 (model_run)
[2018-10-04 16:26:06] INFO: Model: preprocessing stage 2 (model_data)
[2018-10-04 16:26:07] INFO: Model: preprocessing complete. Time since start: 0:00:01.335305

In [3]:
# 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


Out[3]:
<xarray.Dataset>
Dimensions:                    (carriers: 1, coordinates: 2, costs: 1, loc_carriers: 5, loc_techs: 15, loc_techs_area: 3, loc_techs_finite_resource: 5, loc_techs_investment_cost: 7, loc_techs_non_conversion: 15, loc_techs_om_cost: 12, loc_techs_store: 4, loc_techs_supply_plus: 3, loc_techs_transmission: 8, locs: 5, techs: 6, timesteps: 120)
Coordinates:
  * costs                      (costs) object 'monetary'
  * techs                      (techs) object 'free_transmission' ...
  * loc_techs_store            (loc_techs_store) object 'region1-1::csp' ...
  * coordinates                (coordinates) object 'lat' 'lon'
  * loc_techs_non_conversion   (loc_techs_non_conversion) object 'region2::ac_transmission:region1' ...
  * loc_techs_area             (loc_techs_area) object 'region1-1::csp' ...
  * loc_techs_investment_cost  (loc_techs_investment_cost) object 'region1-1::csp' ...
  * loc_techs_transmission     (loc_techs_transmission) object 'region2::ac_transmission:region1' ...
  * loc_techs_finite_resource  (loc_techs_finite_resource) object 'region1::demand_power' ...
  * timesteps                  (timesteps) datetime64[ns] 2005-01-01 ...
  * carriers                   (carriers) object 'power'
  * loc_carriers               (loc_carriers) object 'region1::power' ...
  * loc_techs_om_cost          (loc_techs_om_cost) object 'region1::free_transmission:region1-1' ...
  * locs                       (locs) object 'region2' 'region1-2' 'region1' ...
  * loc_techs                  (loc_techs) object 'region2::ac_transmission:region1' ...
  * loc_techs_supply_plus      (loc_techs_supply_plus) object 'region1-1::csp' ...
Data variables:
    resource_area_max          (loc_techs_area) float64 inf inf inf
    resource_eff               (loc_techs_finite_resource) float64 nan nan ...
    energy_prod                (loc_techs) float64 1.0 1.0 1.0 nan 1.0 1.0 ...
    energy_ramping             (loc_techs) float64 nan nan nan nan nan nan ...
    resource                   (loc_techs_finite_resource, timesteps) float64 -2.528e+04 ...
    force_resource             (loc_techs_finite_resource) float64 1.0 1.0 ...
    lifetime                   (loc_techs) float64 25.0 25.0 nan nan nan nan ...
    energy_cap_max             (loc_techs) float64 1e+04 1e+04 inf nan inf ...
    resource_unit              (loc_techs_finite_resource) <U15 'energy' ...
    charge_rate                (loc_techs_store) int64 1 4 1 1
    energy_eff                 (loc_techs) float64 0.85 0.85 1.0 nan 1.0 1.0 ...
    energy_con                 (loc_techs) float64 1.0 1.0 1.0 1.0 1.0 1.0 ...
    parasitic_eff              (loc_techs_supply_plus) float64 0.9 0.9 0.9
    storage_loss               (loc_techs_store) float64 0.002 0.0 0.002 0.002
    storage_cap_max            (loc_techs_store) float64 6.14e+05 inf ...
    reserve_margin             (carriers) float64 nan
    cost_depreciation_rate     (costs, loc_techs_investment_cost) float64 0.1102 ...
    cost_resource_area         (costs, loc_techs_investment_cost) float64 200.0 ...
    cost_energy_cap            (costs, loc_techs_investment_cost) float64 1e+03 ...
    cost_resource_cap          (costs, loc_techs_investment_cost) float64 200.0 ...
    cost_om_prod               (costs, loc_techs_om_cost) float64 0.0 0.002 ...
    cost_om_con                (costs, loc_techs_om_cost) float64 nan nan ...
    cost_storage_cap           (costs, loc_techs_investment_cost) float64 50.0 ...
    lookup_remotes             (loc_techs_transmission) <U36 'region1::ac_transmission:region2' ...
    loc_coordinates            (coordinates, locs) int64 40 39 40 39 41 -8 ...
    colors                     (techs) <U7 '#6783E3' '#8465A9' '#E37A72' ...
    inheritance                (techs) <U12 'transmission' 'transmission' ...
    names                      (techs) <U26 'Local power transmission' ...
    energy_cap_max_systemwide  (techs) float64 nan nan 1e+05 nan nan nan
    lookup_loc_carriers        (loc_carriers) <U221 'region1::ccgt::power,region1::free_transmission:region1-2::power,region1::demand_power::power,region1::free_transmission:region1-1::power,region1::ac_transmission:region2::power,region1::free_transmission:region1-3::power' ...
    lookup_loc_techs           (loc_techs_non_conversion) <U43 'region2::ac_transmission:region1::power' ...
    lookup_loc_techs_area      (locs) <U14 '' 'region1-2::csp' '' ...
    timestep_resolution        (timesteps) float64 1.0 1.0 1.0 1.0 1.0 1.0 ...
    timestep_weights           (timesteps) float64 1.0 1.0 1.0 1.0 1.0 1.0 ...
    max_demand_timesteps       (carriers) datetime64[ns] 2005-01-05T16:00:00
Attributes:
    model.calliope_version:            0.6.3
    model.name:                        National-scale example model
    model.subset_time:                 ['2005-01-01', '2005-01-05']
    model.timeseries_dateformat:       %Y-%m-%d %H:%M:%S
    run.backend:                       pyomo
    run.bigM:                          1000000.0
    run.cyclic_storage:                True
    run.ensure_feasibility:            True
    run.mode:                          plan
    run.objective:                     minmax_cost_optimization
    run.objective_options.cost_class:  monetary
    run.objective_options.sense:       minimize
    run.operation.use_cap_results:     False
    run.solver:                        glpk
    run.zero_threshold:                1e-10
    calliope_version:                  0.6.3
    applied_overrides:                 
    scenario:                          None
    defaults:                          available_area: null\ncarrier_ratios: ...
    allow_operate_mode:                1

In [4]:
# Individual data variables can be accessed easily, `to_pandas()` reformats the data to look nicer
model.inputs.resource.to_pandas()


Out[4]:
timesteps 2005-01-01 00:00:00 2005-01-01 01:00:00 2005-01-01 02:00:00 2005-01-01 03:00:00 2005-01-01 04:00:00 2005-01-01 05:00:00 2005-01-01 06:00:00 2005-01-01 07:00:00 2005-01-01 08:00:00 2005-01-01 09:00:00 ... 2005-01-05 14:00:00 2005-01-05 15:00:00 2005-01-05 16:00:00 2005-01-05 17:00:00 2005-01-05 18:00:00 2005-01-05 19:00:00 2005-01-05 20:00:00 2005-01-05 21:00:00 2005-01-05 22:00:00 2005-01-05 23:00:00
loc_techs_finite_resource
region1::demand_power -25284.480 -24387.440 -23730.656 -23123.040 -23119.600 -23683.280 -24364.720 -25249.968000 -26090.208000 -26870.464000 ... -37078.160000 -38203.12000 -39033.520 -38631.008 -36990.800 -35330.832 -33623.456 -31341.168 -29390.624 -28132.928
region2::demand_power -2254.098 -2131.148 -2090.164 -2131.148 -2172.132 -2172.132 -2213.114 -2295.082000 -2459.016000 -2459.016000 ... -2295.082000 -2459.01600 -2909.836 -2868.852 -2786.886 -2745.902 -2622.950 -2459.016 -2254.098 -2295.082
region1-1::csp 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.021060 0.263805 0.434037 ... 0.322062 0.07927 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
region1-3::csp 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000000 0.000000 0.026837 ... 0.118691 0.00000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
region1-2::csp 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.009056 0.096755 0.245351 ... 0.000000 0.00000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000

5 rows × 120 columns


In [5]:
# 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()


Out[5]:
timesteps 2005-01-01 00:00:00 2005-01-01 01:00:00 2005-01-01 02:00:00 2005-01-01 03:00:00 2005-01-01 04:00:00 2005-01-01 05:00:00 2005-01-01 06:00:00 2005-01-01 07:00:00 2005-01-01 08:00:00 2005-01-01 09:00:00 ... 2005-01-05 14:00:00 2005-01-05 15:00:00 2005-01-05 16:00:00 2005-01-05 17:00:00 2005-01-05 18:00:00 2005-01-05 19:00:00 2005-01-05 20:00:00 2005-01-05 21:00:00 2005-01-05 22:00:00 2005-01-05 23:00:00
techs
csp 0.000 0.000 0.00 0.000 0.000 0.000 0.000 0.030116 0.36056 0.706225 ... 0.440753 0.07927 0.000 0.00 0.000 0.000 0.000 0.000 0.000 0.00
demand_power -27538.578 -26518.588 -25820.82 -25254.188 -25291.732 -25855.412 -26577.834 -27545.050000 -28549.22400 -29329.480000 ... -39373.242000 -40662.13600 -41943.356 -41499.86 -39777.686 -38076.734 -36246.406 -33800.184 -31644.722 -30428.01

2 rows × 120 columns


In [6]:
# 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()


[2018-10-04 16:26:07] INFO: Backend: starting model run
[2018-10-04 16:26:09] INFO: Backend: model generated. Time since start: 0:00:03.356609
[2018-10-04 16:26:09] INFO: Backend: sending model to solver
[2018-10-04 16:26:11] INFO: Backend: solver finished running. Time since start: 0:00:05.309883
[2018-10-04 16:26:11] INFO: Backend: loaded results
[2018-10-04 16:26:11] INFO: Backend: generated solution array. Time since start: 0:00:05.408545
[2018-10-04 16:26:11] INFO: Postprocessing: started
[2018-10-04 16:26:11] INFO: Postprocessing: All values < 1e-10 set to 0 in carrier_prod, carrier_con, cost, storage, cost_var, capacity_factor
[2018-10-04 16:26:11] INFO: Postprocessing: Model was feasible, deleting unmet_demand variable
[2018-10-04 16:26:11] INFO: Postprocessing: ended. Time since start: 0:00:05.636784

In [7]:
# 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


Out[7]:
<xarray.Dataset>
Dimensions:                     (carriers: 1, costs: 1, loc_tech_carriers_con: 11, loc_tech_carriers_prod: 13, loc_techs: 15, loc_techs_area: 3, loc_techs_cost: 13, loc_techs_investment_cost: 7, loc_techs_om_cost: 12, loc_techs_store: 4, loc_techs_supply_plus: 3, techs: 6, timesteps: 120)
Coordinates:
  * costs                       (costs) object 'monetary'
  * techs                       (techs) object 'free_transmission' ...
  * loc_techs_store             (loc_techs_store) object 'region1-1::csp' ...
  * loc_techs_cost              (loc_techs_cost) object 'region1::free_transmission:region1-1' ...
  * loc_techs_area              (loc_techs_area) object 'region1-1::csp' ...
  * loc_techs_investment_cost   (loc_techs_investment_cost) object 'region1-1::csp' ...
  * timesteps                   (timesteps) datetime64[ns] 2005-01-01 ...
  * loc_tech_carriers_con       (loc_tech_carriers_con) object 'region1-1::free_transmission:region1::power' ...
  * carriers                    (carriers) <U5 'power'
  * loc_techs_om_cost           (loc_techs_om_cost) object 'region1::free_transmission:region1-1' ...
  * loc_tech_carriers_prod      (loc_tech_carriers_prod) object 'region1-1::csp::power' ...
  * loc_techs                   (loc_techs) object 'region2::ac_transmission:region1' ...
  * loc_techs_supply_plus       (loc_techs_supply_plus) object 'region1-1::csp' ...
Data variables:
    energy_cap                  (loc_techs) float64 3.23e+03 3.23e+03 9e+03 ...
    carrier_prod                (loc_tech_carriers_prod, timesteps) float64 0.0 ...
    carrier_con                 (loc_tech_carriers_con, timesteps) float64 0.0 ...
    cost                        (costs, loc_techs_cost) float64 0.0 ...
    resource_area               (loc_techs_area) float64 1.304e+05 0.0 8.486e+03
    storage_cap                 (loc_techs_store) float64 2.443e+05 ...
    storage                     (loc_techs_store, timesteps) float64 0.0 0.0 ...
    resource_con                (loc_techs_supply_plus, timesteps) float64 0.0 ...
    resource_cap                (loc_techs_supply_plus) float64 6.722e+04 ...
    cost_var                    (costs, loc_techs_om_cost, timesteps) float64 0.0 ...
    cost_investment             (costs, loc_techs_investment_cost) float64 9.317e+04 ...
    capacity_factor             (loc_tech_carriers_prod, timesteps) float64 0.0 ...
    systemwide_capacity_factor  (techs, carriers) float64 nan nan 0.9469 ...
    systemwide_levelised_cost   (carriers, techs, costs) float64 nan nan ...
    total_levelised_cost        (carriers, costs) float64 0.05361
Attributes:
    model.calliope_version:            0.6.3
    model.name:                        National-scale example model
    model.subset_time:                 ['2005-01-01', '2005-01-05']
    model.timeseries_dateformat:       %Y-%m-%d %H:%M:%S
    run.backend:                       pyomo
    run.bigM:                          1000000.0
    run.cyclic_storage:                True
    run.ensure_feasibility:            True
    run.mode:                          plan
    run.objective:                     minmax_cost_optimization
    run.objective_options.cost_class:  monetary
    run.objective_options.sense:       minimize
    run.operation.use_cap_results:     False
    run.solver:                        glpk
    run.zero_threshold:                1e-10
    calliope_version:                  0.6.3
    applied_overrides:                 
    scenario:                          None
    defaults:                          available_area: null\ncarrier_ratios: ...
    allow_operate_mode:                1
    termination_condition:             optimal
    solution_time:                     3.724045
    time_finished:                     2018-10-04 16:26:11

In [8]:
# 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()


<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 120 entries, 2005-01-01 00:00:00 to 2005-01-05 23:00:00
Data columns (total 9 columns):
ac_transmission:region1        120 non-null float64
ac_transmission:region2        120 non-null float64
battery                        120 non-null float64
ccgt                           120 non-null float64
csp                            120 non-null float64
free_transmission:region1      120 non-null float64
free_transmission:region1-1    120 non-null float64
free_transmission:region1-2    120 non-null float64
free_transmission:region1-3    120 non-null float64
dtypes: float64(9)
memory usage: 9.4 KB

In [9]:
# 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()


Out[9]:
techs ac_transmission:region1 ac_transmission:region2 battery ccgt csp free_transmission:region1 free_transmission:region1-1 free_transmission:region1-2 free_transmission:region1-3
timesteps
2005-01-01 00:00:00 2254.098 0.0 0.0 27936.360000 0.0 0.0 0.0 0.0 0.0
2005-01-01 01:00:00 2131.148 0.0 0.0 26894.672941 0.0 0.0 0.0 0.0 0.0
2005-01-01 02:00:00 2090.164 0.0 0.0 26189.672471 0.0 0.0 0.0 0.0 0.0
2005-01-01 03:00:00 2131.148 0.0 0.0 25630.272941 0.0 0.0 0.0 0.0 0.0
2005-01-01 04:00:00 2172.132 0.0 0.0 25675.049412 0.0 0.0 0.0 0.0 0.0

In [10]:
# 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()



In [11]:
# plot.capacities gives a graphical view of the non-timeseries variables, both input and output
model.plot.capacity()



In [12]:
# 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


Out[12]:
techs ac_transmission:region1 ac_transmission:region2 battery ccgt csp free_transmission:region1 free_transmission:region1-1 free_transmission:region1-2 free_transmission:region1-3
locs
region1 NaN 487.527365 NaN 170306.866419 NaN NaN 0.0 0.0 0.0
region1-1 NaN NaN NaN NaN 94592.427594 0.0 NaN NaN NaN
region1-2 NaN NaN NaN NaN 0.000000 0.0 NaN NaN NaN
region1-3 NaN NaN NaN NaN 9013.121389 0.0 NaN NaN NaN
region2 1067.172474 NaN 1581.363398 NaN NaN NaN NaN NaN NaN

In [13]:
# We can examine levelized costs for each location and technology

lcoes = model.results.systemwide_levelised_cost.loc[{'carriers': 'power', 'costs':'monetary'}].to_pandas()
lcoes


Out[13]:
techs
free_transmission         NaN
ac_transmission           NaN
ccgt                 0.049961
csp                  0.142719
demand_power              NaN
battery              0.100035
dtype: float64

In [14]:
# We can just plot this directly using calliope analysis functionality
model.plot.capacity(array='systemwide_levelised_cost')



In [15]:
# 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()



In [16]:
# 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)



In [17]:
# `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()



In [18]:
# 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.