Calliope National 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.national_scale()
[2021-07-29 16:03:53] INFO     Model: initialising
[2021-07-29 16:03:53] INFO     Model: preprocessing stage 1 (model_run)
[2021-07-29 16:03:54] INFO     NumExpr defaulting to 8 threads.
[2021-07-29 16:03:54] INFO     Model: preprocessing stage 2 (model_data)
[2021-07-29 16:03: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. `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: (12/16)
  * carriers                        (carriers) <U5 'power'
  * coordinates                     (coordinates) object 'lon' 'lat'
  * costs                           (costs) object 'monetary'
  * loc_carriers                    (loc_carriers) object 'region1-3::power' ...
  * loc_techs                       (loc_techs) object 'region1-1::csp' ... '...
  * loc_techs_area                  (loc_techs_area) object 'region1-1::csp' ...
    ...                              ...
  * loc_techs_store                 (loc_techs_store) object 'region1-1::csp'...
  * loc_techs_supply_plus           (loc_techs_supply_plus) object 'region1-1...
  * loc_techs_transmission          (loc_techs_transmission) object 'region1:...
  * locs                            (locs) object 'region2' ... 'region1-2'
  * techs                           (techs) object 'csp' ... 'free_transmission'
  * timesteps                       (timesteps) datetime64[ns] 2005-01-01 ......
Data variables: (12/34)
    energy_ramping                  (loc_techs) float64 nan nan nan ... nan nan
    resource_eff                    (loc_techs_finite_resource) float64 1.0 ....
    storage_cap_max                 (loc_techs_store) float64 6.14e+05 ... inf
    energy_prod                     (loc_techs) float64 1.0 1.0 1.0 ... 1.0 nan
    energy_con                      (loc_techs) float64 nan nan 1.0 ... 1.0 1.0
    energy_cap_per_storage_cap_max  (loc_techs_store) int64 1 1 1 4
    ...                              ...
    lookup_loc_carriers             (loc_carriers) object 'region1-3::csp::po...
    lookup_loc_techs                (loc_techs_non_conversion) object 'region...
    lookup_loc_techs_area           (locs) object '' '' ... 'region1-2::csp'
    timestep_resolution             (timesteps) float64 1.0 1.0 1.0 ... 1.0 1.0
    timestep_weights                (timesteps) float64 1.0 1.0 1.0 ... 1.0 1.0
    max_demand_timesteps            (carriers) datetime64[ns] 2005-01-05T16:0...
Attributes:
    calliope_version:    0.6.7-dev
    applied_overrides:   
    scenario:            None
    defaults:            available_area: null\ncarrier_ratios: false\ncharge_...
    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-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
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-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
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

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()
[2021-07-29 16:03:54] INFO     Backend: starting model run
[2021-07-29 16:03:54] INFO     Loading sets
[2021-07-29 16:03:54] INFO     Loading parameters
[2021-07-29 16:03:54] INFO     constraints are loaded in the following order: ['capacity', 'dispatch', 'policy', 'energy_balance', 'costs', 'network', 'conversion', 'group', 'conversion_plus', 'export', 'milp']
[2021-07-29 16:03:54] INFO     creating capacity constraints
[2021-07-29 16:03:54] INFO     creating dispatch constraints
[2021-07-29 16:03:54] INFO     creating policy constraints
[2021-07-29 16:03:54] INFO     creating energy_balance constraints
[2021-07-29 16:03:54] INFO     creating costs constraints
[2021-07-29 16:03:55] INFO     creating network constraints
[2021-07-29 16:03:55] INFO     creating conversion constraints
[2021-07-29 16:03:55] INFO     creating group constraints
[2021-07-29 16:03:55] INFO     creating conversion_plus constraints
[2021-07-29 16:03:55] INFO     creating export constraints
[2021-07-29 16:03:55] INFO     creating milp constraints
[2021-07-29 16:03:55] INFO     Backend: model generated. Time since start of model run: 0:00:00.580078
[2021-07-29 16:03:55] INFO     Backend: sending model to solver
[2021-07-29 16:03:55] INFO     Backend: solver finished running. Time since start of model run: 0:00:01.238192
[2021-07-29 16:03:55] INFO     Backend: loaded results
[2021-07-29 16:03:55] INFO     Backend: generated solution array. Time since start of model run: 0:00:01.336960
[2021-07-29 16:03:55] INFO     Postprocessing: started
[2021-07-29 16:03:56] INFO     Postprocessing: All values < 1e-10 set to 0 in energy_cap, carrier_prod, carrier_con, cost, storage, cost_var, system_balance, cost_var_rhs
[2021-07-29 16:03:56] INFO     Postprocessing: ended. Time since start of model run: 0:00:01.455625
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_carriers: 5, loc_carriers_system_balance_constraint: 5, loc_tech_carriers_con: 11, loc_tech_carriers_prod: 13, loc_techs: 15, loc_techs_area: 3, loc_techs_balance_demand_constraint: 2, loc_techs_cost: 13, loc_techs_cost_investment_constraint: 7, loc_techs_investment_cost: 7, loc_techs_om_cost: 12, loc_techs_store: 4, loc_techs_supply_plus: 3, techs: 12, timesteps: 120)
Coordinates: (12/17)
  * loc_carriers                            (loc_carriers) object 'region1-1:...
  * loc_carriers_system_balance_constraint  (loc_carriers_system_balance_constraint) object ...
  * loc_tech_carriers_con                   (loc_tech_carriers_con) object 'r...
  * loc_tech_carriers_prod                  (loc_tech_carriers_prod) object '...
  * loc_techs_balance_demand_constraint     (loc_techs_balance_demand_constraint) object ...
  * loc_techs_cost                          (loc_techs_cost) object 'region1-...
    ...                                      ...
  * techs                                   (techs) object 'ac_transmission' ...
  * costs                                   (costs) object 'monetary'
  * loc_techs                               (loc_techs) object 'region1-1::cs...
  * loc_techs_area                          (loc_techs_area) object 'region1-...
  * timesteps                               (timesteps) datetime64[ns] 2005-0...
  * carriers                                (carriers) <U5 'power'
Data variables: (12/20)
    energy_cap                              (loc_techs) float64 1e+04 ... 3.9...
    carrier_prod                            (loc_tech_carriers_prod, timesteps) float64 ...
    carrier_con                             (loc_tech_carriers_con, timesteps) float64 ...
    cost                                    (costs, loc_techs_cost) float64 9...
    resource_area                           (loc_techs_area) float64 1.304e+0...
    storage_cap                             (loc_techs_store) float64 2.443e+...
    ...                                      ...
    cost_investment_rhs                     (costs, loc_techs_cost_investment_constraint) float64 ...
    cost_var_rhs                            (costs, loc_techs_om_cost, timesteps) float64 ...
    capacity_factor                         (timesteps, loc_tech_carriers_prod) float64 ...
    systemwide_capacity_factor              (carriers, techs) float64 nan ......
    systemwide_levelised_cost               (carriers, costs, techs) float64 ...
    total_levelised_cost                    (carriers, costs) float64 0.06701
Attributes:
    termination_condition:     optimal
    objective_function_value:  277048.48266
    solution_time:             1.33696
    time_finished:             2021-07-29 16:03:55
    calliope_version:          0.6.7-dev
    applied_overrides:         
    scenario:                  None
    defaults:                  available_area: null\ncarrier_ratios: false\nc...
    allow_operate_mode:        1
    model_config:              calliope_version: 0.6.7\nname: National-scale ...
    run_config:                backend: pyomo\nbigM: 1000000.0\ncyclic_storag...
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):
 #   Column                       Non-Null Count  Dtype  
---  ------                       --------------  -----  
 0   ac_transmission:region1      120 non-null    float64
 1   ac_transmission:region2      120 non-null    float64
 2   battery                      120 non-null    float64
 3   ccgt                         120 non-null    float64
 4   csp                          120 non-null    float64
 5   free_transmission:region1    120 non-null    float64
 6   free_transmission:region1-1  120 non-null    float64
 7   free_transmission:region1-2  120 non-null    float64
 8   free_transmission:region1-3  120 non-null    float64
dtypes: float64(9)
memory usage: 13.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.360 0.0 0.0 0.0 0.0 0.0
2005-01-01 01:00:00 2131.148 0.0 0.0 26894.673 0.0 0.0 0.0 0.0 0.0
2005-01-01 02:00:00 2090.164 0.0 0.0 26189.672 0.0 0.0 0.0 0.0 0.0
2005-01-01 03:00:00 2131.148 0.0 0.0 25630.273 0.0 0.0 0.0 0.0 0.0
2005-01-01 04:00:00 2172.132 0.0 0.0 25675.049 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()
[2021-07-29 16:03:56] WARNING  /Users/brynmorp/Repos/calliope-project/calliope/calliope/postprocess/plotting/plotting.py:105: FutureWarning:

Plotting will no longer be available as a method of the Calliope model object infuture versions of Calliope. In the meantime, as of v0.6.6, plotting is untested; this functionality should now be used with caution. We expect to reintroduce it as a seperate module in v0.7.0.