Skip to content

Client caching fix (ImmutableGeophiresInputParameters); Fervo_Project_Cape-4 Multilaterals & Reservoir Volume #79

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 40 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d7eb31e
Add ImmutableGeophiresInputParameters to enable caching behavior in G…
softwareengineerprogrammer Jun 19, 2025
17b2e15
Bump version: 3.9.19 → 3.9.20
softwareengineerprogrammer Jun 19, 2025
537702d
fix issue when from_file_path is str instead of Path
softwareengineerprogrammer Jun 19, 2025
422af6c
Fix combining a base file and params when the base file lacks a trail…
softwareengineerprogrammer Jun 19, 2025
863bbb6
Fix deepcopy
softwareengineerprogrammer Jun 19, 2025
7b2b78d
Make GeophiresXClient cache thread-safe
softwareengineerprogrammer Jun 19, 2025
83343e4
Use a shared cache across instances of GeophiresXClient so consumers …
softwareengineerprogrammer Jun 19, 2025
955138a
Bump version: 3.9.20 → 3.9.21
softwareengineerprogrammer Jun 19, 2025
dfac749
WIP - progress on multiprocessing safety
softwareengineerprogrammer Jun 19, 2025
301ee84
Use ImmutableGeophiresInputParameters in TestMultiprocessingSafety
softwareengineerprogrammer Jun 19, 2025
9f0648f
test resource management to not interfere with other tests
softwareengineerprogrammer Jun 19, 2025
247182e
test_multiprocessing_safety.py passes
softwareengineerprogrammer Jun 19, 2025
0d748d8
Bump version: 3.9.21 → 3.9.22
softwareengineerprogrammer Jun 19, 2025
9933a6c
fix ImmutableGeophiresInputParameters pickling serialization
softwareengineerprogrammer Jun 19, 2025
ff90e78
Bump version: 3.9.22 → 3.9.23
softwareengineerprogrammer Jun 19, 2025
c6b6605
automatically call shutdown with atexit hook
softwareengineerprogrammer Jun 20, 2025
7be5922
Bump version: 3.9.23 → 3.9.24
softwareengineerprogrammer Jun 20, 2025
75b55b7
Don't initialize shared resources unless caching enabled
softwareengineerprogrammer Jun 20, 2025
b97ee90
mark relevant methods in ImmutableGeophiresInputParameters with @over…
softwareengineerprogrammer Jun 20, 2025
c6957ea
Increase max Number of Multilateral SEctions to 1199 (3 laterals per …
softwareengineerprogrammer Jun 20, 2025
efefa76
Add 'Drilling and completion costs per well' as derived output (adds …
softwareengineerprogrammer Jun 20, 2025
6985fe5
Set Number of Multilateral Sections = 0 with comment explaining how v…
softwareengineerprogrammer Jun 20, 2025
8c133ac
Cost adjustment factor so well cost = .96M including 5% indirect costs
softwareengineerprogrammer Jun 20, 2025
cb253b4
Move test to test_fervo_project_cape_4.py
softwareengineerprogrammer Jun 20, 2025
4d39011
Move sig_figs to GeoPHIRESUtils.py
softwareengineerprogrammer Jun 20, 2025
d09824d
test_case_study_documentation
softwareengineerprogrammer Jun 20, 2025
87b8ce4
test that result capex $/kW matches documentation (tangentially relev…
softwareengineerprogrammer Jun 20, 2025
348db7b
Check relevant attributes exist before calculating derivation of dril…
softwareengineerprogrammer Jun 20, 2025
638d943
py38 annotation fix
softwareengineerprogrammer Jun 20, 2025
4a89fb4
Fix apparently-windows-incompatible unicode character in comment (thu…
softwareengineerprogrammer Jun 20, 2025
349eb68
Open Fervo_Project_Cape-4.md with utf-8 encoding by adding open_kw_ar…
softwareengineerprogrammer Jun 20, 2025
05d07fc
Fix number of fractures to actually be 102 per well (previous version…
softwareengineerprogrammer Jun 20, 2025
ce3fd60
Bump version: 3.9.24 → 3.9.25
softwareengineerprogrammer Jun 20, 2025
1733a0a
client - only initialize shared resources if current_process().name =…
softwareengineerprogrammer Jun 20, 2025
d8e2973
client - only initialize shared resources if current_process().name =…
softwareengineerprogrammer Jun 20, 2025
be62077
Disable client caching by default - this was effectively the default …
softwareengineerprogrammer Jun 20, 2025
ee0a1fa
Disable client caching by default - this was effectively the default …
softwareengineerprogrammer Jun 20, 2025
6085086
Merge pull request #81 from softwareengineerprogrammer/fervo-project-…
softwareengineerprogrammer Jun 20, 2025
cb38f7f
Bump version: 3.9.25 → 3.9.26
softwareengineerprogrammer Jun 21, 2025
3fdc98d
Fix 'Peaking Boiler Cost per kW' unit capitalization ('kW' instead of…
softwareengineerprogrammer Jun 21, 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.19
current_version = 3.9.26
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.19
version: 3.9.26
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 @@ -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.9.19.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.26.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.19...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.26...main

.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
:target: https://nrel.github.io/GEOPHIRES-X
Expand Down
44 changes: 22 additions & 22 deletions docs/Fervo_Project_Cape-4.md

Large diffs are not rendered by default.

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.19'
version = release = '3.9.26'

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.19',
version='3.9.26',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
42 changes: 31 additions & 11 deletions src/geophires_x/Economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1125,17 +1125,17 @@ def __init__(self, model: Model):
ErrMessage="assume default peaking boiler efficiency (85%)",
ToolTipText="Peaking boiler efficiency"
)
self._default_peaking_boiler_cost_USD_per_kw = 65
self.peaking_boiler_cost_per_kw = self.ParameterDict[self.peaking_boiler_cost_per_kw.Name] = floatParameter(
"Peaking Boiler Cost per KW",
DefaultValue=self._default_peaking_boiler_cost_USD_per_kw,
self._default_peaking_boiler_cost_USD_per_kW = 65
self.peaking_boiler_cost_per_kW = self.ParameterDict[self.peaking_boiler_cost_per_kW.Name] = floatParameter(
"Peaking Boiler Cost per kW",
DefaultValue=self._default_peaking_boiler_cost_USD_per_kW,
Min=0,
Max=1000,
UnitType=Units.ENERGYCOST,
PreferredUnits=EnergyCostUnit.DOLLARSPERKW,
CurrentUnits=EnergyCostUnit.DOLLARSPERKW,
Required=False,
ToolTipText="Peaking boiler cost per KW of maximum peaking boiler demand"
ToolTipText="Peaking boiler cost per kW of maximum peaking boiler demand"
)
self.dhpipingcostrate = self.ParameterDict[self.dhpipingcostrate.Name] = floatParameter(
"District Heating Piping Cost Rate",
Expand Down Expand Up @@ -1633,18 +1633,29 @@ def __init__(self, model: Model):
f'Provide {self.ccexplfixed.Name} to override the default correlation and set your own cost.'
)

# noinspection SpellCheckingInspection
self.Cwell = self.OutputParameterDict[self.Cwell.Name] = OutputParameter(
Name="Wellfield cost",
display_name='Drilling and completion costs',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,

# See TODO re:parameterizing indirect costs at src/geophires_x/Economics.py:652
# (https://github.com/NREL/GEOPHIRES-X/issues/383)
# TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor
ToolTipText="Includes total drilling and completion cost of all injection and production wells and "
"laterals, plus 5% indirect costs."
)
self.drilling_and_completion_costs_per_well = self.OutputParameterDict[
self.drilling_and_completion_costs_per_well.Name] = OutputParameter(
Name='Drilling and completion costs per well',
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,

# TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor
ToolTipText='Includes total drilling and completion cost per well, '
'including injection and production wells and laterals, plus 5% indirect costs.'
)
self.Coamwell = self.OutputParameterDict[self.Coamwell.Name] = OutputParameter(
Name="O&M Wellfield cost",
display_name='Wellfield maintenance costs',
Expand Down Expand Up @@ -1722,9 +1733,9 @@ def __init__(self, model: Model):
UnitType=Units.CURRENCY,
PreferredUnits=CurrencyUnit.MDOLLARS,
CurrentUnits=CurrencyUnit.MDOLLARS,
ToolTipText=f'Default cost: ${self._default_peaking_boiler_cost_USD_per_kw}/KW '
ToolTipText=f'Default cost: ${self._default_peaking_boiler_cost_USD_per_kW}/KW '
f'of maximum peaking boiler demand. '
f'Provide {self.peaking_boiler_cost_per_kw.Name} override the default.'
f'Provide {self.peaking_boiler_cost_per_kW.Name} override the default.'
)

self.dhdistrictcost = self.OutputParameterDict[self.dhdistrictcost.Name] = OutputParameter(
Expand Down Expand Up @@ -2313,7 +2324,9 @@ def Calculate(self, model: Model) -> None:
else:
self.cost_lateral_section.value = 0.0
# cost of the well field
# 1.05 for 5% indirect costs - see TODO re:parameterizing at src/geophires_x/Economics.py:652

# 1.05 for 5% indirect costs
# TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor
self.Cwell.value = 1.05 * ((self.cost_one_production_well.value * model.wellbores.nprod.value) +
(self.cost_one_injection_well.value * model.wellbores.ninj.value) +
self.cost_lateral_section.value)
Expand Down Expand Up @@ -2685,7 +2698,7 @@ def calculate_plant_costs(self, model:Model) -> None:
model.surfaceplant.HeatExtracted.value) * 1000.

# add 65$/KW for peaking boiler
self.peakingboilercost.value = (self.peaking_boiler_cost_per_kw.quantity()
self.peakingboilercost.value = (self.peaking_boiler_cost_per_kW.quantity()
.to('USD / kilowatt').magnitude
* model.surfaceplant.max_peaking_boiler_demand.value / 1000)

Expand Down Expand Up @@ -2972,6 +2985,7 @@ def calculate_cashflow(self, model: Model) -> None:
for i in range(1, model.surfaceplant.plant_lifetime.value + model.surfaceplant.construction_years.value, 1):
self.TotalCummRevenue.value[i] = self.TotalCummRevenue.value[i-1] + self.TotalRevenue.value[i]

# noinspection SpellCheckingInspection
def _calculate_derived_outputs(self, model: Model) -> None:
"""
Subclasses should call _calculate_derived_outputs at the end of their Calculate methods to populate output
Expand All @@ -2988,5 +3002,11 @@ def _calculate_derived_outputs(self, model: Model) -> None:
self.real_discount_rate.value = self.discountrate.quantity().to(convertible_unit(
self.real_discount_rate.CurrentUnits)).magnitude

if hasattr(self, 'Cwell') and hasattr(model.wellbores, 'nprod') and hasattr(model.wellbores, 'ninj'):
self.drilling_and_completion_costs_per_well.value = (
self.Cwell.value /
(model.wellbores.nprod.value + model.wellbores.ninj.value)
)

def __str__(self):
return "Economics"
24 changes: 3 additions & 21 deletions src/geophires_x/EconomicsSam.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@
project_vir_parameter,
project_payback_period_parameter,
)
from geophires_x.GeoPHIRESUtils import is_float, is_int
from geophires_x.GeoPHIRESUtils import is_float, is_int, sig_figs
from geophires_x.OptionList import EconomicModel, EndUseOptions
from geophires_x.Parameter import Parameter, OutputParameter, floatParameter
from geophires_x.Units import convertible_unit, EnergyCostUnit, CurrencyUnit, Units, PercentUnit
from geophires_x.Units import convertible_unit, EnergyCostUnit, CurrencyUnit, Units


@dataclass
Expand Down Expand Up @@ -162,7 +162,7 @@ def calculate_sam_economics(model: Model) -> SamEconomicsCalculations:
cash_flow = _calculate_sam_economics_cash_flow(model, single_owner)

def sf(_v: float, num_sig_figs: int = 5) -> float:
return _sig_figs(_v, num_sig_figs)
return sig_figs(_v, num_sig_figs)

sam_economics: SamEconomicsCalculations = SamEconomicsCalculations(sam_cash_flow_profile=cash_flow)
sam_economics.lcoe_nominal.value = sf(single_owner.Outputs.lcoe_nom)
Expand Down Expand Up @@ -435,21 +435,3 @@ def _ppa_pricing_model(

def _get_max_total_generation_kW(model: Model) -> float:
return np.max(model.surfaceplant.ElectricityProduced.quantity().to(convertible_unit('kW')).magnitude)


def _sig_figs(val: float | list | tuple, num_sig_figs: int) -> float:
"""
TODO move to utilities, probably
"""

if val is None:
return None

if isinstance(val, list) or isinstance(val, tuple):
return [_sig_figs(v, num_sig_figs) for v in val]

try:
return float('%s' % float(f'%.{num_sig_figs}g' % val)) # pylint: disable=consider-using-f-string
except TypeError:
# TODO warn
return val
13 changes: 13 additions & 0 deletions src/geophires_x/GeoPHIRESUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,3 +642,16 @@ def is_float(o: Any) -> bool:
else:
return True


def sig_figs(val: float | list | tuple, num_sig_figs: int) -> float:
if val is None:
return None

if isinstance(val, list) or isinstance(val, tuple):
return [sig_figs(v, num_sig_figs) for v in val]

try:
return float('%s' % float(f'%.{num_sig_figs}g' % val)) # pylint: disable=consider-using-f-string
except TypeError:
# TODO warn
return val
3 changes: 2 additions & 1 deletion src/geophires_x/Outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ def PrintOutputs(self, model: Model):
f.write(f' Drilling and completion costs per 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 injection well: {econ.cost_one_injection_well.value:10.2f} ' + econ.cost_one_injection_well.CurrentUnits.value + NL)
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)
cpw_label = Outputs._field_label(econ.drilling_and_completion_costs_per_well.display_name, 47)
f.write(f' {cpw_label}{econ.drilling_and_completion_costs_per_well.value:10.2f} {econ.Cwell.CurrentUnits.value}\n')
f.write(f' {econ.Cstim.display_name}: {econ.Cstim.value:10.2f} {econ.Cstim.CurrentUnits.value}\n')
f.write(f' Surface power plant costs: {model.economics.Cplant.value:10.2f} ' + model.economics.Cplant.CurrentUnits.value + NL)
if model.surfaceplant.plant_type.value == PlantType.ABSORPTION_CHILLER:
Expand Down
14 changes: 12 additions & 2 deletions src/geophires_x/WellBores.py
Original file line number Diff line number Diff line change
Expand Up @@ -1035,14 +1035,24 @@ def __init__(self, model: Model):
ErrMessage="assume default for Non-vertical Wellbore Diameter (0.156 m)",
ToolTipText="Non-vertical Wellbore Diameter"
)

max_allowed_total_wells = max(self.nprod.AllowableRange) + max(self.ninj.AllowableRange)
max_allowed_laterals_per_well_when_max_wells = 3
"""Arbitrary upper limit, could be increased in future if needed"""

# noinspection SpellCheckingInspection
self.numnonverticalsections = self.ParameterDict[self.numnonverticalsections.Name] = intParameter(
"Number of Multilateral Sections",
DefaultValue=0,
AllowableRange=list(range(0, 101, 1)),
AllowableRange=list(range(0, max_allowed_total_wells * max_allowed_laterals_per_well_when_max_wells, 1)),
UnitType=Units.NONE,
ErrMessage="assume default for Number of Nonvertical Wellbore Sections (0)",
ToolTipText="Number of Nonvertical Wellbore Sections"
ToolTipText='Number of Nonvertical Wellbore Sections, aka laterals or horizontals. '
'Note that this is the total number of sections for the entire project and not the number of '
'sections per well. For example, a project with 2 injectors and 2 producers with 3 laterals '
'per well should set Number of Multilateral Sections = 2 * 2 * 3 = 12.'
)

self.NonverticalsCased = self.ParameterDict[self.NonverticalsCased.Name] = boolParameter(
"Multilaterals Cased",
DefaultValue=False,
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.19'
__version__ = '3.9.26'
Loading