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.7.5
current_version = 3.7.6
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.7.5
version: 3.7.6
version_manager: "bump2version"
website: "https://github.com/NREL"
year_from: "2023"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ GEOPHIRES-X (2023-2025)

3.7.5: Injection Well Drilling and Completion Capital Cost Adjustment Factor, if not provided, is now automatically set to the provided value of Well Drilling and Completion Capital Cost Adjustment Factor.

3.7.6: Fixes bugs pertaining to specifying non-CLGS laterals and display of per-lateral costs.

3.6
^^^
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,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.7.5.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.7.6.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.5...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.6...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.7.5'
version = release = '3.7.6'

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.7.5',
version='3.7.6',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
1 change: 1 addition & 0 deletions src/geophires_x/AGSEconomics.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,5 +259,6 @@ def Calculate(self, model: Model) -> None:
self.Coam.value = self.AverageOPEX_Plant * 1000
self.Coam.CurrentUnits = CurrencyFrequencyUnit.KDOLLARSPERYEAR

self._calculate_derived_outputs(model)
model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')

34 changes: 14 additions & 20 deletions src/geophires_x/AGSWellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,11 @@ def __init__(self, model: Model):
# NB: inputs we already have ("already have it") need to be set at ReadParameter time so values are set at the
# last possible time

# Assume CLGS has 1 lateral by default (Non-CLGS default value is 0)
self.numnonverticalsections.value = 1
self.numnonverticalsections.ErrMessage = (f'assume default for Number of Nonvertical Wellbore Sections '
f'({self.numnonverticalsections.value})')

self.time_operation = self.ParameterDict[self.time_operation.Name] = floatParameter(
"Closed Loop Calculation Start Year",
DefaultValue=0.01,
Expand Down Expand Up @@ -561,39 +566,28 @@ def read_parameters(self, model: Model) -> None:
model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}')
super().read_parameters(model) # read the default parameters
# if we call super, we don't need to deal with setting the parameters here, just deal with the special cases
# for the variables in this class because the call to the super.readparameters will set all the variables,
# for the variables in this class because the call to the super.read_parameters will set all the variables,
# including the ones that are specific to this class

if len(model.InputParameters) > 0:
# loop through all the parameters that the user wishes to set, looking for parameters that match this object
for item in self.ParameterDict.items():
ParameterToModify = item[1]
key = ParameterToModify.Name.strip()
if key in model.InputParameters:
ParameterReadIn = model.InputParameters[key]
# just handle special cases for this class - the call to super set all the values,
# including the value unique to this class
else:
model.logger.info("No parameters read because no content provided")

# handle error checking and special cases:

if model.reserv.numseg.value > 1:
msg = ('Warning: CLGS model can only handle a single layer gradient segment. '
msg = ('CLGS model can only handle a single layer gradient segment. '
'Number of Segments set to 1, Gradient set to Gradient[0], and Depth set to Reservoir Depth.')
print(msg)
print(f'Warning: {msg}')
model.logger.warning(msg)
model.reserv.numseg.value = 1

if self.ninj.value > 0:
msg = ('Warning: CLGS model considers the only the production wellbore parameters. '
msg = ('CLGS model considers the only the production wellbore parameters. '
'Anything related to the injection wellbore is ignored.')
print(msg)
print(f'Warning: {msg}')
model.logger.warning(msg)

if self.nprod.value != 1:
msg = ('Warning: CLGS model considers the only a single production wellbore (coaxial or uloop). '
'Number of production wellboreset set 1.')
print(msg)
msg = ('CLGS model considers the only a single production wellbore (coaxial or uloop). '
'Number of production wellbores set to 1.')
print(f'Warning: {msg}')
model.logger.warning(msg)

# inputs we already have - needs to be set at ReadParameter time so values set at the latest possible time
Expand Down
49 changes: 37 additions & 12 deletions src/geophires_x/Economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,8 @@ def calculate_cost_of_non_vertical_section(model: Model, length_m: float, well_c
f'{fixed_well_cost_name} (fixed cost per well) instead.'
)

casing_factor = 1.0
if not NonverticalsCased:
# assume that casing & cementing costs 50% of drilling costs
casing_factor = 0.5
# assume that casing & cementing costs 50% of drilling costs
casing_factor = 1.0 if NonverticalsCased else 0.5

if model.economics.Nonvertical_drilling_cost_per_m.Provided or well_correlation is WellDrillingCostCorrelation.SIMPLE:
cost_of_non_vertical_section = casing_factor * ((num_nonvertical_sections * nonvertical_drilling_cost_per_m * length_per_section_m)) * 1E-6
Expand Down Expand Up @@ -1554,11 +1552,16 @@ def __init__(self, model: Model):
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)

self.Cwell = self.OutputParameterDict[self.Cwell.Name] = OutputParameter(
Name="Wellfield cost",
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
CurrentUnits=CurrencyUnit.MDOLLARS,

# See TODO re:parameterizing indirect costs at src/geophires_x/Economics.py:652
ToolTipText="Includes total drilling and completion cost of all injection and production wells and "
"laterals, plus 5% indirect costs."
)
self.Coamwell = self.OutputParameterDict[self.Coamwell.Name] = OutputParameter(
Name="O&M Wellfield cost",
Expand Down Expand Up @@ -1797,6 +1800,12 @@ def __init__(self, model: Model):
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.cost_per_lateral_section = self.OutputParameterDict[self.cost_per_lateral_section.Name] = OutputParameter(
Name='Drilling and completion costs per non-vertical section',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS
)
self.cost_to_junction_section = self.OutputParameterDict[self.cost_to_junction_section.Name] = OutputParameter(
Name="Cost of the entire section of a well from bottom of vertical to junction with laterals",
UnitType=Units.CURRENCY,
Expand Down Expand Up @@ -2286,13 +2295,16 @@ def Calculate(self, model: Model) -> None:
self.injection_well_cost_adjustment_factor.value)

if hasattr(model.wellbores, 'numnonverticalsections') and model.wellbores.numnonverticalsections.Provided:
self.cost_lateral_section.value = calculate_cost_of_non_vertical_section(model, tot_horiz_m,
self.wellcorrelation.value,
self.Nonvertical_drilling_cost_per_m.value,
model.wellbores.numnonverticalsections.value,
self.per_injection_well_cost.Name,
model.wellbores.NonverticalsCased.value,
self.production_well_cost_adjustment_factor.value)
self.cost_lateral_section.value = calculate_cost_of_non_vertical_section(
model,
tot_horiz_m,
self.wellcorrelation.value,
self.Nonvertical_drilling_cost_per_m.value,
model.wellbores.numnonverticalsections.value,
self.Nonvertical_drilling_cost_per_m.Name,
model.wellbores.NonverticalsCased.value,
self.production_well_cost_adjustment_factor.value
)
else:
self.cost_lateral_section.value = 0.0
# cost of the well field
Expand Down Expand Up @@ -2881,7 +2893,20 @@ def Calculate(self, model: Model) -> None:
np.average(model.surfaceplant.ElectricityProduced.quantity().to(
'MW').magnitude * self.jobs_created_per_MW_electricity.value))

self._calculate_derived_outputs(model)
model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')

def _calculate_derived_outputs(self, model: Model) -> None:
"""
Subclasses should call _calculate_derived_outputs at the end of their Calculate methods to populate output
values that are derived from subclass-calculated outputs.
"""

if hasattr(self, 'cost_lateral_section') and self.cost_lateral_section.value != 0:
self.cost_per_lateral_section.value = (
self.cost_lateral_section.quantity().to(self.cost_per_lateral_section.CurrentUnits).magnitude
/ model.wellbores.numnonverticalsections.value
)

def __str__(self):
return "Economics"
1 change: 1 addition & 0 deletions src/geophires_x/EconomicsAddOns.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ def Calculate(self, model: Model) -> None:
# recalculate LCOE/LCOH
self.LCOE.value, self.LCOH.value, LCOC = Economics.CalculateLCOELCOHLCOC(self, model)

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

def __str__(self):
Expand Down
3 changes: 2 additions & 1 deletion src/geophires_x/EconomicsCCUS.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ def Calculate(self, model) -> None:
self.ProjectMOIC.value = self.ProjectCummCashFlow.value[len(self.ProjectCummCashFlow.value) - 1] / (
model.economics.CCap.value + (model.economics.Coam.value * model.surfaceplant.plant_lifetime.value))

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

def __str__(self):
return "EconomicsCCUS"
4 changes: 3 additions & 1 deletion src/geophires_x/EconomicsS_DAC_GT.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,4 +676,6 @@ def Calculate(self, model: Model) -> None:
# (self.CarbonExtractedAnnually.value[i] * model.economics.CarbonPrice.value[i]))
# if i > 0:
# model.economics.CarbonCummCashFlow.value[i] = model.economics.CarbonCummCashFlow.value[i - 1] + model.economics.CarbonRevenue.value[i]
model.logger.info("Complete " + str(__class__) + ": " + sys._getframe().f_code.co_name)

self._calculate_derived_outputs(model)
model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}')
2 changes: 1 addition & 1 deletion src/geophires_x/Outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1810,7 +1810,7 @@ def PrintOutputs(self, model: Model):
elif econ.cost_lateral_section.value > 0.0:
f.write(f' Drilling and completion costs per vertical production well: {econ.cost_one_production_well.value:10.2f} ' + econ.cost_one_production_well.CurrentUnits.value + NL)
f.write(f' Drilling and completion costs per vertical injection well: {econ.cost_one_injection_well.value:10.2f} ' + econ.cost_one_injection_well.CurrentUnits.value + NL)
f.write(f' Drilling and completion costs per non-vertical section: {econ.cost_lateral_section.value:10.2f} ' + econ.cost_lateral_section.CurrentUnits.value + NL)
f.write(f' {econ.cost_per_lateral_section.Name}: {econ.cost_per_lateral_section.value:10.2f} {econ.cost_lateral_section.CurrentUnits.value}\n')
else:
f.write(f' Drilling and completion costs per well: {model.economics.Cwell.value/(model.wellbores.nprod.value+model.wellbores.ninj.value):10.2f} ' + model.economics.Cwell.CurrentUnits.value + NL)
f.write(f' Stimulation costs: {model.economics.Cstim.value:10.2f} ' + model.economics.Cstim.CurrentUnits.value + NL)
Expand Down
1 change: 1 addition & 0 deletions src/geophires_x/SBTEconomics.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,5 +845,6 @@ def Calculate(self, model: Model) -> None:
# Calculate LCOE/LCOH
self.LCOE.value, self.LCOH.value, self.LCOC.value = CalculateLCOELCOHLCOC(self, model)

self._calculate_derived_outputs(model)
model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}')

5 changes: 5 additions & 0 deletions src/geophires_x/SBTWellbores.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def __init__(self, model: Model):
# NB: inputs we already have ("already have it") need to be set at ReadParameter time so values are set at the
# last possible time

# Assume CLGS has 1 lateral by default (Non-CLGS default value is 0)
self.numnonverticalsections.value = 1
self.numnonverticalsections.ErrMessage = (f'assume default for Number of Nonvertical Wellbore Sections '
f'({self.numnonverticalsections.value})')

self.vertical_section_length = self.ParameterDict[self.vertical_section_length.Name] = floatParameter(
'Vertical Section Length',
DefaultValue=2000.0,
Expand Down
3 changes: 2 additions & 1 deletion src/geophires_x/SUTRAEconomics.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ def Calculate(self, model: Model) -> None:
* 1e8
) # cents/kWh

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

def __str__(self):
return "Economics"
8 changes: 5 additions & 3 deletions src/geophires_x/WellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,10 +1030,10 @@ def __init__(self, model: Model):
)
self.numnonverticalsections = self.ParameterDict[self.numnonverticalsections.Name] = intParameter(
"Number of Multilateral Sections",
DefaultValue=1,
DefaultValue=0,
AllowableRange=list(range(0, 101, 1)),
UnitType=Units.NONE,
ErrMessage="assume default for Number of Nonvertical Wellbore Sections (1)",
ErrMessage="assume default for Number of Nonvertical Wellbore Sections (0)",
ToolTipText="Number of Nonvertical Wellbore Sections"
)
self.NonverticalsCased = self.ParameterDict[self.NonverticalsCased.Name] = boolParameter(
Expand All @@ -1042,7 +1042,9 @@ def __init__(self, model: Model):
Required=False,
Provided=False,
Valid=True,
ErrMessage="assume default value (False)"
ErrMessage="assume default value (False)",
ToolTipText="If set to True, casing & cementing are assumed to comprise 50% of drilling costs "
"(doubling cost compared to uncased)."
)

# local variable initiation
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.7.5'
__version__ = '3.7.6'
4 changes: 2 additions & 2 deletions src/geophires_x_schema_generator/geophires-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -916,12 +916,12 @@
"type": "integer",
"units": null,
"category": "Well Bores",
"default": 1,
"default": 0,
"minimum": 0,
"maximum": 100
},
"Multilaterals Cased": {
"description": "",
"description": "If set to True, casing & cementing are assumed to comprise 50% of drilling costs (doubling cost compared to uncased).",
"type": "boolean",
"units": null,
"category": "Well Bores",
Expand Down
10 changes: 5 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.7.1
Simulation Date: 2025-01-22
Simulation Time: 10:48
Calculation Time: 0.443 sec
GEOPHIRES Version: 3.7.5
Simulation Date: 2025-01-31
Simulation Time: 11:14
Calculation Time: 0.437 sec

***SUMMARY OF RESULTS***

Expand Down Expand Up @@ -97,7 +97,7 @@ Simulation Metadata
Drilling and completion costs: 10.13 MUSD
Drilling and completion costs per vertical production well: 3.02 MUSD
Drilling and completion costs per vertical injection well: 3.02 MUSD
Drilling and completion costs per non-vertical section: 3.61 MUSD
Drilling and completion costs per non-vertical section: 1.80 MUSD
Stimulation costs: 1.51 MUSD
Surface power plant costs: 11.34 MUSD
Field gathering system costs: 1.52 MUSD
Expand Down
2 changes: 1 addition & 1 deletion tests/examples/Fervo_Norbeck_Latimer_2023.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Well Geometry Configuration, 4
Has Nonvertical Section, True
Multilaterals Cased, True
Number of Multilateral Sections, 2, -- Two parallel horizontal sections
Total Nonvertical Length, 3250 feet, -- per the paper
Nonvertical Length per Multilateral Section, 3250 feet, -- per the paper
Well Drilling Cost Correlation, 10, -- per the drill cost paper - works out to $400/ft
Horizontal Well Drilling Cost Correlation, 10, -- per the drill cost paper - works out to $400/ft
Production Flow Rate per Well, 41, -- =650 gpm per the paper - per the paper the maximum flow rate was 63 L/s but the range was 550-750 gpm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

Simulation Metadata
----------------------
GEOPHIRES Version: 3.7.1
Simulation Date: 2025-01-22
Simulation Time: 10:48
Calculation Time: 1.658 sec
GEOPHIRES Version: 3.7.5
Simulation Date: 2025-01-31
Simulation Time: 11:36
Calculation Time: 1.595 sec

***SUMMARY OF RESULTS***

Expand Down Expand Up @@ -77,7 +77,7 @@ The AGS models contain an intrinsic reservoir model that doesn't expose values t
Drilling and completion costs: 65.80 MUSD
Drilling and completion costs per vertical production well: 10.24 MUSD
Drilling and completion costs per vertical injection well: 10.24 MUSD
Drilling and completion costs per non-vertical section: 42.18 MUSD
Drilling and completion costs per non-vertical section: 14.06 MUSD
Stimulation costs: 0.00 MUSD
Surface power plant costs: 6.74 MUSD
Field gathering system costs: 0.99 MUSD
Expand Down
Loading