Skip to content

Redrilling costs fixup; Don't display incorrect SAM-EM ITC [v3.9.40] #87

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 13 commits into from
Jul 26, 2025
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.36
current_version = 3.9.40
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.36
version: 3.9.40
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.36.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.40.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.36...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.40...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.36'
version = release = '3.9.40'

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.36',
version='3.9.40',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
30 changes: 26 additions & 4 deletions src/geophires_x/Economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,7 +868,8 @@ def __init__(self, model: Model):
"Total Capital Cost",
DefaultValue=-1.0,
Min=0,
Max=1000,
# pint treats GUSD as billions of dollars (G for giga)
Max=quantity(100, 'GUSD').to('MUSD').magnitude,
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
Expand Down Expand Up @@ -1775,6 +1776,18 @@ def __init__(self, model: Model):
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
# TODO TooltipText to document how this is calculated
)
self.redrilling_annual_cost = self.OutputParameterDict[self.redrilling_annual_cost.Name] = OutputParameter(
Name="Redrilling costs",
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
ToolTipText=f'Total redrilling costs over the {model.surfaceplant.plant_lifetime.Name} are calculated as '
f'({self.Cwell.display_name} + {self.Cstim.display_name}) '
f'× {model.wellbores.redrill.display_name}. '
f'The total is then divided over {model.surfaceplant.plant_lifetime.Name} years to calculate '
f'Redrilling costs per year.'
)
self.Cplant = self.OutputParameterDict[self.Cplant.Name] = OutputParameter(
Name="Surface Plant cost",
Expand Down Expand Up @@ -1809,7 +1822,7 @@ def __init__(self, model: Model):
UnitType=Units.CURRENCYFREQUENCY,
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
ToolTipText='Assumes $3.5/1,000 gallons of water'
ToolTipText='Assumes $3.5/1,000 gallons of water' # TODO parameterize
)
self.CCap = self.OutputParameterDict[self.CCap.Name] = OutputParameter(
Name="Total Capital Cost",
Expand Down Expand Up @@ -2521,8 +2534,10 @@ def Calculate(self, model: Model) -> None:

if model.wellbores.redrill.value > 0:
# account for well redrilling
self.Coam.value = self.Coam.value + \
(self.Cwell.value + self.Cstim.value) * model.wellbores.redrill.value / model.surfaceplant.plant_lifetime.value
redrilling_costs: PlainQuantity = self.calculate_redrilling_costs(model)
self.redrilling_annual_cost.value = redrilling_costs.to(self.redrilling_annual_cost.CurrentUnits).magnitude
self.Coam.value += redrilling_costs.to(self.Coam.CurrentUnits).magnitude


# Add in the AnnualLicenseEtc and TaxRelief
self.Coam.value = self.Coam.value + self.AnnualLicenseEtc.value - self.TaxRelief.value
Expand Down Expand Up @@ -2751,6 +2766,11 @@ def calculate_stimulation_costs(self, model: Model) -> PlainQuantity:

return quantity(stimulation_costs, self.Cstim.CurrentUnits)

def calculate_redrilling_costs(self, model) -> PlainQuantity:
return ((self.Cwell.quantity() + self.Cstim.quantity())
* model.wellbores.redrill.quantity()
/ model.surfaceplant.plant_lifetime.quantity())

def calculate_field_gathering_costs(self, model: Model) -> None:
if self.ccgathfixed.Valid:
self.Cgath.value = self.ccgathfixed.value
Expand Down Expand Up @@ -3164,5 +3184,7 @@ def _calculate_derived_outputs(self, model: Model) -> None:
(model.wellbores.nprod.value + model.wellbores.ninj.value)
)



def __str__(self):
return "Economics"
23 changes: 19 additions & 4 deletions src/geophires_x/Outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def PrintOutputs(self, model: Model):
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' Number of times redrilling: {model.wellbores.redrill.value:10.0f}'+NL)
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)
f.write(NL)
Expand Down Expand Up @@ -475,12 +475,23 @@ def PrintOutputs(self, model: Model):
f.write(f' District Heating System Cost: {model.economics.dhdistrictcost.value:10.2f} {model.economics.dhdistrictcost.CurrentUnits.value}\n')
f.write(f' Total surface equipment costs: {(model.economics.Cplant.value+model.economics.Cgath.value):10.2f} ' + model.economics.Cplant.CurrentUnits.value + NL)
f.write(f' {model.economics.Cexpl.display_name}: {model.economics.Cexpl.value:10.2f} {model.economics.Cexpl.CurrentUnits.value}\n')

if model.economics.totalcapcost.Valid and model.wellbores.redrill.value > 0:
f.write(f' Drilling and completion costs (for redrilling):{model.economics.Cwell.value:10.2f} ' + model.economics.Cwell.CurrentUnits.value + NL)
f.write(f' Drilling and completion costs per redrilled well: {(model.economics.Cwell.value/(model.wellbores.nprod.value+model.wellbores.ninj.value)):10.2f} ' + model.economics.Cwell.CurrentUnits.value + NL)
f.write(f' Drilling and completion costs (for redrilling):{econ.Cwell.value:10.2f} {econ.Cwell.CurrentUnits.value}\n')
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:
f.write(f' {model.economics.RITCValue.display_name}: {-1*model.economics.RITCValue.value:10.2f} {model.economics.RITCValue.CurrentUnits.value}\n')
if model.economics.econmodel.value != EconomicModel.SAM_SINGLE_OWNER_PPA:
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

capex_label = Outputs._field_label(econ.CCap.display_name, 50)
f.write(f' {capex_label}{econ.CCap.value:10.2f} {econ.CCap.CurrentUnits.value}\n')
Expand All @@ -506,6 +517,10 @@ def PrintOutputs(self, model: Model):
f.write(f' Annual District Heating O&M Cost: {model.economics.dhdistrictoandmcost.value:10.2f} {model.economics.dhdistrictoandmcost.CurrentUnits.value}\n')
f.write(f' Average Annual Peaking Fuel Cost: {model.economics.averageannualngcost.value:10.2f} {model.economics.averageannualngcost.CurrentUnits.value}\n')

if model.wellbores.redrill.value > 0:
redrill_label = Outputs._field_label(econ.redrilling_annual_cost.display_name, 47)
f.write(f' {redrill_label}{econ.redrilling_annual_cost.value:10.2f} {econ.redrilling_annual_cost.CurrentUnits.value}\n')

f.write(f' {econ.Coam.display_name}: {(econ.Coam.value + econ.averageannualpumpingcosts.value + econ.averageannualheatpumpelectricitycost.value):10.2f} {econ.Coam.CurrentUnits.value}\n')
else:
f.write(f' {econ.Coam.display_name}: {econ.Coam.value:10.2f} {econ.Coam.CurrentUnits.value}\n')
Expand Down
1 change: 1 addition & 0 deletions src/geophires_x/WellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,7 @@ def __init__(self, model: Model):
)
self.redrill = self.OutputParameterDict[self.redrill.Name] = OutputParameter(
Name="redrill",
display_name='Number of times redrilling',
UnitType=Units.NONE
)
self.PumpingPowerProd = self.OutputParameterDict[self.PumpingPowerProd.Name] = OutputParameter(
Expand Down
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.36'
__version__ = '3.9.40'
7 changes: 4 additions & 3 deletions src/geophires_x_client/geophires_x_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,13 +284,14 @@ class GeophiresXResult:
'Annual District Heating O&M Cost',
'Average Annual Peaking Fuel Cost',
'Average annual pumping costs',
'Total operating and maintenance costs',
# AGS/CLGS
'OPEX',
# SUTRA
'Average annual auxiliary fuel cost',
'Average annual pumping cost',
'Redrilling costs',
'Total average annual O&M costs',
'Total operating and maintenance costs',
# AGS/CLGS
'OPEX',
],
'SURFACE EQUIPMENT SIMULATION RESULTS': [
'Initial geofluid availability',
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 @@ -1591,7 +1591,7 @@
"category": "Economics",
"default": -1.0,
"minimum": 0,
"maximum": 1000
"maximum": 100000.0
},
"Total O&M Cost": {
"description": "Total initial O&M cost.",
Expand Down
19 changes: 14 additions & 5 deletions src/geophires_x_schema_generator/geophires-result.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,11 @@
"Flowrate per production well": {},
"Injection well casing ID": {},
"Production well casing ID": {},
"Number of times redrilling": {},
"Number of times redrilling": {
"type": "number",
"description": "redrill",
"units": null
},
"Power plant type": {},
"Fluid": {},
"Design": {},
Expand Down Expand Up @@ -432,15 +436,20 @@
"units": "MUSD/yr"
},
"Average annual pumping costs": {},
"Average annual auxiliary fuel cost": {},
"Average annual pumping cost": {},
"Redrilling costs": {
"type": "number",
"description": "Total redrilling costs over the Plant Lifetime are calculated as (Drilling and completion costs + Stimulation costs) \u00d7 Number of times redrilling. The total is then divided over Plant Lifetime years to calculate Redrilling costs per year.",
"units": "MUSD/yr"
},
"Total average annual O&M costs": {},
"Total operating and maintenance costs": {
"type": "number",
"description": "Total O&M Cost",
"units": "MUSD/yr"
},
"OPEX": {},
"Average annual auxiliary fuel cost": {},
"Average annual pumping cost": {},
"Total average annual O&M costs": {}
"OPEX": {}
}
},
"SURFACE EQUIPMENT SIMULATION RESULTS": {
Expand Down
10 changes: 5 additions & 5 deletions tests/examples/Fervo_Project_Cape-4.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: 1.773 sec
GEOPHIRES Version: 3.9.39
Simulation Date: 2025-07-25
Simulation Time: 14:32
Calculation Time: 1.794 sec

***SUMMARY OF RESULTS***

Expand Down Expand Up @@ -104,7 +104,6 @@ Simulation Metadata
Field gathering system costs: 56.44 MUSD
Total surface equipment costs: 1560.49 MUSD
Exploration costs: 30.00 MUSD
Investment Tax Credit: -688.54 MUSD
Total CAPEX: 2639.39 MUSD


Expand All @@ -113,6 +112,7 @@ Simulation Metadata
Wellfield maintenance costs: 6.20 MUSD/yr
Power plant maintenance costs: 25.43 MUSD/yr
Water costs: 24.86 MUSD/yr
Redrilling costs: 70.46 MUSD/yr
Total operating and maintenance costs: 126.95 MUSD/yr


Expand Down
9 changes: 5 additions & 4 deletions tests/examples/example13.out
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

Simulation Metadata
----------------------
GEOPHIRES Version: 3.9.7
Simulation Date: 2025-05-15
Simulation Time: 10:12
Calculation Time: 0.032 sec
GEOPHIRES Version: 3.9.36
Simulation Date: 2025-07-25
Simulation Time: 11:30
Calculation Time: 0.037 sec

***SUMMARY OF RESULTS***

Expand Down Expand Up @@ -109,6 +109,7 @@ Simulation Metadata
Wellfield maintenance costs: 0.62 MUSD/yr
Power plant maintenance costs: 1.30 MUSD/yr
Water costs: 0.00 MUSD/yr
Redrilling costs: 0.92 MUSD/yr
Total operating and maintenance costs: 2.84 MUSD/yr


Expand Down
9 changes: 4 additions & 5 deletions tests/examples/example_SAM-single-owner-PPA-2.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.975 sec
GEOPHIRES Version: 3.9.39
Simulation Date: 2025-07-25
Simulation Time: 14:32
Calculation Time: 0.983 sec

***SUMMARY OF RESULTS***

Expand Down Expand Up @@ -105,7 +105,6 @@ Simulation Metadata
Field gathering system costs: 70.43 MUSD
Total surface equipment costs: 969.26 MUSD
Exploration costs: 30.00 MUSD
Investment Tax Credit: -459.83 MUSD
Total CAPEX: 1609.42 MUSD


Expand Down
9 changes: 4 additions & 5 deletions tests/examples/example_SAM-single-owner-PPA.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: 1.163 sec
GEOPHIRES Version: 3.9.39
Simulation Date: 2025-07-25
Simulation Time: 14:32
Calculation Time: 1.177 sec

***SUMMARY OF RESULTS***

Expand Down Expand Up @@ -106,7 +106,6 @@ Simulation Metadata
Field gathering system costs: 5.80 MUSD
Total surface equipment costs: 150.23 MUSD
Exploration costs: 3.89 MUSD
Investment Tax Credit: -63.71 MUSD
Total CAPEX: 222.97 MUSD


Expand Down
42 changes: 42 additions & 0 deletions tests/test_geophires_x.py
Original file line number Diff line number Diff line change
Expand Up @@ -1219,3 +1219,45 @@ def test_exploration_cost(self):

self.assertEqual(exploration_cost_MUSD, result.result['CAPITAL COSTS (M$)']['Exploration costs']['value'])
self.assertEqual('MUSD', result.result['CAPITAL COSTS (M$)']['Exploration costs']['unit'])

def test_redrilling_costs(self):
total_capex_specified_result = GeophiresXClient().get_geophires_result(
ImmutableGeophiresInputParameters(
from_file_path=self._get_test_file_path('examples/Fervo_Project_Cape-4.txt'),
params={'Total Capital Cost': 2500},
)
)

for result in [
GeophiresXResult(self._get_test_file_path('examples/Fervo_Project_Cape-4.out')),
total_capex_specified_result,
]:
result_redrills = result.result['ENGINEERING PARAMETERS']['Number of times redrilling']['value']
self.assertGreater(result_redrills, 0)

result_opex = result.result['OPERATING AND MAINTENANCE COSTS (M$/yr)']
opex_sum = 0
expected_opex_line_items = [
'Wellfield maintenance costs',
'Power plant maintenance costs',
'Water costs',
'Redrilling costs',
]
for opex_line_item in expected_opex_line_items:
opex_sum += result_opex[opex_line_item]['value']

self.assertAlmostEqual(result_opex['Total operating and maintenance costs']['value'], opex_sum, places=1)

result_capex = result.result['CAPITAL COSTS (M$)']
capex_field_suffix = (
'' if result_capex.get('Drilling and completion costs') is not None else ' (for redrilling)'
)
expected_annual_redrilling_cost = (
(
result_capex[f'Drilling and completion costs{capex_field_suffix}']['value']
+ result_capex[f'Stimulation costs{capex_field_suffix}']['value']
)
* result_redrills
) / result.result['ECONOMIC PARAMETERS']['Project lifetime']['value']

self.assertAlmostEqual(expected_annual_redrilling_cost, result_opex['Redrilling costs']['value'], places=2)
Loading