From 635f73ea135c4f236a8b6b6960f43a5a87d44ead Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:52:11 -0800 Subject: [PATCH 1/4] calculate SAM-EM ITC and display in ECONOMIC PARAMETERS (https://github.com/NREL/GEOPHIRES-X/issues/404) (WIP - tooltip text + examples regen) --- src/geophires_x/Economics.py | 13 +++++-------- src/geophires_x/EconomicsSam.py | 22 ++++++++++++++++++++++ src/geophires_x/EconomicsUtils.py | 11 +++++++++++ src/geophires_x/Outputs.py | 20 +++++++++----------- tests/examples/Fervo_Project_Cape-5.out | 7 ++++--- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index ca2edcf9..de1afa2a 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -15,7 +15,7 @@ project_payback_period_parameter, inflation_cost_during_construction_output_parameter, \ interest_during_construction_output_parameter, total_capex_parameter_output_parameter, \ overnight_capital_cost_output_parameter, CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME, \ - _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET + _YEAR_INDEX_VALUE_EXPLANATION_SNIPPET, investment_tax_credit_output_parameter from geophires_x.GeoPHIRESUtils import quantity from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \ _WellDrillingCostCorrelationCitation @@ -2302,13 +2302,7 @@ def __init__(self, model: Model): self.ProjectMOIC = self.OutputParameterDict[self.ProjectMOIC.Name] = moic_parameter() self.ProjectPaybackPeriod = self.OutputParameterDict[self.ProjectPaybackPeriod.Name] = ( project_payback_period_parameter()) - self.RITCValue = self.OutputParameterDict[self.RITCValue.Name] = OutputParameter( - Name="Investment Tax Credit Value", - display_name='Investment Tax Credit', - UnitType=Units.CURRENCY, - PreferredUnits=CurrencyUnit.MDOLLARS, - CurrentUnits=CurrencyUnit.MDOLLARS - ) + self.RITCValue = self.OutputParameterDict[self.RITCValue.Name] = investment_tax_credit_output_parameter() self.cost_one_production_well = self.OutputParameterDict[self.cost_one_production_well.Name] = OutputParameter( Name="Cost of One Production Well", UnitType=Units.CURRENCY, @@ -3587,6 +3581,9 @@ def _calculate_sam_economics(self, model: Model) -> None: self.ProjectPaybackPeriod.value = self.sam_economics_calculations.project_payback_period.value + self.RITCValue.value = self.sam_economics_calculations.investment_tax_credit.quantity().to( + self.RITCValue.CurrentUnits).magnitude + # noinspection SpellCheckingInspection def _calculate_derived_outputs(self, model: Model) -> None: """ diff --git a/src/geophires_x/EconomicsSam.py b/src/geophires_x/EconomicsSam.py index 9b9139d2..450def3b 100644 --- a/src/geophires_x/EconomicsSam.py +++ b/src/geophires_x/EconomicsSam.py @@ -24,6 +24,7 @@ # noinspection PyPackageRequirements import PySAM.Utilityrate5 as UtilityRate +from pint.facets.plain import PlainQuantity from tabulate import tabulate from geophires_x import Model as Model @@ -43,6 +44,7 @@ royalty_cost_output_parameter, overnight_capital_cost_output_parameter, _SAM_EM_MOIC_RETURNS_TAX_QUALIFIER, + investment_tax_credit_output_parameter, ) from geophires_x.EconomicsSamPreRevenue import ( _AFTER_TAX_NET_CASH_FLOW_ROW_NAME, @@ -97,6 +99,8 @@ class SamEconomicsCalculations: project_payback_period: OutputParameter = field(default_factory=project_payback_period_parameter) + investment_tax_credit: OutputParameter = field(default_factory=investment_tax_credit_output_parameter) + @property def _pre_revenue_years_count(self) -> int: return len( @@ -367,6 +371,11 @@ def sf(_v: float, num_sig_figs: int = 5) -> float: sam_economics.project_payback_period.value = _calculate_project_payback_period( sam_economics.sam_cash_flow_profile, model ) + sam_economics.investment_tax_credit.value = ( + _calculate_investment_tax_credit_value(sam_economics.sam_cash_flow_profile) + .to(sam_economics.investment_tax_credit.CurrentUnits.value) + .magnitude + ) return sam_economics @@ -567,6 +576,19 @@ def _calculate_project_payback_period(cash_flow: list[list[Any]], model) -> floa return None +def _calculate_investment_tax_credit_value(sam_cash_flow_profile) -> PlainQuantity: + total_itc_sum_q: PlainQuantity = quantity(0, 'USD') + + for itc_line_item in ['Federal ITC total income ($)', 'State ITC total income ($)']: + itc_numeric_entries = [ + float(it) for it in _cash_flow_profile_row(sam_cash_flow_profile, itc_line_item) if is_float(it) + ] + itc_sum_q = quantity(sum(itc_numeric_entries), 'USD') + total_itc_sum_q += itc_sum_q + + return total_itc_sum_q + + def get_sam_cash_flow_profile_tabulated_output(model: Model, **tabulate_kw_args) -> str: """ Note model must have already calculated economics for this to work (used in Outputs) diff --git a/src/geophires_x/EconomicsUtils.py b/src/geophires_x/EconomicsUtils.py index 25a9913e..450495b3 100644 --- a/src/geophires_x/EconomicsUtils.py +++ b/src/geophires_x/EconomicsUtils.py @@ -198,3 +198,14 @@ def royalty_cost_output_parameter() -> OutputParameter: ToolTipText='The annual costs paid to a royalty holder, calculated as a percentage of the ' 'project\'s gross annual revenue. This is modeled as a variable operating expense.', ) + + +def investment_tax_credit_output_parameter() -> OutputParameter: + return OutputParameter( + Name="Investment Tax Credit Value", + display_name='Investment Tax Credit', + UnitType=Units.CURRENCY, + PreferredUnits=CurrencyUnit.MDOLLARS, + CurrentUnits=CurrencyUnit.MDOLLARS, + # TODO tooltip text + ) diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 18ba85f0..c9ab609a 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -266,6 +266,12 @@ def PrintOutputs(self, model: Model): label = Outputs._field_label(field.Name, 49) f.write(f' {label}{field.value:10.2f} {field.CurrentUnits.value}\n') + if econ.RITCValue.value and is_sam_econ_model: + # Non-SAM-EMs (inaccurately) treat ITC as a capital cost and thus are displayed in the capital + # costs category rather than here. + f.write( + f' {econ.RITCValue.display_name}: {abs(econ.RITCValue.value):10.2f} {econ.RITCValue.CurrentUnits.value}\n') + if not is_sam_econ_model: # (parameter is ambiguous to the point of meaninglessness for SAM-EM) acf: OutputParameter = econ.accrued_financing_during_construction_percentage acf_label = Outputs._field_label(acf.display_name, 49) @@ -495,17 +501,9 @@ def PrintOutputs(self, model: Model): f.write(f' Drilling and completion costs per redrilled well: {(econ.Cwell.value/(model.wellbores.nprod.value+model.wellbores.ninj.value)):10.2f} {econ.Cwell.CurrentUnits.value}\n') f.write(f' Stimulation costs (for redrilling): {econ.Cstim.value:10.2f} {econ.Cstim.CurrentUnits.value}\n') - if model.economics.RITCValue.value: - if not is_sam_econ_model: - f.write(f' {model.economics.RITCValue.display_name}: {-1*model.economics.RITCValue.value:10.2f} {model.economics.RITCValue.CurrentUnits.value}\n') - else: - # TODO Extract value from SAM Cash Flow Profile per - # https://github.com/NREL/GEOPHIRES-X/issues/404. - # For now we skip displaying the value because it can be/probably is usually mathematically - # inaccurate, and even if it's not, it's redundant with the cash flow profile and also - # misleading/confusing/wrong to display it as a capital cost since it is not a capital - # expenditure. - pass + if model.economics.RITCValue.value and not is_sam_econ_model: + # Note ITC is in ECONOMIC PARAMETERS category for SAM-EM (not capital costs) + f.write(f' {econ.RITCValue.display_name}: {-1 * econ.RITCValue.value:10.2f} {econ.RITCValue.CurrentUnits.value}\n') display_occ_and_inflation_during_construction_in_capital_costs = is_sam_econ_model if display_occ_and_inflation_during_construction_in_capital_costs: diff --git a/tests/examples/Fervo_Project_Cape-5.out b/tests/examples/Fervo_Project_Cape-5.out index 31c5d155..7b08b111 100644 --- a/tests/examples/Fervo_Project_Cape-5.out +++ b/tests/examples/Fervo_Project_Cape-5.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.11.0 + GEOPHIRES Version: 3.11.1 Simulation Date: 2026-01-16 - Simulation Time: 11:42 - Calculation Time: 1.791 sec + Simulation Time: 12:50 + Calculation Time: 1.783 sec ***SUMMARY OF RESULTS*** @@ -32,6 +32,7 @@ Simulation Metadata Real Discount Rate: 12.00 % Nominal Discount Rate: 15.02 % WACC: 8.31 % + Investment Tax Credit: 857.78 MUSD Project lifetime: 30 yr Capacity factor: 90.0 % Project NPV: 268.60 MUSD From 0b6c16c09b54e462552f7871a1d33ed397b941ad Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:04:11 -0800 Subject: [PATCH 2/4] ITC output param tooltip text --- src/geophires_x/EconomicsUtils.py | 4 +++- src/geophires_x_schema_generator/geophires-result.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/geophires_x/EconomicsUtils.py b/src/geophires_x/EconomicsUtils.py index 450495b3..9044deff 100644 --- a/src/geophires_x/EconomicsUtils.py +++ b/src/geophires_x/EconomicsUtils.py @@ -207,5 +207,7 @@ def investment_tax_credit_output_parameter() -> OutputParameter: UnitType=Units.CURRENCY, PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS, - # TODO tooltip text + ToolTipText='Represents the total undiscounted ITC sum. ' + 'For SAM Economic Models, this accounts for the standard Year 1 Federal ITC as well as any ' + 'applicable State ITCs or multi-year credit schedules.', ) diff --git a/src/geophires_x_schema_generator/geophires-result.json b/src/geophires_x_schema_generator/geophires-result.json index f0db4734..9fe3b3f0 100644 --- a/src/geophires_x_schema_generator/geophires-result.json +++ b/src/geophires_x_schema_generator/geophires-result.json @@ -439,7 +439,7 @@ }, "Investment Tax Credit": { "type": "number", - "description": "Investment Tax Credit Value", + "description": "Investment Tax Credit Value. Represents the total undiscounted ITC sum. For SAM Economic Models, this accounts for the standard Year 1 Federal ITC as well as any applicable State ITCs or multi-year credit schedules.", "units": "MUSD" }, "Overnight Capital Cost": { From a3891bd8ca2acf01bdf060c6af7d48b149f28cf9 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:06:22 -0800 Subject: [PATCH 3/4] =?UTF-8?q?Bump=20version:=203.11.1=20=E2=86=92=203.11?= =?UTF-8?q?.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- .cookiecutterrc | 2 +- README.rst | 4 ++-- docs/conf.py | 2 +- setup.py | 2 +- src/geophires_x/__init__.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 3d6b2d61..94e51e4a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.11.1 +current_version = 3.11.2 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 8140eb80..4414e110 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.11.1 + version: 3.11.2 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/README.rst b/README.rst index 904097d3..a8388479 100644 --- a/README.rst +++ b/README.rst @@ -58,9 +58,9 @@ Free software: `MIT license `__ :alt: Supported implementations :target: https://pypi.org/project/geophires-x -.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.11.1.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.11.2.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.11.1...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.11.2...main .. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat :target: https://nrel.github.io/GEOPHIRES-X diff --git a/docs/conf.py b/docs/conf.py index b3cdd21a..e2a275de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2025' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.11.1' +version = release = '3.11.2' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 638e5c1a..7ec02487 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.11.1', + version='3.11.2', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 94015dc8..669870b1 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.11.1' +__version__ = '3.11.2' From 4cba65fddd5b2e2a710b4fd25850a94afb0740e9 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:08:23 -0800 Subject: [PATCH 4/4] 3.11.2 CHANGELOG entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c087b033..a4f2c94f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,8 @@ GEOPHIRES v3 (2023-2025) 3.11 ^^^^ +3.11.2: `SAM Economic Models ITC result output `__ | `release `__ + 3.11: `SAM Economic Models Project Payback Period fix `__ | `release `__