Calliope Urban Scale Example Model


In [1]:
import calliope

# We increase logging verbosity
calliope.set_log_verbosity('INFO', include_solver_output=False)

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


[2020-01-14 17:55:53] INFO     Model: initialising
[2020-01-14 17:55:53] INFO     Model: preprocessing stage 1 (model_run)
[2020-01-14 17:55:53] INFO     NumExpr defaulting to 8 threads.
[2020-01-14 17:55:53] INFO     Model: preprocessing stage 2 (model_data)
[2020-01-14 17:55:54] INFO     Model: preprocessing complete

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_supply_plus                 (loc_techs_supply_plus) object 'X3::pv' ... 'X1::pv'
  * carriers                              (carriers) object 'electricity' ... 'gas'
  * carrier_tiers                         (carrier_tiers) object 'out_2' ... 'out'
  * loc_techs_area                        (loc_techs_area) object 'X3::pv' ... 'X1::pv'
  * loc_techs                             (loc_techs) object 'X3::heat_pipes:N1' ... 'X2::pv'
  * loc_carriers                          (loc_carriers) object 'X1::heat' ... 'X1::gas'
  * coordinates                           (coordinates) object 'y' 'x'
  * loc_techs_non_conversion              (loc_techs_non_conversion) object 'X3::heat_pipes:N1' ... 'X2::pv'
  * loc_techs_om_cost                     (loc_techs_om_cost) object 'X3::pv' ... 'X2::pv'
  * loc_techs_investment_cost             (loc_techs_investment_cost) object 'X3::heat_pipes:N1' ... 'X2::pv'
  * loc_techs_transmission                (loc_techs_transmission) object 'X3::heat_pipes:N1' ... 'N1::heat_pipes:X2'
  * costs                                 (costs) object 'monetary'
  * locs                                  (locs) object 'X1' 'X2' 'X3' 'N1'
  * loc_techs_conversion                  (loc_techs_conversion) object 'X3::boiler' 'X2::boiler'
  * timesteps                             (timesteps) datetime64[ns] 2005-07-01 ... 2005-07-02T23:00:00
  * loc_techs_finite_resource             (loc_techs_finite_resource) object 'X3::demand_electricity' ... 'X2::pv'
  * techs                                 (techs) object 'supply_grid_power' ... 'boiler'
  * loc_techs_export                      (loc_techs_export) object 'X2::pv' ... 'X1::pv'
  * loc_techs_conversion_plus             (loc_techs_conversion_plus) object 'X1::chp'
  * loc_tech_carriers_conversion_plus     (loc_tech_carriers_conversion_plus) object 'X1::chp::gas' ... 'X1::chp::electricity'
Data variables:
    parasitic_eff                         (loc_techs_supply_plus) float64 0.85 ... 0.85
    energy_con                            (loc_techs) float64 1.0 1.0 ... nan
    energy_cap_max                        (loc_techs) float64 2e+03 ... 250.0
    lifetime                              (loc_techs) float64 25.0 nan ... 25.0
    resource_eff                          (loc_techs_finite_resource) float64 nan ... 1.0
    energy_eff                            (loc_techs) float64 0.9037 nan ... nan
    resource                              (loc_techs_finite_resource, timesteps) float64 -18.76 ... 0.0
    resource_area_max                     (loc_techs_area) int64 1500 1500 1500
    resource_area_per_energy_cap          (loc_techs_area) int64 7 7 7
    force_resource                        (loc_techs_finite_resource) bool True ... True
    energy_prod                           (loc_techs) float64 1.0 nan ... 1.0
    resource_unit                         (loc_techs_finite_resource) <U15 'energy' ... 'energy_per_area'
    export_carrier                        (loc_techs_export) <U11 'electricity' ... 'electricity'
    cost_om_annual                        (costs, loc_techs_om_cost) float64 -80.5 ... nan
    cost_export                           (costs, loc_techs_om_cost, timesteps) float64 nan ... -0.0491
    cost_om_prod                          (costs, loc_techs_om_cost) float64 nan ... -0.0203
    cost_om_con                           (costs, loc_techs_om_cost) float64 nan ... nan
    cost_energy_cap                       (costs, loc_techs_investment_cost) float64 1.2 ... 1.35e+03
    cost_depreciation_rate                (costs, loc_techs_investment_cost) float64 0.1102 ... 0.1102
    distance                              (loc_techs_transmission) float64 4.0 ... 3.0
    lookup_remotes                        (loc_techs_transmission) <U18 'N1::heat_pipes:X3' ... 'X2::heat_pipes:N1'
    available_area                        (locs) float64 500.0 1.3e+03 900.0 nan
    loc_coordinates                       (coordinates, locs) int64 7 7 ... 5 5
    colors                                (techs) <U7 '#C5ABE3' ... '#8E2999'
    inheritance                           (techs) <U29 'supply' ... 'conversion'
    names                                 (techs) <U29 'National grid import' ... 'Natural gas boiler'
    carrier_ratios                        (carrier_tiers, loc_tech_carriers_conversion_plus) float64 1.0 ... 1.0
    lookup_loc_carriers                   (loc_carriers) <U175 'X1::demand_heat::heat,X1::chp::heat,X1::heat_pipes:N1::heat' ... 'X1::chp::gas,X1::supply_gas::gas'
    lookup_loc_techs                      (loc_techs_non_conversion) <U35 'X3::heat_pipes:N1::heat' ... 'X2::pv::electricity'
    lookup_loc_techs_conversion           (carrier_tiers, loc_techs_conversion) object None ... 'X2::boiler::heat'
    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' ... 'X1::chp::electricity'
    lookup_loc_techs_export               (loc_techs_export) <U20 'X2::pv::electricity' ... 'X1::pv::electricity'
    lookup_loc_techs_area                 (locs) <U6 'X1::pv' 'X2::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 ... 2005-07-01
Attributes:
    calliope_version:    0.6.5-dev
    applied_overrides:   
    scenario:            None
    defaults:            available_area: null\ncarrier_ratios: {}\ncharge_rat...
    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
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
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::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::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
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_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_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

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()


[2020-01-14 17:55:54] INFO     Backend: starting model run
[2020-01-14 17:55:55] INFO     constraints are loaded in the following order: ['capacity', 'dispatch', 'policy', 'energy_balance', 'costs', 'network', 'conversion', 'group', 'conversion_plus', 'export', 'milp']
[2020-01-14 17:55:55] INFO     Backend: model generated. Time since start of model run: 0:00:01.055545
[2020-01-14 17:55:55] INFO     Backend: sending model to solver
[2020-01-14 17:55:56] INFO     Backend: solver finished running. Time since start of model run: 0:00:01.866474
[2020-01-14 17:55:56] INFO     Backend: loaded results
[2020-01-14 17:55:56] INFO     Backend: generated solution array. Time since start of model run: 0:00:01.928103
[2020-01-14 17:55:56] INFO     Postprocessing: started
[2020-01-14 17:55:56] INFO     Postprocessing: zero threshold of 1e-10 not required
[2020-01-14 17:55:56] INFO     Postprocessing: Model was feasible, deleting unmet_demand variable
[2020-01-14 17:55:56] INFO     Postprocessing: ended. Time since start of model run: 0:00:02.119985

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_supply_plus       (loc_techs_supply_plus) object 'X3::pv' ... 'X1::pv'
  * carriers                    (carriers) object 'electricity' 'heat' 'gas'
  * loc_tech_carriers_con       (loc_tech_carriers_con) object 'X3::demand_electricity::electricity' ... 'X3::power_lines:X1::electricity'
  * loc_tech_carriers_export    (loc_tech_carriers_export) object 'X1::chp::electricity' ... 'X2::pv::electricity'
  * timesteps                   (timesteps) datetime64[ns] 2005-07-01 ... 2005-07-02T23:00:00
  * loc_techs_area              (loc_techs_area) object 'X3::pv' ... 'X1::pv'
  * loc_techs                   (loc_techs) object 'X3::heat_pipes:N1' ... 'X2::pv'
  * loc_techs_om_cost           (loc_techs_om_cost) object 'X3::pv' ... 'X2::pv'
  * techs                       (techs) object 'supply_grid_power' ... 'boiler'
  * loc_tech_carriers_prod      (loc_tech_carriers_prod) object 'X2::power_lines:X1::electricity' ... 'X3::boiler::heat'
  * loc_techs_cost              (loc_techs_cost) object 'X3::heat_pipes:N1' ... 'X2::pv'
  * loc_techs_investment_cost   (loc_techs_investment_cost) object 'X3::heat_pipes:N1' ... 'X2::pv'
  * costs                       (costs) object 'monetary'
Data variables:
    energy_cap                  (loc_techs) float64 10.38 64.93 ... 33.62 63.31
    carrier_prod                (loc_tech_carriers_prod, timesteps) float64 94.55 ... 0.0156
    carrier_con                 (loc_tech_carriers_con, timesteps) float64 -18.76 ... 0.0
    cost                        (costs, loc_techs_cost) float64 0.003761 ... 30.6
    resource_area               (loc_techs_area) float64 350.0 443.2 0.0
    resource_con                (loc_techs_supply_plus, timesteps) float64 0.0 ... 0.0
    resource_cap                (loc_techs_supply_plus) float64 38.95 49.32 0.0
    carrier_export              (loc_tech_carriers_export, timesteps) float64 0.0 ... 0.0
    cost_var                    (costs, loc_techs_om_cost, timesteps) float64 0.0 ... 0.0
    cost_investment             (costs, loc_techs_investment_cost) float64 0.003761 ... 51.59
    capacity_factor             (loc_tech_carriers_prod, timesteps) float64 0.3444 ... 1.0
    systemwide_capacity_factor  (carriers, techs) float64 0.07874 0.7356 ... 0.0
    systemwide_levelised_cost   (carriers, costs, techs) float64 0.116 ... inf
    total_levelised_cost        (carriers, costs) float64 0.08069 ... 0.03449
Attributes:
    calliope_version:          0.6.5-dev
    applied_overrides:         
    scenario:                  None
    defaults:                  available_area: null\ncarrier_ratios: {}\nchar...
    allow_operate_mode:        1
    model_config:              calliope_version: 0.6.5\nname: Urban-scale exa...
    run_config:                backend: pyomo\nbigM: 1000000.0\ncyclic_storag...
    termination_condition:     optimal
    objective_function_value:  842.3529094335403
    solution_time:             1.928103
    time_finished:             2020-01-14 17:55:56

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 170.055601 183.475520 0.0 0.0 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.0 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.0 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.0 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.0 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.16246 0.051679 0.003761 NaN NaN NaN NaN NaN NaN
X1 NaN 154.9988 0.162460 NaN NaN NaN NaN 0.008286 0.000691 0.000000 569.159880 14.73589
X2 11.713117 NaN 0.051679 NaN NaN NaN 0.008286 NaN NaN 30.598098 41.987552 NaN
X3 0.002496 NaN 0.003761 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_grid_power     0.115972
chp                   0.016822
supply_gas                 inf
power_lines                NaN
pv                    0.044896
heat_pipes                 NaN
demand_electricity         NaN
demand_heat                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])



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