Пример #1
0
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'])
Пример #2
0
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'])
Пример #3
0
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')]
Пример #4
0
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")]
Пример #5
0
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')]
Пример #6
0
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()"),
    )