From cfa2910c17c85f22f5a081e22a768514901161be Mon Sep 17 00:00:00 2001 From: Will Usher Date: Mon, 7 Oct 2024 12:58:04 +0200 Subject: [PATCH 01/11] Update changelog --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 36c9b23..c155b9d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -(Development) Version 1.1.3 +Version 1.1.3 =========================== - Lock pandas to 2.1.4 or later - Capital Investment result calculation fixed From deaf43ecefb7f6f33e0366a27ce0a6d312ec1348 Mon Sep 17 00:00:00 2001 From: trevorb1 Date: Fri, 25 Oct 2024 16:08:58 -0700 Subject: [PATCH 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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