<jupyter_start><jupyter_text>Westeros Tutorial - Introducing sub-annual time-steps: Modeling of variability in energy supply and demand "*Winter is coming!*" Time-dependent variations in demand and supply are common characteristics of an electricity system, and Westeros is not anexception. This tutorial helps to learn how to add sub-annual time steps to a MESSAGEix model and investigate the impact of thevariability in supply and demand. It is structured as follows:1. A short note on seasonality2. Adding sub-annual time steps3. Analyzing the results**Pre-requisites**- You have the *MESSAGEix* framework installed and working- You have run Westeros baseline scenario (``westeros_baseline.ipynb``) and solved it successfully_This tutorial was developed by <NAME> ([@behnam-zakeri](https://github.com/behnam-zakeri)) for the course EnergyEconomics and Modeling held at the International Summer School in Energy Technology, St. Petersburg Polytechnique University in August 2018._  1. Seasonal variations in demand and supplyThe electricity demand can vary in different time scales, e.g., seasonally, monthly, daily, and hourly. In Westeros, the winter time is typically cold and long, which increases the demand for electricity and lighting. We investigate this seasonality in this tutorial, but the procedure discussed here can be adopted for studying different lengths of temporal resolution. Seasonality can be relevant to some supply technologies as well. For example, Westeros has more windy days in winter months compared to summer. This means the capacity factor of a wind power plant is higher in winter compared to summer. Figure below, rendered from renewables.ninja*, shows the monthly capacity factor of wind power plant somewhere near Westeros.                                                                                                                        *You can also find the capacity factor of wind and solar PV for your location at www.renewables.ninja, as we found for a place near Westeros.  2. Implementation of seasonalityIn this tutorial we add two sub-annual time steps, winter and summer. First, we load the baseline scenario, then we add the required sets related to seasonality, next we modify the parameters, and finally we analyze the results. 2.1. Loading Westeros baseline scenario<jupyter_code># Importing required packages
import pandas as pd
import ixmp as ix
import message_ix

from message_ix.util import make_df

%matplotlib inline
# Loading modelling platform
mp = ix.Platform()
# Specifying model/scenario to be loaded from the database
model = 'Westeros Electrified'
scenario='baseline'
base = message_ix.Scenario(mp, model, scenario)

# Cloning a scenario for adding time steps
scen = base.clone(model, 'westeros_seasonal', 'introducing seasonality', keep_solution=False)
scen.check_out()<jupyter_output><empty_output><jupyter_text>2.2 Modifying setsFirst, we specify subannual time steps and add them to relevant MESSAGE sets. In the MESSAGEix framework set "time" is devoted for sub-annual time steps, denoted as index *h* in the mathematical formulation:| Set name | Math notation | Explanation ||----------|---------------|----------------------------------------------|| time     | $h \in H$     | subannual time periods |<jupyter_code># Adding sub-annual time steps
time_steps = ['winter', 'summer']
scen.add_set('time', time_steps)
# We can see the elements of the set
scen.set('time')
# Defining a new temporal level
time_level = 'season'
scen.add_set('lvl_temporal', time_level)<jupyter_output><empty_output><jupyter_text>Next, the temporal hierarchy will be defined to map different levels of time with respect to "year", which is the parent temporal level for any "time".<jupyter_code># Adding temporal hierarchy
for t in time_steps:
    scen.add_set('map_temporal_hierarchy', [time_level, t, 'year'])
    
# We can see the content of the set
scen.set('map_temporal_hierarchy')<jupyter_output><empty_output><jupyter_text>2.3. Modifying parametersIn this Section, we modify some parameters based on the new time steps. In principle, we need to examine all parameters that have an index of "time" to see if we need to modify them or not.<jupyter_code># All parameters with at least one sub-annual time index
parameters = [p for p in scen.par_list() if 'time' in scen.idx_sets(p)]

# Those parameters with "time" index that are not empty in our model
[p for p in parameters if not scen.par(p).empty]<jupyter_output><empty_output><jupyter_text>Parameter "duration_time"We start by modifying the parameter "duration_time", which shows the length of each subannual time step relative to the whole year:| Parameter | Index set| Explanation||-----------|----------| -----------|| duration_time	| time	|duration of sub-annual time slices (relative to 1)| In our example, winter and summer are defined as each half of the year. However, the duration of time steps can bedifferent in a MESSAGEix model, e.g., winter 0.4 and summer 0.6. But the sum of the duration times must be equal to 1.<jupyter_code># Adding duration time
for t in time_steps:
    scen.add_par('duration_time', [t], 0.5, '-')<jupyter_output><empty_output><jupyter_text>A function for modifying parametersIn this stage, we introduce a function that helps us to modify the parameters after adding new time steps. This function called "yearly_to_season" does the following:- removing old values, where the "time" index was "year"- populating data for new "time" indexes- using the ratios defined by the user to convert yearly values to seasonal onesThis is a common code pattern when modelling using MESSAGEix:writing re-usable code that helps modify existing parameter data to reflect some desired change in the reference energy system.<jupyter_code># A function for adding sub-annual data to a parameter
def yearly_to_season(scen, parameter, data, filters=None):
    if filters:
        old = scen.par(parameter, filters)
    else:
        old = scen.par(parameter)
    scen.remove_par(parameter, old)
    
    # Finding "time" related indexes
    time_idx = [x for x in scen.idx_names(parameter) if 'time' in x]
    for h in data.keys():
        new = old.copy()
        for time in time_idx:
            new[time] = h
        new['value'] = data[h] * old['value']
        scen.add_par(parameter, new)<jupyter_output><empty_output><jupyter_text>Modifying electricity demand in sub-annual time stepsThe seasonality in demand for electricity and lighting can be taken into account by estimating different values for each time step. For example, the share of electricity demand in winter and summer in Westeros is approximately 0.6 and 0.4 of the yearly demand. We can get the yearly demand from baseline scenario and divide it to the two seasons according to their shares.<jupyter_code># Before modifying, let's look at "demand" in baseline
scen.par('demand')
# Modifying demand for each season
demand_data = {'winter': 0.60, 'summer': 0.40}
yearly_to_season(scen, 'demand', demand_data)

# let's look at "demand" now
scen.par('demand')<jupyter_output><empty_output><jupyter_text>Modifying "input" and "output"However, not all parameters that have subannual time steps need to divide their annual values for each time step. For example, "output" parameter shows the output efficiency, commodities and the level of a technology. Hence, as far as the efficiency of a technology remains unchanged in different seasons, the value of "output" will be the same. As such, we only need to add the sub-annual time steps but with the same value as for the yearly one.<jupyter_code># Modifying input and output parameters for each season
# output
fixed_data = {'winter': 1, 'summer': 1}
yearly_to_season(scen, 'output', fixed_data)

# input
yearly_to_season(scen, 'input', fixed_data)<jupyter_output><empty_output><jupyter_text>Modifying dynamic constraintsNext, we modify dynamic constraints with a "time" index, i.e. growth and decline rates of activities. In the Westeros baseline scenario, there is only growth_activity_up, so this parameter will be modified for seasonality but with the same values.<jupyter_code># Modifying growth rates for each season
yearly_to_season(scen, 'growth_activity_up', fixed_data)<jupyter_output><empty_output><jupyter_text>Modifying capacity factorWe discussed about the variation in the capacity factor of wind power in each month. By averaging the values for the respective months, we reach a capacity factor of 0.46 for winter and 0.25 for summer in Westeros.<jupyter_code># Modifying capacity factor
# Let's get the yearly capacity factor of wind in the baseline scenario
cf_wind = scen.par('capacity_factor', {'technology': 'wind_ppl'})['value'].mean()

# Converting yearly capacity factor to seasonal
cf_data = {'winter': 0.46 / cf_wind, 'summer': 0.25 / cf_wind} 
cf_filters = {'technology': 'wind_ppl'}
yearly_to_season(scen, 'capacity_factor', cf_data, cf_filters)

# Capacity factor of other technologies remains unchanged in each season
cf_filters = {'technology': ['coal_ppl', 'bulb', 'grid']}
yearly_to_season(scen, 'capacity_factor', fixed_data, cf_filters)

# Let's look at capacity factor in year 710
scen.par('capacity_factor', {'year_act':710, 'year_vtg':710})<jupyter_output><empty_output><jupyter_text>Modifying historical activity"historical_activity" is one of the parameters that the data should be divided between the seasons. In the absence of recorded data in Westeros before 690, we assume historical values can be divided by half for each season:<jupyter_code># Modifying historical activity 
hist_data = {'winter': 0.5, 'summer': 0.5}
yearly_to_season(scen, 'historical_activity', hist_data)<jupyter_output><empty_output><jupyter_text>Economic parametersInvestment cost is defined per installed capacity per year and it is not related to subannual time steps, so it remains unchanged. Variable cost is time-dependent so the "time" index should be updated. However, as variable cost is defined per unit of activity, the "value" can remain unchanged for different time steps.<jupyter_code># Modifying variable cost
yearly_to_season(scen, 'var_cost', fixed_data)<jupyter_output><empty_output><jupyter_text>2.4. Solving the model<jupyter_code>scen.commit(comment='introducing seasonality')
scen.set_as_default()
scen.solve()
scen.var('OBJ')['lvl']<jupyter_output><empty_output><jupyter_text>3. Postprocessing and analyzing results 3.1. Plotting results<jupyter_code>from message_ix.reporting import Reporter
from message_ix.util.tutorial import prepare_plots

rep = Reporter.from_scenario(scen)
prepare_plots(rep)<jupyter_output><empty_output><jupyter_text>ActivityHow much energy is generated in each time period from the different potential sources?<jupyter_code>rep.set_filters(t=["coal_ppl", "wind_ppl"])
rep.get("plot activity")<jupyter_output><empty_output><jupyter_text>Installed capacityGiven how many new plants are built, how many are actually used?<jupyter_code>rep.get("plot capacity")<jupyter_output><empty_output><jupyter_text>Electricity PriceElectricity prices are **dual variables** of the optimization solution called **shadow prices** as well. They reflect the marginal cost of producing one more unit of electricity, hence, representing the marginal cost of the most expensive generator in the system.<jupyter_code>rep.set_filters(t=None, c=["light"])
rep.get("plot prices")<jupyter_output><empty_output><jupyter_text>3.2. Questions and discussion1. Compare the objective value of this scenario with the baseline; which power system is more costly? Given the fact that electricity demand and the cost of technologies are equal between these two scenarios, how do you justify the change in the total costs?2. Compare the figure for "Activity" between this scenario and the baseline; do you observe any changes in the energy mix? The results show more wind integration in this scenario. What is the reason? 3. If you check out the figure for "Capacity" in both scenarios, you'll notice that the increase in the installed capacity of wind is much higher than the growth in wind generation. Can you explain the reason?4. Rerun this tutorial for a scenario with 4 sub-annual time steps: winter, spring, summer and autumn (each one-forth of the whole year)  *Discussion*: The greater avilability of wind in wintertime coincides with the higher demand for electricity in winter compared to summer. Hence, when coal power plants are not able to cover the increased electricity demand in winter, the system needs more wind generation compared to the baseline (for example, see the results for "Activity" in 700):<jupyter_code>scen.var('ACT', {'year_act': 700})<jupyter_output><empty_output><jupyter_text>Adding seasonality to the model shows the impact of any coincidence or mismatch between wind generation and electricity demand. This seasonality can be averaged out if the yearly values are taken into account. The capacity factor of wind is different in winter and summer, while it was an average yearly value in the baseline scenario. Without any bound or tax on emissions, coal is the cheapest option. Wind will be installed only when coal cannot ramp up quickly to meet the whole demand, mainly in winter. Therefore, we observe relatively much higher installed capacity for wind compared in this scenario to the baseline, even wind is more expensive. Thus, we see a more expensive system when considering seasonality (compare the objective values).   Final noteThank you for trying this tutorial!Check us out on Github: https://github.com/iiasa/message_ix  Get in touch with us online: https://groups.google.com/forum/message-ix  And feel free to contact us with any further questions or feedback for improving this tutorial: <EMAIL><jupyter_code>mp.close_db()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07657134562345769, 'fraction_numerical': 0.00587893743649296, 'mean_word_length': 5.040771591407277, 'pattern_counts': {'":': 0, '<': 79, '<?xml version=': 0, '>': 79, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '185170', 'license': 'Apache-2.0', 'n_tokens_mistral': 3784, 'n_tokens_neox': 3609, 'n_words': 1699, 'path': 'tutorial/westeros/westeros_seasonality.ipynb', 'repo_name': 'LauWien/message_ix', 'size': '13796'}