Calliope Urban Scale Example Model

In [1]:
import calliope

# cufflinks allows for easy plotly plots to be 
# produced from a pandas DataFrame
import cufflinks

# We increase logging verbosity
calliope.set_log_level('INFO')
In [2]:
model = calliope.examples.urban_scale()
[2018-06-04 12:03:17] INFO: Model: initialising
[2018-06-04 12:03:19] INFO: Model: preprocessing stage 1 (model_run)
[2018-06-04 12:03:21] INFO: Model: preprocessing stage 2 (model_data)
[2018-06-04 12:03:22] INFO: Model: preprocessing complete. Time since start: 0:00:05.266276
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_om_cost                     (loc_techs_om_cost) object 'X1::supply_grid_power' ...
  * loc_techs_export                      (loc_techs_export) object 'X2::pv' ...
  * loc_techs_finite_resource             (loc_techs_finite_resource) object 'X2::demand_heat' ...
  * coordinates                           (coordinates) object 'x' 'y'
  * loc_techs_supply_plus                 (loc_techs_supply_plus) object 'X2::pv' ...
  * loc_techs_conversion                  (loc_techs_conversion) <U10 'X3::boiler' ...
  * locs                                  (locs) object 'X1' 'X3' 'X2' 'N1'
  * carrier_tiers                         (carrier_tiers) <U5 'out_2' 'in' 'out'
  * costs                                 (costs) object 'monetary'
  * techs                                 (techs) object 'chp' 'demand_heat' ...
  * timesteps                             (timesteps) datetime64[ns] 2005-07-01 ...
  * loc_techs_non_conversion              (loc_techs_non_conversion) object 'X1::supply_grid_power' ...
  * loc_techs_area                        (loc_techs_area) object 'X2::pv' ...
  * loc_techs                             (loc_techs) object 'X1::supply_grid_power' ...
  * carriers                              (carriers) object 'gas' 'heat' ...
  * loc_techs_investment_cost             (loc_techs_investment_cost) object 'X2::power_lines:X1' ...
  * loc_techs_transmission                (loc_techs_transmission) object 'X1::power_lines:X2' ...
  * loc_techs_conversion_plus             (loc_techs_conversion_plus) <U7 'X1::chp' ...
  * loc_carriers                          (loc_carriers) object 'X2::heat' ...
  * loc_tech_carriers_conversion_plus     (loc_tech_carriers_conversion_plus) object 'X1::chp::electricity' ...
Data variables:
    resource_area_max                     (loc_techs_area) int32 1500 1500 1500
    energy_eff                            (loc_techs) float64 nan nan nan ...
    resource_unit                         (loc_techs_finite_resource) <U5 'power' ...
    force_resource                        (loc_techs_finite_resource) bool True ...
    energy_con                            (loc_techs) float64 nan nan nan ...
    lifetime                              (loc_techs) float64 25.0 25.0 25.0 ...
    export_carrier                        (loc_techs_export) <U11 'electricity' ...
    parasitic_eff                         (loc_techs_supply_plus) float64 0.85 ...
    resource_area_per_energy_cap          (loc_techs_area) int32 7 7 7
    resource                              (loc_techs_finite_resource, timesteps) float64 -64.73 ...
    resource_eff                          (loc_techs_finite_resource) float64 nan ...
    energy_cap_max                        (loc_techs) float64 2e+03 50.0 ...
    energy_prod                           (loc_techs) float64 1.0 1.0 1.0 ...
    reserve_margin                        (carriers) float64 nan nan nan
    cost_export                           (costs, loc_techs_om_cost, timesteps) float64 nan ...
    cost_om_con                           (costs, loc_techs_om_cost) float64 0.1 ...
    cost_depreciation_rate                (costs, loc_techs_investment_cost) float64 0.1102 ...
    cost_om_annual                        (costs, loc_techs_om_cost) float64 nan ...
    cost_om_prod                          (costs, loc_techs_om_cost) float64 nan ...
    cost_energy_cap                       (costs, loc_techs_investment_cost) float64 0.1 ...
    distance                              (loc_techs_transmission) float64 10.0 ...
    lookup_remotes                        (loc_techs_transmission) <U18 'X2::power_lines:X1' ...
    available_area                        (locs) float64 500.0 900.0 1.3e+03 nan
    loc_coordinates                       (coordinates, locs) int32 2 5 8 5 ...
    colors                                (techs) <U7 '#E4AB97' '#660507' ...
    inheritance                           (techs) <U29 'conversion_plus' ...
    names                                 (techs) <U29 'Combined heat and power' ...
    carrier_ratios                        (carrier_tiers, loc_tech_carriers_conversion_plus) float64 1.0 ...
    carrier_ratios_min                    (carrier_tiers, loc_techs_conversion_plus) float64 0.8 ...
    lookup_loc_carriers                   (loc_carriers) <U175 'X2::demand_heat::heat,X2::boiler::heat,X2::heat_pipes:N1::heat' ...
    lookup_loc_techs                      (loc_techs_non_conversion) <U35 'X1::supply_grid_power::electricity' ...
    lookup_loc_techs_conversion           (carrier_tiers, loc_techs_conversion) object None ...
    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::heat' ...
    lookup_loc_techs_export               (loc_techs_export) <U20 'X2::pv::electricity' ...
    lookup_loc_techs_area                 (locs) <U6 'X1::pv' 'X3::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-01 ...
Attributes:
    model.calliope_version:            0.6.2
    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:                False
    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.2-dev
    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
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
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
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
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
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
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
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
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

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-06-04 12:03:23] INFO: Backend: starting model run
[2018-06-04 12:03:25] INFO: Backend: model generated. Time since start: 0:00:07.971998
[2018-06-04 12:03:25] INFO: Backend: sending model to solver
[2018-06-04 12:03:29] INFO: Backend: solver finished running. Time since start: 0:00:12.056859
[2018-06-04 12:03:29] INFO: Backend: loaded results
[2018-06-04 12:03:29] INFO: Backend: generated solution array. Time since start: 0:00:12.259319
[2018-06-04 12:03:29] INFO: Postprocessing: started
[2018-06-04 12:03:30] INFO: Postprocessing: All values < 1e-10 set to 0 in carrier_prod, carrier_con, cost_var, capacity_factor
[2018-06-04 12:03:30] INFO: Postprocessing: Model was feasible, deleting unmet_demand variable
[2018-06-04 12:03:30] INFO: Postprocessing: ended. Time since start: 0:00:13.171154
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_om_cost           (loc_techs_om_cost) object 'X1::supply_grid_power' ...
  * loc_techs_cost              (loc_techs_cost) object 'X2::power_lines:X1' ...
  * loc_techs_supply_plus       (loc_techs_supply_plus) object 'X2::pv' ...
  * loc_tech_carriers_prod      (loc_tech_carriers_prod) object 'X2::boiler::heat' ...
  * costs                       (costs) object 'monetary'
  * techs                       (techs) object 'chp' 'demand_heat' 'boiler' ...
  * timesteps                   (timesteps) datetime64[ns] 2005-07-01 ...
  * loc_tech_carriers_export    (loc_tech_carriers_export) object 'X1::chp::electricity' ...
  * loc_techs_area              (loc_techs_area) object 'X2::pv' 'X3::pv' ...
  * loc_techs                   (loc_techs) object 'X1::supply_grid_power' ...
  * loc_tech_carriers_con       (loc_tech_carriers_con) object 'X1::power_lines:X2::electricity' ...
  * carriers                    (carriers) object 'gas' 'heat' 'electricity'
  * loc_techs_investment_cost   (loc_techs_investment_cost) object 'X2::power_lines:X1' ...
Data variables:
    energy_cap                  (loc_techs) float64 33.62 50.0 0.0 63.31 ...
    carrier_prod                (loc_tech_carriers_prod, timesteps) float64 0.0 ...
    carrier_con                 (loc_tech_carriers_con, timesteps) float64 -96.48 ...
    cost                        (costs, loc_techs_cost) float64 0.008286 ...
    resource_area               (loc_techs_area) float64 443.2 350.0 0.0
    resource_con                (loc_techs_supply_plus, timesteps) float64 0.0 ...
    resource_cap                (loc_techs_supply_plus) float64 49.32 38.95 0.0
    carrier_export              (loc_tech_carriers_export, timesteps) float64 0.0 ...
    cost_var                    (costs, loc_techs_om_cost, timesteps) float64 0.0 ...
    cost_investment             (costs, loc_techs_investment_cost) float64 0.008286 ...
    capacity_factor             (loc_tech_carriers_prod, timesteps) float64 0.0 ...
    systemwide_capacity_factor  (techs, carriers) float64 0.0 0.5885 0.7356 ...
    systemwide_levelised_cost   (carriers, techs, costs) float64 inf nan inf ...
    total_levelised_cost        (carriers, costs) float64 0.03449 0.03131 ...
Attributes:
    model.calliope_version:            0.6.2
    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:                False
    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.2-dev
    defaults:                          available_area: null\ncarrier_ratios: ...
    allow_operate_mode:                1
    termination_condition:             optimal
    solution_time:                     6.144758
    time_finished:                     2018-06-04 12:03:29
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 might like to plot this, 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

costs.iplot(kind='bar')
Out[13]:
In [14]:
# 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[14]:
techs
chp                   0.016822
demand_heat                NaN
boiler                     inf
heat_pipes                 NaN
demand_electricity         NaN
power_lines                NaN
supply_gas                 inf
pv                    0.044896
supply_grid_power     0.115972
dtype: float64
In [15]:
# We could plot this using lcoes.iplot(kind='bar', title='Systemwide levelised costs')
# Or we can just plot it directly using calliope analysis functionality
model.plot.capacity(array='systemwide_levelised_cost')
In [16]:
# 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()

See the Calliope documentation for more details on setting up and running a Calliope model.