Skip to content

Well diameter outputs, surface plant cost, non-SAM-EM Inflation costs during construction [v3.9.48] #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.9.47
current_version = 3.9.48
commit = True
tag = True

Expand Down
2 changes: 1 addition & 1 deletion .cookiecutterrc
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ default_context:
sphinx_doctest: "no"
sphinx_theme: "sphinx-py3doc-enhanced-theme"
test_matrix_separate_coverage: "no"
version: 3.9.47
version: 3.9.48
version_manager: "bump2version"
website: "https://github.com/NREL"
year_from: "2023"
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Free software: `MIT license <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.9.47.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.48.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.47...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.48...main

.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
:target: https://nrel.github.io/GEOPHIRES-X
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
year = '2025'
author = 'NREL'
copyright = f'{year}, {author}'
version = release = '3.9.47'
version = release = '3.9.48'

pygments_style = 'trac'
templates_path = ['./templates']
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def read(*names, **kwargs):

setup(
name='geophires-x',
version='3.9.47',
version='3.9.48',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
2 changes: 2 additions & 0 deletions src/geophires_x/AGSWellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -1028,6 +1028,8 @@ def Calculate(self, model: Model) -> None:
# (b/c we are not generating electricity) = thermosiphon is happening!
self.PumpingPower.value = [max(x, 0.) for x in self.PumpingPower.value]

self._sync_output_params_from_input_params()

model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}')

def __str__(self):
Expand Down
104 changes: 71 additions & 33 deletions src/geophires_x/Economics.py

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions src/geophires_x/EconomicsUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ def total_capex_parameter_output_parameter() -> OutputParameter:
UnitType=Units.CURRENCY,
CurrentUnits=CurrencyUnit.MDOLLARS,
PreferredUnits=CurrencyUnit.MDOLLARS,
ToolTipText="The total capital expenditure (CAPEX) required to construct the plant. "
"This value includes all direct and indirect costs, contingency, and any cost escalation from "
"inflation during construction. It is used as the total installed cost input for "
"SAM Economic Models."
ToolTipText='The total capital expenditure (CAPEX) required to construct the plant. '
'This value includes all direct and indirect costs, and contingency. '
'For SAM Economic models, it also includes any cost escalation from inflation during construction. '
'It is used as the total installed cost input for SAM Economic Models.'
)
22 changes: 18 additions & 4 deletions src/geophires_x/Outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ def PrintOutputs(self, model: Model):
acf_label = Outputs._field_label(acf.display_name, 49)
f.write(f' {acf_label}{acf.value:10.2f} {acf.CurrentUnits.value}\n')

display_inflation_costs_in_economic_parameters: bool = (
econ.econmodel.value in [EconomicModel.BICYCLE,
EconomicModel.FCR,
EconomicModel.STANDARDIZED_LEVELIZED_COST]
and
econ.inflation_cost_during_construction.value != 0.
)
if display_inflation_costs_in_economic_parameters:
# Inflation cost is displayed here for economic models that don't treat inflation cost as a
# capital cost
icc: OutputParameter = econ.inflation_cost_during_construction
icc_label = Outputs._field_label(icc.display_name, 49)
f.write(f' {icc_label}{icc.value:10.2f} {icc.CurrentUnits.value}\n')

f.write(f' Project lifetime: {model.surfaceplant.plant_lifetime.value:10.0f} {model.surfaceplant.plant_lifetime.CurrentUnits.value}\n')
f.write(f' Capacity factor: {model.surfaceplant.utilization_factor.value * 100:10.1f} %\n')

Expand Down Expand Up @@ -327,8 +341,8 @@ def PrintOutputs(self, model: Model):
f.write(' User-provided production well temperature drop\n')
f.write(f' Constant production well temperature drop: {model.wellbores.tempdropprod.value:10.1f} ' + model.wellbores.tempdropprod.PreferredUnits.value + NL)
f.write(f' Flowrate per production well: {model.wellbores.prodwellflowrate.value:10.1f} ' + model.wellbores.prodwellflowrate.CurrentUnits.value + NL)
f.write(f' Injection well casing ID: {model.wellbores.injwelldiam.value:10.3f} ' + model.wellbores.injwelldiam.CurrentUnits.value + NL)
f.write(f' Production well casing ID: {model.wellbores.prodwelldiam.value:10.3f} ' + model.wellbores.prodwelldiam.CurrentUnits.value + NL)
f.write(f' {model.wellbores.injection_well_casing_inner_diameter.display_name}: {model.wellbores.injection_well_casing_inner_diameter.value:10.3f} {model.wellbores.injection_well_casing_inner_diameter.CurrentUnits.value}\n')
f.write(f' {model.wellbores.production_well_casing_inner_diameter.display_name}: {model.wellbores.production_well_casing_inner_diameter.value:10.3f} {model.wellbores.production_well_casing_inner_diameter.CurrentUnits.value}\n')
f.write(f' {model.wellbores.redrill.display_name}: {model.wellbores.redrill.value:10.0f}\n')
if model.surfaceplant.enduse_option.value in [EndUseOptions.ELECTRICITY, EndUseOptions.COGENERATION_TOPPING_EXTRA_HEAT, EndUseOptions.COGENERATION_TOPPING_EXTRA_ELECTRICITY, EndUseOptions.COGENERATION_BOTTOMING_EXTRA_ELECTRICITY, EndUseOptions.COGENERATION_BOTTOMING_EXTRA_HEAT, EndUseOptions.COGENERATION_PARALLEL_EXTRA_HEAT, EndUseOptions.COGENERATION_PARALLEL_EXTRA_ELECTRICITY]:
f.write(' Power plant type: ' + str(model.surfaceplant.plant_type.value.value) + NL)
Expand Down Expand Up @@ -497,8 +511,8 @@ def PrintOutputs(self, model: Model):
# expenditure.
pass

if is_sam_econ_model:
# TODO calculate & display for other economic models
display_inflation_during_construction_in_capital_costs = is_sam_econ_model
if display_inflation_during_construction_in_capital_costs:
icc_label = Outputs._field_label(econ.inflation_cost_during_construction.display_name, 47)
f.write(f' {icc_label}{econ.inflation_cost_during_construction.value:10.2f} {econ.inflation_cost_during_construction.CurrentUnits.value}\n')

Expand Down
2 changes: 2 additions & 0 deletions src/geophires_x/SBTWellbores.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ def Calculate(self, model: Model) -> None:
# (b/c we are not generating electricity) = thermosiphon is happening!
self.PumpingPower.value = [max(x, 0.) for x in self.PumpingPower.value]

self._sync_output_params_from_input_params()

model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}')

def CalculateNonverticalPressureDrop(self, model, value, time_max, al):
Expand Down
4 changes: 2 additions & 2 deletions src/geophires_x/SUTRAOutputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ def PrintOutputs(self, model: Model):
f.write(f' Pump efficiency: {pump_efficiency_display}{NL}')

f.write(f" Lifetime Average Well Flow Rate: {np.average(abs(model.wellbores.ProductionWellFlowRates.value)):10.1f} " + model.wellbores.ProductionWellFlowRates.CurrentUnits.value + NL)
f.write(f" Injection well casing ID: {model.wellbores.injwelldiam.value:10.3f} " + model.wellbores.injwelldiam.CurrentUnits.value + NL)
f.write(f" Production well casing ID: {model.wellbores.prodwelldiam.value:10.3f} " + model.wellbores.prodwelldiam.CurrentUnits.value + NL)
f.write(f' {model.wellbores.injection_well_casing_inner_diameter.display_name}: {model.wellbores.injection_well_casing_inner_diameter.value:10.3f} {model.wellbores.injection_well_casing_inner_diameter.CurrentUnits.value}\n')
f.write(f' {model.wellbores.production_well_casing_inner_diameter.display_name}: {model.wellbores.production_well_casing_inner_diameter.value:10.3f} {model.wellbores.production_well_casing_inner_diameter.CurrentUnits.value}\n')
f.write(NL)
f.write(NL)
f.write(" ***RESERVOIR SIMULATION RESULTS***" + NL)
Expand Down
4 changes: 3 additions & 1 deletion src/geophires_x/SUTRAWellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,6 @@ def Calculate(self, model: Model) -> None:
/ model.surfaceplant.pump_efficiency.value
)

model.logger.info("complete " + str(__class__) + ": " + sys._getframe().f_code.co_name)
self._sync_output_params_from_input_params()

model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}')
42 changes: 37 additions & 5 deletions src/geophires_x/WellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pint.facets.plain import PlainQuantity

from .Parameter import floatParameter, intParameter, boolParameter, OutputParameter, ReadParameter, \
coerce_int_params_to_enum_values
coerce_int_params_to_enum_values, Parameter
from geophires_x.GeoPHIRESUtils import vapor_pressure_water_kPa, quantity, static_pressure_MPa
from geophires_x.GeoPHIRESUtils import density_water_kg_per_m3
from geophires_x.GeoPHIRESUtils import viscosity_water_Pa_sec
Expand Down Expand Up @@ -738,6 +738,7 @@ def __init__(self, model: Model):
"same value."
)

# noinspection SpellCheckingInspection
self.prodwelldiam = self.ParameterDict[self.prodwelldiam.Name] = floatParameter(
"Production Well Diameter",
DefaultValue=8.0,
Expand All @@ -748,9 +749,10 @@ def __init__(self, model: Model):
CurrentUnits=LengthUnit.INCHES,
Required=True,
ErrMessage="assume default production well diameter (8 inch)",
ToolTipText="Inner diameter of production wellbore (assumed constant along the wellbore) to calculate \
frictional pressure drop and wellbore heat transmission with Rameys model"
ToolTipText='Inner diameter of production wellbore (assumed constant along the wellbore) to calculate '
'frictional pressure drop and wellbore heat transmission with Rameys model'
)
# noinspection SpellCheckingInspection
self.injwelldiam = self.ParameterDict[self.injwelldiam.Name] = floatParameter(
"Injection Well Diameter",
DefaultValue=8.0,
Expand All @@ -761,8 +763,8 @@ def __init__(self, model: Model):
CurrentUnits=LengthUnit.INCHES,
Required=True,
ErrMessage="assume default injection well diameter (8 inch)",
ToolTipText="Inner diameter of production wellbore (assumed constant along the wellbore) to calculate "
"frictional pressure drop and wellbore heat transmission with Rameys model"
ToolTipText='Inner diameter of injection wellbore (assumed constant along the wellbore) to calculate '
'frictional pressure drop and wellbore heat transmission with Rameys model'
)
self.rameyoptionprod = self.ParameterDict[self.rameyoptionprod.Name] = boolParameter(
"Ramey Production Wellbore Model",
Expand Down Expand Up @@ -1097,6 +1099,21 @@ def __init__(self, model: Model):
self.MyPath = __file__

# Results - used by other objects or printed in output downstream

self.injection_well_casing_inner_diameter = self.OutputParameterDict[self.injection_well_casing_inner_diameter.Name] = OutputParameter(
Name='Injection well casing ID',
UnitType=self.injwelldiam.UnitType,
PreferredUnits=self.injwelldiam.PreferredUnits,
CurrentUnits=self.injwelldiam.CurrentUnits,
ToolTipText=self.injwelldiam.ToolTipText,
)
self.production_well_casing_inner_diameter = self.OutputParameterDict[self.production_well_casing_inner_diameter.Name] = OutputParameter(
Name='Production well casing ID',
UnitType=self.prodwelldiam.UnitType,
PreferredUnits=self.prodwelldiam.PreferredUnits,
CurrentUnits=self.prodwelldiam.CurrentUnits,
ToolTipText=self.prodwelldiam.ToolTipText,
)
self.production_reservoir_pressure = self.OutputParameterDict[self.production_reservoir_pressure.Name] = OutputParameter(
Name="Calculated Reservoir Pressure",
value=self.Phydrostatic.value,
Expand Down Expand Up @@ -1537,4 +1554,19 @@ def Calculate(self, model: Model) -> None:
# negative pumping power values become zero (b/c we are not generating electricity)
self.PumpingPower.value = [0. if x < 0. else x for x in self.PumpingPower.value]

self._sync_output_params_from_input_params()

model.logger.info(f'complete {self.__class__.__name__}: {__name__}')

def _sync_output_params_from_input_params(self) -> None:
"""
Handles setting output parameters whose values are based on 1:1 corresponding input parameters.
"""

def _set_output_param_from_input_param(input_param: Parameter, output_param: OutputParameter) -> None:
output_param.value = input_param.quantity().to(output_param.CurrentUnits).magnitude

# Injection/production well casing ID have same value as inputs but exist as separate output parameters due to
# having a different display name.
_set_output_param_from_input_param(self.injwelldiam, self.injection_well_casing_inner_diameter)
_set_output_param_from_input_param(self.prodwelldiam, self.production_well_casing_inner_diameter)
2 changes: 1 addition & 1 deletion src/geophires_x/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '3.9.47'
__version__ = '3.9.48'
3 changes: 3 additions & 0 deletions src/geophires_x_client/geophires_x_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class GeophiresXResult:
'Nominal Discount Rate',
'WACC',
'Accrued financing during construction',
# Displayed for economic models that don't treat inflation costs as capital costs (non-SAM-EM)
'Inflation costs during construction',
'Project lifetime',
'Capacity factor',
'Project NPV',
Expand Down Expand Up @@ -261,6 +263,7 @@ class GeophiresXResult:
'Total surface equipment costs',
'Exploration costs',
'Investment Tax Credit',
# Displayed for economic models that treat inflation costs as capital costs (SAM-EM)
'Inflation costs during construction',
'Total Add-on CAPEX',
'Total capital costs',
Expand Down
2 changes: 1 addition & 1 deletion src/geophires_x_schema_generator/geophires-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,7 @@
"category": "Economics",
"default": -1.0,
"minimum": 0,
"maximum": 1000
"maximum": 10000
},
"Surface Plant Capital Cost Adjustment Factor": {
"description": "Multiplier for built-in surface plant capital cost correlation",
Expand Down
21 changes: 17 additions & 4 deletions src/geophires_x_schema_generator/geophires-result.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"Total CAPEX": {
"type": "number",
"description": "The total capital expenditure (CAPEX) required to construct the plant. This value includes all direct and indirect costs, contingency, and any cost escalation from inflation during construction. It is used as the total installed cost input for SAM Economic Models.",
"description": "The total capital expenditure (CAPEX) required to construct the plant. This value includes all direct and indirect costs, and contingency. For SAM Economic models, it also includes any cost escalation from inflation during construction. It is used as the total installed cost input for SAM Economic Models.",
"units": "MUSD"
},
"Average Direct-Use Heat Production": {},
Expand Down Expand Up @@ -100,6 +100,11 @@
"description": "The accrued inflation on total capital costs over the construction period, as defined by Inflation Rate During Construction. For SAM Economic Models, this is calculated automatically by compounding Inflation Rate over Construction Years if Inflation Rate During Construction is not provided.",
"units": "%"
},
"Inflation costs during construction": {
"type": "number",
"description": "The calculated amount of cost escalation due to inflation over the construction period.",
"units": "MUSD"
},
"Project lifetime": {},
"Capacity factor": {},
"Project NPV": {
Expand Down Expand Up @@ -212,8 +217,16 @@
},
"Average production well temperature drop": {},
"Flowrate per production well": {},
"Injection well casing ID": {},
"Production well casing ID": {},
"Injection well casing ID": {
"type": "number",
"description": "Inner diameter of injection wellbore (assumed constant along the wellbore) to calculate frictional pressure drop and wellbore heat transmission with Rameys model",
"units": "in"
},
"Production well casing ID": {
"type": "number",
"description": "Inner diameter of production wellbore (assumed constant along the wellbore) to calculate frictional pressure drop and wellbore heat transmission with Rameys model",
"units": "in"
},
"Number of times redrilling": {
"type": "number",
"description": "redrill",
Expand Down Expand Up @@ -424,7 +437,7 @@
"Annualized capital costs": {},
"Total CAPEX": {
"type": "number",
"description": "The total capital expenditure (CAPEX) required to construct the plant. This value includes all direct and indirect costs, contingency, and any cost escalation from inflation during construction. It is used as the total installed cost input for SAM Economic Models.",
"description": "The total capital expenditure (CAPEX) required to construct the plant. This value includes all direct and indirect costs, and contingency. For SAM Economic models, it also includes any cost escalation from inflation during construction. It is used as the total installed cost input for SAM Economic Models.",
"units": "MUSD"
},
"Drilling Cost": {},
Expand Down
11 changes: 6 additions & 5 deletions tests/examples/Fervo_Norbeck_Latimer_2023.out
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

Simulation Metadata
----------------------
GEOPHIRES Version: 3.9.28
Simulation Date: 2025-07-02
Simulation Time: 12:19
Calculation Time: 0.481 sec
GEOPHIRES Version: 3.9.47
Simulation Date: 2025-07-31
Simulation Time: 08:57
Calculation Time: 0.475 sec

***SUMMARY OF RESULTS***

Expand All @@ -24,7 +24,8 @@ Simulation Metadata
***ECONOMIC PARAMETERS***

Economic Model = BICYCLE
Accrued financing during construction: 5.00 %
Accrued financing during construction: 5.00 %
Inflation costs during construction: 1.41 MUSD
Project lifetime: 10 yr
Capacity factor: 90.0 %
Project NPV: -13.03 MUSD
Expand Down
Loading
Loading