def test_new_timeseries_long_name64plus(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO["dantzig multi-year"]) scen = scen.clone(keep_solution=False) scen.check_out(timeseries_only=True) df = pd.DataFrame( { "region": [ "India", ], "variable": [ ( "Emissions|CO2|Energy|Demand|Transportation|Aviation|" "Domestic|Freight|Oil" ), ], "unit": [ "Mt CO2/yr", ], "2012": [ 0.257009, ], } ) scen.add_timeseries(df) scen.commit("importing a testing timeseries")
def test_init(test_mp): scen = Scenario(test_mp, *msg_args) scen = scen.clone('foo', 'bar') scen.check_out() macro.init(scen) scen.commit('foo') scen.solve() assert np.isclose(scen.var('OBJ')['lvl'], 153.675)
def test_years_active_extend(test_mp): scen = Scenario(test_mp, *msg_multiyear_args) scen = scen.clone(keep_solution=False) scen.check_out() scen.add_set('year', ['2040', '2050']) scen.add_par('duration_period', '2040', 10, 'y') scen.add_par('duration_period', '2050', 10, 'y') df = scen.years_active('seattle', 'canning_plant', '2020') npt.assert_array_equal(df, [2020, 2030, 2040]) scen.discard_changes()
def test_init(test_mp): scen = Scenario(test_mp, *msg_args) obs = scen.var('OBJ')['lvl'] scen = scen.clone('foo', 'bar', keep_solution=False) scen.check_out() macro.init(scen) scen.commit('foo') scen.solve() exp = scen.var('OBJ')['lvl'] assert np.isclose(obs, exp)
def test_new_timeseries_long_name64plus(test_mp): scen = Scenario(test_mp, *msg_multiyear_args) scen = scen.clone(keep_solution=False) scen.check_out(timeseries_only=True) df = pd.DataFrame({ 'region': ['India', ], 'variable': [('Emissions|CO2|Energy|Demand|Transportation|Aviation|' 'Domestic|Freight|Oil'), ], 'unit': ['Mt CO2/yr', ], '2012': [0.257009, ] }) scen.add_timeseries(df) scen.commit('importing a testing timeseries')
def test_init(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO['dantzig']) scen = scen.clone('foo', 'bar') scen.check_out() MACRO.initialize(scen) scen.commit('foo') scen.solve() assert np.isclose(scen.var('OBJ')['lvl'], 153.675) assert 'mapping_macro_sector' in scen.set_list() assert 'aeei' in scen.par_list() assert 'DEMAND' in scen.var_list() assert 'COST_ACCOUNTING_NODAL' in scen.equ_list()
def test_addon_up(test_mp): scen = Scenario(test_mp, *msg_args).clone(scenario='addon_up', keep_solution=False) add_addon(scen, costs=-1, zero_output=True) scen.check_out() scen.add_par('addon_up', addon_share) scen.commit('adding upper bound on addon technology') scen.solve() exp = scen.var('ACT', f)['lvl'] * 0.5 obs = scen.var('ACT', g)['lvl'] assert np.isclose(exp, obs)
def test_init(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO["dantzig"]) scen = scen.clone("foo", "bar") scen.check_out() MACRO.initialize(scen) scen.commit("foo") scen.solve(quiet=True) assert np.isclose(scen.var("OBJ")["lvl"], 153.675) assert "mapping_macro_sector" in scen.set_list() assert "aeei" in scen.par_list() assert "DEMAND" in scen.var_list() assert "COST_ACCOUNTING_NODAL" in scen.equ_list()
def test_addon_up(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO["dantzig"]).clone(scenario="addon_up", keep_solution=False) add_addon(scen, costs=-1, zero_output=True) scen.check_out() scen.add_par("addon_up", addon_share) scen.commit("adding upper bound on addon technology") scen.solve() exp = scen.var("ACT", f)["lvl"] * 0.5 obs = scen.var("ACT", g)["lvl"] assert np.isclose(exp, obs)
def test_addon_lo(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO['dantzig']) \ .clone(scenario='addon_lo', keep_solution=False) add_addon(scen, costs=1, zero_output=True) scen.check_out() scen.add_par('addon_lo', addon_share) scen.commit('adding lower bound on addon technology') scen.solve() exp = scen.var('ACT', f)['lvl'] * 0.5 obs = scen.var('ACT', g)['lvl'] assert np.isclose(exp, obs)
def test_commodity_price_equality(test_mp): scen = Scenario(test_mp, "test_commodity_price", "equality", version="new") model_setup(scen, var_cost=-1) scen.commit("initialize test model with negative variable costs") # negative variable costs and supply >= demand causes an unbounded ray pytest.raises(CalledProcessError, scen.solve) # use the commodity-balance equality feature scen.check_out() scen.add_set("balance_equality", ["comm", "level"]) scen.commit("set commodity-balance for `[comm, level]` as equality") scen.solve(case="price_commodity_equality") assert scen.var("OBJ")["lvl"] == -1 assert scen.var("PRICE_COMMODITY")["lvl"][0] == -1
def test_commodity_price_equality(test_mp): scen = Scenario(test_mp, 'test_commodity_price', 'equality', version='new') model_setup(scen, var_cost=-1) scen.commit('initialize test model with negative variable costs') # negative variable costs and supply >= demand causes an unbounded ray pytest.raises(CalledProcessError, scen.solve) # use the commodity-balance equality feature scen.check_out() scen.add_set('balance_equality', ['comm', 'level']) scen.commit('set commodity-balance for `[comm, level]` as equality') scen.solve(case='price_commodity_equality') assert scen.var('OBJ')['lvl'] == -1 assert scen.var('PRICE_COMMODITY')['lvl'][0] == -1
def test_addon_tec(test_mp): scen = Scenario(test_mp, *msg_args).clone(scenario='addon', keep_solution=False) add_addon(scen, costs=-1) scen.check_out() bda = scen.par('bound_activity_up', f) bda['value'] = bda['value'] / 2 scen.add_par('bound_activity_up', bda) scen.commit('changing output and bounds') scen.solve() exp = scen.var('ACT', f)['lvl'] obs = scen.var('ACT', g)['lvl'] assert np.isclose(exp, obs)
def test_addon_tec(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO["dantzig"]).clone(scenario="addon", keep_solution=False) add_addon(scen, costs=-1) scen.check_out() bda = scen.par("bound_activity_up", f) bda["value"] = bda["value"] / 2 scen.add_par("bound_activity_up", bda) scen.commit("changing output and bounds") scen.solve() exp = scen.var("ACT", f)["lvl"] obs = scen.var("ACT", g)["lvl"] assert np.isclose(exp, obs)
def test_excel_read_write(message_test_mp, tmp_path): # Path to temporary file tmp_path /= 'excel_read_write.xlsx' # Convert to string to ensure this can be handled fname = str(tmp_path) scen1 = Scenario(message_test_mp, **SCENARIO['dantzig']) scen1 = scen1.clone(keep_solution=False) scen1.check_out() scen1.init_set('new_set') scen1.add_set('new_set', 'member') scen1.init_par('new_par', idx_sets=['new_set']) scen1.add_par('new_par', 'member', 2, '-') scen1.commit('new set and parameter added.') # Writing to Excel without solving scen1.to_excel(fname) # Writing to Excel when scenario has a solution scen1.solve() scen1.to_excel(fname) scen2 = Scenario(message_test_mp, model='foo', scenario='bar', version='new') # Fails without init_items=True with pytest.raises(ValueError, match="no set 'new_set'"): scen2.read_excel(fname) # Succeeds with init_items=True scen2.read_excel(fname, init_items=True, commit_steps=True) exp = scen1.par('input') obs = scen2.par('input') pdt.assert_frame_equal(exp, obs) assert scen2.has_par('new_par') assert float(scen2.par('new_par')['value']) == 2 scen2.commit('foo') # must be checked in scen2.solve() assert np.isclose(scen2.var('OBJ')['lvl'], scen1.var('OBJ')['lvl'])
def test_excel_read_write(message_test_mp, tmp_path): # Path to temporary file tmp_path /= "excel_read_write.xlsx" # Convert to string to ensure this can be handled fname = str(tmp_path) scen1 = Scenario(message_test_mp, **SCENARIO["dantzig"]) scen1 = scen1.clone(keep_solution=False) scen1.check_out() scen1.init_set("new_set") scen1.add_set("new_set", "member") scen1.init_par("new_par", idx_sets=["new_set"]) scen1.add_par("new_par", "member", 2, "-") scen1.commit("new set and parameter added.") # Writing to Excel without solving scen1.to_excel(fname) # Writing to Excel when scenario has a solution scen1.solve() scen1.to_excel(fname) scen2 = Scenario(message_test_mp, model="foo", scenario="bar", version="new") # Fails without init_items=True with pytest.raises(ValueError, match="no set 'new_set'"): scen2.read_excel(fname) # Succeeds with init_items=True scen2.read_excel(fname, init_items=True, commit_steps=True) exp = scen1.par("input") obs = scen2.par("input") pdt.assert_frame_equal(exp, obs) assert scen2.has_par("new_par") assert float(scen2.par("new_par")["value"]) == 2 scen2.solve() assert np.isclose(scen2.var("OBJ")["lvl"], scen1.var("OBJ")["lvl"])
def test_new_timeseries_long_name64(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO['dantzig multi-year']) scen = scen.clone(keep_solution=False) scen.check_out(timeseries_only=True) df = pd.DataFrame({ 'region': [ 'India', ], 'variable': [ ('Emissions|CO2|Energy|Demand|Transportation|Aviation|' 'Domestic|Fre'), ], 'unit': [ 'Mt CO2/yr', ], '2012': [ 0.257009, ] }) scen.add_timeseries(df) scen.commit('importing a testing timeseries')
def test_years_active_extend(test_mp): scen = Scenario(test_mp, *msg_multiyear_args) # Existing time horizon years = [2010, 2020, 2030] result = scen.years_active('seattle', 'canning_plant', years[1]) npt.assert_array_equal(result, years[1:]) # Add years to the scenario years.extend([2040, 2050]) scen.check_out() scen.add_set('year', years[-2:]) scen.add_par('duration_period', '2040', 10, 'y') scen.add_par('duration_period', '2050', 10, 'y') # technical_lifetime of seattle/canning_plant/2020 is 30 years. # - constructed in 2011-01-01. # - by 2020-12-31, has operated 10 years. # - operates until 2040-12-31. # - is NOT active within the period '2050' (2041-01-01 to 2050-12-31) result = scen.years_active('seattle', 'canning_plant', '2020') npt.assert_array_equal(result, years[1:-1])
def test_years_active_extend(message_test_mp): scen = Scenario(message_test_mp, **SCENARIO["dantzig multi-year"]) # Existing time horizon years = [1963, 1964, 1965] result = scen.years_active("seattle", "canning_plant", years[1]) npt.assert_array_equal(result, years[1:]) # Add years to the scenario years.extend([1993, 1995]) scen.check_out() scen.add_set("year", years[-2:]) scen.add_par("duration_period", "1993", 28, "y") scen.add_par("duration_period", "1995", 2, "y") # technical_lifetime of seattle/canning_plant/1964 is 30 years. # - constructed in 1964-01-01. # - by 1964-12-31, has operated 1 year. # - by 1965-12-31, has operated 2 years. # - operates until 1993-12-31. # - is NOT active within the period '1995' (1994-01-01 to 1995-12-31) result = scen.years_active("seattle", "canning_plant", 1964) npt.assert_array_equal(result, years[1:-1])
def create_timeseries_df(results: message_ix.Scenario) -> message_ix.Scenario: logger.info('Create timeseries') results.check_out(timeseries_only=True) for var in ['ACT', 'CAP', 'CAP_NEW', 'EMISS']: df = group_data(var, results) if var != 'EMISS': df['variable'] = ([ f'{df.loc[i, "technology"]}|{df.loc[i, "variable"]}' for i in df.index ]) else: df['variable'] = [ f'{df.loc[i, "emission"]}|{df.loc[i, "variable"]}' for i in df.index ] df['node'] = 'World' # TODO: wenn #6 gelöst, dann implementieren df = df.rename(columns={'node': 'region'}) ts = pd.pivot_table(df, values='lvl', index=['region', 'variable', 'unit'], columns=['year']).reset_index(drop=False) results.add_timeseries(ts) results.commit('timeseries added') return results
def storage_setup(test_mp, time_duration, comment): # First, building a simple model and adding seasonality scen = Scenario(test_mp, "no_storage", "standard", version="new") model_setup(scen, [2020]) add_seasonality(scen, time_duration) # Fixed share for parameters that don't change across timesteps fixed_share = {"a": 1, "b": 1, "c": 1, "d": 1} year_to_time(scen, "output", fixed_share) year_to_time(scen, "var_cost", fixed_share) # Variable share for parameters that are changing in each timestep # share of demand in each season from annual demand demand_share = {"a": 0.15, "b": 0.2, "c": 0.4, "d": 0.25} year_to_time(scen, "demand", demand_share) scen.commit("initialized a model with timesteps") scen.solve(case="no_storage" + comment) # Second, adding upper bound on activity of the cheap technology (wind_ppl) scen.remove_solution() scen.check_out() for h in time_duration.keys(): scen.add_par( "bound_activity_up", ["node", "wind_ppl", 2020, "mode", h], 0.25, "GWa" ) scen.commit("activity bounded") scen.solve(case="no_storage_bounded" + comment) cost_no_stor = scen.var("OBJ")["lvl"] act_no_stor = scen.var("ACT", {"technology": "gas_ppl"})["lvl"].sum() # Third, adding storage technologies but with no input to storage device scen.remove_solution() scen.check_out() # Chronological order of timesteps in the year time_order = {"a": 1, "b": 2, "c": 3, "d": 4} add_storage_data(scen, time_order) scen.commit("storage data added") scen.solve(case="with_storage_no_input" + comment) act = scen.var("ACT") # Forth, adding storage technologies and providing input to storage device scen.remove_solution() scen.check_out() # Adding a new technology "cooler" to provide input of "cooling" to dam scen.add_set("technology", "cooler") df = scen.par("output", {"technology": "turbine"}) df["technology"] = "cooler" df["commodity"] = "cooling" scen.add_par("output", df) # Changing input of dam from 1 to 1.2 to test commodity balance df = scen.par("input", {"technology": "dam"}) df["value"] = 1.2 scen.add_par("input", df) scen.commit("storage needs no separate input") scen.solve(case="with_storage_and_input" + comment) cost_with_stor = scen.var("OBJ")["lvl"] act_with_stor = scen.var("ACT", {"technology": "gas_ppl"})["lvl"].sum() # Fifth. Tests for the functionality of storage # 1. Check that "dam" is not active if no "input" commodity is defined assert "dam" not in act[act["lvl"] > 0]["technology"].tolist() # 2. Testing functionality of storage # Check the contribution of storage to the system cost assert cost_with_stor < cost_no_stor # Activity of expensive technology should be lower with storage assert act_with_stor < act_no_stor # 3. Activity of discharger <= activity of charger + initial content act_pump = scen.var("ACT", {"technology": "pump"})["lvl"] act_turb = scen.var("ACT", {"technology": "turbine"})["lvl"] initial_content = float(scen.par("storage_initial")["value"]) assert act_turb.sum() <= act_pump.sum() + initial_content # 4. Activity of input provider to storage = act of storage * storage input for ts in time_duration.keys(): act_cooler = scen.var("ACT", {"technology": "cooler", "time": ts})["lvl"] inp = scen.par("input", {"technology": "dam", "time": ts})["value"] act_stor = scen.var("ACT", {"technology": "dam", "time": ts})["lvl"] assert float(act_cooler) == float(inp) * float(act_stor) # 5. Max activity of charger <= max activity of storage max_pump = max(act_pump) act_storage = scen.var("ACT", {"technology": "dam"})["lvl"] max_stor = max(act_storage) assert max_pump <= max_stor # 6. Max activity of discharger <= max storage act - self discharge losses max_turb = max(act_turb) loss = scen.par("storage_self_discharge")["value"][0] assert max_turb <= max_stor * (1 - loss) # Sixth, testing equations of storage (when added to ixmp variables) if scen.has_var("STORAGE"): # 1. Equality: storage content in the beginning and end is related storage_first = scen.var("STORAGE", {"time": "a"})["lvl"] storage_last = scen.var("STORAGE", {"time": "d"})["lvl"] relation = scen.par("relation_storage", {"time_first": "d", "time_last": "a"})[ "value" ][0] assert storage_last >= storage_first * relation # 2. Storage content should never exceed storage activity assert max(scen.var("STORAGE")["lvl"]) <= max_stor # 3. Commodity balance: charge - discharge - losses = 0 change = scen.var("STORAGE_CHARGE").set_index(["year_act", "time"])["lvl"] loss = scen.par("storage_self_discharge").set_index(["year", "time"])["value"] assert sum(change[change > 0] * (1 - loss)) == -sum(change[change < 0]) # 4. Energy balance: storage change + losses = storage content storage = scen.var("STORAGE").set_index(["year", "time"])["lvl"] assert storage[(2020, "b")] * (1 - loss[(2020, "b")]) == -change[(2020, "c")]
def make_westeros(mp, emissions=False, solve=False, quiet=True): """Return an :class:`message_ix.Scenario` for the Westeros model. This is the same model used in the ``westeros_baseline.ipynb`` tutorial. Parameters ---------- mp : ixmp.Platform Platform on which to create the scenario. emissions : bool, optional If True, the ``emissions_factor`` parameter is also populated for CO2. solve : bool, optional If True, the scenario is solved. """ mp.add_unit("USD/kW") mp.add_unit("tCO2/kWa") scen = Scenario(mp, version="new", **SCENARIO["westeros"]) # Sets history = [690] model_horizon = [700, 710, 720] scen.add_horizon(year=history + model_horizon, firstmodelyear=model_horizon[0]) year_df = scen.vintage_and_active_years() vintage_years, act_years = year_df["year_vtg"], year_df["year_act"] country = "Westeros" scen.add_spatial_sets({"country": country}) for name, values in ( ("technology", ["coal_ppl", "wind_ppl", "grid", "bulb"]), ("mode", ["standard"]), ("level", ["secondary", "final", "useful"]), ("commodity", ["electricity", "light"]), ): scen.add_set(name, values) # Parameters — copy & paste from the tutorial notebook common = dict( mode="standard", node_dest=country, node_loc=country, node_origin=country, node=country, time_dest="year", time_origin="year", time="year", year_act=act_years, year_vtg=vintage_years, year=model_horizon, ) gdp_profile = np.array([1.0, 1.5, 1.9]) demand_per_year = 40 * 12 * 1000 / 8760 scen.add_par( "demand", make_df( "demand", **common, commodity="light", level="useful", # FIXME should use demand_per_year; requires adjustments elsewhere. value=(100 * gdp_profile).round(), unit="GWa", ), ) grid_efficiency = 0.9 common.update(unit="-") for name, tec, c, l, value in [ ("input", "bulb", "electricity", "final", 1.0), ("output", "bulb", "light", "useful", 1.0), ("input", "grid", "electricity", "secondary", 1.0), ("output", "grid", "electricity", "final", grid_efficiency), ("output", "coal_ppl", "electricity", "secondary", 1.0), ("output", "wind_ppl", "electricity", "secondary", 1.0), ]: scen.add_par( name, make_df(name, **common, technology=tec, commodity=c, level=l, value=value), ) # FIXME the value for wind_ppl should be 0.36; requires adjusting other tests. name = "capacity_factor" capacity_factor = dict(coal_ppl=1.0, wind_ppl=1.0, bulb=1.0) for tec, value in capacity_factor.items(): scen.add_par(name, make_df(name, **common, technology=tec, value=value)) name = "technical_lifetime" common.update(year_vtg=model_horizon, unit="y") for tec, value in dict(coal_ppl=20, wind_ppl=20, bulb=1).items(): scen.add_par(name, make_df(name, **common, technology=tec, value=value)) name = "growth_activity_up" common.update(year_act=model_horizon, unit="-") for tec in "coal_ppl", "wind_ppl": scen.add_par(name, make_df(name, **common, technology=tec, value=0.1)) historic_demand = 0.85 * demand_per_year historic_generation = historic_demand / grid_efficiency coal_fraction = 0.6 common.update(year_act=history, year_vtg=history, unit="GWa") for tec, value in ( ("coal_ppl", coal_fraction * historic_generation), ("wind_ppl", (1 - coal_fraction) * historic_generation), ): name = "historical_activity" scen.add_par(name, make_df(name, **common, technology=tec, value=value)) # 20 year lifetime name = "historical_new_capacity" scen.add_par( name, make_df( name, **common, technology=tec, value=value / (2 * 10 * capacity_factor[tec]), ), ) name = "interestrate" scen.add_par(name, make_df(name, year=model_horizon, value=0.05, unit="-")) for name, tec, value in [ ("inv_cost", "coal_ppl", 500), ("inv_cost", "wind_ppl", 1500), ("inv_cost", "bulb", 5), ("fix_cost", "coal_ppl", 30), ("fix_cost", "wind_ppl", 10), ("var_cost", "coal_ppl", 30), ("var_cost", "grid", 50), ]: common.update( dict(year_vtg=model_horizon, unit="USD/kW") if name == "inv_cost" else dict( year_vtg=vintage_years, year_act=act_years, unit="USD/kWa")) scen.add_par(name, make_df(name, **common, technology=tec, value=value)) scen.commit("basic model of Westerosi electrification") scen.set_as_default() if emissions: scen.check_out() # Introduce the emission species CO2 and the emission category GHG scen.add_set("emission", "CO2") scen.add_cat("emission", "GHG", "CO2") # we now add CO2 emissions to the coal powerplant name = "emission_factor" common.update(year_vtg=vintage_years, year_act=act_years, unit="tCO2/kWa") scen.add_par( name, make_df(name, **common, technology="coal_ppl", emission="CO2", value=100.0), ) scen.commit("Added emissions sets/params to Westeros model.") if solve: scen.solve(quiet=quiet) return scen
def make_dantzig(mp, solve=False, multi_year=False, **solve_opts): """Return an :class:`message_ix.Scenario` for Dantzig's canning problem. Parameters ---------- mp : ixmp.Platform Platform on which to create the scenario. solve : bool, optional If True, the scenario is solved. multi_year : bool, optional If True, the scenario has years 1963--1965 inclusive. Otherwise, the scenario has the single year 1963. """ # add custom units and region for timeseries data mp.add_unit("USD/case") mp.add_unit("case") mp.add_region("DantzigLand", "country") # initialize a new (empty) instance of an `ixmp.Scenario` scen = Scenario( mp, model=SCENARIO["dantzig"]["model"], scenario="multi-year" if multi_year else "standard", annotation="Dantzig's canning problem as a MESSAGE-scheme Scenario", version="new", ) # Sets # NB commit() is refused if technology and year are not given t = ["canning_plant", "transport_from_seattle", "transport_from_san-diego"] sets = { "technology": t, "node": "seattle san-diego new-york chicago topeka".split(), "mode": "production to_new-york to_chicago to_topeka".split(), "level": "supply consumption".split(), "commodity": ["cases"], } for name, values in sets.items(): scen.add_set(name, values) scen.add_horizon(year=[1962, 1963], firstmodelyear=1963) # Parameters par = {} # Common values common = dict( commodity="cases", year=1963, year_vtg=1963, year_act=1963, time="year", time_dest="year", time_origin="year", ) par["demand"] = make_df( "demand", **common, node=["new-york", "chicago", "topeka"], level="consumption", value=[325, 300, 275], unit="case", ) par["bound_activity_up"] = make_df( "bound_activity_up", **common, node_loc=["seattle", "san-diego"], mode="production", technology="canning_plant", value=[350, 600], unit="case", ) par["ref_activity"] = par["bound_activity_up"].copy() input = pd.DataFrame( [ ["to_new-york", "seattle", "seattle", t[1]], ["to_chicago", "seattle", "seattle", t[1]], ["to_topeka", "seattle", "seattle", t[1]], ["to_new-york", "san-diego", "san-diego", t[2]], ["to_chicago", "san-diego", "san-diego", t[2]], ["to_topeka", "san-diego", "san-diego", t[2]], ], columns=["mode", "node_loc", "node_origin", "technology"], ) par["input"] = make_df( "input", **input, **common, level="supply", value=1, unit="case", ) output = pd.DataFrame( [ ["supply", "production", "seattle", "seattle", t[0]], ["supply", "production", "san-diego", "san-diego", t[0]], ["consumption", "to_new-york", "new-york", "seattle", t[1]], ["consumption", "to_chicago", "chicago", "seattle", t[1]], ["consumption", "to_topeka", "topeka", "seattle", t[1]], ["consumption", "to_new-york", "new-york", "san-diego", t[2]], ["consumption", "to_chicago", "chicago", "san-diego", t[2]], ["consumption", "to_topeka", "topeka", "san-diego", t[2]], ], columns=["level", "mode", "node_dest", "node_loc", "technology"], ) par["output"] = make_df("output", **output, **common, value=1, unit="case") # Variable cost: cost per kilometre × distance (neither parametrized # explicitly) var_cost = pd.DataFrame( [ ["to_new-york", "seattle", "transport_from_seattle", 0.225], ["to_chicago", "seattle", "transport_from_seattle", 0.153], ["to_topeka", "seattle", "transport_from_seattle", 0.162], ["to_new-york", "san-diego", "transport_from_san-diego", 0.225], ["to_chicago", "san-diego", "transport_from_san-diego", 0.162], ["to_topeka", "san-diego", "transport_from_san-diego", 0.126], ], columns=["mode", "node_loc", "technology", "value"], ) par["var_cost"] = make_df("var_cost", **var_cost, **common, unit="USD/case") for name, value in par.items(): scen.add_par(name, value) if multi_year: scen.add_set("year", [1964, 1965]) scen.add_par("technical_lifetime", ["seattle", "canning_plant", 1964], 3, "y") if solve: # Always read one equation. Used by test_core.test_year_int. scen.init_equ("COMMODITY_BALANCE_GT", ["node", "commodity", "level", "year", "time"]) solve_opts["equ_list"] = solve_opts.get("equ_list", []) + ["COMMODITY_BALANCE_GT"] scen.commit("Created a MESSAGE-scheme version of the transport problem.") scen.set_as_default() if solve: solve_opts.setdefault("quiet", True) scen.solve(**solve_opts) scen.check_out(timeseries_only=True) scen.add_timeseries(HIST_DF, meta=True) scen.add_timeseries(INP_DF) scen.commit("Import Dantzig's transport problem for testing.") return scen
def storage_setup(test_mp, time_duration, comment): # First building a simple model and adding seasonality scen = Scenario(test_mp, 'no_storage', 'standard', version='new') model_setup(scen, [2020]) add_seasonality(scen, time_duration) fixed_share = {'a': 1, 'b': 1, 'c': 1, 'd': 1} year_to_time(scen, 'output', fixed_share) year_to_time(scen, 'var_cost', fixed_share) demand_share = {'a': 0.15, 'b': 0.2, 'c': 0.4, 'd': 0.25} year_to_time(scen, 'demand', demand_share) scen.commit('initialized test model') scen.solve(case='no_storage' + comment) # Second adding bound on the activity of the cheap technology scen.remove_solution() scen.check_out() for h in time_duration.keys(): scen.add_par('bound_activity_up', ['node', 'tec1', 2020, 'mode', h], 0.25, 'GWa') scen.commit('activity bounded') scen.solve(case='no_storage_bounded' + comment) cost_no_storage = scen.var('OBJ')['lvl'] act_no = scen.var('ACT', {'technology': 'tec2'})['lvl'].sum() # Third, adding storage technologies and their parameters scen.remove_solution() scen.check_out() time_order = {'a': 1, 'b': 2, 'c': 3, 'd': 4} add_storage_data(scen, time_order) scen.commit('storage added') scen.solve(case='with_storage' + comment) cost_with_storage = scen.var('OBJ')['lvl'] act_with = scen.var('ACT', {'technology': 'tec2'})['lvl'].sum() # I. Tests for functionality of storage # I.1. Contribution of storage to the system assert cost_with_storage < cost_no_storage # Or, activity of expensive technology should be lower with storage assert act_with < act_no # I.2. Activity of discharger should be always <= activity of charger act_pump = scen.var('ACT', {'technology': 'pump'})['lvl'] act_turb = scen.var('ACT', {'technology': 'turbine'})['lvl'] assert act_turb.sum() <= act_pump.sum() # I.3. Max activity of charger is lower than storage capacity max_pump = max(act_pump) cap_storage = float(scen.var('CAP', {'technology': 'dam'})['lvl']) assert max_pump <= cap_storage # I.4. Max activity of discharger is lower than storage capacity - losses max_turb = max(act_turb) loss = scen.par('storage_loss')['value'][0] assert max_turb <= cap_storage * (1 - loss) # II. Testing equations of storage (when added to ixmp variables) if scen.has_var('STORAGE'): # II.1. Equality: storage content in the beginning and end is equal storage_first = scen.var('STORAGE', {'time': 'a'})['lvl'] storage_last = scen.var('STORAGE', {'time': 'd'})['lvl'] assert storage_first == storage_last # II.2. Storage content should never exceed storage capacity assert max(scen.var('STORAGE')['lvl']) <= cap_storage # II.3. Commodity balance: charge - discharge - losses = 0 change = scen.var('STORAGE_CHG').set_index(['year_act', 'time'])['lvl'] loss = scen.par('storage_loss').set_index(['year', 'time'])['value'] assert sum(change[change > 0] * (1 - loss)) == -sum(change[change < 0]) # II.4. Energy balance: storage change + losses = storage content storage = scen.var('STORAGE').set_index(['year', 'time'])['lvl'] assert storage[(2020, 'b')] * (1 - loss[(2020, 'b')]) == -change[(2020, 'c')]
def make_westeros(mp, emissions=False, solve=False): """Return an :class:`message_ix.Scenario` for the Westeros model. This is the same model used in the ``westeros_baseline.ipynb`` tutorial. Parameters ---------- mp : ixmp.Platform Platform on which to create the scenario. emissions : bool, optional If True, the ``emissions_factor`` parameter is also populated for CO2. solve : bool, optional If True, the scenario is solved. """ scen = Scenario(mp, version='new', **SCENARIO['westeros']) # Sets history = [690] model_horizon = [700, 710, 720] scen.add_horizon({ 'year': history + model_horizon, 'firstmodelyear': model_horizon[0] }) country = 'Westeros' scen.add_spatial_sets({'country': country}) sets = { 'technology': 'coal_ppl wind_ppl grid bulb'.split(), 'mode': ['standard'], 'level': 'secondary final useful'.split(), 'commodity': 'electricity light'.split(), } for name, values in sets.items(): scen.add_set(name, values) # Parameters — copy & paste from the tutorial notebook gdp_profile = pd.Series([1., 1.5, 1.9], index=pd.Index(model_horizon, name='Time')) demand_per_year = 40 * 12 * 1000 / 8760 light_demand = pd.DataFrame({ 'node': country, 'commodity': 'light', 'level': 'useful', 'year': model_horizon, 'time': 'year', 'value': (100 * gdp_profile).round(), 'unit': 'GWa', }) scen.add_par("demand", light_demand) year_df = scen.vintage_and_active_years() vintage_years, act_years = year_df['year_vtg'], year_df['year_act'] base = { 'node_loc': country, 'year_vtg': vintage_years, 'year_act': act_years, 'mode': 'standard', 'time': 'year', 'unit': '-', } base_input = make_df(base, node_origin=country, time_origin='year') base_output = make_df(base, node_dest=country, time_dest='year') bulb_out = make_df(base_output, technology='bulb', commodity='light', level='useful', value=1.0) scen.add_par('output', bulb_out) bulb_in = make_df(base_input, technology='bulb', commodity='electricity', level='final', value=1.0) scen.add_par('input', bulb_in) grid_efficiency = 0.9 grid_out = make_df(base_output, technology='grid', commodity='electricity', level='final', value=grid_efficiency) scen.add_par('output', grid_out) grid_in = make_df(base_input, technology='grid', commodity='electricity', level='secondary', value=1.0) scen.add_par('input', grid_in) coal_out = make_df(base_output, technology='coal_ppl', commodity='electricity', level='secondary', value=1.) scen.add_par('output', coal_out) wind_out = make_df(base_output, technology='wind_ppl', commodity='electricity', level='secondary', value=1.) scen.add_par('output', wind_out) base_capacity_factor = { 'node_loc': country, 'year_vtg': vintage_years, 'year_act': act_years, 'time': 'year', 'unit': '-', } capacity_factor = { 'coal_ppl': 1, 'wind_ppl': 1, 'bulb': 1, } for tec, val in capacity_factor.items(): df = make_df(base_capacity_factor, technology=tec, value=val) scen.add_par('capacity_factor', df) base_technical_lifetime = { 'node_loc': country, 'year_vtg': model_horizon, 'unit': 'y', } lifetime = { 'coal_ppl': 20, 'wind_ppl': 20, 'bulb': 1, } for tec, val in lifetime.items(): df = make_df(base_technical_lifetime, technology=tec, value=val) scen.add_par('technical_lifetime', df) base_growth = { 'node_loc': country, 'year_act': model_horizon, 'time': 'year', 'unit': '-', } growth_technologies = [ "coal_ppl", "wind_ppl", ] for tec in growth_technologies: df = make_df(base_growth, technology=tec, value=0.1) scen.add_par('growth_activity_up', df) historic_demand = 0.85 * demand_per_year historic_generation = historic_demand / grid_efficiency coal_fraction = 0.6 base_capacity = { 'node_loc': country, 'year_vtg': history, 'unit': 'GWa', } base_activity = { 'node_loc': country, 'year_act': history, 'mode': 'standard', 'time': 'year', 'unit': 'GWa', } old_activity = { 'coal_ppl': coal_fraction * historic_generation, 'wind_ppl': (1 - coal_fraction) * historic_generation, } for tec, val in old_activity.items(): df = make_df(base_activity, technology=tec, value=val) scen.add_par('historical_activity', df) act_to_cap = { # 20 year lifetime 'coal_ppl': 1 / 10 / capacity_factor['coal_ppl'] / 2, 'wind_ppl': 1 / 10 / capacity_factor['wind_ppl'] / 2, } for tec in act_to_cap: value = old_activity[tec] * act_to_cap[tec] df = make_df(base_capacity, technology=tec, value=value) scen.add_par('historical_new_capacity', df) rate = [0.05] * len(model_horizon) unit = ['-'] * len(model_horizon) scen.add_par("interestrate", model_horizon, rate, unit) base_inv_cost = { 'node_loc': country, 'year_vtg': model_horizon, 'unit': 'USD/GWa', } # in $ / kW costs = { 'coal_ppl': 500, 'wind_ppl': 1500, 'bulb': 5, } for tec, val in costs.items(): df = make_df(base_inv_cost, technology=tec, value=val) scen.add_par('inv_cost', df) base_fix_cost = { 'node_loc': country, 'year_vtg': vintage_years, 'year_act': act_years, 'unit': 'USD/GWa', } # in $ / kW costs = { 'coal_ppl': 30, 'wind_ppl': 10, } for tec, val in costs.items(): df = make_df(base_fix_cost, technology=tec, value=val) scen.add_par('fix_cost', df) base_var_cost = { 'node_loc': country, 'year_vtg': vintage_years, 'year_act': act_years, 'mode': 'standard', 'time': 'year', 'unit': 'USD/GWa', } # in $ / MWh costs = { 'coal_ppl': 30, 'grid': 50, } for tec, val in costs.items(): df = make_df(base_var_cost, technology=tec, value=val) scen.add_par('var_cost', df) scen.commit('basic model of Westerosi electrification') scen.set_as_default() if emissions: scen.check_out() # Introduce the emission species CO2 and the emission category GHG scen.add_set('emission', 'CO2') scen.add_cat('emission', 'GHG', 'CO2') # we now add CO2 emissions to the coal powerplant base_emission_factor = { 'node_loc': country, 'year_vtg': vintage_years, 'year_act': act_years, 'mode': 'standard', 'unit': 'USD/GWa', } emission_factor = make_df(base_emission_factor, technology='coal_ppl', emission='CO2', value=100.) scen.add_par('emission_factor', emission_factor) scen.commit('Added emissions sets/params to Westeros model.') if solve: scen.solve() return scen
def make_dantzig(mp, solve=False, multi_year=False, **solve_opts): """Return an :class:`message_ix.Scenario` for Dantzig's canning problem. Parameters ---------- mp : ixmp.Platform Platform on which to create the scenario. solve : bool, optional If True, the scenario is solved. multi_year : bool, optional If True, the scenario has years 1963--1965 inclusive. Otherwise, the scenario has the single year 1963. """ # add custom units and region for timeseries data mp.add_unit('USD/case') mp.add_unit('case') mp.add_region('DantzigLand', 'country') # initialize a new (empty) instance of an `ixmp.Scenario` scen = Scenario( mp, model=SCENARIO['dantzig']['model'], scenario='multi-year' if multi_year else 'standard', annotation="Dantzig's canning problem as a MESSAGE-scheme Scenario", version='new') # Sets # NB commit() is refused if technology and year are not given t = ['canning_plant', 'transport_from_seattle', 'transport_from_san-diego'] sets = { 'technology': t, 'node': 'seattle san-diego new-york chicago topeka'.split(), 'mode': 'production to_new-york to_chicago to_topeka'.split(), 'level': 'supply consumption'.split(), 'commodity': ['cases'], } for name, values in sets.items(): scen.add_set(name, values) scen.add_horizon({'year': [1962, 1963], 'firstmodelyear': 1963}) # Parameters par = {} demand = { 'node': 'new-york chicago topeka'.split(), 'value': [325, 300, 275] } par['demand'] = make_df(pd.DataFrame.from_dict(demand), commodity='cases', level='consumption', time='year', unit='case', year=1963) b_a_u = {'node_loc': ['seattle', 'san-diego'], 'value': [350, 600]} par['bound_activity_up'] = make_df(pd.DataFrame.from_dict(b_a_u), mode='production', technology='canning_plant', time='year', unit='case', year_act=1963) par['ref_activity'] = par['bound_activity_up'].copy() input = pd.DataFrame( [ ['to_new-york', 'seattle', 'seattle', t[1]], ['to_chicago', 'seattle', 'seattle', t[1]], ['to_topeka', 'seattle', 'seattle', t[1]], ['to_new-york', 'san-diego', 'san-diego', t[2]], ['to_chicago', 'san-diego', 'san-diego', t[2]], ['to_topeka', 'san-diego', 'san-diego', t[2]], ], columns=['mode', 'node_loc', 'node_origin', 'technology']) par['input'] = make_df(input, commodity='cases', level='supply', time='year', time_origin='year', unit='case', value=1, year_act=1963, year_vtg=1963) output = pd.DataFrame( [ ['supply', 'production', 'seattle', 'seattle', t[0]], ['supply', 'production', 'san-diego', 'san-diego', t[0]], ['consumption', 'to_new-york', 'new-york', 'seattle', t[1]], ['consumption', 'to_chicago', 'chicago', 'seattle', t[1]], ['consumption', 'to_topeka', 'topeka', 'seattle', t[1]], ['consumption', 'to_new-york', 'new-york', 'san-diego', t[2]], ['consumption', 'to_chicago', 'chicago', 'san-diego', t[2]], ['consumption', 'to_topeka', 'topeka', 'san-diego', t[2]], ], columns=['level', 'mode', 'node_dest', 'node_loc', 'technology']) par['output'] = make_df(output, commodity='cases', time='year', time_dest='year', unit='case', value=1, year_act=1963, year_vtg=1963) # Variable cost: cost per kilometre × distance (neither parametrized # explicitly) var_cost = pd.DataFrame( [ ['to_new-york', 'seattle', 'transport_from_seattle', 0.225], ['to_chicago', 'seattle', 'transport_from_seattle', 0.153], ['to_topeka', 'seattle', 'transport_from_seattle', 0.162], ['to_new-york', 'san-diego', 'transport_from_san-diego', 0.225], ['to_chicago', 'san-diego', 'transport_from_san-diego', 0.162], ['to_topeka', 'san-diego', 'transport_from_san-diego', 0.126], ], columns=['mode', 'node_loc', 'technology', 'value']) par['var_cost'] = make_df(var_cost, time='year', unit='USD/case', year_act=1963, year_vtg=1963) for name, value in par.items(): scen.add_par(name, value) if multi_year: scen.add_set('year', [1964, 1965]) scen.add_par('technical_lifetime', ['seattle', 'canning_plant', 1964], 3, 'y') if solve: # Always read one equation. Used by test_core.test_year_int. scen.init_equ('COMMODITY_BALANCE_GT', ['node', 'commodity', 'level', 'year', 'time']) solve_opts['equ_list'] = solve_opts.get('equ_list', []) \ + ['COMMODITY_BALANCE_GT'] scen.commit('Created a MESSAGE-scheme version of the transport problem.') scen.set_as_default() if solve: scen.solve(**solve_opts) scen.check_out(timeseries_only=True) scen.add_timeseries(HIST_DF, meta=True) scen.add_timeseries(INP_DF) scen.commit("Import Dantzig's transport problem for testing.") return scen
def storage_setup(test_mp, time_duration, comment): # First, building a simple model and adding seasonality scen = Scenario(test_mp, 'no_storage', 'standard', version='new') model_setup(scen, [2020]) add_seasonality(scen, time_duration) # Fixed share for parameters that don't change across timesteps fixed_share = {'a': 1, 'b': 1, 'c': 1, 'd': 1} year_to_time(scen, 'output', fixed_share) year_to_time(scen, 'var_cost', fixed_share) # Variable share for parameters that are changing in each timestep # share of demand in each season from annual demand demand_share = {'a': 0.15, 'b': 0.2, 'c': 0.4, 'd': 0.25} year_to_time(scen, 'demand', demand_share) scen.commit('initialized a model with timesteps') scen.solve(case='no_storage' + comment) # Second, adding upper bound on activity of the cheap technology (wind_ppl) scen.remove_solution() scen.check_out() for h in time_duration.keys(): scen.add_par('bound_activity_up', ['node', 'wind_ppl', 2020, 'mode', h], 0.25, 'GWa') scen.commit('activity bounded') scen.solve(case='no_storage_bounded' + comment) cost_no_stor = scen.var('OBJ')['lvl'] act_no_stor = scen.var('ACT', {'technology': 'gas_ppl'})['lvl'].sum() # Third, adding storage technologies but with no input to storage device scen.remove_solution() scen.check_out() # Chronological order of timesteps in the year time_order = {'a': 1, 'b': 2, 'c': 3, 'd': 4} add_storage_data(scen, time_order) scen.commit('storage data added') scen.solve(case='with_storage_no_input' + comment) act = scen.var('ACT') # Forth, adding storage technologies and providing input to storage device scen.remove_solution() scen.check_out() # Adding a new technology "cooler" to provide input of "cooling" to dam scen.add_set('technology', 'cooler') df = scen.par('output', {'technology': 'turbine'}) df['technology'] = 'cooler' df['commodity'] = 'cooling' scen.add_par('output', df) # Changing input of dam from 1 to 1.2 to test commodity balance df = scen.par('input', {'technology': 'dam'}) df['value'] = 1.2 scen.add_par('input', df) scen.commit('storage needs no separate input') scen.solve(case='with_storage_and_input' + comment) cost_with_stor = scen.var('OBJ')['lvl'] act_with_stor = scen.var('ACT', {'technology': 'gas_ppl'})['lvl'].sum() # Fifth. Tests for the functionality of storage # 1. Check that "dam" is not active if no "input" commodity is defined assert 'dam' not in act[act['lvl'] > 0]['technology'].tolist() # 2. Testing functionality of storage # Check the contribution of storage to the system cost assert cost_with_stor < cost_no_stor # Activity of expensive technology should be lower with storage assert act_with_stor < act_no_stor # 3. Activity of discharger <= activity of charger + initial content act_pump = scen.var('ACT', {'technology': 'pump'})['lvl'] act_turb = scen.var('ACT', {'technology': 'turbine'})['lvl'] initial_content = float(scen.par('storage_initial')['value']) assert act_turb.sum() <= act_pump.sum() + initial_content # 4. Activity of input provider to storage = act of storage * storage input for ts in time_duration.keys(): act_cooler = scen.var('ACT', { 'technology': 'cooler', 'time': ts })['lvl'] inp = scen.par('input', {'technology': 'dam', 'time': ts})['value'] act_stor = scen.var('ACT', {'technology': 'dam', 'time': ts})['lvl'] assert float(act_cooler) == float(inp) * float(act_stor) # 5. Max activity of charger <= max activity of storage max_pump = max(act_pump) act_storage = scen.var('ACT', {'technology': 'dam'})['lvl'] max_stor = max(act_storage) assert max_pump <= max_stor # 6. Max activity of discharger <= max storage act - self discharge losses max_turb = max(act_turb) loss = scen.par('storage_self_discharge')['value'][0] assert max_turb <= max_stor * (1 - loss) # Sixth, testing equations of storage (when added to ixmp variables) if scen.has_var('STORAGE'): # 1. Equality: storage content in the beginning and end is related storage_first = scen.var('STORAGE', {'time': 'a'})['lvl'] storage_last = scen.var('STORAGE', {'time': 'd'})['lvl'] relation = scen.par('relation_storage', { 'time_first': 'd', 'time_last': 'a' })['value'][0] assert storage_last >= storage_first * relation # 2. Storage content should never exceed storage activity assert max(scen.var('STORAGE')['lvl']) <= max_stor # 3. Commodity balance: charge - discharge - losses = 0 change = scen.var('STORAGE_CHARGE').set_index(['year_act', 'time'])['lvl'] loss = scen.par('storage_self_discharge').set_index(['year', 'time'])['value'] assert sum(change[change > 0] * (1 - loss)) == -sum(change[change < 0]) # 4. Energy balance: storage change + losses = storage content storage = scen.var('STORAGE').set_index(['year', 'time'])['lvl'] assert storage[(2020, 'b')] * (1 - loss[(2020, 'b')]) == -change[(2020, 'c')]