Issue with Implementing Storage Feature in MESSAGEix Framework #883
-
I am encountering challenges while building a national-level model using the MESSAGEix framework. Specifically, I am attempting to integrate the storage feature into the model. To accomplish this, I have referred to The issue is that the model is not showing any activity related to the battery, charging, or discharging processes. I have modified test_feature_storage.py from using 4 time slices to 2 time slices, dividing the year into peak and off-peak periods. However, the model does not seem to store energy during the off-peak slice as expected. If I try to force bound activity on power plants, either the bounds get relaxed or model gives infeasibility. I have attached the relevant code and output results below for reference. Any insights or suggestions for resolving this issue would be greatly appreciated. Thank you in advance. Libraryimport pandas as pd
import numpy as np
import ixmp
import message_ix
import itertools
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot')
from io import StringIO
from message_ix import make_df
#from message_ix_models.util import broadcast
from message_ix.utils import make_df
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
pd.set_option('display.max_colwidth', None)
## Model Platform, Scenario, Spatial, Horizon, Interest Rate, Mode
mp = ixmp.Platform()
scenario = message_ix.Scenario(mp,
model="Storage Model",
scenario="baseline",
version="new")
scenario.add_spatial_sets({"country":"XYZ"})
model_horizon = [2020]
scenario.add_horizon(
year= model_horizon,
firstmodelyear=model_horizon[0]
)
scenario.add_set("year", model_horizon)
scenario.add_set("type_year", model_horizon)
year_df = scenario.vintage_and_active_years()
vintage_years, act_years = year_df["year_vtg"], year_df["year_act"]
year_vtg_df=year_df[["year_vtg"]]
year_act_df=year_df[["year_act"]]
scenario.add_par("interestrate", model_horizon, value=0.05, unit='-')
scenario.add_set("mode", ["standard"])
scenario.add_set("time",["peak","offpeak"])
scenario.add_set("lvl_temporal","division")
scenario.add_set("map_temporal_hierarchy", ["division", "peak", "year"])
scenario.add_set("map_temporal_hierarchy", ["division", "offpeak", "year"])
scenario.add_par("duration_time", "peak", 0.4, "%")
scenario.add_par("duration_time", "offpeak", 0.6, "%")
# All parameters with at least one sub-annual time index
parameters = [p for p in scenario.par_list() if "time" in scenario.idx_sets(p)]
# Those parameters with time index that are not empty in our model
[p for p in parameters if not scenario.par(p).empty]
# A function for adding sub-annual data to a parameter
def yearly_to_season(scenario, parameter, data, filters=None):
if filters:
old = scenario.par(parameter, filters)
else:
old = scenario.par(parameter)
scenario.remove_par(parameter, old)
# Finding time related indexes
time_idx = [x for x in scenario.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"]
scenario.add_par(parameter, new)
scenario.add_set("level","storage")
scenario.add_set("technology",["battery","charger","discharger","dummer"])
scenario.add_set("commodity", ["elec_stored","dummy1","dummy2"])
# Specifying storage reservoir technology
scenario.add_set("storage_tec", "battery")
# Specifying storage level
scenario.add_set("level_storage", "storage")
# Adding time sequence
scenario.add_par("time_order",["division","peak"],1,"-")
scenario.add_par("time_order",["division","offpeak"],2,"-")
# Adding mapping for storage and charger/discharger technologies
for tec in ["charger", "discharger"]:
scenario.add_set(
"map_tec_storage",
["XYZ", tec, "standard", "battery", "standard", "storage", "elec_stored", "division"],
)
data_storage_self_discharge = """
node,technology,mode,level,commodity,time,value,unit
XYZ,battery,standard,storage,elec_stored,peak,0.05,-
XYZ,battery,standard,storage,elec_stored,offpeak,0.05,-
"""
df_storage_self_discharge = pd.read_csv(StringIO(data_storage_self_discharge))
year_act_df['key'] = 0
df_storage_self_discharge['key'] = 0
merged_storage_self_discharge = pd.merge(year_act_df, df_storage_self_discharge, on='key').drop('key', axis=1)
merged_storage_self_discharge = merged_storage_self_discharge.rename(columns={'year_act': 'year'})
scenario.add_par("storage_self_discharge",merged_storage_self_discharge)
data_storage_initial = """
node,technology,mode,level,commodity,time,value,unit
XYZ,battery,standard,storage,elec_stored,peak,0.05,-
XYZ,battery,standard,storage,elec_stored,offpeak,0.05,-
"""
df_storage_initial = pd.read_csv(StringIO(data_storage_initial))
year_act_df['key'] = 0
df_storage_initial['key'] = 0
merged_storage_initial = pd.merge(year_act_df, df_storage_initial, on='key').drop('key', axis=1)
merged_storage_initial = merged_storage_initial.rename(columns={'year_act': 'year'})
scenario.add_par("storage_initial",merged_storage_initial)
scenario.par("storage_initial")
scenario.add_set("commodity", "electricity")
scenario.add_set("level", "final")
scenario.add_set("mode", "standard")
scenario.add_set("technology", ["wind_ppl",
"gas_cc_ppl",
])
data_output = """
node_loc,technology,mode,node_dest,commodity,level,time,time_dest,value,unit
XYZ,gas_cc_ppl,standard,XYZ,electricity,final,year,year,1.0,-
XYZ,wind_ppl,standard,XYZ,electricity,final,year,year,1.0,-
XYZ,battery,standard,XYZ,dummy1,final,year,year,1.0,-
XYZ,charger,standard,XYZ,elec_stored,storage,year,year,1.0,-
XYZ,discharger,standard,XYZ,electricity,final,year,year,1.0,-
XYZ,dummer,standard,XYZ,dummy2,final,year,year,1.0,-
"""
df_output = pd.read_csv(StringIO(data_output))
year_df['key'] = 0
df_output['key'] = 0
merged_output = pd.merge(year_df, df_output, on='key').drop('key', axis=1)
scenario.add_par("output",merged_output)
data_input = """
node_loc,technology,mode,node_origin,commodity,level,time,time_origin,value,unit
XYZ,battery,standard,XYZ,dummy2,final,year,year,1.0,-
XYZ,charger,standard,XYZ,electricity,final,year,year,1.0,-
XYZ,discharger,standard,XYZ,elec_stored,storage,year,year,1.0,-
"""
df_input = pd.read_csv(StringIO(data_input))
year_df['key'] = 0
df_input['key'] = 0
merged_input = pd.merge(year_df, df_input, on='key').drop('key', axis=1)
scenario.add_par("input",merged_input)
data_var_cost = """
node_loc,technology,mode,time,value,unit
XYZ,gas_cc_ppl,standard,year,2,USD/GWa
XYZ,wind_ppl,standard,year,0,USD/GWa
XYZ,battery,standard,year,0,USD/GWa
XYZ,charger,standard,year,0.2,USD/GWa
XYZ,discharger,standard,year,0.3,USD/GWa
"""
df_var_cost = pd.read_csv(StringIO(data_var_cost))
year_df['key'] = 0
df_var_cost['key'] = 0
merged_var_cost = pd.merge(year_df, df_var_cost, on='key').drop('key', axis=1)
scenario.add_par("var_cost",merged_var_cost)
data_bound_activity = """
node_loc,technology,year_act,mode,time,value,unit
XYZ,wind_ppl,2020,standard,year,0.05,GWa
XYZ,gas_cc_ppl,2020,standard,year,0.05,GWa
"""
df_bound_activity = pd.read_csv(StringIO(data_bound_activity))
scenario.add_par("bound_activity_up",df_bound_activity)
# Modifying the demand for each season
demand_data = {"peak": 0.70, "offpeak": 0.30}
yearly_to_season(scenario, "demand", demand_data)
bound_data = {"peak": 0.30, "offpeak": 0.70}
# Let's look at demand now
scenario.par("demand")
# Modifying input and output parameters for each season
fixed_data = {"peak": 1, "offpeak": 1}
# Output
yearly_to_season(scenario, "output", fixed_data)
# Input
yearly_to_season(scenario, "input", fixed_data)
# Modifying growth rates for each season
# yearly_to_season(scenario, "growth_activity_up", bound_data)
# Modifying variable cost
yearly_to_season(scenario, "var_cost", fixed_data)
# Modifying variable cost
yearly_to_season(scenario, "bound_activity_up", fixed_data)
scenario.commit("Basic Model")
scenario.set_as_default()
scenario.solve(solve_options={'lpmethod': '4'})
scenario.var("OBJ")
scenario.var("ACT")
act_no_stor = scenario.var("ACT", {"technology": "gas_cc_ppl"})["lvl"].sum()
activitydf = scenario.var("ACT")
pivotact = activitydf.pivot_table('lvl',index='technology', columns='year_act',
aggfunc= 'sum')
pivotact = pivotact.round(2)
print("ACTIVITY - Unit GWa")
pivotact.round(2)
# Multiply the sum values by 8.76
pivotactTWh = activitydf.pivot_table('lvl',index='technology', columns='year_act',
aggfunc= lambda x: sum(x) * 8.76)
pivotactTWh = pivotactTWh.round(2)
# Print the modified pivot table
print("ACTIVITY - Unit TWh")
pivotactTWh.round(2)
mp.close_db() |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
Hi @shreeyashn20, thanks for opening this discussion :) Next, I'm wondering if you have found our tutorial for the storage feature? If not, then working through it may clarify some additional things. Now, I don't perform a lot of modelling activity myself, so I don't know intuitively which parts of your code might cause the issue (thanks for providing all of that, this is very helpful!). However, I can go through the code, comparing it to other uses of the feature and try to spot differences.
Once you have covered all that, the model might already show different results. Please keep in mind that you seem to add a demand equal to 1 (possibly Hope this helps :) |
Beta Was this translation helpful? Give feedback.
Thank you @glatterf42 ! I was able to use storage feature in MESSAGEix Framework. I am sharing modified test code for storage feature which might be helpful for someone.