Calliope Urban Scale Example Model


In [1]:
import calliope

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

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


[2018-10-04 16:29:10] INFO: Model: initialising
[2018-10-04 16:29:11] INFO: Model: preprocessing stage 1 (model_run)
[2018-10-04 16:29:12] INFO: Model: preprocessing stage 2 (model_data)
[2018-10-04 16:29:13] INFO: Model: preprocessing complete. Time since start: 0:00:03.231415

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. `chp`, `X1`, `heat` -> `X1::chp::heat` 
model.inputs


Out[3]:
<xarray.Dataset>
Dimensions:                               (carrier_tiers: 3, carriers: 3, coordinates: 2, costs: 1, loc_carriers: 10, loc_tech_carriers_conversion_plus: 3, loc_techs: 26, loc_techs_area: 3, loc_techs_conversion: 2, loc_techs_conversion_plus: 1, loc_techs_export: 4, loc_techs_finite_resource: 9, loc_techs_investment_cost: 20, loc_techs_non_conversion: 23, loc_techs_om_cost: 9, loc_techs_supply_plus: 3, loc_techs_transmission: 10, locs: 4, techs: 9, timesteps: 48)
Coordinates:
  * loc_techs                             (loc_techs) object 'X2::demand_heat' ...
  * loc_techs_finite_resource             (loc_techs_finite_resource) object 'X1::demand_electricity' ...
  * techs                                 (techs) object 'supply_gas' ...
  * loc_techs_conversion                  (loc_techs_conversion) <U10 'X2::boiler' ...
  * loc_techs_supply_plus                 (loc_techs_supply_plus) object 'X1::pv' ...
  * loc_techs_non_conversion              (loc_techs_non_conversion) object 'X2::demand_heat' ...
  * locs                                  (locs) object 'X3' 'X1' 'N1' 'X2'
  * loc_techs_area                        (loc_techs_area) object 'X1::pv' ...
  * timesteps                             (timesteps) datetime64[ns] 2005-07-01 ...
  * loc_techs_investment_cost             (loc_techs_investment_cost) object 'X1::power_lines:X3' ...
  * loc_techs_conversion_plus             (loc_techs_conversion_plus) <U7 'X1::chp' ...
  * loc_techs_om_cost                     (loc_techs_om_cost) object 'X1::supply_gas' ...
  * carrier_tiers                         (carrier_tiers) <U5 'in' 'out' 'out_2'
  * coordinates                           (coordinates) object 'y' 'x'
  * loc_techs_transmission                (loc_techs_transmission) object 'X1::power_lines:X3' ...
  * carriers                              (carriers) object 'electricity' ...
  * costs                                 (costs) object 'monetary'
  * loc_carriers                          (loc_carriers) object 'X2::electricity' ...
  * loc_techs_export                      (loc_techs_export) object 'X1::pv' ...
  * loc_tech_carriers_conversion_plus     (loc_tech_carriers_conversion_plus) object 'X1::chp::electricity' ...
Data variables:
    resource                              (loc_techs_finite_resource, timesteps) float64 -0.4556 ...
    resource_unit                         (loc_techs_finite_resource) <U15 'energy' ...
    export_carrier                        (loc_techs_export) <U11 'electricity' ...
    energy_con                            (loc_techs) float64 1.0 nan 1.0 ...
    energy_cap_max                        (loc_techs) float64 nan 2e+03 ...
    lifetime                              (loc_techs) float64 nan 25.0 25.0 ...
    resource_area_per_energy_cap          (loc_techs_area) int64 7 7 7
    force_resource                        (loc_techs_finite_resource) bool True ...
    resource_area_max                     (loc_techs_area) int64 1500 1500 1500
    parasitic_eff                         (loc_techs_supply_plus) float64 0.85 ...
    energy_prod                           (loc_techs) float64 nan 1.0 1.0 ...
    resource_eff                          (loc_techs_finite_resource) float64 nan ...
    energy_eff                            (loc_techs) float64 nan nan 0.9037 ...
    reserve_margin                        (carriers) float64 nan nan nan
    cost_om_con                           (costs, loc_techs_om_cost) float64 0.025 ...
    cost_om_prod                          (costs, loc_techs_om_cost) float64 nan ...
    cost_depreciation_rate                (costs, loc_techs_investment_cost) float64 0.1102 ...
    cost_energy_cap                       (costs, loc_techs_investment_cost) float64 0.05 ...
    cost_om_annual                        (costs, loc_techs_om_cost) float64 nan ...
    cost_export                           (costs, loc_techs_om_cost, timesteps) float64 nan ...
    distance                              (loc_techs_transmission) float64 5.0 ...
    lookup_remotes                        (loc_techs_transmission) <U18 'X3::power_lines:X1' ...
    available_area                        (locs) float64 900.0 500.0 nan 1.3e+03
    loc_coordinates                       (coordinates, locs) int64 3 7 7 7 ...
    colors                                (techs) <U7 '#C98AAD' '#072486' ...
    inheritance                           (techs) <U29 'supply' 'demand' ...
    names                                 (techs) <U29 'Natural gas import' ...
    carrier_ratios                        (carrier_tiers, loc_tech_carriers_conversion_plus) float64 1.0 ...
    carrier_ratios_min                    (carrier_tiers, loc_techs_conversion_plus) float64 1.0 ...
    lookup_loc_carriers                   (loc_carriers) <U175 'X2::demand_electricity::electricity,X2::power_lines:X1::electricity,X2::pv::electricity' ...
    lookup_loc_techs                      (loc_techs_non_conversion) <U35 'X2::demand_heat::heat' ...
    lookup_loc_techs_conversion           (carrier_tiers, loc_techs_conversion) object 'X2::boiler::gas' ...
    lookup_primary_loc_tech_carriers_in   (loc_techs_conversion_plus) <U12 'X1::chp::gas' ...
    lookup_primary_loc_tech_carriers_out  (loc_techs_conversion_plus) <U20 'X1::chp::electricity' ...
    lookup_loc_techs_conversion_plus      (carrier_tiers, loc_techs_conversion_plus) object 'X1::chp::gas' ...
    lookup_loc_techs_export               (loc_techs_export) <U20 'X1::pv::electricity' ...
    lookup_loc_techs_area                 (locs) <U6 'X3::pv' 'X1::pv' '' ...
    timestep_resolution                   (timesteps) float64 1.0 1.0 1.0 ...
    timestep_weights                      (timesteps) float64 1.0 1.0 1.0 ...
    max_demand_timesteps                  (carriers) datetime64[ns] 2005-07-01T08:00:00 ...
Attributes:
    model.calliope_version:            0.6.3
    model.name:                        Urban-scale example model
    model.subset_time:                 ['2005-07-01', '2005-07-02']
    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-07-01 00:00:00 2005-07-01 01:00:00 2005-07-01 02:00:00 2005-07-01 03:00:00 2005-07-01 04:00:00 2005-07-01 05:00:00 2005-07-01 06:00:00 2005-07-01 07:00:00 2005-07-01 08:00:00 2005-07-01 09:00:00 ... 2005-07-02 14:00:00 2005-07-02 15:00:00 2005-07-02 16:00:00 2005-07-02 17:00:00 2005-07-02 18:00:00 2005-07-02 19:00:00 2005-07-02 20:00:00 2005-07-02 21:00:00 2005-07-02 22:00:00 2005-07-02 23:00:00
loc_techs_finite_resource
X1::demand_electricity -0.455564 -0.405798 -0.393291 -0.393992 -0.440085 -0.567821 -0.732535 -0.713803 -0.689992 -0.707650 ... -0.683060 -0.780370 -0.940634 -0.978388 -1.022063 -1.169519 -1.307938 -1.099334 -0.826212 -0.559499
X2::demand_heat -64.731991 -70.453439 -77.192976 -104.556436 -123.228444 -167.668819 -264.887092 -365.137675 -258.172589 -190.585578 ... -105.261025 -84.614417 -104.549875 -122.646451 -166.442507 -161.099889 -166.931078 -240.034833 -143.576460 -86.082014
X2::pv 0.000000 0.000000 0.000000 0.000000 0.000000 0.007857 0.029143 0.055571 0.078429 0.096000 ... 0.057857 0.041143 0.027000 0.014714 0.006143 0.000857 0.000000 0.000000 0.000000 0.000000
X1::pv 0.000000 0.000000 0.000000 0.000000 0.000000 0.007857 0.029143 0.055571 0.078429 0.096000 ... 0.057857 0.041143 0.027000 0.014714 0.006143 0.000857 0.000000 0.000000 0.000000 0.000000
X3::pv 0.000000 0.000000 0.000000 0.000000 0.000000 0.007857 0.029143 0.055571 0.078429 0.096000 ... 0.057857 0.041143 0.027000 0.014714 0.006143 0.000857 0.000000 0.000000 0.000000 0.000000
X3::demand_heat -0.015600 -0.860322 -0.015600 -0.015600 -0.860407 -7.263327 -9.398229 -5.792842 -3.322585 -1.927264 ... -0.015600 -0.015600 -0.860335 -0.015600 -0.015600 -0.860291 -0.015600 -0.860300 -0.015600 -0.860290
X1::demand_heat -0.215376 -0.200838 -0.207306 -0.318949 -0.650734 -1.039384 -1.181567 -1.285403 -1.209117 -1.219912 ... -0.491097 -0.540068 -0.641510 -0.741098 -0.761822 -0.707075 -0.634092 -0.523707 -0.400787 -0.272040
X3::demand_electricity -18.762912 -18.762912 -18.762912 -18.762912 -18.762912 -30.212425 -35.233307 -61.395269 -63.642962 -62.679665 ... -18.762912 -18.954418 -19.145924 -18.762912 -18.762912 -18.762912 -18.762912 -18.762912 -18.762912 -18.762912
X2::demand_electricity -94.545801 -76.960619 -77.475750 -77.475750 -82.731496 -148.533479 -189.570817 -238.734711 -244.284493 -231.440181 ... -199.895515 -221.324570 -188.344877 -249.962248 -248.894106 -269.344347 -245.412357 -196.280957 -135.289242 -103.741556

9 rows × 48 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-07-01 00:00:00 2005-07-01 01:00:00 2005-07-01 02:00:00 2005-07-01 03:00:00 2005-07-01 04:00:00 2005-07-01 05:00:00 2005-07-01 06:00:00 2005-07-01 07:00:00 2005-07-01 08:00:00 2005-07-01 09:00:00 ... 2005-07-02 14:00:00 2005-07-02 15:00:00 2005-07-02 16:00:00 2005-07-02 17:00:00 2005-07-02 18:00:00 2005-07-02 19:00:00 2005-07-02 20:00:00 2005-07-02 21:00:00 2005-07-02 22:00:00 2005-07-02 23:00:00
techs
demand_electricity -113.764277 -96.129329 -96.631954 -96.632654 -101.934493 -179.313724 -225.536658 -300.843784 -308.617446 -294.827496 ... -219.341487 -241.059359 -208.431436 -269.703548 -268.679081 -289.276777 -265.483207 -216.143204 -154.878366 -123.063967
demand_heat -64.962967 -71.514599 -77.415882 -104.890985 -124.739585 -175.971530 -275.466888 -372.215920 -262.704292 -193.732754 ... -105.767723 -85.170084 -106.051720 -123.403148 -167.219928 -162.667255 -167.580770 -241.418840 -143.992847 -87.214344
pv 0.000000 0.000000 0.000000 0.000000 0.000000 0.023571 0.087429 0.166714 0.235286 0.288000 ... 0.173571 0.123429 0.081000 0.044143 0.018429 0.002571 0.000000 0.000000 0.000000 0.000000

3 rows × 48 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:29:14] INFO: Backend: starting model run
[2018-10-04 16:29:16] INFO: Backend: model generated. Time since start: 0:00:06.084728
[2018-10-04 16:29:16] INFO: Backend: sending model to solver
[2018-10-04 16:29:18] INFO: Backend: solver finished running. Time since start: 0:00:07.665871
[2018-10-04 16:29:18] INFO: Backend: loaded results
[2018-10-04 16:29:18] INFO: Backend: generated solution array. Time since start: 0:00:07.779079
[2018-10-04 16:29:18] INFO: Postprocessing: started
[2018-10-04 16:29:19] INFO: Postprocessing: All values < 1e-10 set to 0 in carrier_prod, carrier_con, cost_var, capacity_factor
[2018-10-04 16:29:19] INFO: Postprocessing: Model was feasible, deleting unmet_demand variable
[2018-10-04 16:29:19] INFO: Postprocessing: ended. Time since start: 0:00:08.479977

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: 3, costs: 1, loc_tech_carriers_con: 19, loc_tech_carriers_export: 4, loc_tech_carriers_prod: 21, loc_techs: 26, loc_techs_area: 3, loc_techs_cost: 20, loc_techs_investment_cost: 20, loc_techs_om_cost: 9, loc_techs_supply_plus: 3, techs: 9, timesteps: 48)
Coordinates:
  * loc_techs                   (loc_techs) object 'X2::demand_heat' ...
  * techs                       (techs) object 'supply_gas' ...
  * loc_techs_supply_plus       (loc_techs_supply_plus) object 'X1::pv' ...
  * loc_tech_carriers_export    (loc_tech_carriers_export) object 'X1::pv::electricity' ...
  * loc_techs_area              (loc_techs_area) object 'X1::pv' 'X3::pv' ...
  * timesteps                   (timesteps) datetime64[ns] 2005-07-01 ...
  * loc_techs_investment_cost   (loc_techs_investment_cost) object 'X1::power_lines:X3' ...
  * loc_techs_om_cost           (loc_techs_om_cost) object 'X1::supply_gas' ...
  * loc_techs_cost              (loc_techs_cost) object 'X1::power_lines:X3' ...
  * loc_tech_carriers_prod      (loc_tech_carriers_prod) object 'N1::heat_pipes:X2::heat' ...
  * carriers                    (carriers) <U11 'electricity' 'gas' 'heat'
  * costs                       (costs) object 'monetary'
  * loc_tech_carriers_con       (loc_tech_carriers_con) object 'X2::demand_electricity::electricity' ...
Data variables:
    energy_cap                  (loc_techs) float64 365.1 644.3 291.3 226.9 ...
    carrier_prod                (loc_tech_carriers_prod, timesteps) float64 0.0 ...
    carrier_con                 (loc_tech_carriers_con, timesteps) float64 -94.55 ...
    cost                        (costs, loc_techs_cost) float64 0.0006909 ...
    resource_area               (loc_techs_area) float64 0.0 350.0 443.2
    resource_con                (loc_techs_supply_plus, timesteps) float64 0.0 ...
    resource_cap                (loc_techs_supply_plus) float64 0.0 38.95 49.32
    carrier_export              (loc_tech_carriers_export, timesteps) float64 0.0 ...
    cost_var                    (costs, loc_techs_om_cost, timesteps) float64 7.165 ...
    cost_investment             (costs, loc_techs_investment_cost) float64 0.0006909 ...
    capacity_factor             (loc_tech_carriers_prod, timesteps) float64 0.0 ...
    systemwide_capacity_factor  (techs, carriers) float64 0.0 0.5841 0.0 nan ...
    systemwide_levelised_cost   (carriers, techs, costs) float64 inf nan ...
    total_levelised_cost        (carriers, costs) float64 0.04284 0.03449 ...
Attributes:
    model.calliope_version:            0.6.3
    model.name:                        Urban-scale example model
    model.subset_time:                 ['2005-07-01', '2005-07-02']
    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.773817
    time_finished:                     2018-10-04 16:29:18

In [8]:
# We can sum heat output over all locations and turn the result into a pandas DataFrame
df_heat = model.get_formatted_array('carrier_prod').loc[{'carriers':'heat'}].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_heat.info()


<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 48 entries, 2005-07-01 00:00:00 to 2005-07-02 23:00:00
Data columns (total 12 columns):
boiler               48 non-null float64
chp                  48 non-null float64
heat_pipes:N1        48 non-null float64
heat_pipes:X1        48 non-null float64
heat_pipes:X2        48 non-null float64
heat_pipes:X3        48 non-null float64
power_lines:X1       48 non-null float64
power_lines:X2       48 non-null float64
power_lines:X3       48 non-null float64
pv                   48 non-null float64
supply_gas           48 non-null float64
supply_grid_power    48 non-null float64
dtypes: float64(12)
memory usage: 4.9 KB

In [9]:
# Using .head() to see the first few rows of heat generation and demand

# Note: carrier production in heat_pipes:N1 is heat received by the heat network at any location connected to `N1`

df_heat.head()


Out[9]:
techs boiler chp heat_pipes:N1 heat_pipes:X1 heat_pipes:X2 heat_pipes:X3 power_lines:X1 power_lines:X2 power_lines:X3 pv supply_gas supply_grid_power
timesteps
2005-07-01 00:00:00 0.000000 92.861360 143.669589 85.869798 0.0 71.320854 0.0 0.0 0.0 0.0 0.0 0.0
2005-07-01 01:00:00 4.100046 78.466297 67.213715 72.541074 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 0.0
2005-07-01 02:00:00 9.626102 78.876806 67.582474 72.915564 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 0.0
2005-07-01 03:00:00 37.084989 78.877367 67.487047 72.812606 0.0 0.000000 0.0 0.0 0.0 0.0 0.0 0.0
2005-07-01 04:00:00 53.191064 83.204646 70.897788 76.515868 0.0 0.000000 0.0 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 boiler chp heat_pipes:N1 heat_pipes:X1 heat_pipes:X2 heat_pipes:X3 power_lines:X1 power_lines:X2 power_lines:X3 pv supply_gas supply_grid_power
locs
N1 NaN NaN NaN 0.066459 0.055975 0.105511 NaN NaN NaN NaN NaN NaN
X1 NaN 154.998801 0.066459 NaN NaN NaN NaN 0.008286 0.000691 0.000000 569.159885 14.73589
X2 11.713117 NaN 0.055975 NaN NaN NaN 0.008286 NaN NaN 30.598098 41.987552 NaN
X3 0.002496 NaN 0.105511 NaN NaN NaN 0.000691 NaN NaN 18.692301 0.011023 NaN

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

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


Out[13]:
techs
supply_gas                 inf
demand_electricity         NaN
pv                    0.044896
demand_heat                NaN
heat_pipes                 NaN
supply_grid_power     0.115972
chp                   0.016822
power_lines                NaN
boiler                     inf
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 the first day here, to improve loading speed.

model.plot.flows(timestep_index_subset=[0, 24])



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.