def test_solve_legacy_scenario(test_legacy_mp): scen = Scenario(test_legacy_mp, *msg_args) exp = scen.var('OBJ')['lvl'] # solve scenario, assert that the new objective value is close to previous scen.remove_solution() scen.solve() assert np.isclose(exp, scen.var('OBJ')['lvl'])
def test_solve_legacy_scenario(tmp_path, test_data_path): db_path = create_test_platform(tmp_path, test_data_path, 'legacy') mp = Platform(backend='jdbc', driver='hsqldb', path=db_path) scen = Scenario(mp, model='canning problem (MESSAGE scheme)', scenario='standard') exp = scen.var('OBJ')['lvl'] # solve scenario, assert that the new objective value is close to previous scen.remove_solution() scen.solve() assert np.isclose(exp, scen.var('OBJ')['lvl'])
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 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 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 apply_spec( scenario: Scenario, spec: Mapping[str, ScenarioInfo], data: Callable = None, **options, ): """Apply `spec` to `scenario`. Parameters ---------- spec A 'specification': :class:`dict` with 'require', 'remove', and 'add' keys and :class:`.ScenarioInfo` objects as values. data : callable, optional Function to add data to `scenario`. `data` can either manipulate the scenario directly, or return a :class:`dict` compatible with :func:`.add_par_data`. Other parameters ---------------- dry_run : bool Don't modify `scenario`; only show what would be done. Default :obj:`False`. Exceptions will still be raised if the elements from ``spec['required']`` are missing; this serves as a check that the scenario has the required features for applying the spec. fast : bool Do not remove existing parameter data; increases speed on large scenarios. quiet : bool Only show log messages at level ``ERROR`` and higher. If :obj:`False` (default), show log messages at level ``DEBUG`` and higher. message : str Commit message. See also -------- .add_par_data .strip_par_data .Code .ScenarioInfo """ dry_run = options.get("dry_run", False) log.setLevel(logging.ERROR if options.get("quiet", False) else logging.DEBUG) if not dry_run: try: scenario.remove_solution() except ValueError: pass maybe_check_out(scenario) dump: Dict[str, pd.DataFrame] = {} # Removed data for set_name in scenario.set_list(): # Check whether this set is mentioned at all in the spec if 0 == sum(map(lambda info: len(info.set[set_name]), spec.values())): # Not mentioned; don't do anything continue log.info(f"Set {repr(set_name)}") # Base contents of the set base_set = scenario.set(set_name) # Unpack a multi-dimensional/indexed set to a list of tuples base = ( list(base_set.itertuples(index=False)) if isinstance(base_set, pd.DataFrame) else base_set.tolist() ) log.info(f" {len(base)} elements") # log.debug(', '.join(map(repr, base))) # All elements; verbose # Check for required elements require = spec["require"].set[set_name] log.info(f" Check {len(require)} required elements") # Raise an exception about the first missing element missing = list(filter(lambda e: e not in base, require)) if len(missing): log.error(f" {len(missing)} elements not found: {repr(missing)}") raise ValueError # Remove elements and associated parameter values remove = spec["remove"].set[set_name] for element in remove: msg = f"{repr(element)} and associated parameter elements" if options.get("fast", False): log.info(f" Skip removing {msg} (fast=True)") continue log.info(f" Remove {msg}") strip_par_data(scenario, set_name, element, dry_run=dry_run, dump=dump) # Add elements add = [] if dry_run else spec["add"].set[set_name] for element in add: scenario.add_set( set_name, element.id if isinstance(element, Code) else element, ) if len(add): log.info(f" Add {len(add)} element(s)") log.debug(" " + ellipsize(add)) log.info(" ---") N_removed = sum(len(d) for d in dump.values()) log.info(f"{N_removed} parameter elements removed") # Add units to the Platform before adding data for unit in spec["add"].set["unit"]: unit = unit if isinstance(unit, Code) else Code(id=unit, name=unit) log.info(f"Add unit {repr(unit)}") scenario.platform.add_unit(unit.id, comment=str(unit.name)) # Add data if callable(data): result = data(scenario, dry_run=dry_run) if result: # `data` function returned some data; use add_par_data() add_par_data(scenario, result, dry_run=dry_run) # Finalize log.info("Commit results.") maybe_commit( scenario, condition=not dry_run, message=options.get("message", f"{__name__}.apply_spec()"), )