Calliope Urban Scale MILP Example Model

For more details on analysing input/output data, see the full 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.milp()

# Note, we see the overrides that we have applied printed here, thanks to inreasing logging verbosity
# We also see a warning that we are applying binary/integer decision variables in a model, to remind us that this
# model may take a while to run


[2020-01-14 17:48:55] INFO     Model: initialising
[2020-01-14 17:48:55] INFO     Applying the following overrides without a specific scenario name: ['milp']
[2020-01-14 17:48:55] INFO     Override applied to model.name: Urban-scale example model -> Urban-scale example model with MILP
`run.solver_options.mipgap`:0.05 applied from override as new configuration
`techs.boiler.costs.monetary.energy_cap`:35 applied from override as new configuration
`techs.boiler.costs.monetary.purchase`:2000 applied from override as new configuration
`techs.chp.constraints.energy_cap_min_use`:0.2 applied from override as new configuration
`techs.chp.constraints.energy_cap_per_unit`:300 applied from override as new configuration
`techs.chp.constraints.units_max`:4 applied from override as new configuration
Override applied to techs.chp.costs.monetary.energy_cap: 750 -> 700
`techs.chp.costs.monetary.purchase`:40000 applied from override as new configuration
`techs.heat_pipes.constraints.force_asynchronous_prod_con`:True applied from override as new configuration
[2020-01-14 17:48:56] INFO     Model: preprocessing stage 1 (model_run)
[2020-01-14 17:48:56] INFO     NumExpr defaulting to 8 threads.
[2020-01-14 17:48:56] INFO     Model: preprocessing stage 2 (model_data)
[2020-01-14 17:48:57] WARNING  Warning: Possible issues found during model processing:
 * Integer and / or binary decision variables are included in this model. This may adversely affect solution time, particularly if you are using a non-commercial solver. To improve solution time, consider changing MILP related solver options (e.g. `mipgap`) or removing MILP constraints.

[2020-01-14 17:48:57] 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_milp: 1, 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_transmission                (loc_techs_transmission) object 'X1::power_lines:X2' ... 'X2::heat_pipes:N1'
  * timesteps                             (timesteps) datetime64[ns] 2005-07-01 ... 2005-07-02T23:00:00
  * loc_techs                             (loc_techs) object 'X1::power_lines:X2' ... 'X1::demand_heat'
  * loc_techs_milp                        (loc_techs_milp) object 'X1::chp'
  * loc_techs_om_cost                     (loc_techs_om_cost) object 'X1::chp' ... 'X2::pv'
  * loc_carriers                          (loc_carriers) object 'X2::heat' ... 'X1::electricity'
  * costs                                 (costs) object 'monetary'
  * carriers                              (carriers) object 'gas' ... 'electricity'
  * loc_techs_non_conversion              (loc_techs_non_conversion) object 'X1::power_lines:X2' ... 'X1::demand_heat'
  * carrier_tiers                         (carrier_tiers) object 'out_2' ... 'in'
  * loc_techs_conversion_plus             (loc_techs_conversion_plus) object 'X1::chp'
  * loc_techs_conversion                  (loc_techs_conversion) object 'X3::boiler' 'X2::boiler'
  * loc_techs_area                        (loc_techs_area) object 'X3::pv' ... 'X1::pv'
  * loc_techs_finite_resource             (loc_techs_finite_resource) object 'X1::demand_electricity' ... 'X1::demand_heat'
  * loc_techs_supply_plus                 (loc_techs_supply_plus) object 'X3::pv' ... 'X1::pv'
  * loc_tech_carriers_conversion_plus     (loc_tech_carriers_conversion_plus) object 'X1::chp::gas' ... 'X1::chp::heat'
  * techs                                 (techs) object 'demand_electricity' ... 'pv'
  * coordinates                           (coordinates) object 'x' 'y'
  * loc_techs_investment_cost             (loc_techs_investment_cost) object 'X1::power_lines:X2' ... 'X2::heat_pipes:N1'
  * loc_techs_export                      (loc_techs_export) object 'X3::pv' ... 'X1::pv'
  * locs                                  (locs) object 'X2' 'X1' 'X3' 'N1'
Data variables:
    parasitic_eff                         (loc_techs_supply_plus) float64 0.85 ... 0.85
    resource_area_max                     (loc_techs_area) int64 1500 1500 1500
    units_max                             (loc_techs_milp) int64 4
    energy_prod                           (loc_techs) float64 1.0 1.0 ... nan
    resource_eff                          (loc_techs_finite_resource) float64 nan ... nan
    energy_cap_min_use                    (loc_techs) float64 nan nan ... nan
    energy_cap_per_unit                   (loc_techs) float64 nan nan ... nan
    lifetime                              (loc_techs) float64 25.0 25.0 ... nan
    export_carrier                        (loc_techs_export) <U11 'electricity' ... 'electricity'
    resource                              (loc_techs_finite_resource, timesteps) float64 -0.4556 ... -0.272
    resource_unit                         (loc_techs_finite_resource) <U15 'energy' ... 'energy'
    energy_eff                            (loc_techs) float64 0.98 nan ... nan
    resource_area_per_energy_cap          (loc_techs_area) int64 7 7 7
    force_asynchronous_prod_con           (loc_techs) float64 nan nan ... nan
    energy_con                            (loc_techs) float64 1.0 nan ... 1.0
    force_resource                        (loc_techs_finite_resource) bool True ... True
    energy_cap_max                        (loc_techs) float64 2e+03 ... nan
    cost_purchase                         (costs, loc_techs_investment_cost) float64 nan ... nan
    cost_energy_cap                       (costs, loc_techs_investment_cost) float64 0.1 ... 0.9
    cost_export                           (costs, loc_techs_om_cost, timesteps) float64 -0.03173 ... -0.0491
    cost_depreciation_rate                (costs, loc_techs_investment_cost) float64 0.1102 ... 0.1102
    cost_om_con                           (costs, loc_techs_om_cost) float64 nan ... nan
    cost_om_annual                        (costs, loc_techs_om_cost) float64 nan ... nan
    cost_om_prod                          (costs, loc_techs_om_cost) float64 0.004 ... -0.0203
    distance                              (loc_techs_transmission) float64 10.0 ... 3.0
    lookup_remotes                        (loc_techs_transmission) <U18 'X2::power_lines:X1' ... 'N1::heat_pipes:X2'
    available_area                        (locs) float64 1.3e+03 500.0 900.0 nan
    loc_coordinates                       (coordinates, locs) int64 8 2 ... 3 7
    colors                                (techs) <U7 '#072486' ... '#F9D956'
    inheritance                           (techs) <U29 'demand' ... 'supply_power_plus.supply_plus'
    names                                 (techs) <U29 'Electrical demand' ... 'Solar photovoltaic power'
    carrier_ratios                        (carrier_tiers, loc_tech_carriers_conversion_plus) float64 1.0 ... 1.0
    lookup_loc_carriers                   (loc_carriers) <U175 'X2::demand_heat::heat,X2::heat_pipes:N1::heat,X2::boiler::heat' ... 'X1::demand_electricity::electricity,X1::pv::electricity,X1::supply_grid_power::electricity,X1::power_lines:X3::electricity,X1::chp::electricity,X1::power_lines:X2::electricity'
    lookup_loc_techs                      (loc_techs_non_conversion) <U35 'X1::power_lines:X2::electricity' ... 'X1::demand_heat::heat'
    lookup_loc_techs_conversion           (carrier_tiers, loc_techs_conversion) object None ... '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::heat' ... 'X1::chp::gas'
    lookup_loc_techs_export               (loc_techs_export) <U20 'X3::pv::electricity' ... 'X1::pv::electricity'
    lookup_loc_techs_area                 (locs) <U6 'X2::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-01 ... 2005-07-01T08:00:00
Attributes:
    calliope_version:    0.6.5-dev
    applied_overrides:   milp
    scenario:            milp
    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
# Here we look at one of the MILP overrides that we have added, the fixed `purchase` cost
model.inputs.cost_purchase.to_pandas().dropna(axis=1)


Out[4]:
loc_techs_investment_cost X3::boiler X2::boiler X1::chp
costs
monetary 2000.0 2000.0 40000.0

In [5]:
# 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:48:57] INFO     Backend: starting model run
[2020-01-14 17:48:58] 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:48:59] INFO     Backend: model generated. Time since start of model run: 0:00:01.697778
[2020-01-14 17:48:59] INFO     Backend: sending model to solver
[2020-01-14 17:49:02] INFO     Backend: solver finished running. Time since start of model run: 0:00:04.734570
[2020-01-14 17:49:02] INFO     Backend: loaded results
[2020-01-14 17:49:02] INFO     Backend: generated solution array. Time since start of model run: 0:00:04.834740
[2020-01-14 17:49:02] INFO     Postprocessing: started
[2020-01-14 17:49:02] INFO     Postprocessing: zero threshold of 1e-10 not required
[2020-01-14 17:49:02] INFO     Postprocessing: Model was feasible, deleting unmet_demand variable
[2020-01-14 17:49:02] INFO     Postprocessing: ended. Time since start of model run: 0:00:05.157601

In [6]:
# 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[6]:
<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_asynchronous_prod_con: 6, loc_techs_cost: 20, loc_techs_investment_cost: 20, loc_techs_milp: 1, loc_techs_om_cost: 9, loc_techs_purchase: 2, loc_techs_supply_plus: 3, techs: 9, timesteps: 48)
Coordinates:
  * loc_techs_purchase               (loc_techs_purchase) object 'X3::boiler' 'X2::boiler'
  * timesteps                        (timesteps) datetime64[ns] 2005-07-01 ... 2005-07-02T23:00:00
  * loc_techs_cost                   (loc_techs_cost) object 'X1::power_lines:X2' ... 'X2::heat_pipes:N1'
  * loc_techs                        (loc_techs) object 'X1::power_lines:X2' ... 'X1::demand_heat'
  * loc_techs_milp                   (loc_techs_milp) object 'X1::chp'
  * loc_techs_om_cost                (loc_techs_om_cost) object 'X1::chp' ... 'X2::pv'
  * costs                            (costs) object 'monetary'
  * carriers                         (carriers) object 'gas' ... 'electricity'
  * loc_tech_carriers_export         (loc_tech_carriers_export) object 'X2::pv::electricity' ... 'X3::pv::electricity'
  * loc_tech_carriers_con            (loc_tech_carriers_con) object 'X2::boiler::gas' ... 'N1::heat_pipes:X1::heat'
  * loc_tech_carriers_prod           (loc_tech_carriers_prod) object 'X3::boiler::heat' ... 'X1::supply_gas::gas'
  * loc_techs_asynchronous_prod_con  (loc_techs_asynchronous_prod_con) object 'N1::heat_pipes:X1' ... 'N1::heat_pipes:X3'
  * loc_techs_area                   (loc_techs_area) object 'X3::pv' ... 'X1::pv'
  * loc_techs_supply_plus            (loc_techs_supply_plus) object 'X3::pv' ... 'X1::pv'
  * techs                            (techs) object 'demand_electricity' ... 'pv'
  * loc_techs_investment_cost        (loc_techs_investment_cost) object 'X1::power_lines:X2' ... 'X2::heat_pipes:N1'
Data variables:
    energy_cap                       (loc_techs) float64 274.1 0.0 ... 1.72
    carrier_prod                     (loc_tech_carriers_prod, timesteps) float64 0.0 ... 310.0
    carrier_con                      (loc_tech_carriers_con, timesteps) float64 0.0 ... 0.0
    cost                             (costs, loc_techs_cost) float64 0.008273 ... 0.05836
    resource_area                    (loc_techs_area) float64 350.0 994.1 0.0
    resource_con                     (loc_techs_supply_plus, timesteps) float64 0.0 ... 0.0
    resource_cap                     (loc_techs_supply_plus) float64 38.95 ... 0.0
    carrier_export                   (loc_tech_carriers_export, timesteps) float64 0.0 ... 0.0
    cost_var                         (costs, loc_techs_om_cost, timesteps) float64 0.3779 ... 0.0
    cost_investment                  (costs, loc_techs_investment_cost) float64 0.008273 ... 0.05836
    purchased                        (loc_techs_purchase) float64 0.0 1.0
    units                            (loc_techs_milp) float64 1.0
    operating_units                  (loc_techs_milp, timesteps) float64 1.0 ... 1.0
    prod_con_switch                  (loc_techs_asynchronous_prod_con, timesteps) float64 1.0 ... 0.0
    capacity_factor                  (loc_tech_carriers_prod, timesteps) float64 0.0 ... 0.4185
    systemwide_capacity_factor       (carriers, techs) float64 nan ... 0.2019
    systemwide_levelised_cost        (carriers, costs, techs) float64 nan ... 0.03844
    total_levelised_cost             (carriers, costs) float64 0.03962 ... 0.08171
Attributes:
    calliope_version:          0.6.5-dev
    applied_overrides:         milp
    scenario:                  milp
    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:  900.2250130640399
    solution_time:             4.83474
    time_finished:             2020-01-14 17:49:02

In [7]:
# We can sum operating units of CHP over all locations and turn the result into a pandas DataFrame
df_units = model.get_formatted_array('operating_units').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_units.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 1 columns):
chp    48 non-null float64
dtypes: float64(1)
memory usage: 768.0 bytes

In [8]:
# Using .head() to see the first few rows of operating units

df_units.head()


Out[8]:
techs chp
timesteps
2005-07-01 00:00:00 1.0
2005-07-01 01:00:00 1.0
2005-07-01 02:00:00 1.0
2005-07-01 03:00:00 1.0
2005-07-01 04:00:00 1.0

In [9]:
# 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 [10]:
# plot.capacities gives a graphical view of the non-timeseries variables, both input and output

# Note, because we fix unit size, CHP now has a maximum capacity of 300kW, 
# compared to 260kW in the non-MILP case

model.plot.capacity()


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