def check_solution(scen: Scenario) -> None:
    """Perform several assertions about the solution of `scen`."""
    # Reading "ACT" and "CAP" from the solution
    act = scen.var("ACT").set_index(["technology", "time"])
    cap = scen.var("CAP").set_index(["technology"])

    # 1) ACT is zero when capacity factor is zero
    cf = scen.par("capacity_factor").set_index(["technology", "time"])
    cf_zero = cf.loc[cf["value"] == 0]
    for i in cf_zero.index:
        assert act.loc[i, "lvl"] == 0

    # 2) CAP is correctly calculated based on ACT and capacity_factor
    for i in act.loc[act["lvl"] > 0].index:
        # Correct ACT based on duration of each time slice
        duration = float(scen.par("duration_time", {"time": i[1]})["value"])
        act.loc[i, "duration-corrected"] = act.loc[i, "lvl"] / duration
        # Divide by (non-zero) capacity factor
        act.loc[i, "cf-corrected"] = act.loc[i, "duration-corrected"] / float(
            cf.loc[i, "value"]
        )
    act = act.fillna(0).reset_index().set_index(["technology"])
    # CAP = max("ACT" / "duration_time" / "capcity_factor")
    for i in cap.index:
        assert max(act.loc[i, "cf-corrected"]) == float(cap.loc[i, "lvl"])
Beispiel #2
0
def group_data(var: str, results: message_ix.Scenario) -> pd.DataFrame:
    # TODO: add as variable
    units = {
        'ACT': 'GWa/a',
        'CAP': 'GW',
        'CAP_NEW': 'GW/a',
        'EMISS': 'MtCO2/a'
    }
    historicals = {
        'ACT': 'historical_activity',
        'CAP_NEW': 'historical_new_capacity'
    }
    df = results.var(var)
    df = df.loc[df.lvl != 0]

    if var in historicals:
        df_hist = results.par(historicals[var])
        df_hist = df_hist.rename(columns={'value': 'lvl'})
        df_hist = df_hist.loc[df_hist.lvl != 0]
        df = df.append(df_hist, sort=False)

    # group Variable by technology and reshape to timeseries format
    if 'year_act' in df.columns:
        df = df[['node_loc', 'technology', 'year_act', 'lvl']]
        df = df.groupby(['node_loc', 'year_act', 'technology'],
                        as_index=False).sum().copy()
        df = df.rename(columns={'node_loc': 'node', 'year_act': 'year'})

    elif var == 'EMISS':
        df_hist = results.par('historical_emission')
        df_hist = df_hist.rename(columns={
            'type_emission': 'emission',
            'type_year': 'year',
            'value': 'lvl'
        })
        df_hist = df_hist.loc[df_hist.lvl != 0]
        df = df.append(df_hist, sort=False)
        df['year'] = df.year.astype(int)

        df = df[['node', 'emission', 'year', 'lvl']]
        df = df.groupby(['node', 'year', 'emission'],
                        as_index=False).sum().copy()
    else:
        df = df[['node_loc', 'technology', 'year_vtg', 'lvl']]
        df = df.groupby(['node_loc', 'year_vtg', 'technology'],
                        as_index=False).sum().copy()
        df = df.rename(columns={'node_loc': 'node', 'year_vtg': 'year'})

    df['unit'] = units[var]
    df['variable'] = var

    return df
Beispiel #3
0
def techs_same_lvl_com(scenario: message_ix.Scenario, level: str,
                       commodity: str) -> List[str]:
    scenario.commodity = commodity
    output = scenario.par('output')
    _techs = output[(output.level == level) & (output.commodity == commodity)]
    _techs = _techs[~_techs.technology.str.contains('slack')]
    techs = sorted(list(set(_techs.technology.values)))
    return techs
Beispiel #4
0
def test_excel_read_write(test_mp):
    fname = 'test_excel_read_write.xlsx'

    scen1 = Scenario(test_mp, *msg_args)
    scen1.to_excel(fname)

    scen2 = Scenario(test_mp, model='foo', scenario='bar', version='new')
    scen2.read_excel(fname)

    exp = scen1.par('input')
    obs = scen2.par('input')
    pdt.assert_frame_equal(exp, obs)

    scen2.commit('foo')  # must be checked in
    scen2.solve()
    assert np.isclose(scen2.var('OBJ')['lvl'], 153.675)

    os.remove(fname)
Beispiel #5
0
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'])
Beispiel #6
0
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"])
Beispiel #7
0
def test_rename_technology(test_mp):
    scen = Scenario(test_mp, *msg_args)
    assert scen.par('output')['technology'].isin(['canning_plant']).any()

    clone = scen.clone('foo', 'bar')
    clone.rename('technology', {'canning_plant': 'foo_bar'})
    assert not clone.par('output')['technology'].isin(['canning_plant']).any()
    assert clone.par('output')['technology'].isin(['foo_bar']).any()
    clone.solve()
    assert np.isclose(clone.var('OBJ')['lvl'], 153.675)
Beispiel #8
0
def test_rename_technology_no_rm(test_mp):
    scen = Scenario(test_mp, *msg_args)
    assert scen.par('output')['technology'].isin(['canning_plant']).any()

    clone = scen.clone('foo', 'bar')
    # also test if already checked out
    clone.check_out()

    clone.rename('technology', {'canning_plant': 'foo_bar'}, keep=True)
    assert clone.par('output')['technology'].isin(['canning_plant']).any()
    assert clone.par('output')['technology'].isin(['foo_bar']).any()
Beispiel #9
0
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 check_solution(scen: Scenario) -> None:
    # Reading "ACT" greater than zero from the solution
    act = scen.var("ACT")[scen.var("ACT")["lvl"] > 0]

    # 1) Testing if linkage between "gas_ppl" with "gas_supply" technology is made
    assert "gas_supply" in set(act["technology"])

    # 2) Testing if "ACT" of "gas_ppl" is correct (with respect to "duration_time_rel")
    # i.e., sum("ACT" of "gas_ppl") = sum("ACT" of "gas_supply") = sum("demand")
    assert (sum(act.loc[act["technology"] == "gas_ppl"]["lvl"]) == sum(
        act.loc[act["technology"] == "gas_supply"]["lvl"]) == sum(
            scen.par("demand")["value"]))

    # 3) Test if "CAP" of "gas_ppl" is correctly calculated based on "ACT"
    # i.e., "CAP" = max("ACT" / "duration_time")
    for h in act.loc[act["technology"] == "gas_ppl"]["time"]:
        act.loc[(act["time"] == h) & (act["technology"] == "gas_ppl"),
                "capacity-corrected", ] = act["lvl"] / float(
                    scen.par("duration_time", {"time": h})["value"])
    assert max(act.loc[act["technology"] == "gas_ppl",
                       "capacity-corrected"]) == float(
                           scen.var("CAP", {"technology": "gas_ppl"})["lvl"])
Beispiel #11
0
def test_add_horizon_repeat(test_mp, caplog):
    """add_horizon() does not handle scenarios with existing years."""
    scen = Scenario(test_mp, **SCENARIO["dantzig"], version="new")

    # Create a base scenario
    scen.add_horizon([2010, 2020, 2030])
    npt.assert_array_equal([10, 10, 10], scen.par("duration_period")["value"])

    with pytest.raises(
        ValueError,
        match=r"Scenario has year=\[2010, 2020, 2030\] and related values",
    ):
        scen.add_horizon([2015, 2020, 2025], firstmodelyear=2010)
Beispiel #12
0
def test_rename_technology(test_mp):
    scen = Scenario(test_mp, *msg_args)
    scen.solve()
    assert scen.par('output')['technology'].isin(['canning_plant']).any()
    exp_obj = scen.var('OBJ')['lvl']

    clone = scen.clone('foo', 'bar', keep_solution=False)
    clone.rename('technology', {'canning_plant': 'foo_bar'})
    assert not clone.par('output')['technology'].isin(['canning_plant']).any()
    assert clone.par('output')['technology'].isin(['foo_bar']).any()
    clone.solve()
    obs_obj = clone.var('OBJ')['lvl']
    assert obs_obj == exp_obj
Beispiel #13
0
def test_add_horizon(test_mp, args, kwargs, exp):
    scen = Scenario(test_mp, **SCENARIO['dantzig'], version='new')

    # Call completes successfully
    if isinstance(args[0], dict):
        with pytest.warns(
                DeprecationWarning,
                match=(r"dict\(\) argument to add_horizon\(\); use year= and "
                       "firstmodelyear=")):
            scen.add_horizon(*args, **kwargs)
    else:
        scen.add_horizon(*args, **kwargs)

    # Sets and parameters have the expected contents
    npt.assert_array_equal(exp["year"], scen.set("year"))
    npt.assert_array_equal(exp["fmy"], scen.cat("year", "firstmodelyear"))
    npt.assert_array_equal(exp["dp"], scen.par("duration_period")["value"])
Beispiel #14
0
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)
Beispiel #16
0
def test_multi_db_run(tmpdir):
    # create a new instance of the transport problem and solve it
    mp1 = Platform(driver="hsqldb", path=tmpdir / "mp1")
    scen1 = make_dantzig(mp1, solve=True)

    mp2 = Platform(driver="hsqldb", path=tmpdir / "mp2")
    # add other unit to make sure that the mapping is correct during clone
    mp2.add_unit("wrong_unit")
    mp2.add_region("wrong_region", "country")

    # check that cloning across platforms must copy the full solution
    dest = dict(platform=mp2)
    pytest.raises(NotImplementedError,
                  scen1.clone,
                  keep_solution=False,
                  **dest)
    pytest.raises(NotImplementedError,
                  scen1.clone,
                  shift_first_model_year=1964,
                  **dest)

    # clone solved model across platforms (with default settings)
    scen1.clone(platform=mp2, keep_solution=True)

    # close the db to ensure that data and solution of the clone are saved
    mp2.close_db()
    del mp2

    # reopen the connection to the second platform and reload scenario
    _mp2 = Platform(driver="hsqldb", path=tmpdir / "mp2")
    scen2 = Scenario(_mp2, **SCENARIO["dantzig"])
    assert_multi_db(mp1, _mp2)

    # check that sets, variables and parameter were copied correctly
    npt.assert_array_equal(scen1.set("node"), scen2.set("node"))
    scen2.firstmodelyear == 1963
    assert_frame_equal(scen1.par("var_cost"), scen2.par("var_cost"))
    assert np.isclose(scen2.var("OBJ")["lvl"], 153.675)
    assert_frame_equal(scen1.var("ACT"), scen2.var("ACT"))

    # check that custom unit, region and timeseries are migrated correctly
    assert_frame_equal(scen2.timeseries(iamc=True), TS_DF)
Beispiel #17
0
def test_multi_db_run(tmpdir):
    # create a new instance of the transport problem and solve it
    mp1 = Platform(tmpdir / 'mp1', dbtype='HSQLDB')
    scen1 = make_dantzig(mp1, solve=True)

    mp2 = Platform(tmpdir / 'mp2', dbtype='HSQLDB')
    # add other unit to make sure that the mapping is correct during clone
    mp2.add_unit('wrong_unit')
    mp2.add_region('wrong_region', 'country')

    # check that cloning across platforms must copy the full solution
    dest = dict(platform=mp2)
    pytest.raises(ValueError, scen1.clone, keep_solution=False, **dest)
    pytest.raises(ValueError, scen1.clone, shift_first_model_year=1964, **dest)

    # clone solved model across platforms (with default settings)
    scen1.clone(platform=mp2, keep_solution=True)

    # close the db to ensure that data and solution of the clone are saved
    mp2.close_db()
    del mp2

    # reopen the connection to the second platform and reload scenario
    _mp2 = Platform(tmpdir / 'mp2', dbtype='HSQLDB')
    scen2 = Scenario(_mp2, **models['dantzig'])
    assert_multi_db(mp1, _mp2)

    # check that sets, variables and parameter were copied correctly
    npt.assert_array_equal(scen1.set('node'), scen2.set('node'))
    scen2.firstmodelyear == 1963
    pdt.assert_frame_equal(scen1.par('var_cost'), scen2.par('var_cost'))
    assert np.isclose(scen2.var('OBJ')['lvl'], 153.675)
    pdt.assert_frame_equal(scen1.var('ACT'), scen2.var('ACT'))

    # check that custom unit, region and timeseries are migrated correctly
    pdt.assert_frame_equal(scen2.timeseries(iamc=True), TS_DF)
Beispiel #18
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')]
Beispiel #19
0
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 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')]