Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.34
current_version = 3.9.35
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.34
version: 3.9.35
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.34.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.35.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.34...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.35...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.34'
version = release = '3.9.35'

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.34',
version='3.9.35',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
160 changes: 97 additions & 63 deletions src/geophires_x/Economics.py

Large diffs are not rendered by default.

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.34'
__version__ = '3.9.35'
11 changes: 10 additions & 1 deletion src/geophires_x_schema_generator/geophires-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -1387,7 +1387,7 @@
]
},
"Reservoir Stimulation Capital Cost": {
"description": "Total reservoir stimulation capital cost, including contingency and indirect costs.",
"description": "Total reservoir stimulation capital cost, including indirect costs and contingency.",
"type": "number",
"units": "MUSD",
"category": "Economics",
Expand Down Expand Up @@ -1719,6 +1719,15 @@
"minimum": 0.0,
"maximum": 1.0
},
"Contingency Percentage": {
"description": "The contingency percentage applied to the direct capital costs for stimulation, field gathering system, exploration, and surface plant. (Note: well drilling and completion costs do not have contingency applied and are not affected by this parameter.)",
"type": "number",
"units": "%",
"category": "Economics",
"default": 15.0,
"minimum": 0.0,
"maximum": 100.0
},
"Well Drilling Cost Correlation": {
"description": "Select the built-in well drilling and completion cost correlation: 1: vertical small diameter, baseline; 2: deviated small diameter, baseline; 3: vertical large diameter, baseline; 4: deviated large diameter, baseline; 5: Simple (per-meter cost); 6: vertical small diameter, intermediate1; 7: vertical small diameter, intermediate2; 8: deviated small diameter, intermediate1; 9: deviated small diameter, intermediate2; 10: vertical large diameter, intermediate1; 11: vertical large diameter, intermediate2; 12: deviated large diameter, intermediate1; 13: deviated large diameter, intermediate2; 14: vertical open-hole, small diameter, ideal; 15: deviated liner, small diameter, ideal; 16: vertical open-hole, large diameter, ideal; 17: deviated liner, large diameter, ideal. Baseline correlations (1-4) are from NREL's 2025 cost curve update. Intermediate and ideal correlations (6-17) are from GeoVision.",
"type": "integer",
Expand Down
6 changes: 5 additions & 1 deletion tests/base_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ def assertAlmostEqualWithinPercentage(self, expected, actual, msg: str | None =
raise ValueError(f'msg must be a string (you may have meant to pass percent={msg})')

if isinstance(expected, numbers.Real):
self.assertAlmostEqual(expected, actual, msg=msg, delta=abs(percent / 100.0 * expected))
try:
self.assertAlmostEqual(expected, actual, msg=msg, delta=abs(percent / 100.0 * expected))
except AssertionError as ae:
difference_percent = abs(100.0 * (actual - expected) / expected)
raise AssertionError(f'{actual} != {expected} within {percent}% ({difference_percent:.2f}%)') from ae
else:
if isinstance(expected, list) and isinstance(actual, list):
suggest = f'self.assertListAlmostEqual({expected}, {actual}, msg={msg}, percent={percent})'
Expand Down
105 changes: 105 additions & 0 deletions tests/test_geophires_x.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,3 +1092,108 @@ def wellfield_cost(result_cap_costs):
# Note this is not necessarily true for all cases, but generally would be expected,
# and is true for Fervo_Project_Cape-4 specifically.
)

def test_contingency(self):
def _get_result(
contingency_percentage: Optional[int] = None,
input_file_path: str = 'geophires_x_tests/generic-egs-case.txt',
) -> float:
p = {}

if contingency_percentage is not None:
p['Contingency Percentage'] = contingency_percentage

return (
GeophiresXClient()
.get_geophires_result(
ImmutableGeophiresInputParameters(
from_file_path=self._get_test_file_path(input_file_path),
params=p,
)
)
.result['CAPITAL COSTS (M$)']
)

def capex(result_cap_costs):
if result_cap_costs.get('Total CAPEX') is not None:
return result_cap_costs['Total CAPEX']['value']

return result_cap_costs['Total capital costs']['value']

default_contingency_percent = 15

for contingency_percent in range(5, 35, 5):
if contingency_percent == default_contingency_percent:
continue

for input_file_path_ in [
'geophires_x_tests/generic-egs-case.txt',
'examples/example10_HP.txt',
'examples/example11_AC.txt',
]:
with self.subTest(msg=f'contingency={contingency_percent}, input_file_path={input_file_path_}'):
result_default = _get_result(input_file_path=input_file_path_)

self.assertEqual(
# Test assumption check, update default_contingency_percent
# if GEOPHIRES default value is changed.
capex(result_default),
capex(
_get_result(
contingency_percentage=default_contingency_percent,
input_file_path=input_file_path_,
)
),
)

result_different_contingency = _get_result(
contingency_percentage=contingency_percent, input_file_path=input_file_path_
)

if contingency_percent > default_contingency_percent:
self.assertGreater(
capex(result_different_contingency),
capex(result_default),
)
else:
self.assertLess(
capex(result_different_contingency),
capex(result_default),
)

self.assertEqual(
# Contingency is not applied to drilling costs
result_default['Drilling and completion costs']['value'],
result_different_contingency['Drilling and completion costs']['value'],
)

for cost_category in [
'Stimulation costs',
'Surface power plant costs',
'Field gathering system costs',
'Total surface equipment costs',
'Exploration costs',
]:
default_contingency_factor = 1.0 + (default_contingency_percent / 100.0)
different_contingency_factor = 1.0 + (contingency_percent / 100.0)

expected = (
result_default[cost_category]['value']
/ default_contingency_factor
* different_contingency_factor
)

actual = result_different_contingency[cost_category]['value']

# Rounding throws off by a few percent
max_allowed_delta_percent = max(
# TODO to audit more thoroughly and avoid usage of these tuned constants
2.5 if contingency_percent > default_contingency_percent else 5.4,
(contingency_percent - default_contingency_percent) / 2.0,
)

self.assertAlmostEqualWithinPercentage(
expected,
actual,
percent=max_allowed_delta_percent,
)
Loading