diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b77d7dd..75233c7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ Changelog Version 1.1.4 ============= - Add result calculations for ``DiscountedCapitalInvestment``, ``DiscountedCostByTechnology``, and ``DiscountedOperationalCost`` +- Add result calculations for ``CapitalInvestmentStorage``, ``DiscountedCapitalInvestmentStorage``, ``DiscountedCostByStorage`` and ``DiscountedSalvageValueStorage`` +- Correct ``TotalDiscountedCost`` calculation to account for storage costs Version 1.1.3 =========================== diff --git a/src/otoole/preprocess/config.yaml b/src/otoole/preprocess/config.yaml index dc309cd..ba1b9db 100644 --- a/src/otoole/preprocess/config.yaml +++ b/src/otoole/preprocess/config.yaml @@ -332,6 +332,11 @@ AnnualVariableOperatingCost: type: result dtype: float default: 0 +CapitalInvestmentStorage: + indices: [REGION, STORAGE, YEAR] + type: result + dtype: float + default: 0 CapitalInvestment: indices: [REGION, TECHNOLOGY, YEAR] type: result @@ -342,11 +347,22 @@ Demand: type: result dtype: float default: 0 +DiscountedCapitalInvestmentStorage: + short_name: DiscountedCapitalInvestStorage + indices: [REGION, STORAGE, YEAR] + type: result + dtype: float + default: 0 DiscountedCapitalInvestment: indices: [REGION, TECHNOLOGY, YEAR] type: result dtype: float default: 0 +DiscountedCostByStorage: + indices: [REGION, STORAGE, YEAR] + type: result + dtype: float + default: 0 DiscountedCostByTechnology: indices: [REGION, TECHNOLOGY, YEAR] type: result @@ -362,6 +378,11 @@ DiscountedSalvageValue: type: result dtype: float default: 0 +DiscountedSalvageValueStorage: + indices: [REGION, STORAGE, YEAR] + type: result + dtype: float + default: 0 DiscountedTechnologyEmissionsPenalty: short_name: DiscountedTechEmissionsPenalty indices: [REGION, TECHNOLOGY, YEAR] diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index dc5ae29..acf1b56 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -43,10 +43,14 @@ def __init__( "AnnualTechnologyEmissionByMode": self.annual_technology_emission_by_mode, "AnnualVariableOperatingCost": self.annual_variable_operating_cost, "CapitalInvestment": self.capital_investment, + "CapitalInvestmentStorage": self.capital_investment_storage, "Demand": self.demand, "DiscountedCapitalInvestment": self.discounted_capital_investment, + "DiscountedCapitalInvestmentStorage": self.discounted_capital_investment_storage, + "DiscountedCostByStorage": self.discounted_storage_cost, "DiscountedCostByTechnology": self.discounted_technology_cost, "DiscountedOperationalCost": self.discounted_operational_cost, + "DiscountedSalvageValueStorage": self.discounted_salvage_value_storage, "DiscountedTechnologyEmissionsPenalty": self.discounted_tech_emis_pen, "ProductionByTechnology": self.production_by_technology, "ProductionByTechnologyAnnual": self.production_by_technology_annual, @@ -347,6 +351,35 @@ def capital_investment(self) -> pd.DataFrame: return data[(data != 0).all(1)] + def capital_investment_storage(self) -> pd.DataFrame: + """CapitalInvestmentStorage + + Notes + ----- + From the formulation:: + + r~REGION, s~STORAGE, y~YEAR, + CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y] + ~VALUE; + """ + try: + capital_cost_storage = self["CapitalCostStorage"] + new_capacity_storage = self["NewStorageCapacity"] + + except KeyError as ex: + raise KeyError(self._msg("CapitalInvestmentStorage", str(ex))) + + capital_investment_storage = capital_cost_storage.mul( + new_capacity_storage, fill_value=0 + ) + + data = capital_investment_storage + + if not data.empty: + data = data.groupby(by=["REGION", "STORAGE", "YEAR"]).sum() + + return data[(data != 0).all(1)] + def demand(self) -> pd.DataFrame: """Demand @@ -440,6 +473,54 @@ def discounted_capital_investment(self) -> pd.DataFrame: return data[(data != 0).all(1)] + def discounted_capital_investment_storage(self) -> pd.DataFrame: + """DiscountedCapitalInvestmentStorage + + Notes + ----- + From the formulation:: + + r~REGION, s~STORAGE, y~YEAR, + DiscountedCapitalInvestmentStorage[r,s,y] := + CapitalCostStorage[r,s,y] * NewCapacity[r,t,y] / DiscountFactor[r,y] + + Alternatively, can be written as:: + + r~REGION, s~STORAGE, y~YEAR, + DiscountedCapitalInvestmentStorage[r,s,y] := UndiscountedCapitalInvestmentStorage[r,s,y] / DiscountFactor[r,y] + + """ + + try: + discount_rate_storage = self["DiscountRateStorage"] + year_df = self["YEAR"].copy(deep=True) + region_df = self["REGION"].copy(deep=True) + + years = year_df["VALUE"].tolist() + regions = region_df["VALUE"].tolist() + capital_investment_storage = self["CapitalInvestmentStorage"] + + storages = self.get_unique_values_from_index( + [ + capital_investment_storage, + ], + "STORAGE", + ) + + except KeyError as ex: + raise KeyError(self._msg("DiscountedCapitalInvestmentStorage", str(ex))) + + dfs = discount_factor_storage( + regions, storages, years, discount_rate_storage, 0.0 + ) + + data = capital_investment_storage.div(dfs, fill_value=0.0) + + if not data.empty: + data = data.groupby(by=["REGION", "STORAGE", "YEAR"]).sum() + + return data[(data != 0).all(1)] + def discounted_operational_cost(self) -> pd.DataFrame: """DiscountedOperationalCosts @@ -510,6 +591,88 @@ def discounted_operational_cost(self) -> pd.DataFrame: return data[(data != 0).all(1)] + def discounted_storage_cost(self) -> pd.DataFrame: + """TotalDiscountedCostByStorage + + Notes + ----- + From the formulation:: + + r~REGION, s~STORAGE, y~YEAR, + TotalDiscountedStorageCost[r,s,y]:= + ( + CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y] / DiscountFactorStorage[r,s,y] - + SalvageValueStorage[r,s,y] / + ( + (1+DiscountRateStorage[r,s])^(max{yy in YEAR} max(yy)-min{yy in YEAR} min(yy)+1)) + ) + ) + + Alternatively, can be written as:: + + r~REGION, s~STORAGE, y~YEAR, + TotalDiscountedStorageCost[r,s,y]:= + DiscountedCapitalInvestmentStorage[r,s,y] - DiscountedSalvageValueStorage[r,s,y] + """ + + try: + discounted_capital_investment_storage = self[ + "DiscountedCapitalInvestmentStorage" + ] + discounted_salvage_value_storage = self["DiscountedSalvageValueStorage"] + + except KeyError as ex: + raise KeyError(self._msg("TotalDiscountedCostByStorage", str(ex))) + + discounted_storage_costs = discounted_capital_investment_storage.sub( + discounted_salvage_value_storage, fill_value=0.0 + ) + + data = discounted_storage_costs + + if not data.empty: + data = data.groupby(by=["REGION", "STORAGE", "YEAR"]).sum() + return data[(data != 0).all(1)] + + def discounted_salvage_value_storage(self) -> pd.DataFrame: + """DiscountedSalvageValueStorage + + Notes + ----- + From the formulation:: + + DiscountedSalvageValueStorage[r,s,y] = SalvageValueStorage[r,s,y] / ((1+DiscountRateStorage[r,s])^(max{yy in YEAR} max(yy)-min{yy in YEAR} min(yy)+1))) + """ + + try: + salvage_value_storage = self["SalvageValueStorage"] + discount_rate_storage = self["DiscountRateStorage"] + year_df = self["YEAR"].copy(deep=True) + region_df = self["REGION"].copy(deep=True) + storage_df = self["STORAGE"].copy(deep=True) + + years = year_df["VALUE"].tolist() + regions = region_df["VALUE"].tolist() + storages = storage_df["VALUE"].tolist() + + except KeyError as ex: + raise KeyError(self._msg("DiscountedSalvageValueStorage", str(ex))) + + df_storage_salvage = discount_factor_storage_salvage( + regions, storages, years, discount_rate_storage + ) + + discounted_salvage_value_storage = salvage_value_storage.div( + df_storage_salvage, fill_value=0 + ) + + data = discounted_salvage_value_storage + + if not data.empty: + data = data.groupby(by=["REGION", "STORAGE", "YEAR"]).sum() + + return data[(data != 0).all(1)] + def discounted_technology_cost(self) -> pd.DataFrame: """TotalDiscountedCostByTechnology @@ -774,20 +937,52 @@ def total_discounted_cost(self) -> pd.DataFrame: / (DiscountFactorMid[r,y]) + CapitalCost[r,t,y] * NewCapacity[r,t,y] * CapitalRecoveryFactor[r,t] * PvAnnuity[r,t] / (DiscountFactor[r,y]) + DiscountedTechnologyEmissionsPenalty[r,t,y] - DiscountedSalvageValue[r,t,y]) - + sum{s in STORAGE} + + sum{r in REGION, s in STORAGE, y in YEAR} ( - CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y] / (DiscountFactorStorage[r,s,y]) - - CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y] / (DiscountFactorStorage[r,s,y] + CapitalCostStorage[r,s,y] * NewStorageCapacity[r,s,y] / (DiscountFactorStorage[r,s,y] + - SalvageValueStorage[r,s,y] / ((1+DiscountRateStorage[r,s])^(max{yy in YEAR} max(yy)-min{yy in YEAR} min(yy)+1)) ) ) ~VALUE; + + Alternatively, can be written as:: + + r~REGION, y~YEAR, + TotalDiscountedCost[r,y] := + sum{t in TECHNOLOGY} TotalDiscountedCostByTechnology[r,t,y] + sum{s in STORAGE} TotalDiscountedStorageCost[r,s,y] """ try: discounted_cost_by_technology = self["DiscountedCostByTechnology"] - except KeyError as ex: raise KeyError(self._msg("TotalDiscountedCost", str(ex))) - data = discounted_cost_by_technology + discounted_tech = ( + discounted_cost_by_technology.droplevel("TECHNOLOGY") + .reset_index() + .groupby(["REGION", "YEAR"]) + .sum() + ) + + try: + discounted_cost_by_storage = self["DiscountedCostByStorage"] + + discounted_storage = ( + discounted_cost_by_storage.droplevel("STORAGE") + .reset_index() + .groupby(["REGION", "YEAR"]) + .sum() + ) + except KeyError as ex: # storage not always included + LOGGER.debug(ex) + + discounted_storage = pd.DataFrame( + columns=["REGION", "YEAR", "VALUE"] + ).set_index(["REGION", "YEAR"]) + + total_discounted_cost = discounted_tech.add( + discounted_storage, fill_value=0 + ).astype(float) + + data = total_discounted_cost if not data.empty: data = data.groupby(by=["REGION", "YEAR"]).sum() @@ -1090,3 +1285,51 @@ def discount_factor_storage( return pd.DataFrame( [], columns=["REGION", "STORAGE", "YEAR", "VALUE"] ).set_index(["REGION", "STORAGE", "YEAR"]) + + +def discount_factor_storage_salvage( + regions: List, + storages: List, + years: List, + discount_rate_storage: pd.DataFrame, +) -> pd.DataFrame: + """Discount Factor used for salvage value claculations + + Arguments + --------- + regions: list + storages: list + years: list + discount_rate_storage: pd.DataFrame + + Notes + ----- + From the formulation:: + + ((1+DiscountRateStorage[r,s])^(1+max{yy in YEAR} max(yy)-min{yy in YEAR} min(yy))); + """ + + if discount_rate_storage.empty: + raise ValueError( + "Cannot calculate discount_factor_storage_salvage due to missing discount rate" + ) + + if regions and years: + index = pd.MultiIndex.from_product( + [regions, storages, years], names=["REGION", "STORAGE", "YEAR"] + ) + discount_fac_storage_salv = discount_rate_storage.reindex(index) + + max_year = max(years) + min_year = min(years) + + discount_fac_storage_salv["VALUE"] = (1 + discount_fac_storage_salv).pow( + 1 + max_year - min_year + ) + + return discount_fac_storage_salv + + else: + return pd.DataFrame( + [], columns=["REGION", "STORAGE", "YEAR", "VALUE"] + ).set_index(["REGION", "STORAGE", "YEAR"]) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index c02076d..8b90d5c 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -7,6 +7,7 @@ capital_recovery_factor, discount_factor, discount_factor_storage, + discount_factor_storage_salvage, pv_annuity, ) @@ -126,6 +127,11 @@ def region(): return pd.DataFrame(data=["SIMPLICITY"], columns=["VALUE"]) +@fixture +def storage(): + return pd.DataFrame(data=["DAM"], columns=["VALUE"]) + + @fixture def accumulated_new_capacity(): data = pd.DataFrame( @@ -349,6 +355,107 @@ def discounted_technology_cost(): return data +@fixture +def capital_cost_storage(): + df = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.23], + ["SIMPLICITY", "DAM", 2015, 2.34], + ["SIMPLICITY", "DAM", 2016, 3.45], + ["SIMPLICITY", "BATTERY", 2014, 4.56], + ["SIMPLICITY", "BATTERY", 2015, 5.67], + ["SIMPLICITY", "BATTERY", 2016, 6.78], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return df + + +@fixture +def new_storage_capacity(): + df = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.3], + ["SIMPLICITY", "DAM", 2016, 1.6], + ["SIMPLICITY", "BATTERY", 2014, 0.9], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return df + + +@fixture +def undiscounted_capital_investment_storage(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.23], + ["SIMPLICITY", "DAM", 2015, 2.34], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return data + + +@fixture +def salvage_value_storage(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.23], + ["SIMPLICITY", "DAM", 2015, 2.34], + ["SIMPLICITY", "DAM", 2016, 3.45], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return data + + +@fixture +def discounted_capital_costs_storage(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "BATTERY", 2014, 11.1], + ["SIMPLICITY", "BATTERY", 2016, 22.2], + ["SIMPLICITY", "DAM", 2014, 33.3], + ["SIMPLICITY", "DAM", 2015, 44.4], + ["SIMPLICITY", "DAM", 2016, 55.5], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return data + + +@fixture +def discounted_salvage_value_storage(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.23], + ["SIMPLICITY", "DAM", 2015, 2.34], + ["SIMPLICITY", "DAM", 2016, 3.45], + ["SIMPLICITY", "BATTERY", 2014, 4.56], + ["SIMPLICITY", "BATTERY", 2015, 5.67], + ["SIMPLICITY", "BATTERY", 2016, 6.78], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return data + + +@fixture +def discounted_storage_cost(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 11.1], + ["SIMPLICITY", "DAM", 2015, 22.2], + ["SIMPLICITY", "DAM", 2016, 33.3], + ["SIMPLICITY", "BATTERY", 2014, 44.4], + ["SIMPLICITY", "BATTERY", 2015, 55.5], + ["SIMPLICITY", "BATTERY", 2016, 66.6], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return data + + @fixture(scope="function") def null() -> ResultsPackage: package = ResultsPackage({}) @@ -868,6 +975,41 @@ def test_null(self, null: ResultsPackage): assert "Cannot calculate CapitalInvestment due to missing data" in str(ex) +class TestCapitalInvestmentStorage: + def test_capital_investment_storage( + self, region, year, capital_cost_storage, new_storage_capacity + ): + + results = { + "REGION": region, + "YEAR": year, + "CapitalCostStorage": capital_cost_storage, + "NewStorageCapacity": new_storage_capacity, + } + + package = ResultsPackage(results) + actual = package.capital_investment_storage() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "BATTERY", 2014, 4.104], + ["SIMPLICITY", "DAM", 2014, 1.599], + ["SIMPLICITY", "DAM", 2016, 5.52], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.capital_investment_storage() + assert "Cannot calculate CapitalInvestmentStorage due to missing data" in str( + ex + ) + + class TestDiscountedCapitalInvestment: def test_calculate_discounted_captital_investment( self, @@ -909,6 +1051,45 @@ def test_null(self, null: ResultsPackage): ) +class TestDiscountedCapitalInvestmentStorage: + def test_calculate_discounted_captital_investment_storage( + self, + region, + year, + undiscounted_capital_investment_storage, + discount_rate_storage, + ): + + results = { + "REGION": region, + "YEAR": year, + "DiscountRateStorage": discount_rate_storage, + "CapitalInvestmentStorage": undiscounted_capital_investment_storage, + } + + package = ResultsPackage(results) + actual = package.discounted_capital_investment_storage() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.23], + ["SIMPLICITY", "DAM", 2015, 2.22857143], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.discounted_capital_investment_storage() + assert ( + "Cannot calculate DiscountedCapitalInvestmentStorage due to missing data" + in str(ex) + ) + + class TestDiscountedOperationalCost: def test_calculate_discounted_operational_cost( self, @@ -995,23 +1176,84 @@ def test_null(self, null: ResultsPackage): ) +class TestDiscountedCostByStorage: + def test_calculate_discounted_cost_by_storage( + self, + discounted_capital_costs_storage, + discounted_salvage_value_storage, + ): + + results = { + "DiscountedCapitalInvestmentStorage": discounted_capital_costs_storage, + "DiscountedSalvageValueStorage": discounted_salvage_value_storage, + } + + package = ResultsPackage(results) + actual = package.discounted_storage_cost() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "BATTERY", 2014, 6.54], + ["SIMPLICITY", "BATTERY", 2015, -5.67], + ["SIMPLICITY", "BATTERY", 2016, 15.42], + ["SIMPLICITY", "DAM", 2014, 32.07], + ["SIMPLICITY", "DAM", 2015, 42.06], + ["SIMPLICITY", "DAM", 2016, 52.05], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.discounted_storage_cost() + assert ( + "Cannot calculate TotalDiscountedCostByStorage due to missing data" + in str(ex) + ) + + class TestTotalDiscountedCost: def test_calculate_total_discounted_cost( - self, - discounted_technology_cost, + self, discounted_technology_cost, discounted_storage_cost ): results = { "DiscountedCostByTechnology": discounted_technology_cost, + "DiscountedCostByStorage": discounted_storage_cost, } package = ResultsPackage(results) actual = package.total_discounted_cost() expected = pd.DataFrame( data=[ - ["SIMPLICITY", 2014, 555], - ["SIMPLICITY", 2015, 777], - ["SIMPLICITY", 2016, 999], + ["SIMPLICITY", 2014, 610.5], + ["SIMPLICITY", 2015, 854.7], + ["SIMPLICITY", 2016, 1098.9], + ], + columns=["REGION", "YEAR", "VALUE"], + ).set_index(["REGION", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_calculate_total_discounted_cost_no_storage( + self, discounted_technology_cost + ): + """Situations where NewStorageCapacity not available""" + + results = { + "DiscountedCostByTechnology": discounted_technology_cost, + } + + package = ResultsPackage(results) + actual = package.total_discounted_cost() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", 2014, 555.0], + ["SIMPLICITY", 2015, 777.0], + ["SIMPLICITY", 2016, 999.0], ], columns=["REGION", "YEAR", "VALUE"], ).set_index(["REGION", "YEAR"]) @@ -1026,6 +1268,43 @@ def test_null(self, null: ResultsPackage): assert "Cannot calculate TotalDiscountedCost due to missing data" in str(ex) +class TestDiscountedSalvageValueStorage: + def test_calculate_discounted_salvage_value_storage( + self, region, year, storage, salvage_value_storage, discount_rate_storage + ): + + results = { + "REGION": region, + "YEAR": year, + "STORAGE": storage, + "DiscountRateStorage": discount_rate_storage, + "SalvageValueStorage": salvage_value_storage, + } + + package = ResultsPackage(results) + actual = package.discounted_salvage_value_storage() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 0.87413804], + ["SIMPLICITY", "DAM", 2015, 1.66299431], + ["SIMPLICITY", "DAM", 2016, 2.45185059], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.discounted_salvage_value_storage() + assert ( + "Cannot calculate DiscountedSalvageValueStorage due to missing data" + in str(ex) + ) + + class TestCapitalRecoveryFactor: def test_crf(self, region, discount_rate_idv, operational_life): @@ -1304,6 +1583,55 @@ def test_df_storage_empty_discount_rate( ) +class TestDiscountFactorStorageSalvage: + def test_discount_factor_storage_salvage(self, region, year, discount_rate_storage): + + storages = ["DAM"] + regions = region["VALUE"].to_list() + years = year["VALUE"].to_list() + actual = discount_factor_storage_salvage( + regions, storages, years, discount_rate_storage + ) + + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "DAM", 2014, 1.40710042], + ["SIMPLICITY", "DAM", 2015, 1.40710042], + ["SIMPLICITY", "DAM", 2016, 1.40710042], + ["SIMPLICITY", "DAM", 2017, 1.40710042], + ["SIMPLICITY", "DAM", 2018, 1.40710042], + ["SIMPLICITY", "DAM", 2019, 1.40710042], + ["SIMPLICITY", "DAM", 2020, 1.40710042], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_df_null(self, discount_rate_storage): + + actual = discount_factor_storage_salvage([], [], [], discount_rate_storage) + + expected = pd.DataFrame( + data=[], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + + assert_frame_equal(actual, expected) + + def test_df_storage_empty_discount_rate( + self, region, year, discount_rate_storage_empty + ): + storages = ["DAM"] + regions = region["VALUE"].to_list() + years = year["VALUE"].to_list() + + with raises(ValueError): + discount_factor_storage_salvage( + regions, storages, years, discount_rate_storage_empty + ) + + class TestResultsPackage: def test_results_package_init(self):