diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index dbfd45083..ad42a0322 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -479,7 +479,10 @@ def CalculateLCOELCOHLCOC(self, model: Model) -> tuple: NPVcap = np.sum((1 + self.inflrateconstruction.value) * self.CCap.value * CRF * discountvector) NPVfc = np.sum((1 + self.inflrateconstruction.value) * self.CCap.value * self.PTR.value * inflationvector * discountvector) NPVit = np.sum(self.CTR.value / (1 - self.CTR.value) * ((1 + self.inflrateconstruction.value) * self.CCap.value * CRF - self.CCap.value / model.surfaceplant.plant_lifetime.value) * discountvector) - NPVitc = (1 + self.inflrateconstruction.value) * self.CCap.value * self.RITC.value / (1 - self.CTR.value) + + npv_itc_discount_factor = discountvector[model.surfaceplant.construction_years.value] + NPVitc = ((1 + self.inflrateconstruction.value) * self.CCap.value * self.RITC.value / (1 - self.CTR.value) + * npv_itc_discount_factor) if model.surfaceplant.enduse_option.value == EndUseOptions.ELECTRICITY: NPVoandm = np.sum(self.Coam.value * inflationvector * discountvector) @@ -502,7 +505,8 @@ def CalculateLCOELCOHLCOC(self, model: Model) -> tuple: NPVcap_elec = np.sum((1 + self.inflrateconstruction.value) * CCap_elec * CRF * discountvector) NPVfc_elec = np.sum((1 + self.inflrateconstruction.value) * CCap_elec * self.PTR.value * inflationvector * discountvector) NPVit_elec = np.sum(self.CTR.value / (1 - self.CTR.value) * ((1 + self.inflrateconstruction.value) * CCap_elec * CRF - CCap_elec / model.surfaceplant.plant_lifetime.value) * discountvector) - NPVitc_elec = (1 + self.inflrateconstruction.value) * CCap_elec * self.RITC.value / (1 - self.CTR.value) + NPVitc_elec = ((1 + self.inflrateconstruction.value) * CCap_elec * self.RITC.value / (1 - self.CTR.value) + * npv_itc_discount_factor) NPVoandm_elec = np.sum(Coam_elec * inflationvector * discountvector) NPVgrt_elec = self.GTR.value / (1 - self.GTR.value) * (NPVcap_elec + NPVoandm_elec + NPVfc_elec + NPVit_elec - NPVitc_elec) @@ -512,7 +516,8 @@ def CalculateLCOELCOHLCOC(self, model: Model) -> tuple: NPVcap_heat = np.sum((1 + self.inflrateconstruction.value) * CCap_heat * CRF * discountvector) NPVfc_heat = np.sum((1 + self.inflrateconstruction.value) * (self.CCap.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)) * self.PTR.value * inflationvector * discountvector) NPVit_heat = np.sum(self.CTR.value / (1 - self.CTR.value) * ((1 + self.inflrateconstruction.value) * CCap_heat * CRF - CCap_heat / model.surfaceplant.plant_lifetime.value) * discountvector) - NPVitc_heat = (1 + self.inflrateconstruction.value) * CCap_heat * self.RITC.value / (1 - self.CTR.value) + NPVitc_heat = ((1 + self.inflrateconstruction.value) * CCap_heat * self.RITC.value / (1 - self.CTR.value) + * npv_itc_discount_factor) NPVoandm_heat = np.sum((self.Coam.value * (1.0 - self.CAPEX_heat_electricity_plant_ratio.value)) * inflationvector * discountvector) NPVgrt_heat = self.GTR.value / (1 - self.GTR.value) * (NPVcap_heat + NPVoandm_heat + NPVfc_heat + NPVit_heat - NPVitc_heat) @@ -535,7 +540,7 @@ def CalculateLCOELCOHLCOC(self, model: Model) -> tuple: NPVgrt = self.GTR.value / (1 - self.GTR.value) * (NPVcap + NPVoandm + NPVfc + NPVit - NPVitc) LCOH = (NPVcap + NPVoandm + NPVfc + NPVit + NPVgrt - NPVitc) / np.sum( model.surfaceplant.HeatkWhProduced.value * inflationvector * discountvector) * 1E8 - LCOH = self.LCOH.value * 2.931 # $/MMBTU + LCOH = LCOH * 2.931 # $/MMBTU elif model.surfaceplant.enduse_option.value == EndUseOptions.HEAT and model.surfaceplant.plant_type.value == PlantType.DISTRICT_HEATING: PumpingCosts = model.surfaceplant.PumpingkWh.value * model.surfaceplant.electricity_cost_to_buy.value / 1E6 @@ -974,7 +979,8 @@ def __init__(self, model: Model): PreferredUnits=PercentUnit.TENTH, CurrentUnits=PercentUnit.TENTH, ErrMessage="assume default investment tax credit rate (0)", - ToolTipText="Investment tax credit rate (see docs)" + ToolTipText="Investment tax credit rate " + "(see https://programs.dsireusa.org/system/program/detail/658)" ) self.PTR = self.ParameterDict[self.PTR.Name] = floatParameter( "Property Tax Rate", @@ -2687,11 +2693,6 @@ def Calculate(self, model: Model) -> None: else: self.CCap.value = self.totalcapcost.value - # update the capitol costs, assuming the entire ITC is used to reduce the capitol costs - if self.RITC.Provided: - self.RITCValue.value = self.RITC.value * self.CCap.value - self.CCap.value = self.CCap.value - self.RITCValue.value - # Add in the FlatLicenseEtc, OtherIncentives, & TotalGrant self.CCap.value = self.CCap.value + self.FlatLicenseEtc.value - self.OtherIncentives.value - self.TotalGrant.value @@ -2928,6 +2929,12 @@ def Calculate(self, model: Model) -> None: model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1): self.TotalRevenue.value[i] = self.TotalRevenue.value[i] - self.Coam.value + + if self.RITC.Provided: + self.RITCValue.value = self.RITC.value * self.CCap.value + # ITC is credited year after construction + self.TotalRevenue.value[model.surfaceplant.construction_years.value] += self.RITCValue.value + # Now do a one-time calculation that calculates the cumulative cash flow after everything else has been accounted for for i in range(1, model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1): self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i-1] + self.TotalRevenue.value[i] diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index 04338a7ef..8bd77f3c2 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -1644,7 +1644,7 @@ "maximum": 1.0 }, "Investment Tax Credit Rate": { - "description": "Investment tax credit rate (see docs)", + "description": "Investment tax credit rate (see https://programs.dsireusa.org/system/program/detail/658)", "type": "number", "units": "", "category": "Economics", diff --git a/tests/examples/Fervo_Project_Cape-3.out b/tests/examples/Fervo_Project_Cape-3.out index 2d30aaaf7..b2e9faf4a 100644 --- a/tests/examples/Fervo_Project_Cape-3.out +++ b/tests/examples/Fervo_Project_Cape-3.out @@ -4,16 +4,16 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.7.23 - Simulation Date: 2025-03-10 - Simulation Time: 10:42 - Calculation Time: 0.871 sec + GEOPHIRES Version: 3.8.9 + Simulation Date: 2025-04-02 + Simulation Time: 12:41 + Calculation Time: 0.860 sec ***SUMMARY OF RESULTS*** End-Use Option: Electricity Average Net Electricity Production: 404.31 MW - Electricity breakeven price: 2.77 cents/kWh + Electricity breakeven price: 3.76 cents/kWh Number of production wells: 39 Number of injection wells: 39 Flowrate per production well: 120.0 kg/sec @@ -27,10 +27,10 @@ Simulation Metadata Accrued financing during construction: 5.00 Project lifetime: 20 yr Capacity factor: 90.0 % - Project NPV: 4580.36 MUSD - Project IRR: 43.75 % - Project VIR=PI=PIR: 5.27 - Project MOIC: 6.30 + Project NPV: 4550.28 MUSD + Project IRR: 39.26 % + Project VIR=PI=PIR: 3.97 + Project MOIC: 4.91 Project Payback Period: 3.38 yr Estimated Jobs Created: 976 @@ -102,7 +102,7 @@ Simulation Metadata Total surface equipment costs: 969.26 MUSD Exploration costs: 30.00 MUSD Investment Tax Credit: -459.83 MUSD - Total capital costs: 1072.95 MUSD + Total capital costs: 1532.78 MUSD ***OPERATING AND MAINTENANCE COSTS (M$/yr)*** @@ -193,8 +193,8 @@ Year Electricity | Heat | Since Price Ann. Rev. Cumm. Rev. | Price Ann. Rev. Cumm. Rev. | Price Ann. Rev. Cumm. Rev. | Price Ann. Rev. Cumm. Rev. | OPEX Net Rev. Net Cashflow Start (cents/kWh)(MUSD/yr) (MUSD) |(cents/kWh) (MUSD/yr) (MUSD) |(cents/kWh) (MUSD/yr) (MUSD) |(USD/lb) (MUSD/yr) (MUSD) |(MUSD/yr) (MUSD/yr) (MUSD) ________________________________________________________________________________________________________________________________________________________________________________________ - 0 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 -1072.95 -1072.95 - 1 15.00 474.16 474.16 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 26.96 447.20 -625.75 + 0 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 -1532.78 -1532.78 + 1 15.00 474.16 474.16 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 26.96 907.04 -625.75 2 15.00 476.35 950.51 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 26.96 449.39 -176.36 3 15.41 489.93 1440.44 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 26.96 462.97 286.61 4 15.81 503.25 1943.69 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 26.96 476.29 762.90 diff --git a/tests/examples/example_ITC.out b/tests/examples/example_ITC.out index 7c2e338e9..65bb8a792 100644 --- a/tests/examples/example_ITC.out +++ b/tests/examples/example_ITC.out @@ -4,16 +4,16 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.8.4 - Simulation Date: 2025-03-19 - Simulation Time: 10:30 - Calculation Time: 0.808 sec + GEOPHIRES Version: 3.8.9 + Simulation Date: 2025-04-02 + Simulation Time: 12:41 + Calculation Time: 0.777 sec ***SUMMARY OF RESULTS*** End-Use Option: Electricity Average Net Electricity Production: 18.84 MW - Electricity breakeven price: 3.21 cents/kWh + Electricity breakeven price: 4.89 cents/kWh Number of production wells: 2 Number of injection wells: 2 Flowrate per production well: 55.0 kg/sec @@ -27,10 +27,10 @@ Simulation Metadata Accrued financing during construction: 0.00 Project lifetime: 30 yr Capacity factor: 90.0 % - Project NPV: 10.31 MUSD - Project IRR: 8.82 % - Project VIR=PI=PIR: 1.19 - Project MOIC: 0.72 + Project NPV: 6.77 MUSD + Project IRR: 8.05 % + Project VIR=PI=PIR: 1.06 + Project MOIC: 0.52 Project Payback Period: 11.46 yr Estimated Jobs Created: 41 @@ -99,7 +99,7 @@ Simulation Metadata Total surface equipment costs: 62.40 MUSD Exploration costs: 8.24 MUSD Investment Tax Credit: -54.20 MUSD - Total capital costs: 54.20 MUSD + Total capital costs: 108.39 MUSD ***OPERATING AND MAINTENANCE COSTS (M$/yr)*** @@ -210,8 +210,8 @@ Year Electricity | Heat | Since Price Ann. Rev. Cumm. Rev. | Price Ann. Rev. Cumm. Rev. | Price Ann. Rev. Cumm. Rev. | Price Ann. Rev. Cumm. Rev. | OPEX Net Rev. Net Cashflow Start (cents/kWh)(MUSD/yr) (MUSD) |(cents/kWh) (MUSD/yr) (MUSD) |(cents/kWh) (MUSD/yr) (MUSD) |(USD/lb) (MUSD/yr) (MUSD) |(MUSD/yr) (MUSD/yr) (MUSD) ________________________________________________________________________________________________________________________________________________________________________________________ - 0 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 -54.20 -54.20 - 1 5.50 8.02 8.02 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 2.95 5.07 -49.13 + 0 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 0.00 0.00 | 0.00 -108.39 -108.39 + 1 5.50 8.02 8.02 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 2.95 59.26 -49.13 2 5.50 8.09 16.11 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 2.95 5.14 -43.99 3 5.50 8.12 24.23 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 2.95 5.17 -38.82 4 5.50 8.13 32.36 | 2.50 0.00 0.00 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 2.95 5.18 -33.64 diff --git a/tests/test_geophires_x.py b/tests/test_geophires_x.py index d26dd6408..9e81cbb51 100644 --- a/tests/test_geophires_x.py +++ b/tests/test_geophires_x.py @@ -610,8 +610,8 @@ def _get_result(base_example: str, do_discount: bool) -> GeophiresXResult: def _npv(r: GeophiresXResult) -> dict: return r.result['ECONOMIC PARAMETERS']['Project NPV']['value'] - self.assertEqual(4580.36, _npv(_get_result('Fervo_Project_Cape-3', False))) - self.assertEqual(4280.71, _npv(_get_result('Fervo_Project_Cape-3', True))) + self.assertEqual(4550.28, _npv(_get_result('Fervo_Project_Cape-3', False))) + self.assertEqual(4252.6, _npv(_get_result('Fervo_Project_Cape-3', True))) def _extended_economics_npv(r: GeophiresXResult) -> dict: return r.result['EXTENDED ECONOMICS']['Project NPV (including AddOns)']['value'] @@ -854,3 +854,16 @@ def test_field_gathering_cost(self): ) self.assertEqual(fg_cost, result.result['CAPITAL COSTS (M$)']['Field gathering system costs']['value']) + + def test_heat_pump_lcoh_bicycle(self): + result = GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=self._get_test_file_path('examples/example10_HP.txt'), + params={ + 'Economic Model': 3, + }, + ) + ) + + lcoh = result.result['SUMMARY OF RESULTS']['Direct-Use heat breakeven price (LCOH)']['value'] + self.assertTrue(10 < lcoh < 20) # Sanity-check that value is non-zero and broadly within the expected range.