diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 86f5fc217..f1f66a937 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.14.1 +current_version = 3.15.0 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 2ad8d90ae..5e8cf6383 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -54,7 +54,7 @@ default_context: sphinx_doctest: "no" sphinx_theme: "sphinx-py3doc-enhanced-theme" test_matrix_separate_coverage: "no" - version: 3.14.1 + version: 3.15.0 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a7d180b42..d4f36f1ef 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,11 @@ Changelog GEOPHIRES v3 (2023-2026) ------------------------ +3.15 +^^^^ + +3.15: `Calculate S-DAC carbon revenue (BICYCLE) `__ | `release `__ | **Changed:** S-DAC carbon revenue is now calculated. See `the tracking issue `__ :alt: Supported implementations :target: https://pypi.org/project/geophires-x -.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.14.1.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.15.0.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.14.1...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.15.0...main .. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat :target: https://softwareengineerprogrammer.github.io/GEOPHIRES @@ -176,22 +176,70 @@ Example-specific web interface deeplinks are listed in the Link column. - `Fervo_Project_Cape-6.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 1: EGS Electricity - `example1.txt `__ - `.out `__ - `link `__ + * - SAM Single Owner PPA: 50 MWe (`documentation `__) + - `example_SAM-single-owner-PPA.txt `__ + - `.out `__ + - `link `__ * - Example 1 with Add-Ons - `example1_addons.txt `__ - `.out `__ - `link `__ + * - SAM Single Owner PPA: 50 MWe with Add-ons + - `example_SAM-single-owner-PPA-3.txt `__ + - `.out `__ + - `link `__ + * -   + -   + -   + -   * - Example 2: EGS Direct-Use Heat - `example2.txt `__ - `.out `__ - `link `__ + * - SAM Single Owner PPA: Direct-Use Heat + - `example_SAM-single-owner-PPA-8_heat.txt `__ + - `.out `__ + - `link `__ + * -   + -   + -   + -   * - Example 3: EGS Co-generation - `example3.txt `__ - `.out `__ - `link `__ + * - SAM Single Owner PPA: CHP + - `example_SAM-single-owner-PPA-7_chp.txt `__ + - `.out `__ + - `link `__ + * - SAM Single Owner PPA: CHP with Carbon Credits + - `example_SAM-single-owner-PPA-7b_chp-cc.txt `__ + - `.out `__ + - `link `__ + * - SAM Single Owner PPA: CHP with Surface Plant Capital Cost + - `example_SAM-single-owner-PPA-7c.txt `__ + - `.out `__ + - `link `__ + * - SAM Single Owner PPA: CHP: Bottoming Cycle + - `example_SAM-single-owner-PPA-7d_chp-bottoming.txt `__ + - `.out `__ + - `link `__ + * - SAM Single Owner PPA: CHP: Parallel Cycle + - `example_SAM-single-owner-PPA-7e_chp-parallel.txt `__ + - `.out `__ + - `link `__ + * -   + -   + -   + -   * - Example 4: Hydrothermal Electricity - `example4.txt `__ - `.out `__ @@ -200,6 +248,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `example4.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 5: User-Provided Reservoir Data (File Parameter) - `example5.txt `__ - `.out `__ @@ -208,6 +260,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `example5b.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 6: TOUGH2 (Multiple Gradients) - `example6.txt `__ - `.out `__ @@ -216,6 +272,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `example7.txt `__ - `.out `__ - \* + * -   + -   + -   + -   * - Example 8: Cornell Direct-Use Heat - `example8.txt `__ - `.out `__ @@ -224,18 +284,38 @@ Example-specific web interface deeplinks are listed in the Link column. - `example9.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 10: Heat Pump - `example10_HP.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 11: Absorption Chiller - `example11_AC.txt `__ - `.out `__ - `link `__ + * - SAM Single Owner PPA: Cooling + - `example_SAM-single-owner-PPA-9_cooling.txt `__ + - `.out `__ + - `link `__ + * -   + -   + -   + -   * - Example 12: District Heating - `example12_DH.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 13: Redrilling due to Drawdown - `example13.txt `__ - `.out `__ @@ -244,10 +324,18 @@ Example-specific web interface deeplinks are listed in the Link column. - `example13.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Example 14: Data Center - `example14_data-center.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - CLGS: Coaxial sCO2: Heat - `[...]Coaxial_sCO2_heat.txt `__ - `.out `__ @@ -284,6 +372,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `example_SBT_ULoop.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - SUTRA Example 1 - `SUTRAExample1.txt `__ - `.out `__ @@ -300,6 +392,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `example_PTC.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Fervo Project Red (2023) - `Fervo_Norbeck_Latimer_2023.txt `__ - `.out `__ @@ -324,6 +420,10 @@ Example-specific web interface deeplinks are listed in the Link column. - `Fervo_Project_Cape-4.txt `__ - `.out `__ - `link `__ + * -   + -   + -   + -   * - Superhot Rock (SHR) Example 1 - `example_SHR-1.txt `__ - `.out `__ @@ -336,18 +436,14 @@ Example-specific web interface deeplinks are listed in the Link column. - `example_SHR-3.txt `__ - `.out `__ - `link `__ - * - SAM Single Owner PPA: 50 MWe (`documentation `__) - - `example_SAM-single-owner-PPA.txt `__ - - `.out `__ - - `link `__ + * -   + -   + -   + -   * - SAM Single Owner PPA: 400 MWe BICYCLE Comparison - `example_SAM-single-owner-PPA-2.txt `__ - `.out `__ - `link `__ - * - SAM Single Owner PPA: 50 MWe with Add-ons - - `example_SAM-single-owner-PPA-3.txt `__ - - `.out `__ - - `link `__ * - SAM Single Owner PPA: 50 MWe with Royalties - `example_SAM-single-owner-PPA-4.txt `__ - `.out `__ @@ -364,34 +460,6 @@ Example-specific web interface deeplinks are listed in the Link column. - `example_SAM-single-owner-PPA-6_carbon-revenue.txt `__ - `.out `__ - `link `__ - * - SAM Single Owner PPA: CHP - - `example_SAM-single-owner-PPA-7_chp.txt `__ - - `.out `__ - - `link `__ - * - SAM Single Owner PPA: CHP with Carbon Credits - - `example_SAM-single-owner-PPA-7b_chp-cc.txt `__ - - `.out `__ - - `link `__ - * - SAM Single Owner PPA: CHP with Surface Plant Capital Cost - - `example_SAM-single-owner-PPA-7c.txt `__ - - `.out `__ - - `link `__ - * - SAM Single Owner PPA: CHP: Bottoming Cycle - - `example_SAM-single-owner-PPA-7d_chp-bottoming.txt `__ - - `.out `__ - - `link `__ - * - SAM Single Owner PPA: CHP: Parallel Cycle - - `example_SAM-single-owner-PPA-7e_chp-parallel.txt `__ - - `.out `__ - - `link `__ - * - SAM Single Owner PPA: Direct-Use Heat - - `example_SAM-single-owner-PPA-8_heat.txt `__ - - `.out `__ - - `link `__ - * - SAM Single Owner PPA: Cooling - - `example_SAM-single-owner-PPA-9_cooling.txt `__ - - `.out `__ - - `link `__ .. raw:: html diff --git a/docs/conf.py b/docs/conf.py index 67e0874a9..5e4e8c58d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2025' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.14.1' +version = release = '3.15.0' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 5f5459147..35b274de5 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.14.1', + version='3.15.0', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index a70f9aa7a..613cf7be0 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -2942,6 +2942,27 @@ def Calculate(self, model: Model) -> None: if self.DoSDACGTCalculations.value: model.sdacgteconomics.Calculate(model) + # Consolidate S-DAC-GT CAPEX and OPEX into the main plant ledgers + max_carbon_capacity_tonnes = np.max(model.sdacgteconomics.CarbonExtractedAnnually.value) + sdac_overnight_capex_musd = ( + model.sdacgteconomics.CAPEX.value * model.sdacgteconomics.CAPEX_mult.value * max_carbon_capacity_tonnes) / 1_000_000.0 + self.CCap.value += sdac_overnight_capex_musd + + avg_carbon_extracted_tonnes = np.average(model.sdacgteconomics.CarbonExtractedAnnually.value) + sdac_annual_opex_usd = ( + model.sdacgteconomics.OPEX.value + model.sdacgteconomics.storage.value + model.sdacgteconomics.transport.value) * avg_carbon_extracted_tonnes + + if model.sdacgteconomics.sorbent_replacement_frequency.value > 0: + max_carbon_capacity_tonnes = np.max(model.sdacgteconomics.CarbonExtractedAnnually.value) + replacements_per_lifetime = int( + model.surfaceplant.plant_lifetime.value / model.sdacgteconomics.sorbent_replacement_frequency.value) + annualized_replacement_usd = ( + max_carbon_capacity_tonnes * model.sdacgteconomics.sorbent_replacement_cost.value * replacements_per_lifetime) / model.surfaceplant.plant_lifetime.value + sdac_annual_opex_usd += annualized_replacement_usd + + sdac_annual_opex_musd = sdac_annual_opex_usd / 1_000_000.0 + self.Coam.value += sdac_annual_opex_musd + self.calculate_cashflow(model) # Calculate more financial values using numpy financials @@ -3822,6 +3843,13 @@ def calculate_cashflow(self, model: Model) -> None: self.TotalRevenue.value[i] = self.TotalRevenue.value[i] + self.CarbonRevenue.value[i] #self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i] + self.CarbonCummCashFlow.value[i] + if self.DoSDACGTCalculations.value: + for i in range(model.surfaceplant.construction_years.value, + model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, + 1): + sdac_index = i - model.surfaceplant.construction_years.value + self.TotalRevenue.value[i] += (model.sdacgteconomics.CarbonRevenue.value[sdac_index] / 1_000_000.0) + # for the sake of display, insert zeros at the beginning of the pricing arrays for i in range(0, model.surfaceplant.construction_years.value, 1): self.ElecPrice.value.insert(0, 0.0) diff --git a/src/geophires_x/EconomicsS_DAC_GT.py b/src/geophires_x/EconomicsS_DAC_GT.py index 78cf9a5c1..d26cc1f2d 100644 --- a/src/geophires_x/EconomicsS_DAC_GT.py +++ b/src/geophires_x/EconomicsS_DAC_GT.py @@ -1,7 +1,9 @@ import sys import os import numpy as np -from geophires_x.Parameter import floatParameter, OutputParameter, ReadParameter + +from geophires_x.EconomicsUtils import carbon_extracted_annually_output_parameter +from geophires_x.Parameter import floatParameter, OutputParameter, ReadParameter, intParameter from geophires_x.Units import * from geophires_x.OptionList import EndUseOptions import geophires_x.Model as Model @@ -43,7 +45,7 @@ def __init__(self, model: Model): """ model.logger.info(f"Init {str(__class__)}: {sys._getframe().f_code.co_name}") - # These dictionaries contains a list of all the parameters set in this object, stored as "Parameter" and + # These dictionaries contain a list of all the parameters set in this object, stored as "Parameter" and # OutputParameter Objects. This will allow us later to access them in a user interface and get that list, # along with unit type, preferred units, etc. self.ParameterDict = {} @@ -219,6 +221,56 @@ def __init__(self, model: Model): ErrMessage="assume default Percent Energy Devoted To Process (50%)", ToolTipText="Percent Energy Devoted To Process (%)" ) + self.carbon_credit_price = self.ParameterDict[self.carbon_credit_price.Name] = floatParameter( + "S-DAC-GT Carbon Credit Price", + value=180.0, + DefaultValue=180.0, + Min=0.0, + Max=1000.0, + UnitType=Units.COSTPERMASS, + PreferredUnits=CostPerMassUnit.DOLLARSPERTONNE, + CurrentUnits=CostPerMassUnit.DOLLARSPERTONNE, + ErrMessage="assume default Carbon Credit Price (180 USD per tonne CO2)", + ToolTipText="Carbon Credit or Market Price (USD per tonne CO2)" + ) + + self.carbon_credit_duration = self.ParameterDict[self.carbon_credit_duration.Name] = floatParameter( + "S-DAC-GT Carbon Credit Duration", + value=12.0, + DefaultValue=12.0, + Min=0.0, + Max=100.0, + UnitType=Units.TIME, + PreferredUnits=TimeUnit.YEAR, + CurrentUnits=TimeUnit.YEAR, + ErrMessage="assume default Carbon Credit Duration (12 years)", + ToolTipText="Duration for which the carbon credit can be claimed (e.g., 12 years for US 45Q)" + ) + + self.sorbent_replacement_frequency = self.ParameterDict[self.sorbent_replacement_frequency.Name] = intParameter( + "S-DAC-GT Sorbent Replacement Frequency", + value=0, + DefaultValue=0, + AllowableRange=list(range(1, 101, 1)), + UnitType=Units.TIME, + PreferredUnits=TimeUnit.YEAR, + CurrentUnits=TimeUnit.YEAR, + ErrMessage="assume default Sorbent Replacement Frequency (0 years - no discrete replacement)", + ToolTipText="Frequency of solid sorbent replacement in years (0 disables step-function replacement costs)" + ) + + self.sorbent_replacement_cost = self.ParameterDict[self.sorbent_replacement_cost.Name] = floatParameter( + "S-DAC-GT Sorbent Replacement Cost", + value=0.0, + DefaultValue=0.0, + Min=0.0, + Max=1000.0, + UnitType=Units.COSTPERMASS, + PreferredUnits=CostPerMassUnit.DOLLARSPERTONNE, + CurrentUnits=CostPerMassUnit.DOLLARSPERTONNE, + ErrMessage="assume default Sorbent Replacement Cost (0 USD per tonne CO2)", + ToolTipText="Cost to replace solid sorbent (USD per tonne CO2 capacity)" + ) # local variable initiation # Capital Recovery Rate or Fixed Charge Factor - set initially for definitions @@ -310,12 +362,8 @@ def __init__(self, model: Model): PreferredUnits=CurrencyUnit.DOLLARS, CurrentUnits=CurrencyUnit.DOLLARS ) - self.CarbonExtractedAnnually = self.OutputParameterDict[self.CarbonExtractedAnnually.Name] = OutputParameter( - Name="Tonnes per Year CO2 extracted", - UnitType=Units.MASSPERTIME, - PreferredUnits=MassPerTimeUnit.TONNEPERYEAR, - CurrentUnits=MassPerTimeUnit.TONNEPERYEAR - ) + self.CarbonExtractedAnnually = self.OutputParameterDict[self.CarbonExtractedAnnually.Name] = \ + carbon_extracted_annually_output_parameter() self.S_DAC_GTCummCarbonExtracted = self.OutputParameterDict[ self.S_DAC_GTCummCarbonExtracted.Name] = OutputParameter( Name="Running Carbon Capture", @@ -335,6 +383,21 @@ def __init__(self, model: Model): PreferredUnits=CostPerMassUnit.DOLLARSPERTONNE, CurrentUnits=CostPerMassUnit.DOLLARSPERTONNE ) + self.CarbonRevenue = OutputParameter( + Name="Annual Carbon Revenue", + UnitType=Units.CURRENCYFREQUENCY, + PreferredUnits=CurrencyFrequencyUnit.DOLLARSPERYEAR, + CurrentUnits=CurrencyFrequencyUnit.DOLLARSPERYEAR + ) + self.OutputParameterDict[self.CarbonRevenue.Name] = self.CarbonRevenue + + self.CarbonCummCashFlow = OutputParameter( + Name="Cumulative Carbon Revenue", + UnitType=Units.CURRENCY, + PreferredUnits=CurrencyUnit.DOLLARS, + CurrentUnits=CurrencyUnit.DOLLARS + ) + self.OutputParameterDict[self.CarbonCummCashFlow.Name] = self.CarbonCummCashFlow model.logger.info(f"Complete {str(__class__)}: {sys._getframe().f_code.co_name}") @@ -374,7 +437,13 @@ def read_parameters(self, model: Model) -> None: # handle special cases # none in this case so far else: - model.logger.info("No parameters read becuase no content provided") + model.logger.info("No parameters read because no content provided") + + + if model.economics.DoCarbonCalculations.value: + raise NotImplementedError(f'Only one of {self.DoSDACGTCalculations.Name} and ' + f'{model.economics.DoCarbonCalculations.Name} may be True.') + model.logger.info(f"read parameters complete {str(__class__)}: {sys._getframe().f_code.co_name}") def calculate_CRF(self, wacc: float, num_years: float) -> float: @@ -483,6 +552,13 @@ def range_check(self) -> tuple: storage_max) return True, error_message + if not (self.carbon_credit_duration.Min + <= self.carbon_credit_duration.value + <= self.carbon_credit_duration.Max): + error_message = "S-DAC-GT ERROR: Carbon Credit Duration should be between {} and {}".format( + self.carbon_credit_duration.Min, self.carbon_credit_duration.Max) + return True, error_message + return False, "" def geo_therm_cost(self, power_cost: float, CAPEX_mult: float, OPEX_mult: float, depth: float, @@ -599,12 +675,13 @@ def Calculate(self, model: Model) -> None: # Convert from $/McF to $/kWh_th, but don't change any parameters value directly - it will throw off the rehydration NG_price = self.NG_price.value / self.NG_EnergyDensity.value NG_totalcost = self.therm.value * NG_price - self.LCOH.value, self.kWh_e_per_kWh_th.value = self.geo_therm_cost(model.surfaceplant.electricity_cost_to_buy.value, - self.CAPEX_mult.value, self.OPEX_mult.value, - model.reserv.depth.value * 3280.84, - np.average(model.wellbores.ProducedTemperature.value), - model.wellbores.Tinj.value, - model.wellbores.nprod.value * model.wellbores.prodwellflowrate.value) + self.LCOH.value, self.kWh_e_per_kWh_th.value = self.geo_therm_cost( + model.surfaceplant.electricity_cost_to_buy.value, + self.CAPEX_mult.value, self.OPEX_mult.value, + model.reserv.depth.value * 3280.84, + np.average(model.wellbores.ProducedTemperature.value), + model.wellbores.Tinj.value, + model.wellbores.nprod.value * model.wellbores.prodwellflowrate.value) geothermal_totalcost = self.LCOH.value * self.therm.value co2_power = self.elec.value / 1000 * self.power_co2intensity.value co2_elec_heat = self.therm.value / 1000 * self.power_co2intensity.value @@ -621,7 +698,8 @@ def Calculate(self, model: Model) -> None: # calculate the net impact of S-DAC-GT on the annual production of the model avg_first_law_eff = np.average(model.surfaceplant.FirstLawEfficiency.value) - self.tot_heat_energy_consumed_per_tonne.value = (self.elec.value / avg_first_law_eff) + self.therm.value # kWh_th/tonne + self.tot_heat_energy_consumed_per_tonne.value = ( + self.elec.value / avg_first_law_eff) + self.therm.value # kWh_th/tonne self.tot_cost_per_tonne.value = CAPEX + self.OPEX.value + self.storage.value + self.transport.value # USD/tonne self.percent_thermal_energy_going_to_heat.value = self.therm.value / self.tot_heat_energy_consumed_per_tonne.value @@ -637,18 +715,35 @@ def Calculate(self, model: Model) -> None: # That then gives us the revenue, since we have a carbon price model # We can also get annual cash flow from it. for i in range(0, model.surfaceplant.plant_lifetime.value, 1): - self.CarbonExtractedAnnually.value[i] = (self.EnergySplit.value * model.surfaceplant.HeatkWhExtracted.value[i]) / self.tot_heat_energy_consumed_per_tonne.value + self.CarbonExtractedAnnually.value[i] = (self.EnergySplit.value * model.surfaceplant.HeatkWhExtracted.value[ + i]) / self.tot_heat_energy_consumed_per_tonne.value if i == 0: self.S_DAC_GTCummCarbonExtracted.value[i] = self.CarbonExtractedAnnually.value[i] else: - self.S_DAC_GTCummCarbonExtracted.value[i] = self.S_DAC_GTCummCarbonExtracted.value[i - 1] + self.CarbonExtractedAnnually.value[i] + self.S_DAC_GTCummCarbonExtracted.value[i] = self.S_DAC_GTCummCarbonExtracted.value[i - 1] + \ + self.CarbonExtractedAnnually.value[i] self.CarbonExtractedTotal.value = self.CarbonExtractedTotal.value + self.CarbonExtractedAnnually.value[i] self.S_DAC_GTAnnualCost.value[i] = self.CarbonExtractedAnnually.value[i] * self.tot_cost_per_tonne.value if i == 0: self.S_DAC_GTCummCashFlow.value[i] = self.S_DAC_GTAnnualCost.value[i] else: - self.S_DAC_GTCummCashFlow.value[i] = self.S_DAC_GTCummCashFlow.value[i - 1] + self.S_DAC_GTAnnualCost.value[i] - self.CummCostPerTonne.value[i] = self.S_DAC_GTCummCashFlow.value[i] / self.S_DAC_GTCummCarbonExtracted.value[i] + self.S_DAC_GTCummCashFlow.value[i] = self.S_DAC_GTCummCashFlow.value[i - 1] + \ + self.S_DAC_GTAnnualCost.value[i] + self.CummCostPerTonne.value[i] = self.S_DAC_GTCummCashFlow.value[i] / \ + self.S_DAC_GTCummCarbonExtracted.value[i] + + max_carbon_capacity_tonnes = np.max(self.CarbonExtractedAnnually.value) + self.AnnualOPEX_USD = [0.0] * model.surfaceplant.plant_lifetime.value + for i in range(0, model.surfaceplant.plant_lifetime.value, 1): + base_opex = self.CarbonExtractedAnnually.value[i] * ( + self.OPEX.value + self.storage.value + self.transport.value) + replacement_cost = 0.0 + operational_year = i + 1 + if self.sorbent_replacement_frequency.value > 0 and ( + operational_year % self.sorbent_replacement_frequency.value) == 0: + replacement_cost = max_carbon_capacity_tonnes * self.sorbent_replacement_cost.value + + self.AnnualOPEX_USD[i] = base_opex + replacement_cost # We need to update the heat and electricity generated because we have consumed # some (all) of it to do the capture, so when they get used in the final economic calculation (below), @@ -657,28 +752,34 @@ def Calculate(self, model: Model) -> None: if model.surfaceplant.enduse_option.value is not EndUseOptions.HEAT: # all these end-use options have an electricity generation component model.surfaceplant.TotalkWhProduced.value[i] = model.surfaceplant.TotalkWhProduced.value[i] - ( - self.CarbonExtractedAnnually.value[i] * self.elec.value) + self.CarbonExtractedAnnually.value[i] * self.elec.value) model.surfaceplant.NetkWhProduced.value[i] = model.surfaceplant.NetkWhProduced.value[i] - ( - self.CarbonExtractedAnnually.value[i] * self.elec.value) + self.CarbonExtractedAnnually.value[i] * self.elec.value) if model.surfaceplant.enduse_option.value is not EndUseOptions.ELECTRICITY: model.surfaceplant.HeatkWhProduced.value[i] = model.surfaceplant.HeatkWhProduced.value[i] - ( - self.CarbonExtractedAnnually.value[i] * self.therm.value) + self.CarbonExtractedAnnually.value[i] * self.therm.value) else: # all the end-use option of direct-use only component model.surfaceplant.HeatkWhProduced.value[i] = (model.surfaceplant.HeatkWhProduced.value[i] - - (self.CarbonExtractedAnnually.value[i] * self.therm.value)) - - # FIXME TODO https://github.com/NREL/GEOPHIRES-X/issues/341?title=S-DAC+does+not+calculate+carbon+revenue - # Build a revenue generation model for the carbon capture, assuming the capture is being sequestered and that - # there is some sort of credit involved for doing that sequestering - # note that there may already be values in the CarbonRevenue array, so we need to - # add to them, not just set them. If there isn't values, there, the array will be filed with zeros, so adding won't be a problem - #total_duration = model.surfaceplant.plant_lifetime.value - #for i in range(0, total_duration, 1): - # model.sdacgteconomics.CarbonRevenue.value[i] = (model.sdacgteconomics.CarbonRevenue.value[i] + - # (self.CarbonExtractedAnnually.value[i] * model.economics.CarbonPrice.value[i])) - # if i > 0: - # model.economics.CarbonCummCashFlow.value[i] = model.economics.CarbonCummCashFlow.value[i - 1] + model.economics.CarbonRevenue.value[i] + (self.CarbonExtractedAnnually.value[ + i] * self.therm.value)) + + # Calculate Carbon Revenue based on S-DAC-GT specific credit price and duration + self.CarbonRevenue.value = [0.0] * model.surfaceplant.plant_lifetime.value + self.CarbonCummCashFlow.value = [0.0] * model.surfaceplant.plant_lifetime.value + model.economics.CarbonPrice.value = [0.0] * model.surfaceplant.plant_lifetime.value + model.economics.CarbonPrice.CurrentUnits = self.carbon_credit_price.CurrentUnits + for i in range(0, model.surfaceplant.plant_lifetime.value, 1): + # Enforce the parameterized statutory limit for tax credits + applicable_price = self.carbon_credit_price.value if i < self.carbon_credit_duration.value else 0.0 + model.economics.CarbonPrice.value[i] = applicable_price + + self.CarbonRevenue.value[i] = self.CarbonExtractedAnnually.value[i] * model.economics.CarbonPrice.value[i] + if i == 0: + self.CarbonCummCashFlow.value[i] = self.CarbonRevenue.value[i] + else: + self.CarbonCummCashFlow.value[i] = self.CarbonCummCashFlow.value[i - 1] + \ + self.CarbonRevenue.value[i] self._calculate_derived_outputs(model) - model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}') + model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}') \ No newline at end of file diff --git a/src/geophires_x/EconomicsSam.py b/src/geophires_x/EconomicsSam.py index 366e01ece..a5990103c 100644 --- a/src/geophires_x/EconomicsSam.py +++ b/src/geophires_x/EconomicsSam.py @@ -145,6 +145,12 @@ def _inv_msg(param_name: str, invalid_value: Any, supported_description: str) -> # temporarily switching/testing and/or migrating between schedule-based and rate-based more user # friendly by only requiring enabling/disabling 2 parameters rather than up to 6. + if econ.DoSDACGTCalculations.value: + raise NotImplementedError( + 'S-DAC is not currently supported for SAM Economic Models. ' + 'See https://github.com/NatLabRockies/GEOPHIRES-X/issues/511.' # TODO to implement + ) + def _validate_construction_capex_schedule( econ_capex_schedule: listParameter, construction_years: int, model: Model diff --git a/src/geophires_x/EconomicsUtils.py b/src/geophires_x/EconomicsUtils.py index 70a7c7598..1498b97ea 100644 --- a/src/geophires_x/EconomicsUtils.py +++ b/src/geophires_x/EconomicsUtils.py @@ -1,7 +1,15 @@ from __future__ import annotations from geophires_x.Parameter import OutputParameter -from geophires_x.Units import Units, PercentUnit, TimeUnit, CurrencyUnit, CurrencyFrequencyUnit, EnergyCostUnit +from geophires_x.Units import ( + Units, + PercentUnit, + TimeUnit, + CurrencyUnit, + CurrencyFrequencyUnit, + EnergyCostUnit, + MassPerTimeUnit, +) CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME = 'Construction CAPEX Schedule' @@ -245,6 +253,15 @@ def investment_tax_credit_output_parameter() -> OutputParameter: ) +def carbon_extracted_annually_output_parameter() -> OutputParameter: + return OutputParameter( + Name="Tonnes per Year CO2 extracted", + UnitType=Units.MASSPERTIME, + PreferredUnits=MassPerTimeUnit.TONNEPERYEAR, + CurrentUnits=MassPerTimeUnit.TONNEPERYEAR, + ) + + def expand_schedule_dsl(schedule_strings: list[str | float], total_years: int) -> list[float]: """ Deprecated, call ParameterUtils.expand_schedule_dsl diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 12d48bed0..c95e46dc3 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import datetime import math import time @@ -933,6 +934,32 @@ def o(output_param: OutputParameter): else: return output_param + econ_CarbonPrice = copy.deepcopy(econ.CarbonPrice) + econ_CarbonRevenue = copy.deepcopy(econ.CarbonRevenue) + econ_CarbonCummCashFlow = copy.deepcopy(econ.CarbonCummCashFlow) + + if econ.DoSDACGTCalculations.value: + econ_CarbonRevenue = copy.deepcopy(model.sdacgteconomics.CarbonRevenue) + econ_CarbonRevenue.value = [ + *([0]*model.surfaceplant.construction_years.value), + *model.sdacgteconomics.CarbonRevenue.value + ] + + def _convert(gt_param, econ_param) -> None: + gt_param.value = gt_param.quantity().to(econ_param.CurrentUnits).magnitude + gt_param.CurrentUnits = econ_param.CurrentUnits + + _convert(econ_CarbonRevenue, econ.CarbonRevenue) + + econ_CarbonCummCashFlow = copy.deepcopy(model.sdacgteconomics.CarbonCummCashFlow) + econ_CarbonCummCashFlow.value = [ + *([0] * model.surfaceplant.construction_years.value), + *model.sdacgteconomics.CarbonCummCashFlow.value + ] + + _convert(econ_CarbonCummCashFlow, econ.CarbonCummCashFlow) + + f.write('Start (' + o(econ.ElecPrice).CurrentUnits.value + ')(' + o(econ.ElecRevenue).CurrentUnits.value + @@ -943,9 +970,9 @@ def o(output_param: OutputParameter): ') |(' + o(econ.CoolingPrice).CurrentUnits.value + ') (' + o(econ.CoolingRevenue).CurrentUnits.value + ') (' + o(econ.CoolingCummRevenue).CurrentUnits.value + - ') |(' + o(econ.CarbonPrice).CurrentUnits.value + - ') (' + o(econ.CarbonRevenue).CurrentUnits.value + - ') (' + o(econ.CarbonCummCashFlow).CurrentUnits.value + + ') |(' + o(econ_CarbonPrice).CurrentUnits.value + + ') (' + o(econ_CarbonRevenue).CurrentUnits.value + + ') (' + o(econ_CarbonCummCashFlow).CurrentUnits.value + ') |(' + o(econ.Coam).CurrentUnits.value + ') (' + o(econ.TotalRevenue).CurrentUnits.value + ') (' + o(econ.TotalCummRevenue).CurrentUnits.value + ')\n') @@ -959,7 +986,7 @@ def o(output_param: OutputParameter): else: opex = o(econ.Coam).value f.write( - f'{ii:3.0f} {o(econ.ElecPrice).value[ii]:5.2f} {o(econ.ElecRevenue).value[ii]:5.2f} {o(econ.ElecCummRevenue).value[ii]:5.2f} | {o(econ.HeatPrice).value[ii]:5.2f} {o(econ.HeatRevenue).value[ii]:5.2f} {o(econ.HeatCummRevenue).value[ii]:5.2f} | {o(econ.CoolingPrice).value[ii]:5.2f} {o(econ.CoolingRevenue).value[ii]:5.2f} {o(econ.CoolingCummRevenue).value[ii]:5.2f} | {o(econ.CarbonPrice).value[ii]:5.2f} {o(econ.CarbonRevenue).value[ii]:5.2f} {o(econ.CarbonCummCashFlow).value[ii]:5.2f} | {opex:5.2f} {o(econ.TotalRevenue).value[ii]:5.2f} {o(econ.TotalCummRevenue).value[ii]:5.2f}\n') + f'{ii:3.0f} {o(econ.ElecPrice).value[ii]:5.2f} {o(econ.ElecRevenue).value[ii]:5.2f} {o(econ.ElecCummRevenue).value[ii]:5.2f} | {o(econ.HeatPrice).value[ii]:5.2f} {o(econ.HeatRevenue).value[ii]:5.2f} {o(econ.HeatCummRevenue).value[ii]:5.2f} | {o(econ.CoolingPrice).value[ii]:5.2f} {o(econ.CoolingRevenue).value[ii]:5.2f} {o(econ.CoolingCummRevenue).value[ii]:5.2f} | {o(econ_CarbonPrice).value[ii]:5.2f} {o(econ_CarbonRevenue).value[ii]:5.2f} {o(econ_CarbonCummCashFlow).value[ii]:5.2f} | {opex:5.2f} {o(econ.TotalRevenue).value[ii]:5.2f} {o(econ.TotalCummRevenue).value[ii]:5.2f}\n') f.write(NL) # noinspection PyMethodMayBeStatic diff --git a/src/geophires_x/OutputsS_DAC_GT.py b/src/geophires_x/OutputsS_DAC_GT.py index ba2838587..b12fef9c7 100644 --- a/src/geophires_x/OutputsS_DAC_GT.py +++ b/src/geophires_x/OutputsS_DAC_GT.py @@ -81,21 +81,26 @@ def PrintOutputs(self, model) -> tuple: model.sdacgteconomics.S_DAC_GTCummCashFlow.value sdac_df[f'Cum. Cost Per Tonne ({model.sdacgteconomics.CummCostPerTonne.PreferredUnits.value})|:,.2f'] = \ model.sdacgteconomics.CummCostPerTonne.value + sdac_df[f'Carbon Revenue ({model.sdacgteconomics.CarbonRevenue.PreferredUnits.value})|:,.2f'] = \ + model.sdacgteconomics.CarbonRevenue.value + sdac_df[f'Cum. Carbon Revenue ({model.sdacgteconomics.CarbonCummCashFlow.PreferredUnits.value})|:,.2f'] = \ + model.sdacgteconomics.CarbonCummCashFlow.value f.write(NL) f.write(" **********************" + NL) f.write(" * S-DAC-GT PROFILE *" + NL) f.write(" **********************" + NL) - f.write("Year Carbon Cumm. Carbon S-DAC-GT S-DAC-GT Cumm. Cumm. Cost" + NL) - f.write("Since Captured Captured Annual Cost Cash Flow Cost Per Tonne" + NL) + f.write("Year Carbon Cumm. Carbon S-DAC-GT S-DAC-GT Cumm. Cumm. Cost Annual Carbon" + NL) + f.write("Since Captured Captured Annual Cost Cash Flow Cost Per Tonne Revenue" + NL) f.write("Start ("+model.sdacgteconomics.CarbonExtractedAnnually.PreferredUnits.value + ") ("+model.sdacgteconomics.S_DAC_GTCummCarbonExtracted.PreferredUnits.value + ") ("+model.sdacgteconomics.S_DAC_GTAnnualCost.PreferredUnits.value + ") ("+model.sdacgteconomics.S_DAC_GTCummCashFlow.PreferredUnits.value + - ") ("+model.sdacgteconomics.CummCostPerTonne.PreferredUnits.value + ")" +NL) + ") ("+model.sdacgteconomics.CummCostPerTonne.PreferredUnits.value + + ") (" + model.sdacgteconomics.CarbonRevenue.PreferredUnits.value + ")" + NL) i = 0 for i in range(0, model.surfaceplant.plant_lifetime.value, 1): - f.write(f" {i+1:3.0f} {model.sdacgteconomics.CarbonExtractedAnnually.value[i]:,.2f} {model.sdacgteconomics.S_DAC_GTCummCarbonExtracted.value[i]:,.2f} {model.sdacgteconomics.S_DAC_GTAnnualCost.value[i]:,.2f} {model.sdacgteconomics.S_DAC_GTCummCashFlow.value[i]:,.2f} {model.sdacgteconomics.CummCostPerTonne.value[i]:.2f}" + NL) + f.write(f" {i+1:3.0f} {model.sdacgteconomics.CarbonExtractedAnnually.value[i]:,.2f} {model.sdacgteconomics.S_DAC_GTCummCarbonExtracted.value[i]:,.2f} {model.sdacgteconomics.S_DAC_GTAnnualCost.value[i]:,.2f} {model.sdacgteconomics.S_DAC_GTCummCashFlow.value[i]:,.2f} {model.sdacgteconomics.CummCostPerTonne.value[i]:.2f} {model.sdacgteconomics.CarbonRevenue.value[i]:,.2f}" + NL) i = i + 1 except BaseException as ex: @@ -106,9 +111,9 @@ def PrintOutputs(self, model) -> tuple: print(msg) model.logger.critical(str(ex)) model.logger.critical(msg) - raise RuntimeError(msg, e) + raise RuntimeError(msg, ex) model.logger.info(f'Complete {str(__class__)}: {__name__}') sdac_df = sdac_df.reset_index() - return sdac_df, sdac_results + return sdac_df, sdac_results \ No newline at end of file diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 7499b9d6d..254db7b7b 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.14.1' +__version__ = '3.15.0' diff --git a/src/geophires_x_client/geophires_x_result.py b/src/geophires_x_client/geophires_x_result.py index a42796a63..8453e34dc 100644 --- a/src/geophires_x_client/geophires_x_result.py +++ b/src/geophires_x_client/geophires_x_result.py @@ -807,6 +807,7 @@ def extract_table_header(lines: list) -> list: 'S-DAC-GT Annual Cost (USD/yr)', 'S-DAC-GT Cumm. Cash Flow (USD)', 'Cumm. Cost Per Tonne (USD/tonne)', + 'Annual Carbon Revenue (USD/yr)', ] try: diff --git a/src/geophires_x_schema_generator/__init__.py b/src/geophires_x_schema_generator/__init__.py index adce8bdc3..f602cedeb 100644 --- a/src/geophires_x_schema_generator/__init__.py +++ b/src/geophires_x_schema_generator/__init__.py @@ -10,6 +10,8 @@ # ruff: noqa: I001 from geophires_x.Model import Model from geophires_x.SBTEconomics import SBTEconomics + +# from geophires_x.EconomicsS_DAC_GT import EconomicsS_DAC_GT # TODO from geophires_x.SBTReservoir import SBTReservoir from geophires_x.SBTWellbores import SBTWellbores @@ -83,6 +85,7 @@ def get_parameter_sources(self) -> list: (SBTEconomics(dummy_model), 'Economics'), (SUTRAEconomics(dummy_model), 'Economics'), (EconomicsAddOns(dummy_model), 'Economics'), + # (EconomicsS_DAC_GT(dummy_model), 'Economics'), # TODO reconcile conflicting output units ] def get_schema_title(self) -> str: diff --git a/tests/examples/S-DAC-GT.out b/tests/examples/S-DAC-GT.out index 75170830c..2de1e3ee9 100644 --- a/tests/examples/S-DAC-GT.out +++ b/tests/examples/S-DAC-GT.out @@ -4,18 +4,18 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.12.1 - Simulation Date: 2026-04-07 - Simulation Time: 10:46 - Calculation Time: 0.135 sec + GEOPHIRES Version: 3.14.1 + Simulation Date: 2026-06-19 + Simulation Time: 10:28 + Calculation Time: 0.141 sec ***SUMMARY OF RESULTS*** End-Use Option: Cogeneration Topping Cycle, Heat sales considered as extra income Average Net Electricity Production: 19.62 MW Average Direct-Use Heat Production: 13.19 MW - Electricity breakeven price: 10.68 cents/kWh - Direct-Use heat breakeven price (LCOH): -56.08 USD/MMBTU + Electricity breakeven price: 24.39 cents/kWh + Direct-Use heat breakeven price (LCOH): -128.08 USD/MMBTU Number of production wells: 3 Number of injection wells: 3 Flowrate per production well: 70.0 kg/sec @@ -29,11 +29,11 @@ Simulation Metadata Accrued financing during construction: 0.00 % Project lifetime: 30 yr Capacity factor: 90.0 % - Project NPV: -82.17 MUSD - Project IRR: -5.86 % - Project VIR=PI=PIR: 0.20 - Project MOIC: -0.30 - Project Payback Period: N/A + Project NPV: -89.80 MUSD + Project IRR: 1.99 % + Project VIR=PI=PIR: 0.57 + Project MOIC: 0.14 + Project Payback Period: 22.52 yr CHP: Percent cost allocation for electrical plant: 92.25 % ***ENGINEERING PARAMETERS*** @@ -99,7 +99,7 @@ Simulation Metadata of which Heat Plant Cost: 5.03 MUSD Field gathering system costs: 3.16 MUSD Total surface equipment costs: 68.06 MUSD - Total capital costs: 102.56 MUSD + Total capital costs: 210.58 MUSD ***OPERATING AND MAINTENANCE COSTS (M$/yr)*** @@ -107,7 +107,7 @@ Simulation Metadata Wellfield maintenance costs: 0.74 MUSD/yr Power plant maintenance costs: 2.34 MUSD/yr Water costs: 0.11 MUSD/yr - Total operating and maintenance costs: 3.19 MUSD/yr + Total operating and maintenance costs: 8.90 MUSD/yr ***SURFACE EQUIPMENT SIMULATION RESULTS*** @@ -214,37 +214,37 @@ 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 -102.56 -102.56 - 1 5.50 5.15 5.15 | 2.50 -0.06 -0.06 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.90 -100.66 - 2 5.50 5.15 10.30 | 2.50 -0.06 -0.13 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.90 -98.77 - 3 5.50 5.15 15.45 | 2.50 -0.06 -0.19 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.90 -96.87 - 4 5.50 5.15 20.60 | 2.50 -0.06 -0.25 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.90 -94.97 - 5 5.50 5.15 25.74 | 2.50 -0.06 -0.32 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.89 -93.08 - 6 5.50 5.15 30.89 | 2.50 -0.06 -0.38 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.89 -91.19 - 7 5.50 5.14 36.03 | 2.50 -0.06 -0.45 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.88 -89.30 - 8 5.50 5.13 41.16 | 2.50 -0.07 -0.51 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.87 -87.43 - 9 5.50 5.11 46.26 | 2.50 -0.07 -0.58 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.85 -85.58 - 10 5.50 5.08 51.35 | 2.50 -0.07 -0.65 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.82 -83.76 - 11 5.50 5.05 56.40 | 2.50 -0.07 -0.72 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.79 -81.97 - 12 5.50 5.01 61.41 | 2.50 -0.08 -0.80 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.74 -80.22 - 13 5.50 4.97 66.38 | 2.50 -0.08 -0.89 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.69 -78.53 - 14 5.50 4.92 71.29 | 2.50 -0.09 -0.97 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.64 -76.89 - 15 5.50 4.86 76.16 | 2.50 -0.09 -1.07 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.58 -75.31 - 16 5.50 4.81 80.96 | 2.50 -0.10 -1.17 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.52 -73.80 - 17 5.50 4.75 85.71 | 2.50 -0.11 -1.28 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.45 -72.35 - 18 5.50 4.68 90.40 | 2.50 -0.12 -1.40 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.38 -70.97 - 19 5.50 4.62 95.02 | 2.50 -0.13 -1.52 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.31 -69.66 - 20 5.50 4.56 99.57 | 2.50 -0.13 -1.66 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.23 -68.43 - 21 5.50 4.49 104.06 | 2.50 -0.14 -1.80 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.16 -67.27 - 22 5.50 4.43 108.49 | 2.50 -0.15 -1.96 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.08 -66.19 - 23 5.50 4.36 112.85 | 2.50 -0.16 -2.12 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 1.01 -65.18 - 24 5.50 4.30 117.15 | 2.50 -0.17 -2.29 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.93 -64.25 - 25 5.50 4.23 121.38 | 2.50 -0.18 -2.48 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.86 -63.40 - 26 5.50 4.17 125.54 | 2.50 -0.20 -2.67 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.78 -62.61 - 27 5.50 4.10 129.65 | 2.50 -0.21 -2.88 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.71 -61.91 - 28 5.50 4.04 133.69 | 2.50 -0.22 -3.10 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.63 -61.27 - 29 5.50 3.98 137.67 | 2.50 -0.23 -3.33 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.56 -60.71 - 30 5.50 3.92 141.59 | 2.50 -0.24 -3.57 | 2.50 0.00 0.00 | 0.00 0.00 0.00 | 3.19 0.49 -60.22 + 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 -210.58 -210.58 + 1 5.50 5.15 5.15 | 2.50 -0.06 -0.06 | 2.50 0.00 0.00 | 0.08 14.10 14.10 | 8.90 10.29 -200.29 + 2 5.50 5.15 10.30 | 2.50 -0.06 -0.13 | 2.50 0.00 0.00 | 0.08 14.10 28.20 | 8.90 10.29 -190.00 + 3 5.50 5.15 15.45 | 2.50 -0.06 -0.19 | 2.50 0.00 0.00 | 0.08 14.10 42.30 | 8.90 10.29 -179.71 + 4 5.50 5.15 20.60 | 2.50 -0.06 -0.25 | 2.50 0.00 0.00 | 0.08 14.10 56.40 | 8.90 10.29 -169.43 + 5 5.50 5.15 25.74 | 2.50 -0.06 -0.32 | 2.50 0.00 0.00 | 0.08 14.10 70.50 | 8.90 10.29 -159.14 + 6 5.50 5.15 30.89 | 2.50 -0.06 -0.38 | 2.50 0.00 0.00 | 0.08 14.10 84.59 | 8.90 10.28 -148.86 + 7 5.50 5.14 36.03 | 2.50 -0.06 -0.45 | 2.50 0.00 0.00 | 0.08 14.09 98.68 | 8.90 10.26 -138.60 + 8 5.50 5.13 41.16 | 2.50 -0.07 -0.51 | 2.50 0.00 0.00 | 0.08 14.07 112.75 | 8.90 10.24 -128.36 + 9 5.50 5.11 46.26 | 2.50 -0.07 -0.58 | 2.50 0.00 0.00 | 0.08 14.05 126.80 | 8.90 10.19 -118.17 + 10 5.50 5.08 51.35 | 2.50 -0.07 -0.65 | 2.50 0.00 0.00 | 0.08 14.01 140.81 | 8.90 10.13 -108.04 + 11 5.50 5.05 56.40 | 2.50 -0.07 -0.72 | 2.50 0.00 0.00 | 0.08 13.97 154.79 | 8.90 10.05 -97.99 + 12 5.50 5.01 61.41 | 2.50 -0.08 -0.80 | 2.50 0.00 0.00 | 0.08 13.92 168.71 | 8.90 9.96 -88.03 + 13 5.50 4.97 66.38 | 2.50 -0.08 -0.89 | 2.50 0.00 0.00 | 0.08 13.86 182.57 | 8.90 9.85 -78.18 + 14 5.50 4.92 71.29 | 2.50 -0.09 -0.97 | 2.50 0.00 0.00 | 0.08 13.80 196.37 | 8.90 9.73 -68.45 + 15 5.50 4.86 76.16 | 2.50 -0.09 -1.07 | 2.50 0.00 0.00 | 0.08 13.73 210.10 | 8.90 9.60 -58.85 + 16 5.50 4.81 80.96 | 2.50 -0.10 -1.17 | 2.50 0.00 0.00 | 0.08 13.65 223.75 | 8.90 9.46 -49.39 + 17 5.50 4.75 85.71 | 2.50 -0.11 -1.28 | 2.50 0.00 0.00 | 0.08 13.57 237.32 | 8.90 9.31 -40.08 + 18 5.50 4.68 90.40 | 2.50 -0.12 -1.40 | 2.50 0.00 0.00 | 0.08 13.49 250.81 | 8.90 9.16 -30.93 + 19 5.50 4.62 95.02 | 2.50 -0.13 -1.52 | 2.50 0.00 0.00 | 0.08 13.40 264.21 | 8.90 9.00 -21.93 + 20 5.50 4.56 99.57 | 2.50 -0.13 -1.66 | 2.50 0.00 0.00 | 0.08 13.31 277.52 | 8.90 8.84 -13.09 + 21 5.50 4.49 104.06 | 2.50 -0.14 -1.80 | 2.50 0.00 0.00 | 0.08 13.22 290.74 | 8.90 8.67 -4.42 + 22 5.50 4.43 108.49 | 2.50 -0.15 -1.96 | 2.50 0.00 0.00 | 0.08 13.13 303.87 | 8.90 8.50 4.08 + 23 5.50 4.36 112.85 | 2.50 -0.16 -2.12 | 2.50 0.00 0.00 | 0.08 13.04 316.90 | 8.90 8.34 12.42 + 24 5.50 4.30 117.15 | 2.50 -0.17 -2.29 | 2.50 0.00 0.00 | 0.08 12.94 329.85 | 8.90 8.17 20.58 + 25 5.50 4.23 121.38 | 2.50 -0.18 -2.48 | 2.50 0.00 0.00 | 0.08 12.85 342.70 | 8.90 8.00 28.58 + 26 5.50 4.17 125.54 | 2.50 -0.20 -2.67 | 2.50 0.00 0.00 | 0.08 12.76 355.46 | 8.90 7.83 36.42 + 27 5.50 4.10 129.65 | 2.50 -0.21 -2.88 | 2.50 0.00 0.00 | 0.08 12.67 368.12 | 8.90 7.66 44.08 + 28 5.50 4.04 133.69 | 2.50 -0.22 -3.10 | 2.50 0.00 0.00 | 0.08 12.57 380.69 | 8.90 7.50 51.58 + 29 5.50 3.98 137.67 | 2.50 -0.23 -3.33 | 2.50 0.00 0.00 | 0.08 12.48 393.18 | 8.90 7.33 58.91 + 30 5.50 3.92 141.59 | 2.50 -0.24 -3.57 | 2.50 0.00 0.00 | 0.08 12.39 405.57 | 8.90 7.18 66.09 @@ -272,36 +272,36 @@ ________________________________________________________________________________ ********************** * S-DAC-GT PROFILE * ********************** -Year Carbon Cumm. Carbon S-DAC-GT S-DAC-GT Cumm. Cumm. Cost -Since Captured Captured Annual Cost Cash Flow Cost Per Tonne -Start (tonne/yr) (tonne) (USD/yr) (USD) (USD/tonne) - 1 78,330.80 78,330.80 17,411,627.98 17,411,627.98 222.28 - 2 78,330.80 156,661.61 17,411,627.98 34,823,255.96 222.28 - 3 78,330.79 234,992.39 17,411,624.49 52,234,880.45 222.28 - 4 78,330.21 313,322.60 17,411,495.60 69,646,376.05 222.28 - 5 78,325.28 391,647.88 17,410,399.97 87,056,776.02 222.28 - 6 78,306.33 469,954.20 17,406,187.21 104,462,963.23 222.28 - 7 78,260.16 548,214.36 17,395,925.55 121,858,888.78 222.28 - 8 78,174.81 626,389.18 17,376,954.53 139,235,843.31 222.28 - 9 78,042.38 704,431.56 17,347,515.71 156,583,359.02 222.28 - 10 77,859.43 782,290.99 17,306,849.76 173,890,208.78 222.28 - 11 77,626.22 859,917.21 17,255,012.02 191,145,220.80 222.28 - 12 77,345.52 937,262.73 17,192,616.14 208,337,836.94 222.28 - 13 77,021.56 1,014,284.29 17,120,605.40 225,458,442.34 222.28 - 14 76,659.30 1,090,943.59 17,040,081.18 242,498,523.53 222.28 - 15 76,263.89 1,167,207.48 16,952,186.81 259,450,710.33 222.28 - 16 75,840.32 1,243,047.80 16,858,034.91 276,308,745.24 222.28 - 17 75,393.28 1,318,441.08 16,758,665.93 293,067,411.17 222.28 - 18 74,927.04 1,393,368.11 16,655,027.43 309,722,438.60 222.28 - 19 74,445.39 1,467,813.51 16,547,966.62 326,270,405.23 222.28 - 20 73,951.72 1,541,765.22 16,438,231.02 342,708,636.25 222.28 - 21 73,448.95 1,615,214.17 16,326,473.66 359,035,109.91 222.28 - 22 72,939.63 1,688,153.81 16,213,260.66 375,248,370.57 222.28 - 23 72,425.96 1,760,579.76 16,099,079.76 391,347,450.33 222.28 - 24 71,909.81 1,832,489.57 15,984,349.02 407,331,799.35 222.28 - 25 71,392.80 1,903,882.37 15,869,425.07 423,201,224.42 222.28 - 26 70,876.27 1,974,758.64 15,754,610.85 438,955,835.27 222.28 - 27 70,361.40 2,045,120.04 15,640,162.58 454,595,997.86 222.28 - 28 69,849.14 2,114,969.18 15,526,295.96 470,122,293.82 222.28 - 29 69,340.31 2,184,309.49 15,413,191.67 485,535,485.49 222.28 - 30 68,860.68 2,253,170.17 15,306,577.89 500,842,063.38 222.28 +Year Carbon Cumm. Carbon S-DAC-GT S-DAC-GT Cumm. Cumm. Cost Annual Carbon +Since Captured Captured Annual Cost Cash Flow Cost Per Tonne Revenue +Start (tonne/yr) (tonne) (USD/yr) (USD) (USD/tonne) (USD/yr) + 1 78,330.80 78,330.80 17,411,627.98 17,411,627.98 222.28 14,099,544.46 + 2 78,330.80 156,661.61 17,411,627.98 34,823,255.96 222.28 14,099,544.46 + 3 78,330.79 234,992.39 17,411,624.49 52,234,880.45 222.28 14,099,541.64 + 4 78,330.21 313,322.60 17,411,495.60 69,646,376.05 222.28 14,099,437.26 + 5 78,325.28 391,647.88 17,410,399.97 87,056,776.02 222.28 14,098,550.05 + 6 78,306.33 469,954.20 17,406,187.21 104,462,963.23 222.28 14,095,138.65 + 7 78,260.16 548,214.36 17,395,925.55 121,858,888.78 222.28 14,086,828.98 + 8 78,174.81 626,389.18 17,376,954.53 139,235,843.31 222.28 14,071,466.68 + 9 78,042.38 704,431.56 17,347,515.71 156,583,359.02 222.28 14,047,627.79 + 10 77,859.43 782,290.99 17,306,849.76 173,890,208.78 222.28 14,014,697.42 + 11 77,626.22 859,917.21 17,255,012.02 191,145,220.80 222.28 13,972,720.38 + 12 77,345.52 937,262.73 17,192,616.14 208,337,836.94 222.28 13,922,193.60 + 13 77,021.56 1,014,284.29 17,120,605.40 225,458,442.34 222.28 13,863,880.93 + 14 76,659.30 1,090,943.59 17,040,081.18 242,498,523.53 222.28 13,798,674.23 + 15 76,263.89 1,167,207.48 16,952,186.81 259,450,710.33 222.28 13,727,499.34 + 16 75,840.32 1,243,047.80 16,858,034.91 276,308,745.24 222.28 13,651,257.25 + 17 75,393.28 1,318,441.08 16,758,665.93 293,067,411.17 222.28 13,570,790.49 + 18 74,927.04 1,393,368.11 16,655,027.43 309,722,438.60 222.28 13,486,866.37 + 19 74,445.39 1,467,813.51 16,547,966.62 326,270,405.23 222.28 13,400,170.93 + 20 73,951.72 1,541,765.22 16,438,231.02 342,708,636.25 222.28 13,311,309.51 + 21 73,448.95 1,615,214.17 16,326,473.66 359,035,109.91 222.28 13,220,810.92 + 22 72,939.63 1,688,153.81 16,213,260.66 375,248,370.57 222.28 13,129,133.58 + 23 72,425.96 1,760,579.76 16,099,079.76 391,347,450.33 222.28 13,036,672.46 + 24 71,909.81 1,832,489.57 15,984,349.02 407,331,799.35 222.28 12,943,766.08 + 25 71,392.80 1,903,882.37 15,869,425.07 423,201,224.42 222.28 12,850,703.25 + 26 70,876.27 1,974,758.64 15,754,610.85 438,955,835.27 222.28 12,757,729.29 + 27 70,361.40 2,045,120.04 15,640,162.58 454,595,997.86 222.28 12,665,051.65 + 28 69,849.14 2,114,969.18 15,526,295.96 470,122,293.82 222.28 12,572,845.02 + 29 69,340.31 2,184,309.49 15,413,191.67 485,535,485.49 222.28 12,481,255.72 + 30 68,860.68 2,253,170.17 15,306,577.89 500,842,063.38 222.28 12,394,922.28 diff --git a/tests/examples/S-DAC-GT.txt b/tests/examples/S-DAC-GT.txt index 9cf16ec8a..764f58650 100644 --- a/tests/examples/S-DAC-GT.txt +++ b/tests/examples/S-DAC-GT.txt @@ -6,6 +6,9 @@ # But only lists those parameters that are different than their default values Do S-DAC-GT Calculations, True +Plant Lifetime, 30 +S-DAC-GT Carbon Credit Duration, 30 +S-DAC-GT Carbon Credit Price, 180.0, ---[USD/tonne CO2] 45Q tax credit rate or voluntary market price ***Subsurface technical parameters*** ************************************* diff --git a/tests/test_geophires_x_client.py b/tests/test_geophires_x_client.py index 53b8ceab7..f85f0f231 100644 --- a/tests/test_geophires_x_client.py +++ b/tests/test_geophires_x_client.py @@ -593,18 +593,19 @@ def test_parse_sdacgt_profile(self): 'S-DAC-GT Annual Cost (USD/yr)', 'S-DAC-GT Cumm. Cash Flow (USD)', 'Cumm. Cost Per Tonne (USD/tonne)', + 'Annual Carbon Revenue (USD/yr)', ], ) # Values below need to be synchronized if S-DAC-GT example output values change. - self.assertEqual([1, 78330.8, 78330.8, 17411627.98, 17411627.98, 222.28], sdacgt_profile[1]) + self.assertEqual([1, 78330.8, 78330.8, 17411627.98, 17411627.98, 222.28, 14099544.46], sdacgt_profile[1]) self.assertEqual( - [15, 76263.89, 1167207.48, 16952186.81, 259450710.33, 222.28], + [15, 76263.89, 1167207.48, 16952186.81, 259450710.33, 222.28, 13727499.34], sdacgt_profile[15], ) - self.assertEqual([30, 68860.68, 2253170.17, 15306577.89, 500842063.38, 222.28], sdacgt_profile[30]) + self.assertEqual([30, 68860.68, 2253170.17, 15306577.89, 500842063.38, 222.28, 12394922.28], sdacgt_profile[30]) def test_parse_economic_model(self): result = GeophiresXResult(self._get_test_file_path('examples/example3.out'))