Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8d57ffd
Update field gathering cost tooltip text from theoretical basis (http…
softwareengineerprogrammer Oct 4, 2025
b1a104d
update stimulation cost output tooltip text with instruction to set c…
softwareengineerprogrammer Oct 4, 2025
cd645ea
Parameterize contingency and indirect costs in field gathering cost t…
softwareengineerprogrammer Oct 4, 2025
2d22b4b
add note to Reservoir Stimulation Capital Cost to set to $0 for tradi…
softwareengineerprogrammer Oct 4, 2025
daee411
define separate end-use option output parameter in preparation to add…
softwareengineerprogrammer Oct 5, 2025
40626ff
Port End-Use Option output parameter tooltip text from theoretical basis
softwareengineerprogrammer Oct 5, 2025
c19a581
incorporate theoretical basis time steps per year into tooltip text
softwareengineerprogrammer Oct 5, 2025
d7470e0
update exploration costs tooltip from theoretical basis (previous ver…
softwareengineerprogrammer Oct 5, 2025
8271d91
add reference to Water Cost Adjustment factor in water costs output p…
softwareengineerprogrammer Oct 5, 2025
a1d6700
Port wellfield & surface plant O&M cost tooltips from theoretical basis
softwareengineerprogrammer Oct 5, 2025
e0d63ca
port tooltip text for surface plant costs from theoretical basis
softwareengineerprogrammer Oct 5, 2025
83aaa81
fix port-related typo in field gathering costs tooltip
softwareengineerprogrammer Oct 5, 2025
04347d5
copy edit: remove extraneous comma in theoretical basis summary
softwareengineerprogrammer Oct 5, 2025
c7f8fb4
Surface power plant costs figure reference, tooltip var interpolation…
softwareengineerprogrammer Oct 8, 2025
c207c18
Set Total O&M Cost (Coam) tooltip text from theoretical basis
softwareengineerprogrammer Oct 8, 2025
66317b4
Bump version: 3.9.62 → 3.9.63
softwareengineerprogrammer Oct 8, 2025
40f1148
Fix non-standard unicode minus sign (causes apparent issues in window…
softwareengineerprogrammer Oct 8, 2025
e17084e
use print([...], file=f) in schema generator to avoid need for precom…
softwareengineerprogrammer Oct 8, 2025
7f7830c
specify utf-8 encoding in schema generator
softwareengineerprogrammer Oct 8, 2025
0b738d3
document use of print instead of f.write in schema generator comment
softwareengineerprogrammer Oct 8, 2025
2b51fa0
Bump version: 3.9.63 → 3.9.64
softwareengineerprogrammer Oct 8, 2025
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.62
current_version = 3.9.64
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.62
version: 3.9.64
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.62.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.64.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.62...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.64...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/Theoretical-Basis-for-GEOPHIRES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Theoretical Basis for GEOPHIRES

This document describes the foundational theoretical basis for GEOPHIRES, adapted from the 2019 paper, "[GEOPHIRES v2.0: updated geothermal techno-economic simulation tool](https://doi.org/10.1186/s40517-019-0119-6)" by Koenraad F. Beckers & Kevin McCabe. The core theories described here remain valid and relevant to the current software.
This document describes the foundational theoretical basis for GEOPHIRES, adapted from the 2019 paper "[GEOPHIRES v2.0: updated geothermal techno-economic simulation tool](https://doi.org/10.1186/s40517-019-0119-6)" by Koenraad F. Beckers & Kevin McCabe. The core theories described here remain valid and relevant to the current software.

However, the text has not been comprehensively updated to include the theory for features added in GEOPHIRES v3 (GEOPHIRES-X) and later.
While pointers to the documentation for newer models have been added, the detailed technical descriptions herein still pertain to v2.0.
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.62'
version = release = '3.9.64'

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.62',
version='3.9.64',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
115 changes: 102 additions & 13 deletions src/geophires_x/Economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@ def __init__(self, model: Model):
CurrentUnits=CurrencyUnit.MDOLLARS,
Provided=False,
Valid=False,
ToolTipText="Total reservoir stimulation capital cost, including indirect costs and contingency."
ToolTipText='Total reservoir stimulation capital cost, including indirect costs and contingency. '
f'For traditional hydrothermal reservoirs, this parameter should be set to $0.'
)

max_stimulation_cost_per_well_MUSD = 100
Expand Down Expand Up @@ -938,7 +939,18 @@ def __init__(self, model: Model):
UnitType=Units.NONE,
Required=True,
ErrMessage="assume default number of time steps per year (4)",
ToolTipText="Number of internal simulation time steps per year"
ToolTipText='Number of internal simulation time steps per year. GEOPHIRES assumes linear time '
'discretization with a user-provided number of time steps per year over the lifetime of the '
'plant. The default is four time steps per year, meaning a time step of 3 months. '
'At every time step, GEOPHIRES calculates the reservoir output temperature, production '
'wellhead temperature, direct-use heat and/or electricity power output (in MW), pressure '
'drops and pumping power. On an annual basis, GEOPHIRES calculates the O&M costs and '
'direct-use heat and/or electricity production. To investigate seasonal effects, e.g., to '
'assess the impact of more geothermal heat demand for district heating in winter than in '
'summer, the user can select a smaller time step, e.g., a month (or 12 time steps per year). '
'For even shorter timescale effects, e.g., to account for an hourly varying ambient '
'temperature or investigate the response in plant operation to a fluctuating revenue rate), '
'the user can select an even smaller time step, e.g., 1 h (or 8760 time steps per year).'
)
self.FCR = self.ParameterDict[self.FCR.Name] = floatParameter(
"Fixed Charge Rate",
Expand Down Expand Up @@ -1824,22 +1836,28 @@ def __init__(self, model: Model):
f'costs per well. '
f'Provide {self.ccstimadjfactor.Name} to multiply the correlation-calculated cost. '
f'Provide {self.ccstimfixed.Name} to override the correlation and set your own '
f'total stimulation cost.'
f'total stimulation cost. '
f'For traditional hydrothermal reservoirs, {self.ccstimfixed.Name} should be set to $0.'
)

contingency_and_indirect_costs_tooltip = (
f'plus {self.contingency_percentage.quantity().to(convertible_unit("%")).magnitude:g}% contingency '
# TODO switch order to align with theoretical basis, which lists indirect costs first
contingency_and_indirect_costs_tooltip_stem = (
f'{self.contingency_percentage.quantity().to(convertible_unit("%")).magnitude:g}% contingency '
f'plus {self.indirect_capital_cost_percentage.quantity().to(convertible_unit("%")).magnitude}% '
f'indirect costs'
)
contingency_and_indirect_costs_tooltip = (
f'plus {contingency_and_indirect_costs_tooltip_stem}'
)

self.Cexpl = self.OutputParameterDict[self.Cexpl.Name] = OutputParameter(
Name="Exploration cost",
display_name='Exploration costs',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
ToolTipText=f'Default correlation: 60% of the cost of one production well '
ToolTipText=f'The built-in exploration cost correlation considers drilling of a slim-hole well at 60% of '
f'the cost of a regular well, $1M for geophysical and field work, '
f'{contingency_and_indirect_costs_tooltip}. '
f'Provide {self.ccexpladjfactor.Name} to multiply the default correlation. '
f'Provide {self.ccexplfixed.Name} to override the default correlation and set your own cost.'
Expand All @@ -1865,14 +1883,21 @@ def __init__(self, model: Model):
ToolTipText='Drilling and completion cost per well, including indirect costs '
f'(default: {self.wellfield_indirect_capital_cost_percentage.DefaultValue}%).'
)

# noinspection SpellCheckingInspection
self.Coamwell = self.OutputParameterDict[self.Coamwell.Name] = OutputParameter(
Name="O&M Wellfield cost",
display_name='Wellfield maintenance costs',
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
# TODO TooltipText to document how this is calculated
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
# TODO parameterize relevant constants in tooltip text
ToolTipText='The built-in correlation for the wellfield O&M costs is similar as the surface plant O&M '
'costs: it assumes that it consists of 1% of the total wellfield plus field gathering system '
'costs (for annual non-labor costs) and 25% of the labor costs (the other 75% of the labor '
'costs are assigned to the surface plant O&M costs).'
)

self.redrilling_annual_cost = self.OutputParameterDict[self.redrilling_annual_cost.Name] = OutputParameter(
Name="Redrilling costs",
UnitType=Units.CURRENCYFREQUENCY,
Expand All @@ -1884,25 +1909,85 @@ def __init__(self, model: Model):
f'The total is then divided over {model.surfaceplant.plant_lifetime.Name} years to calculate '
f'Redrilling costs per year.'
)
# noinspection SpellCheckingInspection
self.Cplant = self.OutputParameterDict[self.Cplant.Name] = OutputParameter(
Name="Surface Plant cost",
display_name='Surface power plant costs',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
CurrentUnits=CurrencyUnit.MDOLLARS,
# TODO incorporate direct references to relevant parameters for adjusting correlation in tooltip text
# TODO interpolate relevant constants (that are currently hardcoded) in tooltip text
ToolTipText='The built-in power plant cost correlations are based on the original correlations developed '
'by Beckers (2016), indexed to 2017 using the IHS Markit North American Power Capital Costs '
'Index (NAPCCI) excluding nuclear plants (IHS 2018). The ORC power plant cost data have been '
'updated with data from the 2016 GETEM tool (DOE 2016) and the geothermal binary power plants '
'study by Verkis (2014). '
# Note: actual author name above is "Verkís" but the unicode accented i may cause unexpected
# problems in consumers.
'Figure 4 in the Theoretical Basis shows the power plant capital cost expressed in $ kWe-1 '
'as a function of plant size and initial production temperature for subcritical ORC and '
'double-flash power plants. '
f'The default correlations in GEOPHIRES include {contingency_and_indirect_costs_tooltip_stem}. '
'For the same plant size and production temperature, double-flash power plants are considered '
'about 25% more expensive than single-flash power plants (Zeyghami 2010), and supercritical '
'ORC plants are roughly 10% more than subcritical ORC plants (Astolfi et al. 2014). A wide '
'range in power plant specific cost values is reported in academic and popular literature. '
'The GEOPHIRES built-in surface plant cost correlations represent typical values. However, '
'the user is recommended to provide their own power plant cost data if available for their '
'case study. The ORC plant specific cost decreases only moderately at higher temperatures. '
'The reasons are that when increasing the temperature, the ORC plant design also changes: '
'(1) a different organic fluid is selected, (2) piping, pump, heat exchangers, and other '
'equipment are designed to handle the higher temperature (and potentially also pressure), '
'requiring thicker walls, potentially different materials, etc., and (3) additional components '
'may be implemented, such as a heat recuperator, making the design and operation more complex. '
'Unlike flash power plants, ORC plants are a small, niche market, typically case specific, '
'and rely on relatively young technology, which has not been subject yet to decades of '
'technological advancement. The cost for direct-use heat applications is highly dependent '
'on the type of application. A generic cost of $250 kWth-1 is assumed '
f'{contingency_and_indirect_costs_tooltip}. '
'However, users are encouraged to provide their own cost figures for '
'their specific application. Beckers and Young (2017) collected several cost figures to '
'estimate the surface equipment cost for geothermal district-heating systems.'
)
self.Coamplant = self.OutputParameterDict[self.Coamplant.Name] = OutputParameter(
Name="O&M Surface Plant costs",
display_name='Power plant maintenance costs',
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
# TODO parameterize relevant constants in tooltip text
# TODO update index year and/or make indexing parameterizable in tooltip text
ToolTipText='GEOPHIRES estimates the annual surface plant O&M costs as the sum of 1.5% of the total plant '
'capital cost (for annual non-labor costs), and 75% of the annual labor costs. The other 25% '
'of the labor costs are assigned to the wellfield O&M cost. The labor costs are calculated '
'internally in GEOPHIRES using the 2014 labor costs provided by Beckers (2016), indexed to '
'2017 using the Bureau of Labor Statistics (BLS) Employment Cost Index for utilities (2018). '
'The original 2014 labor cost correlation expresses the labor costs as a function of the plant '
'size (MW) using an approximate logarithmic curve fit to the built-in labor cost data in '
'GETEM.'
)
# noinspection SpellCheckingInspection
self.Cgath = self.OutputParameterDict[self.Cgath.Name] = OutputParameter(
Name="Field gathering system cost",
display_name='Field gathering system costs',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
CurrentUnits=CurrencyUnit.MDOLLARS,
# TODO interpolate constant values in tooltip text instead of hardcoding in tooltip text
ToolTipText='The built-in cost correlation for estimating the field gathering system cost includes '
'the cost for surface piping from each well to the plant and pumps for production and '
'injection wells. The length of the surface piping is assumed 750 m per well at a cost of '
'$500 per meter. The pumping cost for each pump in the production wells (line-shaft pumps) '
'and a single pump for the injection wells is calculated with the same correlation as GETEM. '
f'Contingency (default: '
f'{self.contingency_percentage.quantity().to(convertible_unit("%")).magnitude:g}%). '
f'and indirect costs (default: '
f'{self.indirect_capital_cost_percentage.quantity().to(convertible_unit("%")).magnitude}%) '
f'are added. '
'The built-in cost correlation does not include the cost of pipelines to an off-site heat '
'user or a district-heating system. These costs are estimated at $750 per meter pipeline '
'length and can be manually added by the user to the pipeline distribution costs.'
)
self.Cpiping = self.OutputParameterDict[self.Cpiping.Name] = OutputParameter(
Name="Transmission pipeline costs",
Expand All @@ -1917,7 +2002,9 @@ def __init__(self, model: Model):
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
ToolTipText='Assumes $3.5/1,000 gallons of water' # TODO parameterize
ToolTipText=f'Default correlation: Assumes $3.50/1,000 gallons of water. '
f'Provide {self.oamwateradjfactor.Name} to multiply the default correlation.'
# Note: $3.50 could possibly be parameterized, but adjustment factor param serves the same purpose for now.
)
self.CCap = self.OutputParameterDict[self.CCap.Name] = OutputParameter(
Name="Total Capital Cost",
Expand All @@ -1934,7 +2021,9 @@ def __init__(self, model: Model):
display_name='Total operating and maintenance costs',
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
ToolTipText=f'GEOPHIRES estimates the annual O&M costs as the sum of the annual surface plant, wellfield, '
f'make-up water, and pumping O&M costs.'
)
self.averageannualpumpingcosts = OutputParameter(
Name="Average Annual Pumping Costs",
Expand Down
30 changes: 20 additions & 10 deletions src/geophires_x/OptionList.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,22 @@ def __ne__(self, other):
return str(self) != str(other)


_EXTRA_HEAT_SNIPPET = 'Heat sales considered as extra income'
_EXTRA_ELECTRICITY_SNIPPET = 'Electricity sales considered as extra income'


class EndUseOptions(GeophiresInputEnum):
ELECTRICITY = 1, "Electricity"
HEAT = 2, "Direct-Use Heat"
COGENERATION_TOPPING_EXTRA_HEAT = 31, "Cogeneration Topping Cycle, Heat sales considered as extra income"
COGENERATION_TOPPING_EXTRA_ELECTRICITY = 32, "Cogeneration Topping Cycle, Electricity sales considered as extra income"
COGENERATION_BOTTOMING_EXTRA_HEAT = 41, "Cogeneration Bottoming Cycle, Heat sales considered as extra income"
COGENERATION_BOTTOMING_EXTRA_ELECTRICITY = 42, "Cogeneration Bottoming Cycle, Electricity sales considered as extra income"
COGENERATION_PARALLEL_EXTRA_HEAT = 51, "Cogeneration Parallel Cycle, Heat sales considered as extra income"
COGENERATION_PARALLEL_EXTRA_ELECTRICITY = 52, "Cogeneration Parallel Cycle, Electricity sales considered as extra income"
ELECTRICITY = 1, 'Electricity'
HEAT = 2, 'Direct-Use Heat'
COGENERATION_TOPPING_EXTRA_HEAT = 31, f'Cogeneration Topping Cycle, {_EXTRA_HEAT_SNIPPET}'
COGENERATION_TOPPING_EXTRA_ELECTRICITY = 32, f'Cogeneration Topping Cycle, {_EXTRA_ELECTRICITY_SNIPPET}'
COGENERATION_BOTTOMING_EXTRA_HEAT = 41, f'Cogeneration Bottoming Cycle, {_EXTRA_HEAT_SNIPPET}'
COGENERATION_BOTTOMING_EXTRA_ELECTRICITY = 42, f'Cogeneration Bottoming Cycle, {_EXTRA_ELECTRICITY_SNIPPET}'
COGENERATION_PARALLEL_EXTRA_HEAT = 51, f'Cogeneration Parallel Cycle, {_EXTRA_HEAT_SNIPPET}'
COGENERATION_PARALLEL_EXTRA_ELECTRICITY = 52, f'Cogeneration Parallel Cycle, {_EXTRA_ELECTRICITY_SNIPPET}'

@staticmethod
def from_input_string(input_string: str):
def from_input_string(input_string: str) -> 'EndUseOptions':
"""
:rtype: EndUseOptions
"""
Expand All @@ -48,11 +52,17 @@ def from_input_string(input_string: str):
raise ValueError(f'Unknown End-Use Option input value: {input_string}')

@staticmethod
def from_int(int_val):
def from_int(int_val: int) -> 'EndUseOptions':
"""
:rtype: EndUseOptions
"""

for member in __class__:
if member.int_value == int_val:
return member

raise ValueError(f'Unknown End-Use Option integer input value: {int_val}')


class PlantType(GeophiresInputEnum):
SUB_CRITICAL_ORC = 1, "Subcritical ORC"
Expand Down
Loading