def test_years_active_extend3(test_mp): test_mp.add_unit("year") scen = Scenario(test_mp, **SCENARIO["dantzig"], version="new") scen.add_set("node", "foo") scen.add_set("technology", "bar") # Periods of uneven length years = [1990, 1995, 2000, 2005, 2010, 2020, 2030] scen.add_horizon(year=years, firstmodelyear=2010) scen.add_set("year", [1992]) scen.add_par("duration_period", "1992", 2, "y") scen.add_par("duration_period", "1995", 3, "y") scen.add_par( "technical_lifetime", pd.DataFrame( dict( node_loc="foo", technology="bar", unit="year", value=[20], year_vtg=1990, ), ), ) obs = scen.years_active("foo", "bar", 1990) assert obs == [1990, 1992, 1995, 2000, 2005]
def test_price_duality(test_mp): years = [2020, 2025, 2030, 2040, 2050] for c in [0.25, 0.5, 0.75]: # set up a scenario for cumulative constraints scen = Scenario(test_mp, MODEL, 'cum_many_tecs', version='new') model_setup(scen, years, simple_tecs=False) scen.add_cat('year', 'cumulative', years) scen.add_par('bound_emission', ['World', 'ghg', 'all', 'cumulative'], 0.5, 'tCO2') scen.commit('initialize test scenario') scen.solve() # set up a new scenario with emissions taxes tax_scen = Scenario(test_mp, MODEL, 'tax_many_tecs', version='new') model_setup(tax_scen, years, simple_tecs=False) for y in years: tax_scen.add_cat('year', y, y) # use emission prices from cumulative-constraint scenario as taxes taxes = scen.var('PRICE_EMISSION')\ .rename(columns={'year': 'type_year', 'lvl': 'value'}) taxes['unit'] = 'USD/tCO2' tax_scen.add_par('tax_emission', taxes) tax_scen.commit('initialize test scenario for taxes') tax_scen.solve() # check that emissions are close between cumulative and tax scenario filters = {'node': 'World'} emiss = scen.var('EMISS', filters).set_index('year').lvl emiss_tax = tax_scen.var('EMISS', filters).set_index('year').lvl npt.assert_allclose(emiss, emiss_tax, rtol=0.20)
def test_years_active(test_mp): test_mp.add_unit('year') scen = Scenario(test_mp, *msg_args, version='new') scen.add_set('node', 'foo') scen.add_set('technology', 'bar') # Periods of uneven length years = [1990, 1995, 2000, 2005, 2010, 2020, 2030] # First period length is immaterial duration = [1900, 5, 5, 5, 5, 10, 10] scen.add_horizon({'year': years, 'firstmodelyear': years[-1]}) scen.add_par('duration_period', pd.DataFrame(zip(years, duration), columns=['year', 'value'])) # 'bar' built in period '1995' with 25-year lifetime: # - is constructed in 1991-01-01. # - by 1995-12-31, has operated 5 years. # - operates until 2015-12-31. This is within the period '2020'. scen.add_par('technical_lifetime', pd.DataFrame(dict( node_loc='foo', technology='bar', unit='year', value=25, year_vtg=years[1]), index=[0])) result = scen.years_active('foo', 'bar', years[1]) # Correct return type assert isinstance(years, list) assert isinstance(years[0], int) # Years 1995 through 2020 npt.assert_array_equal(result, years[1:-1])
def add_par_data( scenario: message_ix.Scenario, data: Mapping[str, pd.DataFrame], dry_run: bool = False, ): """Add `data` to `scenario`. Parameters ---------- data Dict with keys that are parameter names, and values are pd.DataFrame or other arguments dry_run : optional Only show what would be done. See also -------- strip_par_data """ total = 0 for par_name, values in data.items(): N = values.shape[0] log.info(f"{N} rows in {repr(par_name)}") log.debug(values.to_string(max_rows=5)) total += N if dry_run: continue scenario.add_par(par_name, values) return total
def test_price_duality(test_mp): years = [2020, 2025, 2030, 2040, 2050] for c in [0.25, 0.5, 0.75]: # set up a scenario for cumulative constraints scen = Scenario(test_mp, MODEL, "cum_many_tecs", version="new") model_setup(scen, years, simple_tecs=False) scen.add_cat("year", "cumulative", years) scen.add_par("bound_emission", ["World", "ghg", "all", "cumulative"], 0.5, "tCO2") scen.commit("initialize test scenario") scen.solve() # set up a new scenario with emissions taxes tax_scen = Scenario(test_mp, MODEL, "tax_many_tecs", version="new") model_setup(tax_scen, years, simple_tecs=False) for y in years: tax_scen.add_cat("year", y, y) # use emission prices from cumulative-constraint scenario as taxes taxes = scen.var("PRICE_EMISSION").rename(columns={ "year": "type_year", "lvl": "value" }) taxes["unit"] = "USD/tCO2" tax_scen.add_par("tax_emission", taxes) tax_scen.commit("initialize test scenario for taxes") tax_scen.solve() # check that emissions are close between cumulative and tax scenario filters = {"node": "World"} emiss = scen.var("EMISS", filters).set_index("year").lvl emiss_tax = tax_scen.var("EMISS", filters).set_index("year").lvl npt.assert_allclose(emiss, emiss_tax, rtol=0.20)
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 base_scen_mp(test_mp): scen = Scenario(test_mp, "model", "standard", version="new") data = {2020: 1, 2030: 2, 2040: 3} years = sorted(list(set(data.keys()))) scen.add_set("node", "node") scen.add_set("commodity", "comm") scen.add_set("level", "level") scen.add_set("year", years) scen.add_set("technology", "tec") scen.add_set("mode", "mode") output_specs = ["node", "comm", "level", "year", "year"] for (yr, value) in data.items(): scen.add_par("demand", ["node", "comm", "level", yr, "year"], 1, "GWa") scen.add_par("technical_lifetime", ["node", "tec", yr], 10, "y") tec_specs = ["node", "tec", yr, yr, "mode"] scen.add_par("output", tec_specs + output_specs, 1, "-") scen.add_par("var_cost", tec_specs + ["year"], value, "USD/GWa") scen.commit("initialize test model") scen.solve(case="original_years", quiet=True) yield scen, test_mp
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 base_scen_mp(test_mp): scen = Scenario(test_mp, 'model', 'standard', version='new') data = {2020: 1, 2030: 2, 2040: 3} years = sorted(list(set(data.keys()))) scen.add_set('node', 'node') scen.add_set('commodity', 'comm') scen.add_set('level', 'level') scen.add_set('year', years) scen.add_set('technology', 'tec') scen.add_set('mode', 'mode') output_specs = ['node', 'comm', 'level', 'year', 'year'] for (yr, value) in data.items(): scen.add_par('demand', ['node', 'comm', 'level', yr, 'year'], 1, 'GWa') scen.add_par('technical_lifetime', ['node', 'tec', yr], 10, 'y') tec_specs = ['node', 'tec', yr, yr, 'mode'] scen.add_par('output', tec_specs + output_specs, 1, '-') scen.add_par('var_cost', tec_specs + ['year'], value, 'USD/GWa') scen.commit('initialize test model') scen.solve(case='original_years') yield scen, test_mp
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_cumulative_equidistant(test_mp): scen = Scenario(test_mp, MODEL, "cum_equidistant", version="new") years = [2020, 2030, 2040] model_setup(scen, years) scen.add_cat("year", "cumulative", years) scen.add_par("bound_emission", ["World", "ghg", "all", "cumulative"], 0, "tCO2") scen.commit("initialize test scenario") scen.solve(quiet=True) # with emissions constraint, the technology with costs satisfies demand assert scen.var("OBJ")["lvl"] > 0 # under a cumulative constraint, the price must increase with the discount # rate starting from the marginal relaxation in the first year obs = scen.var("PRICE_EMISSION")["lvl"].values npt.assert_allclose(obs, [1.05 ** (y - years[0]) for y in years])
def test_per_period_equidistant(test_mp): scen = Scenario(test_mp, MODEL, 'per_period_equidistant', version='new') years = [2020, 2030, 2040] model_setup(scen, years) for y in years: scen.add_cat('year', y, y) scen.add_par('bound_emission', ['World', 'ghg', 'all', y], 0, 'tCO2') scen.commit('initialize test scenario') scen.solve() # with emissions constraint, the technology with costs satisfies demand assert scen.var('OBJ')['lvl'] > 0 # under per-year emissions constraints, the emission price must be equal to # the marginal relaxation, ie. the difference in costs between technologies npt.assert_allclose(scen.var('PRICE_EMISSION')['lvl'], [1] * 3)
def test_per_period_equidistant(test_mp): scen = Scenario(test_mp, MODEL, "per_period_equidistant", version="new") years = [2020, 2030, 2040] model_setup(scen, years) for y in years: scen.add_cat("year", y, y) scen.add_par("bound_emission", ["World", "ghg", "all", y], 0, "tCO2") scen.commit("initialize test scenario") scen.solve() # with emissions constraint, the technology with costs satisfies demand assert scen.var("OBJ")["lvl"] > 0 # under per-year emissions constraints, the emission price must be equal to # the marginal relaxation, ie. the difference in costs between technologies npt.assert_allclose(scen.var("PRICE_EMISSION")["lvl"], [1] * 3)
def test_cumulative_equidistant(test_mp): scen = Scenario(test_mp, MODEL, 'cum_equidistant', version='new') years = [2020, 2030, 2040] model_setup(scen, years) scen.add_cat('year', 'cumulative', years) scen.add_par('bound_emission', ['World', 'ghg', 'all', 'cumulative'], 0, 'tCO2') scen.commit('initialize test scenario') scen.solve() # with emissions constraint, the technology with costs satisfies demand assert scen.var('OBJ')['lvl'] > 0 # under a cumulative constraint, the price must increase with the discount # rate starting from the marginal relaxation in the first year obs = scen.var('PRICE_EMISSION')['lvl'].values npt.assert_allclose(obs, [1.05**(y - years[0]) for y in years])
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_years_active_extended2(test_mp): test_mp.add_unit("year") scen = Scenario(test_mp, **SCENARIO["dantzig"], version="new") scen.add_set("node", "foo") scen.add_set("technology", "bar") # Periods of uneven length years = [1990, 1995, 2000, 2005, 2010, 2020, 2030] # First period length is immaterial duration = [1900, 5, 5, 5, 5, 10, 10] scen.add_horizon(year=years, firstmodelyear=years[-1]) scen.add_par( "duration_period", pd.DataFrame(zip(years, duration), columns=["year", "value"]) ) # 'bar' built in period '2020' with 10-year lifetime: # - is constructed in 2011-01-01. # - by 2020-12-31, has operated 10 years. # - operates until 2020-12-31. This is within the period '2020'. # The test ensures that the correct lifetime value is retrieved, # i.e. the lifetime for the vintage 2020. scen.add_par( "technical_lifetime", pd.DataFrame( dict( node_loc="foo", technology="bar", unit="year", value=[20, 20, 20, 20, 20, 10, 10], year_vtg=years, ), ), ) result = scen.years_active("foo", "bar", years[-2]) # Correct return type assert isinstance(result, list) assert isinstance(result[0], int) # Years 2020 npt.assert_array_equal(result, years[-2])
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_vintage_and_active_years_with_lifetime(test_mp): scen = Scenario(test_mp, *msg_args, version='new') years = ['2000', '2010', '2020'] scen.add_horizon({'year': years, 'firstmodelyear': '2010'}) scen.add_set('node', 'foo') scen.add_set('technology', 'bar') scen.add_par('duration_period', pd.DataFrame({ 'unit': '???', 'value': 10, 'year': years })) scen.add_par('technical_lifetime', pd.DataFrame({ 'node_loc': 'foo', 'technology': 'bar', 'unit': '???', 'value': 20, 'year_vtg': years, })) # part is before horizon obs = scen.vintage_and_active_years(ya_args=('foo', 'bar', '2000')) exp = pd.DataFrame({'year_vtg': (2000,), 'year_act': (2010,)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order obs = scen.vintage_and_active_years(ya_args=('foo', 'bar', '2000'), in_horizon=False) exp = pd.DataFrame({'year_vtg': (2000, 2000), 'year_act': (2000, 2010)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order # fully in horizon obs = scen.vintage_and_active_years(ya_args=('foo', 'bar', '2010')) exp = pd.DataFrame({'year_vtg': (2010, 2010), 'year_act': (2010, 2020)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order # part after horizon obs = scen.vintage_and_active_years(ya_args=('foo', 'bar', '2020')) exp = pd.DataFrame({'year_vtg': (2020,), 'year_act': (2020,)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order
def test_years_active(test_mp): test_mp.add_unit("year") scen = Scenario(test_mp, **SCENARIO["dantzig"], version="new") scen.add_set("node", "foo") scen.add_set("technology", "bar") # Periods of uneven length years = [1990, 1995, 2000, 2005, 2010, 2020, 2030] # First period length is immaterial duration = [1900, 5, 5, 5, 5, 10, 10] scen.add_horizon(year=years, firstmodelyear=years[-1]) scen.add_par( "duration_period", pd.DataFrame(zip(years, duration), columns=["year", "value"]) ) # 'bar' built in period '1995' with 25-year lifetime: # - is constructed in 1991-01-01. # - by 1995-12-31, has operated 5 years. # - operates until 2015-12-31. This is within the period '2020'. scen.add_par( "technical_lifetime", pd.DataFrame( dict( node_loc="foo", technology="bar", unit="year", value=25, year_vtg=years[1], ), index=[0], ), ) result = scen.years_active("foo", "bar", years[1]) # Correct return type assert isinstance(result, list) assert isinstance(result[0], int) # Years 1995 through 2020 npt.assert_array_equal(result, years[1:-1])
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 change_tech_costs(scenario: message_ix.Scenario, techs: Union[str, List[str]], costs: Costs) -> message_ix.Scenario: if isinstance(techs, str): techs = [techs] inv = scenario.par('inv_cost') if not inv.empty: inv.loc[inv['technology'].isin(techs), 'value'] = costs.inv_cost scenario.add_par('inv_cost', inv) fix = scenario.par('fix_cost') if not fix.empty: fix.loc[fix['technology'].isin(techs), 'value'] = costs.fix_cost scenario.add_par('fix_cost', fix) var = scenario.par('var_cost') if not var.empty: var.loc[var['technology'].isin(techs), 'value'] = costs.var_cost scenario.add_par('var_cost', var) return scenario
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 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 model_generator( test_mp, comment, tec_time, demand_time, time_steps, com_dict, yr=2020, ): """ Generates a simple model with a few technologies, and a flexible number of time slices. Parameters ---------- comment : string Annotation for saving different scenarios and comparing their results. tec_time : dict A dictionary for mapping a technology to its input/output temporal levels. demand_time : dict A dictionary for mapping the total "demand" specified at a temporal level. time_steps : list of tuples Information about each time slice, packed in a tuple with three elements, including: "temporal_lvl", number of time slices, and the parent time slice. com_dict : dict A dictionary for specifying "input" and "output" commodities. yr : int, optional Model year. The default is 2020. """ # Building an empty scenario scen = Scenario(test_mp, "test_duration_time", comment, version="new") # Adding required sets scen.add_set("node", "fairyland") for c in com_dict.values(): scen.add_set("commodity", [x for x in list(c.values()) if x]) scen.add_set("level", "final") scen.add_set("year", yr) scen.add_set("type_year", yr) scen.add_set("technology", list(tec_time.keys())) scen.add_set("mode", "standard") # Adding "time" related info to the model: "lvl_temporal", "time", # "map_temporal_hierarchy", and "duration_time" map_time = {} for [tmp_lvl, number, parent] in time_steps: scen.add_set("lvl_temporal", tmp_lvl) if parent == "year": times = [tmp_lvl[0] + "-" + str(x + 1) for x in range(number)] else: times = [ p + "_" + tmp_lvl[0] + "-" + str(x + 1) for (p, x) in product(map_time[parent], range(number)) ] map_time[tmp_lvl] = times scen.add_set("time", times) # Adding "map_temporal_hierarchy" and "duration_time" for h in times: if parent == "year": p = "year" else: p = h.split("_" + tmp_lvl[0])[0] # Temporal hierarchy (order: temporal level, time, parent time) scen.add_set("map_temporal_hierarchy", [tmp_lvl, h, p]) # Duration time is relative to the duration of the parent temporal level dur_parent = float(scen.par("duration_time", {"time": p})["value"]) scen.add_par("duration_time", [h], dur_parent / number, "-") # Adding "demand" at a temporal level (total demand divided by the number of # time slices in that temporal level) for tmp_lvl, value in demand_time.items(): times = scen.set("map_temporal_hierarchy", {"lvl_temporal": tmp_lvl})["time"] for h in times: scen.add_par( "demand", ["fairyland", "electr", "final", yr, h], value / len(times), "GWa", ) # Adding "input" and "output" parameters of technologies for tec, [tmp_lvl_in, tmp_lvl_out] in tec_time.items(): times_in = scen.set("map_temporal_hierarchy", {"lvl_temporal": tmp_lvl_in})[ "time" ] times_out = scen.set("map_temporal_hierarchy", {"lvl_temporal": tmp_lvl_out})[ "time" ] # If technology is linking two different temporal levels if tmp_lvl_in != tmp_lvl_out: time_pairs = product(times_in, times_out) else: time_pairs = zip(times_in, times_out) # Configuring data for "time_origin" and "time" in "input" for (h_in, h_act) in time_pairs: # "input" inp = com_dict[tec]["input"] if inp: inp_spec = [yr, yr, "standard", "fairyland", inp, "final", h_act, h_in] scen.add_par("input", ["fairyland", tec] + inp_spec, 1, "-") # "output" for h in times_out: out = com_dict[tec]["output"] out_spec = [yr, yr, "standard", "fairyland", out, "final", h, h] scen.add_par("output", ["fairyland", tec] + out_spec, 1, "-") # Committing scen.commit("scenario was set up.") # Testing if the model solves in GAMS scen.solve(case=comment) # Testing if sum of "duration_time" is almost 1 for tmp_lvl in scen.set("lvl_temporal"): times = scen.set("map_temporal_hierarchy", {"lvl_temporal": tmp_lvl})[ "time" ].to_list() assert ( abs(sum(scen.par("duration_time", {"time": times})["value"]) - 1.0) < 1e-12 )
def make_austria(mp, solve=False, quiet=True): """Return an :class:`message_ix.Scenario` for the Austrian energy system. This is the same model used in the ``austria.ipynb`` tutorial. Parameters ---------- mp : ixmp.Platform Platform on which to create the scenario. solve : bool, optional If True, the scenario is solved. """ mp.add_unit("USD/kW") mp.add_unit("MtCO2") mp.add_unit("tCO2/kWa") scen = Scenario( mp, version="new", **SCENARIO["austria"], annotation= "A stylized energy system model for illustration and testing", ) # Structure year = dict(all=list(range(2010, 2041, 10))) scen.add_horizon(year=year["all"]) year_df = scen.vintage_and_active_years() year["vtg"] = year_df["year_vtg"] year["act"] = year_df["year_act"] country = "Austria" scen.add_spatial_sets({"country": country}) sets = dict( commodity=["electricity", "light", "other_electricity"], emission=["CO2"], level=["secondary", "final", "useful"], mode=["standard"], ) sets["technology"] = AUSTRIA_TECH.index.to_list() plants = sets["technology"][:7] lights = sets["technology"][10:] for name, values in sets.items(): scen.add_set(name, values) scen.add_cat("emission", "GHGs", "CO2") # Parameters name = "interestrate" scen.add_par(name, make_df(name, year=year["all"], value=0.05, unit="-")) 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=year["act"], year_vtg=year["vtg"], year=year["all"], ) gdp_profile = np.array([1.0, 1.21631, 1.4108, 1.63746]) beta = 0.7 demand_profile = gdp_profile**beta # From IEA statistics, in GW·h, converted to GW·a base_annual_demand = dict(other_electricity=55209.0 / 8760, light=6134.0 / 8760) name = "demand" common.update(level="useful", unit="GWa") for c, base in base_annual_demand.items(): scen.add_par( name, make_df(name, **common, commodity=c, value=base * demand_profile)) common.pop("level") # input, output common.update(unit="-") for name, (tec, info) in product(("input", "output"), AUSTRIA_TECH.iterrows()): value = info[f"{name}_value"] if np.isnan(value): continue scen.add_par( name, make_df( name, **common, technology=tec, commodity=info[f"{name}_commodity"], level=info[f"{name}_level"], value=value, ), ) data = AUSTRIA_PAR # Convert GW·h to GW·a data["activity"] = data["activity"] / 8760.0 # Convert USD / MW·h to USD / GW·a data["var_cost"] = data["var_cost"] * 8760.0 / 1e3 # Convert t / MW·h to t / kw·a data["emission_factor"] = data["emission_factor"] * 8760.0 / 1e3 def _add(): """Add using values from the calling scope.""" scen.add_par(name, make_df(name, **common, technology=tec, value=value)) name = "capacity_factor" for tec, value in data[name].dropna().items(): _add() name = "technical_lifetime" common.update(year_vtg=year["all"], unit="y") for tec, value in data[name].dropna().items(): _add() name = "growth_activity_up" common.update(year_act=year["all"][1:], unit="%") value = 0.05 for tec in plants + lights: _add() name = "initial_activity_up" common.update(year_act=year["all"][1:], unit="%") value = 0.01 * base_annual_demand["light"] * demand_profile[1:] for tec in lights: _add() # bound_activity_lo, bound_activity_up common.update(year_act=year["all"][0], unit="GWa") for (tec, value), kind in product(data["activity"].dropna().items(), ("up", "lo")): name = f"bound_activity_{kind}" _add() name = "bound_activity_up" common.update(year_act=year["all"][1:]) for tec in ("bio_ppl", "hydro_ppl", "import"): value = data.loc[tec, "activity"] _add() name = "bound_new_capacity_up" common.update(year_vtg=year["all"][0], unit="GW") for tec, value in (data["activity"] / data["capacity_factor"]).dropna().items(): _add() name = "inv_cost" common.update(dict(year_vtg=year["all"], unit="USD/kW")) for tec, value in data[name].dropna().items(): _add() # fix_cost, var_cost common.update( dict(year_vtg=year["vtg"], year_act=year["act"], unit="USD/kWa")) for name in ("fix_cost", "var_cost"): for tec, value in data[name].dropna().items(): _add() name = "emission_factor" common.update(year_vtg=year["vtg"], year_act=year["act"], unit="tCO2/kWa", emission="CO2") for tec, value in data[name].dropna().items(): _add() scen.commit("Initial commit for Austria model") scen.set_as_default() if solve: scen.solve(quiet=quiet) return scen
def test_vintage_and_active_years(test_mp): scen = Scenario(test_mp, **SCENARIO["dantzig"], version="new") years = [2000, 2010, 2020] scen.add_horizon(year=years, firstmodelyear=2010) obs = scen.vintage_and_active_years() exp = pd.DataFrame( { "year_vtg": (2000, 2000, 2010, 2010, 2020), "year_act": (2010, 2020, 2010, 2020, 2020), } ) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order # Add a technology, its lifetime, and period durations scen.add_set("node", "foo") scen.add_set("technology", "bar") scen.add_par( "duration_period", pd.DataFrame({"unit": "???", "value": 10, "year": years}) ) scen.add_par( "technical_lifetime", pd.DataFrame( { "node_loc": "foo", "technology": "bar", "unit": "???", "value": 20, "year_vtg": years, } ), ) # part is before horizon obs = scen.vintage_and_active_years(ya_args=("foo", "bar", "2000")) exp = pd.DataFrame({"year_vtg": (2000,), "year_act": (2010,)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order obs = scen.vintage_and_active_years( ya_args=("foo", "bar", "2000"), in_horizon=False ) exp = pd.DataFrame({"year_vtg": (2000, 2000), "year_act": (2000, 2010)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order # fully in horizon obs = scen.vintage_and_active_years(ya_args=("foo", "bar", "2010")) exp = pd.DataFrame({"year_vtg": (2010, 2010), "year_act": (2010, 2020)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order # part after horizon obs = scen.vintage_and_active_years(ya_args=("foo", "bar", "2020")) exp = pd.DataFrame({"year_vtg": (2020,), "year_act": (2020,)}) pdt.assert_frame_equal(exp, obs, check_like=True) # ignore col order # Advance the first model year scen.add_cat("year", "firstmodelyear", years[-1], is_unique=True) # Empty data frame: only 2000 and 2010 valid year_act for this node/tec; # but both are before the first model year obs = scen.vintage_and_active_years( ya_args=("foo", "bar", years[0]), in_horizon=True ) pdt.assert_frame_equal(pd.DataFrame(columns=["year_vtg", "year_act"]), obs) # Exception is raised for incorrect arguments with pytest.raises(ValueError, match="3 arguments are required if using `ya_args`"): scen.vintage_and_active_years(ya_args=("foo", "bar"))