Skip to content

Commit bdaed4a

Browse files
Merge pull request #85 from softwareengineerprogrammer/parameterize-contingency
Parameterize contingency
2 parents 16dd1a1 + 9533abf commit bdaed4a

File tree

10 files changed

+224
-72
lines changed

10 files changed

+224
-72
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 3.9.34
2+
current_version = 3.9.35
33
commit = True
44
tag = True
55

.cookiecutterrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ default_context:
5454
sphinx_doctest: "no"
5555
sphinx_theme: "sphinx-py3doc-enhanced-theme"
5656
test_matrix_separate_coverage: "no"
57-
version: 3.9.34
57+
version: 3.9.35
5858
version_manager: "bump2version"
5959
website: "https://github.com/NREL"
6060
year_from: "2023"

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ Free software: `MIT license <LICENSE>`__
5858
:alt: Supported implementations
5959
:target: https://pypi.org/project/geophires-x
6060

61-
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.34.svg
61+
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.35.svg
6262
:alt: Commits since latest release
63-
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.34...main
63+
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.35...main
6464

6565
.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
6666
:target: https://nrel.github.io/GEOPHIRES-X

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
year = '2025'
1919
author = 'NREL'
2020
copyright = f'{year}, {author}'
21-
version = release = '3.9.34'
21+
version = release = '3.9.35'
2222

2323
pygments_style = 'trac'
2424
templates_path = ['./templates']

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def read(*names, **kwargs):
1313

1414
setup(
1515
name='geophires-x',
16-
version='3.9.34',
16+
version='3.9.35',
1717
license='MIT',
1818
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
1919
long_description='{}\n{}'.format(

src/geophires_x/Economics.py

Lines changed: 97 additions & 63 deletions
Large diffs are not rendered by default.

src/geophires_x/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '3.9.34'
1+
__version__ = '3.9.35'

src/geophires_x_schema_generator/geophires-request.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@
13871387
]
13881388
},
13891389
"Reservoir Stimulation Capital Cost": {
1390-
"description": "Total reservoir stimulation capital cost, including contingency and indirect costs.",
1390+
"description": "Total reservoir stimulation capital cost, including indirect costs and contingency.",
13911391
"type": "number",
13921392
"units": "MUSD",
13931393
"category": "Economics",
@@ -1719,6 +1719,15 @@
17191719
"minimum": 0.0,
17201720
"maximum": 1.0
17211721
},
1722+
"Contingency Percentage": {
1723+
"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.)",
1724+
"type": "number",
1725+
"units": "%",
1726+
"category": "Economics",
1727+
"default": 15.0,
1728+
"minimum": 0.0,
1729+
"maximum": 100.0
1730+
},
17221731
"Well Drilling Cost Correlation": {
17231732
"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.",
17241733
"type": "integer",

tests/base_test_case.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@ def assertAlmostEqualWithinPercentage(self, expected, actual, msg: str | None =
2727
raise ValueError(f'msg must be a string (you may have meant to pass percent={msg})')
2828

2929
if isinstance(expected, numbers.Real):
30-
self.assertAlmostEqual(expected, actual, msg=msg, delta=abs(percent / 100.0 * expected))
30+
try:
31+
self.assertAlmostEqual(expected, actual, msg=msg, delta=abs(percent / 100.0 * expected))
32+
except AssertionError as ae:
33+
difference_percent = abs(100.0 * (actual - expected) / expected)
34+
raise AssertionError(f'{actual} != {expected} within {percent}% ({difference_percent:.2f}%)') from ae
3135
else:
3236
if isinstance(expected, list) and isinstance(actual, list):
3337
suggest = f'self.assertListAlmostEqual({expected}, {actual}, msg={msg}, percent={percent})'

tests/test_geophires_x.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,3 +1092,108 @@ def wellfield_cost(result_cap_costs):
10921092
# Note this is not necessarily true for all cases, but generally would be expected,
10931093
# and is true for Fervo_Project_Cape-4 specifically.
10941094
)
1095+
1096+
def test_contingency(self):
1097+
def _get_result(
1098+
contingency_percentage: Optional[int] = None,
1099+
input_file_path: str = 'geophires_x_tests/generic-egs-case.txt',
1100+
) -> float:
1101+
p = {}
1102+
1103+
if contingency_percentage is not None:
1104+
p['Contingency Percentage'] = contingency_percentage
1105+
1106+
return (
1107+
GeophiresXClient()
1108+
.get_geophires_result(
1109+
ImmutableGeophiresInputParameters(
1110+
from_file_path=self._get_test_file_path(input_file_path),
1111+
params=p,
1112+
)
1113+
)
1114+
.result['CAPITAL COSTS (M$)']
1115+
)
1116+
1117+
def capex(result_cap_costs):
1118+
if result_cap_costs.get('Total CAPEX') is not None:
1119+
return result_cap_costs['Total CAPEX']['value']
1120+
1121+
return result_cap_costs['Total capital costs']['value']
1122+
1123+
default_contingency_percent = 15
1124+
1125+
for contingency_percent in range(5, 35, 5):
1126+
if contingency_percent == default_contingency_percent:
1127+
continue
1128+
1129+
for input_file_path_ in [
1130+
'geophires_x_tests/generic-egs-case.txt',
1131+
'examples/example10_HP.txt',
1132+
'examples/example11_AC.txt',
1133+
]:
1134+
with self.subTest(msg=f'contingency={contingency_percent}, input_file_path={input_file_path_}'):
1135+
result_default = _get_result(input_file_path=input_file_path_)
1136+
1137+
self.assertEqual(
1138+
# Test assumption check, update default_contingency_percent
1139+
# if GEOPHIRES default value is changed.
1140+
capex(result_default),
1141+
capex(
1142+
_get_result(
1143+
contingency_percentage=default_contingency_percent,
1144+
input_file_path=input_file_path_,
1145+
)
1146+
),
1147+
)
1148+
1149+
result_different_contingency = _get_result(
1150+
contingency_percentage=contingency_percent, input_file_path=input_file_path_
1151+
)
1152+
1153+
if contingency_percent > default_contingency_percent:
1154+
self.assertGreater(
1155+
capex(result_different_contingency),
1156+
capex(result_default),
1157+
)
1158+
else:
1159+
self.assertLess(
1160+
capex(result_different_contingency),
1161+
capex(result_default),
1162+
)
1163+
1164+
self.assertEqual(
1165+
# Contingency is not applied to drilling costs
1166+
result_default['Drilling and completion costs']['value'],
1167+
result_different_contingency['Drilling and completion costs']['value'],
1168+
)
1169+
1170+
for cost_category in [
1171+
'Stimulation costs',
1172+
'Surface power plant costs',
1173+
'Field gathering system costs',
1174+
'Total surface equipment costs',
1175+
'Exploration costs',
1176+
]:
1177+
default_contingency_factor = 1.0 + (default_contingency_percent / 100.0)
1178+
different_contingency_factor = 1.0 + (contingency_percent / 100.0)
1179+
1180+
expected = (
1181+
result_default[cost_category]['value']
1182+
/ default_contingency_factor
1183+
* different_contingency_factor
1184+
)
1185+
1186+
actual = result_different_contingency[cost_category]['value']
1187+
1188+
# Rounding throws off by a few percent
1189+
max_allowed_delta_percent = max(
1190+
# TODO to audit more thoroughly and avoid usage of these tuned constants
1191+
2.5 if contingency_percent > default_contingency_percent else 5.4,
1192+
(contingency_percent - default_contingency_percent) / 2.0,
1193+
)
1194+
1195+
self.assertAlmostEqualWithinPercentage(
1196+
expected,
1197+
actual,
1198+
percent=max_allowed_delta_percent,
1199+
)

0 commit comments

Comments
 (0)