From deaf43ecefb7f6f33e0366a27ce0a6d312ec1348 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Fri, 25 Oct 2024 16:08:58 -0700 Subject: [PATCH 01/20] discounted capital costs --- src/otoole/results/result_package.py | 47 +++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 8a70bdd..00ea91c 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -43,6 +43,7 @@ def __init__( "AnnualTechnologyEmissionByMode": self.annual_technology_emission_by_mode, "AnnualVariableOperatingCost": self.annual_variable_operating_cost, "CapitalInvestment": self.capital_investment, + "DiscountedCapitalInvestment": self.discounted_capital_investment, "Demand": self.demand, "DiscountedTechnologyEmissionsPenalty": self.discounted_tech_emis_pen, "ProductionByTechnology": self.production_by_technology, @@ -398,6 +399,45 @@ def discounted_tech_emis_pen(self) -> pd.DataFrame: return data[(data != 0).all(1)] + def discounted_capital_investment(self) -> pd.DataFrame: + """DiscountingCapitalInvestment + + Notes + ----- + From the formulation:: + + r~REGION, t~TECHNOLOGY, y~YEAR, + DiscountingCapitalInvestment[r,t,y] := + CapitalCost[r,t,y] * NewCapacity[r,t,y] * CapitalRecoveryFactor[r,t] * PvAnnuity[r,t] / DiscountFactor[r,y] + + Alternatively, can be written as:: + + r~REGION, t~TECHNOLOGY, y~YEAR, + DiscountingCapitalInvestment[r,t,y] := UndiscountedCapitalInvestment[r,t,y] / DiscountFactor[r,y] + + """ + + try: + discount_rate = self["DiscountRate"] + 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 = self["CapitalInvestment"] + + except KeyError as ex: + raise KeyError(self._msg("CapitalInvestment", str(ex))) + + df = discount_factor(regions, years, discount_rate, 0.0) + + data = capital_investment.div(df, fill_value=0.0) + + if not data.empty: + data = data.groupby(by=["REGION", "TECHNOLOGY", "YEAR"]).sum() + + return data[(data != 0).all(1)] + def production_by_technology(self) -> pd.DataFrame: """ProductionByTechnology @@ -620,7 +660,8 @@ def total_discounted_cost(self) -> pd.DataFrame: annual_fixed_operating_cost = self["AnnualFixedOperatingCost"] annual_variable_operating_cost = self["AnnualVariableOperatingCost"] - capital_investment = self["CapitalInvestment"] + + discounted_capital_costs = self["DiscountedCapitalInvestment"] discounted_emissions_penalty = self["DiscountedTechnologyEmissionsPenalty"] discounted_salvage_value = self["DiscountedSalvageValue"] @@ -629,8 +670,6 @@ def total_discounted_cost(self) -> pd.DataFrame: except KeyError as ex: raise KeyError(self._msg("TotalDiscountedCost", str(ex))) - df_start = discount_factor(regions, years, discount_rate, 0.0) - df_mid = discount_factor(regions, years, discount_rate, 0.5) undiscounted_operational_costs = annual_fixed_operating_cost.add( @@ -641,8 +680,6 @@ def total_discounted_cost(self) -> pd.DataFrame: df_mid, fill_value=0.0 ) - discounted_capital_costs = capital_investment.div(df_start, fill_value=0.0) - discounted_total_costs = discounted_operational_costs.add( discounted_capital_costs, fill_value=0.0 ) From 983f239319c1b58da6fabaa42b38fa95865e482e Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 12:22:03 -0700 Subject: [PATCH 02/20] added discounted operational costs --- src/otoole/results/result_package.py | 96 +++++++++++++++++++++------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 00ea91c..e33ba33 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -43,8 +43,9 @@ def __init__( "AnnualTechnologyEmissionByMode": self.annual_technology_emission_by_mode, "AnnualVariableOperatingCost": self.annual_variable_operating_cost, "CapitalInvestment": self.capital_investment, - "DiscountedCapitalInvestment": self.discounted_capital_investment, "Demand": self.demand, + "DiscountedCapitalInvestment": self.discounted_capital_investment, + "DiscountedOperationalCost": self.discounted_operational_costs, "DiscountedTechnologyEmissionsPenalty": self.discounted_tech_emis_pen, "ProductionByTechnology": self.production_by_technology, "ProductionByTechnologyAnnual": self.production_by_technology_annual, @@ -438,6 +439,76 @@ def discounted_capital_investment(self) -> pd.DataFrame: return data[(data != 0).all(1)] + def discounted_operational_costs(self) -> pd.DataFrame: + """DiscountedOperationalCosts + + Notes + ----- + From the formulation:: + + r~REGION, t~TECHNOLOGY, y~YEAR, + DiscountedOperatingCost[r,t,y] := + ( + ( + ( + sum{yy in YEAR: y-yy < OperationalLife[r,t] && y-yy>=0} + NewCapacity[r,t,yy] + ) + + ResidualCapacity[r,t,y] + ) + * FixedCost[r,t,y] + + sum{l in TIMESLICE, m in MODEperTECHNOLOGY[t]} + RateOfActivity[r,l,t,m,y] * YearSplit[l,y] * VariableCost[r,t,m,y] + ) + / (DiscountFactorMid[r,y]) + + Alternatively, can be written as:: + + r~REGION, t~TECHNOLOGY, y~YEAR, + DiscountedOperatingCost[r,t,y] := + ( + AnnualVariableOperatingCost[r,t,y] + AnnualFixedOperatingCost[r,t,y] + ) + / DiscountFactorMid[r, y] + + OR + + r~REGION, t~TECHNOLOGY, y~YEAR, + DiscountedOperatingCost[r,t,y] := OperatingCost[r,t,y] / DiscountFactorMid[r, y] + + """ + + try: + discount_rate = self["DiscountRate"] + year_df = self["YEAR"].copy(deep=True) + region_df = self["REGION"].copy(deep=True) + + years = year_df["VALUE"].tolist() + regions = region_df["VALUE"].tolist() + + annual_fixed_operating_cost = self["AnnualFixedOperatingCost"] + annual_variable_operating_cost = self["AnnualVariableOperatingCost"] + + except KeyError as ex: + raise KeyError(self._msg("CapitalInvestment", str(ex))) + + df_mid = discount_factor(regions, years, discount_rate, 0.5) + + undiscounted_operational_costs = annual_fixed_operating_cost.add( + annual_variable_operating_cost, fill_value=0.0 + ) + + discounted_operational_costs = undiscounted_operational_costs.div( + df_mid, fill_value=0.0 + ) + + data = discounted_operational_costs + + if not data.empty: + data = data.groupby(by=["REGION", "TECHNOLOGY", "YEAR"]).sum() + + return data[(data != 0).all(1)] + def production_by_technology(self) -> pd.DataFrame: """ProductionByTechnology @@ -651,35 +722,14 @@ def total_discounted_cost(self) -> pd.DataFrame: ) ~VALUE; """ try: - discount_rate = self["DiscountRate"] - year_df = self["YEAR"].copy(deep=True) - region_df = self["REGION"].copy(deep=True) - - years = year_df["VALUE"].tolist() - regions = region_df["VALUE"].tolist() - - annual_fixed_operating_cost = self["AnnualFixedOperatingCost"] - annual_variable_operating_cost = self["AnnualVariableOperatingCost"] - discounted_capital_costs = self["DiscountedCapitalInvestment"] - + discounted_operational_costs = self["DiscountedOperationalCost"] discounted_emissions_penalty = self["DiscountedTechnologyEmissionsPenalty"] discounted_salvage_value = self["DiscountedSalvageValue"] - # capital_cost_storage = self["CapitalCostStorage"] except KeyError as ex: raise KeyError(self._msg("TotalDiscountedCost", str(ex))) - df_mid = discount_factor(regions, years, discount_rate, 0.5) - - undiscounted_operational_costs = annual_fixed_operating_cost.add( - annual_variable_operating_cost, fill_value=0.0 - ) - - discounted_operational_costs = undiscounted_operational_costs.div( - df_mid, fill_value=0.0 - ) - discounted_total_costs = discounted_operational_costs.add( discounted_capital_costs, fill_value=0.0 ) From 6744a8546e494d64202c0cfc408d295357628441 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 12:42:54 -0700 Subject: [PATCH 03/20] cost by technology --- src/otoole/results/result_package.py | 85 +++++++++++++++++++++------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index e33ba33..4f4950c 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -45,7 +45,8 @@ def __init__( "CapitalInvestment": self.capital_investment, "Demand": self.demand, "DiscountedCapitalInvestment": self.discounted_capital_investment, - "DiscountedOperationalCost": self.discounted_operational_costs, + "DiscountedCostByTechnology": self.discounted_technology_cost, + "DiscountedOperationalCost": self.discounted_operational_cost, "DiscountedTechnologyEmissionsPenalty": self.discounted_tech_emis_pen, "ProductionByTechnology": self.production_by_technology, "ProductionByTechnologyAnnual": self.production_by_technology_annual, @@ -439,7 +440,7 @@ def discounted_capital_investment(self) -> pd.DataFrame: return data[(data != 0).all(1)] - def discounted_operational_costs(self) -> pd.DataFrame: + def discounted_operational_cost(self) -> pd.DataFrame: """DiscountedOperationalCosts Notes @@ -490,7 +491,7 @@ def discounted_operational_costs(self) -> pd.DataFrame: annual_variable_operating_cost = self["AnnualVariableOperatingCost"] except KeyError as ex: - raise KeyError(self._msg("CapitalInvestment", str(ex))) + raise KeyError(self._msg("DiscountedOperatingCost", str(ex))) df_mid = discount_factor(regions, years, discount_rate, 0.5) @@ -509,6 +510,65 @@ def discounted_operational_costs(self) -> pd.DataFrame: return data[(data != 0).all(1)] + def discounted_technology_cost(self) -> pd.DataFrame: + """TotalDiscountedCostByTechnology + + Notes + ----- + From the formulation:: + + r~REGION, t~TECHNOLOGY, y~YEAR, + TotalDiscountedCostByTechnology[r,t,y]:= + ( + ( + ( + sum{yy in YEAR: y-yy < OperationalLife[r,t] && y-yy>=0} + NewCapacity[r,t,yy] + ) + + ResidualCapacity[r,t,y] + ) + * FixedCost[r,t,y] + + sum{l in TIMESLICE, m in MODEperTECHNOLOGY[t]} + RateOfActivity[r,l,t,m,y] * YearSplit[l,y] * VariableCost[r,t,m,y] + ) + / (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]) + + Alternatively, can be written as:: + + r~REGION, t~TECHNOLOGY, y~YEAR, + TotalDiscountedCostByTechnology[r,t,y]: = + DiscountedOperatingCost[r,t,y] + DiscountedCapitalInvestment[r,t,y] + DiscountedTechnologyEmissionsPenalty[r,t,y] - DiscountedSalvageValue[r,t,y] + """ + + try: + discounted_capital_costs = self["DiscountedCapitalInvestment"] + discounted_operational_costs = self["DiscountedOperationalCost"] + discounted_emissions_penalty = self["DiscountedTechnologyEmissionsPenalty"] + discounted_salvage_value = self["DiscountedSalvageValue"] + + except KeyError as ex: + raise KeyError(self._msg("TotalDiscountedCostByTechnology", str(ex))) + + discounted_total_costs = discounted_operational_costs.add( + discounted_capital_costs, fill_value=0.0 + ) + + discounted_total_costs = discounted_total_costs.add( + discounted_emissions_penalty, fill_value=0.0 + ) + + discounted_total_costs = discounted_total_costs.sub( + discounted_salvage_value, fill_value=0.0 + ) + + data = discounted_total_costs + + if not data.empty: + data = data.groupby(by=["REGION", "TECHNOLOGY", "YEAR"]).sum() + return data[(data != 0).all(1)] + def production_by_technology(self) -> pd.DataFrame: """ProductionByTechnology @@ -722,27 +782,12 @@ def total_discounted_cost(self) -> pd.DataFrame: ) ~VALUE; """ try: - discounted_capital_costs = self["DiscountedCapitalInvestment"] - discounted_operational_costs = self["DiscountedOperationalCost"] - discounted_emissions_penalty = self["DiscountedTechnologyEmissionsPenalty"] - discounted_salvage_value = self["DiscountedSalvageValue"] + discounted_cost_by_technology = self["DiscountedCostByTechnology"] except KeyError as ex: raise KeyError(self._msg("TotalDiscountedCost", str(ex))) - discounted_total_costs = discounted_operational_costs.add( - discounted_capital_costs, fill_value=0.0 - ) - - discounted_total_costs = discounted_total_costs.add( - discounted_emissions_penalty, fill_value=0.0 - ) - - discounted_total_costs = discounted_total_costs.sub( - discounted_salvage_value, fill_value=0.0 - ) - - data = discounted_total_costs + data = discounted_cost_by_technology if not data.empty: data = data.groupby(by=["REGION", "YEAR"]).sum() From 0568679e594431d6bb88db5365ba9c423d57aa51 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 14:03:13 -0700 Subject: [PATCH 04/20] Discounted Capital Investment Tests --- tests/results/test_results_package.py | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index a597d4e..c7a7147 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -227,6 +227,21 @@ def variable_cost(): return data +@fixture +def undiscounted_capital_investment(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 10], + ["SIMPLICITY", "DUMMY", 2015, 0], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 10], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + @fixture(scope="function") def null() -> ResultsPackage: package = ResultsPackage({}) @@ -739,6 +754,37 @@ def test_calculate_captital_investment_no_dr_idv( assert_frame_equal(actual, expected) +class TestDiscountedCapitalInvestment: + def test_calculate_discounted_captital_investment( + self, + region, + year, + undiscounted_capital_investment, + discount_rate, + ): + + results = { + "REGION": region, + "YEAR": year, + "DiscountRate": discount_rate, + "CapitalInvestment": undiscounted_capital_investment, + } + + package = ResultsPackage(results) + actual = package.discounted_capital_investment() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 9.52380952], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 9.07029478], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + + assert_frame_equal(actual, expected) + + class TestCapitalRecoveryFactor: def test_crf(self, region, discount_rate_idv, operational_life): From 5881e99b7e6be98f1fa3833791cf2ab163f0de28 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 14:22:14 -0700 Subject: [PATCH 05/20] Discounted Operational Costs added --- tests/results/test_results_package.py | 79 +++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index c7a7147..33d6a95 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -233,9 +233,41 @@ def undiscounted_capital_investment(): data=[ ["SIMPLICITY", "DUMMY", 2014, 10], ["SIMPLICITY", "DUMMY", 2015, 0], - ["SIMPLICITY", "GAS_EXTRACTION", 2014, 10], - ["SIMPLICITY", "GAS_EXTRACTION", 2015, 10], - ["SIMPLICITY", "GAS_EXTRACTION", 2016, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 123], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 456], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 789], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + +@fixture +def annual_fixed_operating_cost(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 10], + ["SIMPLICITY", "DUMMY", 2015, 0], + ["SIMPLICITY", "DUMMY", 2016, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 123], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 456], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 789], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + +@fixture +def annual_variable_operating_cost(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 10], + ["SIMPLICITY", "DUMMY", 2015, 10], + ["SIMPLICITY", "DUMMY", 2016, 0], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 321], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 654], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 987], ], columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) @@ -775,9 +807,44 @@ def test_calculate_discounted_captital_investment( expected = pd.DataFrame( data=[ ["SIMPLICITY", "DUMMY", 2014, 10], - ["SIMPLICITY", "GAS_EXTRACTION", 2014, 10], - ["SIMPLICITY", "GAS_EXTRACTION", 2015, 9.52380952], - ["SIMPLICITY", "GAS_EXTRACTION", 2016, 9.07029478], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 123], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 434.28571428], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 715.64625850], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + + assert_frame_equal(actual, expected) + + +class TestDiscountedOperationalCost: + def test_calculate_discounted_operational_cost( + self, + region, + year, + discount_rate, + annual_fixed_operating_cost, + annual_variable_operating_cost, + ): + + results = { + "REGION": region, + "YEAR": year, + "DiscountRate": discount_rate, + "AnnualFixedOperatingCost": annual_fixed_operating_cost, + "AnnualVariableOperatingCost": annual_variable_operating_cost, + } + + package = ResultsPackage(results) + actual = package.discounted_operational_cost() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 19.51800146], + ["SIMPLICITY", "DUMMY", 2015, 9.29428640], + ["SIMPLICITY", "DUMMY", 2016, 8.85170134], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 433.29963238], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 1031.66579140], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 1572.06215832], ], columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) From a26dc4b9d8e332c229107b50a94d74f7d2255007 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 14:35:18 -0700 Subject: [PATCH 06/20] costs by technoloy test --- tests/results/test_results_package.py | 92 +++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 33d6a95..918d3d2 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -274,6 +274,65 @@ def annual_variable_operating_cost(): return data +@fixture +def discounted_capital_costs(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 10], + ["SIMPLICITY", "DUMMY", 2015, 0], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 111], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 222], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 333], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + +@fixture +def discounted_operational_costs(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 5], + ["SIMPLICITY", "DUMMY", 2015, 10], + ["SIMPLICITY", "DUMMY", 2016, 20], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 444], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 555], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 666], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + +@fixture +def discounted_emissions_penalty(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 10], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 777], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + +@fixture +def discounted_salvage_value(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 1], + ["SIMPLICITY", "DUMMY", 2015, 2], + ["SIMPLICITY", "DUMMY", 2016, 3], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 888], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 999], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 1], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + @fixture(scope="function") def null() -> ResultsPackage: package = ResultsPackage({}) @@ -852,6 +911,39 @@ def test_calculate_discounted_operational_cost( assert_frame_equal(actual, expected) +class TestDiscountedCostByTechnology: + def test_calculate_discounted_operational_cost( + self, + discounted_capital_costs, + discounted_operational_costs, + discounted_emissions_penalty, + discounted_salvage_value, + ): + + results = { + "DiscountedCapitalInvestment": discounted_capital_costs, + "DiscountedOperationalCost": discounted_operational_costs, + "DiscountedTechnologyEmissionsPenalty": discounted_emissions_penalty, + "DiscountedSalvageValue": discounted_salvage_value, + } + + package = ResultsPackage(results) + actual = package.discounted_technology_cost() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 24.0], + ["SIMPLICITY", "DUMMY", 2015, 8.0], + ["SIMPLICITY", "DUMMY", 2016, 17.0], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, -333.0], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, -222.0], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 1775.0], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + + assert_frame_equal(actual, expected) + + class TestCapitalRecoveryFactor: def test_crf(self, region, discount_rate_idv, operational_life): From f86d3133b5a1da2a58f82e326b925ff105c1719d Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 14:43:10 -0700 Subject: [PATCH 07/20] total discounted costs test --- tests/results/test_results_package.py | 42 ++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 918d3d2..6d68405 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -333,6 +333,22 @@ def discounted_salvage_value(): return data +@fixture +def discounted_technology_cost(): + data = pd.DataFrame( + data=[ + ["SIMPLICITY", "DUMMY", 2014, 111], + ["SIMPLICITY", "DUMMY", 2015, 222], + ["SIMPLICITY", "DUMMY", 2016, 333], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 444], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 555], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 666], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + @fixture(scope="function") def null() -> ResultsPackage: package = ResultsPackage({}) @@ -912,7 +928,7 @@ def test_calculate_discounted_operational_cost( class TestDiscountedCostByTechnology: - def test_calculate_discounted_operational_cost( + def test_calculate_discounted_cost_by_technology( self, discounted_capital_costs, discounted_operational_costs, @@ -944,6 +960,30 @@ def test_calculate_discounted_operational_cost( assert_frame_equal(actual, expected) +class TestTotalDiscountedCost: + def test_calculate_total_discounted_cost( + self, + discounted_technology_cost, + ): + + results = { + "DiscountedCostByTechnology": discounted_technology_cost, + } + + package = ResultsPackage(results) + actual = package.total_discounted_cost() + expected = pd.DataFrame( + data=[ + ["SIMPLICITY", 2014, 555], + ["SIMPLICITY", 2015, 777], + ["SIMPLICITY", 2016, 999], + ], + columns=["REGION", "YEAR", "VALUE"], + ).set_index(["REGION", "YEAR"]) + + assert_frame_equal(actual, expected) + + class TestCapitalRecoveryFactor: def test_crf(self, region, discount_rate_idv, operational_life): From 2bcd28e98600517bd894505fdaade2f2688a2d70 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sat, 26 Oct 2024 14:47:21 -0700 Subject: [PATCH 08/20] update template config --- CHANGELOG.rst | 4 ++++ src/otoole/preprocess/config.yaml | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c155b9d..b77d7dd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,10 @@ Changelog ========= +Version 1.1.4 +============= +- Add result calculations for ``DiscountedCapitalInvestment``, ``DiscountedCostByTechnology``, and ``DiscountedOperationalCost`` + Version 1.1.3 =========================== - Lock pandas to 2.1.4 or later diff --git a/src/otoole/preprocess/config.yaml b/src/otoole/preprocess/config.yaml index a915644..dc309cd 100644 --- a/src/otoole/preprocess/config.yaml +++ b/src/otoole/preprocess/config.yaml @@ -342,6 +342,21 @@ Demand: type: result dtype: float default: 0 +DiscountedCapitalInvestment: + indices: [REGION, TECHNOLOGY, YEAR] + type: result + dtype: float + default: 0 +DiscountedCostByTechnology: + indices: [REGION, TECHNOLOGY, YEAR] + type: result + dtype: float + default: 0 +DiscountedOperationalCost: + indices: [REGION, TECHNOLOGY, YEAR] + type: result + dtype: float + default: 0 DiscountedSalvageValue: indices: [REGION, TECHNOLOGY, YEAR] type: result From af7dd63a49594ceb848d85c40bac4dd9268ce85b Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sun, 27 Oct 2024 12:43:55 -0700 Subject: [PATCH 09/20] added null tests --- tests/results/test_results_package.py | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 6d68405..c02076d 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -860,6 +860,13 @@ def test_calculate_captital_investment_no_dr_idv( assert_frame_equal(actual, expected) + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.capital_investment() + assert "Cannot calculate CapitalInvestment due to missing data" in str(ex) + class TestDiscountedCapitalInvestment: def test_calculate_discounted_captital_investment( @@ -891,6 +898,16 @@ def test_calculate_discounted_captital_investment( assert_frame_equal(actual, expected) + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.discounted_capital_investment() + assert ( + "Cannot calculate DiscountedCapitalInvestment due to missing data" + in str(ex) + ) + class TestDiscountedOperationalCost: def test_calculate_discounted_operational_cost( @@ -926,6 +943,15 @@ def test_calculate_discounted_operational_cost( assert_frame_equal(actual, expected) + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.discounted_operational_cost() + assert "Cannot calculate DiscountedOperationalCost due to missing data" in str( + ex + ) + class TestDiscountedCostByTechnology: def test_calculate_discounted_cost_by_technology( @@ -959,6 +985,15 @@ def test_calculate_discounted_cost_by_technology( assert_frame_equal(actual, expected) + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.discounted_technology_cost() + assert "Cannot calculate DiscountedCostByTechnology due to missing data" in str( + ex + ) + class TestTotalDiscountedCost: def test_calculate_total_discounted_cost( @@ -983,6 +1018,13 @@ def test_calculate_total_discounted_cost( assert_frame_equal(actual, expected) + def test_null(self, null: ResultsPackage): + """ """ + package = null + with raises(KeyError) as ex: + package.total_discounted_cost() + assert "Cannot calculate TotalDiscountedCost due to missing data" in str(ex) + class TestCapitalRecoveryFactor: def test_crf(self, region, discount_rate_idv, operational_life): From a7c3c11089e4950e3aaf4ad6368e5ea686bdb7ee Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sun, 27 Oct 2024 12:49:21 -0700 Subject: [PATCH 10/20] update result packae for null tests --- src/otoole/results/result_package.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 4f4950c..dc5ae29 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -429,7 +429,7 @@ def discounted_capital_investment(self) -> pd.DataFrame: capital_investment = self["CapitalInvestment"] except KeyError as ex: - raise KeyError(self._msg("CapitalInvestment", str(ex))) + raise KeyError(self._msg("DiscountedCapitalInvestment", str(ex))) df = discount_factor(regions, years, discount_rate, 0.0) @@ -491,7 +491,7 @@ def discounted_operational_cost(self) -> pd.DataFrame: annual_variable_operating_cost = self["AnnualVariableOperatingCost"] except KeyError as ex: - raise KeyError(self._msg("DiscountedOperatingCost", str(ex))) + raise KeyError(self._msg("DiscountedOperationalCost", str(ex))) df_mid = discount_factor(regions, years, discount_rate, 0.5) @@ -549,7 +549,7 @@ def discounted_technology_cost(self) -> pd.DataFrame: discounted_salvage_value = self["DiscountedSalvageValue"] except KeyError as ex: - raise KeyError(self._msg("TotalDiscountedCostByTechnology", str(ex))) + raise KeyError(self._msg("DiscountedCostByTechnology", str(ex))) discounted_total_costs = discounted_operational_costs.add( discounted_capital_costs, fill_value=0.0 From fba0933064c4f9865ee2b622482e2fb34b733e5a Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 10:50:58 -0800 Subject: [PATCH 11/20] capital investment storage tests --- tests/results/test_results_package.py | 121 ++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index c02076d..5044baa 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -349,6 +349,50 @@ 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_capacity_storage(): + 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], + ["SIMPLICITY", "BATTERY", 2014, 3.45], + ["SIMPLICITY", "BATTERY", 2015, 4.56], + ["SIMPLICITY", "BATTERY", 2016, 5.67], + ], + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) + return data + + @fixture(scope="function") def null() -> ResultsPackage: package = ResultsPackage({}) @@ -868,6 +912,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_capacity_storage + ): + + results = { + "REGION": region, + "YEAR": year, + "CapitalCostStorage": capital_cost_storage, + "NewCapacityStorage": new_capacity_storage, + } + + 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 +988,48 @@ 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", "BATTERY", 2014, 3.45], + ["SIMPLICITY", "BATTERY", 2015, 4.34285714], + ["SIMPLICITY", "BATTERY", 2016, 5.14285714], + ["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() + assert ( + "Cannot calculate DiscountedCapitalInvestmentStorage due to missing data" + in str(ex) + ) + + class TestDiscountedOperationalCost: def test_calculate_discounted_operational_cost( self, From d79aff0233fa355203a037a9e23a1487b3d7a928 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 13:43:53 -0800 Subject: [PATCH 12/20] add storage investment costs --- src/otoole/results/result_package.py | 85 ++++++++++++++++++++++++++- tests/results/test_results_package.py | 14 ++--- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index dc5ae29..c38e907 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -43,8 +43,10 @@ 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, "DiscountedCostByTechnology": self.discounted_technology_cost, "DiscountedOperationalCost": self.discounted_operational_cost, "DiscountedTechnologyEmissionsPenalty": self.discounted_tech_emis_pen, @@ -347,6 +349,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["NewCapacityStorage"] + + 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 +471,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 @@ -774,10 +853,10 @@ 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; """ diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 5044baa..0ab17ad 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -384,9 +384,9 @@ def undiscounted_capital_investment_storage(): data=[ ["SIMPLICITY", "DAM", 2014, 1.23], ["SIMPLICITY", "DAM", 2015, 2.34], - ["SIMPLICITY", "BATTERY", 2014, 3.45], - ["SIMPLICITY", "BATTERY", 2015, 4.56], - ["SIMPLICITY", "BATTERY", 2016, 5.67], + # ["SIMPLICITY", "BATTERY", 2014, 3.45], + # ["SIMPLICITY", "BATTERY", 2015, 4.56], + # ["SIMPLICITY", "BATTERY", 2016, 5.67], ], columns=["REGION", "STORAGE", "YEAR", "VALUE"], ).set_index(["REGION", "STORAGE", "YEAR"]) @@ -1008,9 +1008,9 @@ def test_calculate_discounted_captital_investment_storage( actual = package.discounted_capital_investment_storage() expected = pd.DataFrame( data=[ - ["SIMPLICITY", "BATTERY", 2014, 3.45], - ["SIMPLICITY", "BATTERY", 2015, 4.34285714], - ["SIMPLICITY", "BATTERY", 2016, 5.14285714], + # ["SIMPLICITY", "BATTERY", 2014, 3.45], + # ["SIMPLICITY", "BATTERY", 2015, 4.34285714], + # ["SIMPLICITY", "BATTERY", 2016, 5.14285714], ["SIMPLICITY", "DAM", 2014, 1.23], ["SIMPLICITY", "DAM", 2015, 2.22857143], ], @@ -1023,7 +1023,7 @@ def test_null(self, null: ResultsPackage): """ """ package = null with raises(KeyError) as ex: - package.discounted_capital_investment() + package.discounted_capital_investment_storage() assert ( "Cannot calculate DiscountedCapitalInvestmentStorage due to missing data" in str(ex) From 39c6ae4ce6ca80f325d40aca51dc8cdf43bcff94 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 15:19:51 -0800 Subject: [PATCH 13/20] discounted storage salvage value --- src/otoole/results/result_package.py | 88 ++++++++++++++++++++ tests/results/test_results_package.py | 111 ++++++++++++++++++++++++-- 2 files changed, 193 insertions(+), 6 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index c38e907..2c17ba4 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -49,6 +49,7 @@ def __init__( "DiscountedCapitalInvestmentStorage": self.discounted_capital_investment_storage, "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, @@ -589,6 +590,45 @@ def discounted_operational_cost(self) -> pd.DataFrame: 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 @@ -1169,3 +1209,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 0ab17ad..1c910bf 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( @@ -384,9 +390,19 @@ def undiscounted_capital_investment_storage(): data=[ ["SIMPLICITY", "DAM", 2014, 1.23], ["SIMPLICITY", "DAM", 2015, 2.34], - # ["SIMPLICITY", "BATTERY", 2014, 3.45], - # ["SIMPLICITY", "BATTERY", 2015, 4.56], - # ["SIMPLICITY", "BATTERY", 2016, 5.67], + ], + 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"]) @@ -1008,9 +1024,6 @@ def test_calculate_discounted_captital_investment_storage( actual = package.discounted_capital_investment_storage() expected = pd.DataFrame( data=[ - # ["SIMPLICITY", "BATTERY", 2014, 3.45], - # ["SIMPLICITY", "BATTERY", 2015, 4.34285714], - # ["SIMPLICITY", "BATTERY", 2016, 5.14285714], ["SIMPLICITY", "DAM", 2014, 1.23], ["SIMPLICITY", "DAM", 2015, 2.22857143], ], @@ -1147,6 +1160,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): @@ -1425,6 +1475,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): From 5f13fc2985a708683ea967562d0a02431063814a Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 16:04:27 -0800 Subject: [PATCH 14/20] discounted cost by storage --- src/otoole/results/result_package.py | 44 ++++++++++++++ tests/results/test_results_package.py | 86 +++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 2c17ba4..33fdb93 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -47,6 +47,7 @@ def __init__( "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, @@ -590,6 +591,49 @@ 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 diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 1c910bf..052d614 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -409,6 +409,53 @@ def salvage_value_storage(): 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", "DUMMY", 2014, 111], + ["SIMPLICITY", "DUMMY", 2015, 222], + ["SIMPLICITY", "DUMMY", 2016, 333], + ["SIMPLICITY", "GAS_EXTRACTION", 2014, 444], + ["SIMPLICITY", "GAS_EXTRACTION", 2015, 555], + ["SIMPLICITY", "GAS_EXTRACTION", 2016, 666], + ], + columns=["REGION", "TECHNOLOGY", "YEAR", "VALUE"], + ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + return data + + @fixture(scope="function") def null() -> ResultsPackage: package = ResultsPackage({}) @@ -1129,6 +1176,45 @@ 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, From f5ff21764080c4d4c11d0869b7302c837f585e27 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 16:18:09 -0800 Subject: [PATCH 15/20] update total discounted cost calculation --- src/otoole/results/result_package.py | 24 +++++++++++++++++++++++- tests/results/test_results_package.py | 26 +++++++++++++------------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 33fdb93..6917781 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -943,14 +943,36 @@ def total_discounted_cost(self) -> pd.DataFrame: - 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"] + discounted_cost_by_storage = self["DiscountedCostByStorage"] 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() + ) + discounted_storage = ( + discounted_cost_by_storage.droplevel("STORAGE") + .reset_index() + .groupby(["REGION", "YEAR"]) + .sum() + ) + + total_discounted_cost = discounted_tech.add(discounted_storage, fill_value=0) + + data = total_discounted_cost if not data.empty: data = data.groupby(by=["REGION", "YEAR"]).sum() diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 052d614..9dc85b3 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -444,15 +444,15 @@ def discounted_salvage_value_storage(): def discounted_storage_cost(): data = pd.DataFrame( data=[ - ["SIMPLICITY", "DUMMY", 2014, 111], - ["SIMPLICITY", "DUMMY", 2015, 222], - ["SIMPLICITY", "DUMMY", 2016, 333], - ["SIMPLICITY", "GAS_EXTRACTION", 2014, 444], - ["SIMPLICITY", "GAS_EXTRACTION", 2015, 555], - ["SIMPLICITY", "GAS_EXTRACTION", 2016, 666], + ["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", "TECHNOLOGY", "YEAR", "VALUE"], - ).set_index(["REGION", "TECHNOLOGY", "YEAR"]) + columns=["REGION", "STORAGE", "YEAR", "VALUE"], + ).set_index(["REGION", "STORAGE", "YEAR"]) return data @@ -1217,21 +1217,21 @@ def test_null(self, null: ResultsPackage): 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"]) From f2a50e9cd35d3a215368ad241a604e1bb71e38ad Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 16:29:44 -0800 Subject: [PATCH 16/20] update setup config --- src/otoole/preprocess/config.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/otoole/preprocess/config.yaml b/src/otoole/preprocess/config.yaml index dc309cd..46b7bac 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,21 @@ Demand: type: result dtype: float default: 0 +DiscountedCapitalInvestmentStorage: + 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 +377,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] From 7a686b9f3a8892728b2385c2b79c16a0aedc0c88 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 16:36:22 -0800 Subject: [PATCH 17/20] add short name to config --- src/otoole/preprocess/config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/otoole/preprocess/config.yaml b/src/otoole/preprocess/config.yaml index 46b7bac..ba1b9db 100644 --- a/src/otoole/preprocess/config.yaml +++ b/src/otoole/preprocess/config.yaml @@ -348,6 +348,7 @@ Demand: dtype: float default: 0 DiscountedCapitalInvestmentStorage: + short_name: DiscountedCapitalInvestStorage indices: [REGION, STORAGE, YEAR] type: result dtype: float From 941bbf22d16a45c750079c50d0318b00482903c8 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Thu, 7 Nov 2024 20:31:58 -0800 Subject: [PATCH 18/20] correct NewStorageCapacity name --- src/otoole/results/result_package.py | 2 +- tests/results/test_results_package.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 6917781..9a961cb 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -364,7 +364,7 @@ def capital_investment_storage(self) -> pd.DataFrame: """ try: capital_cost_storage = self["CapitalCostStorage"] - new_capacity_storage = self["NewCapacityStorage"] + new_capacity_storage = self["NewStorageCapacity"] except KeyError as ex: raise KeyError(self._msg("CapitalInvestmentStorage", str(ex))) diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index 9dc85b3..d3e7619 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -372,7 +372,7 @@ def capital_cost_storage(): @fixture -def new_capacity_storage(): +def new_storage_capacity(): df = pd.DataFrame( data=[ ["SIMPLICITY", "DAM", 2014, 1.3], @@ -977,14 +977,14 @@ def test_null(self, null: ResultsPackage): class TestCapitalInvestmentStorage: def test_capital_investment_storage( - self, region, year, capital_cost_storage, new_capacity_storage + self, region, year, capital_cost_storage, new_storage_capacity ): results = { "REGION": region, "YEAR": year, "CapitalCostStorage": capital_cost_storage, - "NewCapacityStorage": new_capacity_storage, + "NewStorageCapacity": new_storage_capacity, } package = ResultsPackage(results) From 4e2a6ab61ef11a3ac7b19aab881cc3709805e8fd Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Fri, 8 Nov 2024 14:07:10 -0800 Subject: [PATCH 19/20] correct total cost for missing storage --- src/otoole/results/result_package.py | 28 ++++++++++++++++++--------- tests/results/test_results_package.py | 22 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/otoole/results/result_package.py b/src/otoole/results/result_package.py index 9a961cb..acf1b56 100644 --- a/src/otoole/results/result_package.py +++ b/src/otoole/results/result_package.py @@ -952,8 +952,6 @@ def total_discounted_cost(self) -> pd.DataFrame: """ try: discounted_cost_by_technology = self["DiscountedCostByTechnology"] - discounted_cost_by_storage = self["DiscountedCostByStorage"] - except KeyError as ex: raise KeyError(self._msg("TotalDiscountedCost", str(ex))) @@ -963,14 +961,26 @@ def total_discounted_cost(self) -> pd.DataFrame: .groupby(["REGION", "YEAR"]) .sum() ) - discounted_storage = ( - discounted_cost_by_storage.droplevel("STORAGE") - .reset_index() - .groupby(["REGION", "YEAR"]) - .sum() - ) - total_discounted_cost = discounted_tech.add(discounted_storage, fill_value=0) + 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 diff --git a/tests/results/test_results_package.py b/tests/results/test_results_package.py index d3e7619..8b90d5c 100644 --- a/tests/results/test_results_package.py +++ b/tests/results/test_results_package.py @@ -1238,6 +1238,28 @@ def test_calculate_total_discounted_cost( 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"]) + + assert_frame_equal(actual, expected) + def test_null(self, null: ResultsPackage): """ """ package = null From 72744e5385b5a19d94e5341fd079921ca30d2f21 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Sun, 10 Nov 2024 10:25:48 -0800 Subject: [PATCH 20/20] update changelog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) 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 ===========================