Skip to content

Commit a1c6da5

Browse files
committed
Merge branch 'main' into AGS-bugs
2 parents 9019cf6 + 2d0208c commit a1c6da5

40 files changed

+1476
-1199
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.8.0
2+
current_version = 3.8.6
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.8.0
57+
version: 3.8.6
5858
version_manager: "bump2version"
5959
website: "https://github.com/NREL"
6060
year_from: "2023"

CHANGELOG.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ GEOPHIRES-X (2023-2025)
1313
Revenue & Cashflow Profile period output aligned with NREL convention used to calculate NPV.
1414
See https://github.com/NREL/GEOPHIRES-X/discussions/344
1515

16+
3.8.6: Baseline well drilling cost curves updated to NREL's 2025 cost curve update:
17+
Akindipe, D. and Witter. E. 2025. "2025 Geothermal Drilling Cost Curves Update". https://pangea.stanford.edu/ERE/db/GeoConf/papers/SGW/2025/Akindipe.pdf?t=1740084555.
18+
19+
Intermediate and ideal correlations retain existing values from GeoVision:
20+
DOE 2019. "GeoVision" p. 163. https://www.energy.gov/sites/prod/files/2019/06/f63/GeoVision-full-report-opt.pdf.
21+
1622
3.7
1723
^^^
1824

@@ -124,7 +130,7 @@ Major, minor, and notable patch versions are documented above.
124130
You may also be interested in viewing the list of all PRs merged into the repository `here <https://github.com/NREL/GEOPHIRES-X/pulls?q=is%3Apr+is%3Amerged+>`__.
125131

126132
Each semantic version has a corresponding tag, the full list of which can be viewed `here <https://github.com/NREL/GEOPHIRES-X/tags>`__.
127-
The latest patch version in this repository and patch versions explicitly mentioned in this changelog are always suitable for public consumption;
133+
The patch version displayed on the package badge in the README and patch versions explicitly mentioned in this changelog are always suitable for public consumption;
128134
but note that not all patch version tags in the list are meant for public consumption
129135
as intermediate internal-only patch versions are sometimes introduced during the development process.
130136
(Improved designation and distribution of releases for public consumption may eventually be addressed by

README.rst

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

59-
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.8.0.svg
59+
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.8.6.svg
6060
:alt: Commits since latest release
61-
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.8.0...main
61+
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.8.6...main
6262

6363
.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
6464
: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.8.0'
21+
version = release = '3.8.6'
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.8.0',
16+
version='3.8.6',
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: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import numpy as np
55
import numpy_financial as npf
66
import geophires_x.Model as Model
7-
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType
7+
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \
8+
_WellDrillingCostCorrelationCitation
89
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter, \
910
coerce_int_params_to_enum_values
1011
from geophires_x.Units import *
@@ -304,12 +305,29 @@ def CalculateCarbonRevenue(model, plant_lifetime: int, construction_years: int,
304305
return cash_flow_musd, cumm_cash_flow_musd, carbon_that_would_have_been_produced_annually_lbs, carbon_that_would_have_been_produced_total_lbs
305306

306307

308+
def calculate_npv(
309+
discount_rate_tenths: float,
310+
cashflow_series: list,
311+
discount_initial_year_cashflow: bool
312+
) -> float:
313+
# TODO warn/raise exception if discount rate > 1 (i.e. it's probably not converted from percent to tenths)
314+
315+
npv_cashflow_series = cashflow_series.copy() # Copy to guard against unintentional mutation of consumer field
316+
317+
if discount_initial_year_cashflow:
318+
# Enable Excel-style NPV calculation - see https://github.com/NREL/GEOPHIRES-X/discussions/344
319+
npv_cashflow_series = [0, *npv_cashflow_series]
320+
321+
return npf.npv(discount_rate_tenths, npv_cashflow_series)
322+
323+
307324
def CalculateFinancialPerformance(plantlifetime: int,
308325
FixedInternalRate: float,
309326
TotalRevenue: list,
310327
TotalCummRevenue: list,
311328
CAPEX: float,
312-
OPEX: float):
329+
OPEX: float,
330+
discount_initial_year_cashflow: bool = False):
313331
"""
314332
CalculateFinancialPerformance calculates the financial performance of the project. It is used to calculate the
315333
financial performance of the project. It is used to calculate the revenue stream for the project.
@@ -325,6 +343,9 @@ def CalculateFinancialPerformance(plantlifetime: int,
325343
:type CAPEX: float
326344
:param OPEX: The total annual operating cost of the project in MUSD
327345
:type OPEX: float
346+
:param discount_initial_year_cashflow: Whether to discount the initial year of cashflow used to calculate NPV
347+
:type discount_initial_year_cashflow: bool
348+
328349
:return: NPV: The net present value of the project in MUSD
329350
:rtype: float
330351
:return: IRR: The internal rate of return of the project in %
@@ -336,7 +357,8 @@ def CalculateFinancialPerformance(plantlifetime: int,
336357
:rtype: tuple
337358
"""
338359
# Calculate financial performance values using numpy financials
339-
NPV = npf.npv(FixedInternalRate / 100, TotalRevenue)
360+
361+
NPV = calculate_npv(FixedInternalRate / 100, TotalRevenue.copy(), discount_initial_year_cashflow)
340362
IRR = npf.irr(TotalRevenue)
341363
if math.isnan(IRR):
342364
IRR = 0.0
@@ -859,6 +881,24 @@ def __init__(self, model: Model):
859881
"Discount Rate is synonymous with Fixed Internal Rate. If one is provided, the other's value "
860882
"will be automatically set to the same value."
861883
)
884+
885+
886+
self.discount_initial_year_cashflow = self.ParameterDict[self.discount_initial_year_cashflow.Name] = boolParameter(
887+
'Discount Initial Year Cashflow',
888+
DefaultValue=False,
889+
UnitType=Units.NONE,
890+
ToolTipText='Whether to discount cashflow in the initial project year when calculating NPV '
891+
'(Net Present Value). '
892+
'The default value of False conforms to NREL\'s standard convention for NPV calculation '
893+
'(Short W et al, 1995. https://www.nrel.gov/docs/legosti/old/5173.pdf). '
894+
'A value of True will, by contrast, cause NPV calculation to follow the convention used by '
895+
'Excel, Google Sheets, and other common spreadsheet software. '
896+
'Although NREL\'s NPV convention may typically be considered more technically correct, '
897+
'Excel-style NPV calculation might be preferred for familiarity '
898+
'or compatibility with existing business processes. '
899+
'See https://github.com/NREL/GEOPHIRES-X/discussions/344 for further details.'
900+
)
901+
862902
self.FIB = self.ParameterDict[self.FIB.Name] = floatParameter(
863903
"Fraction of Investment in Bonds",
864904
DefaultValue=0.5,
@@ -965,9 +1005,15 @@ def __init__(self, model: Model):
9651005
UnitType=Units.NONE,
9661006
ErrMessage="assume default well drilling cost correlation (10)",
9671007
ToolTipText="Select the built-in well drilling and completion cost correlation: " +
968-
'; '.join([f'{it.int_value}: {it.value}' for it in WellDrillingCostCorrelation])
969-
)
1008+
'; '.join([f'{it.int_value}: {it.value}'
1009+
for it in WellDrillingCostCorrelation]) +
1010+
f'. '
1011+
f'Baseline correlations (1-4) are from '
1012+
f'{_WellDrillingCostCorrelationCitation.NREL_COST_CURVE_2025.value}.'
1013+
f' Intermediate and ideal correlations (6-17) are from '
1014+
f'{_WellDrillingCostCorrelationCitation.GEOVISION.value}.'
9701015

1016+
)
9711017
self.DoAddOnCalculations = self.ParameterDict[self.DoAddOnCalculations.Name] = boolParameter(
9721018
"Do AddOn Calculations",
9731019
DefaultValue=False,
@@ -1739,18 +1785,25 @@ def __init__(self, model: Model):
17391785
PreferredUnits=PercentUnit.PERCENT,
17401786
CurrentUnits=PercentUnit.PERCENT
17411787
)
1788+
1789+
# TODO this is displayed as "Project Net Revenue" in Revenue & Cashflow Profile which is probably not an
1790+
# accurate synonym for annual revenue
17421791
self.TotalRevenue = self.OutputParameterDict[self.TotalRevenue.Name] = OutputParameter(
17431792
Name="Annual Revenue from Project",
17441793
UnitType=Units.CURRENCYFREQUENCY,
17451794
PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR,
17461795
CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR
17471796
)
1797+
1798+
# TODO this is displayed as "Project Net Cashflow" in Revenue & Cashflow Profile which is probably not an
1799+
# accurate synonym for cumulative revenue
17481800
self.TotalCummRevenue = self.OutputParameterDict[self.TotalCummRevenue.Name] = OutputParameter(
17491801
Name="Cumulative Revenue from Project",
17501802
UnitType=Units.CURRENCY,
17511803
PreferredUnits=CurrencyUnit.MDOLLARS,
17521804
CurrentUnits=CurrencyUnit.MDOLLARS
17531805
)
1806+
17541807
self.ProjectNPV = self.OutputParameterDict[self.ProjectNPV.Name] = OutputParameter(
17551808
"Project Net Present Value",
17561809
UnitType=Units.CURRENCY,
@@ -2881,9 +2934,15 @@ def Calculate(self, model: Model) -> None:
28812934

28822935
# Calculate more financial values using numpy financials
28832936
self.ProjectNPV.value, self.ProjectIRR.value, self.ProjectVIR.value, self.ProjectMOIC.value = \
2884-
CalculateFinancialPerformance(model.surfaceplant.plant_lifetime.value, self.FixedInternalRate.value,
2885-
self.TotalRevenue.value, self.TotalCummRevenue.value, self.CCap.value,
2886-
self.Coam.value)
2937+
CalculateFinancialPerformance(
2938+
model.surfaceplant.plant_lifetime.value,
2939+
self.FixedInternalRate.value,
2940+
self.TotalRevenue.value,
2941+
self.TotalCummRevenue.value,
2942+
self.CCap.value,
2943+
self.Coam.value,
2944+
self.discount_initial_year_cashflow.value
2945+
)
28872946

28882947
# Calculate the project payback period
28892948
self.ProjectPaybackPeriod.value = 0.0 # start by assuming the project never pays back

src/geophires_x/EconomicsAddOns.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,12 @@ def Calculate(self, model: Model) -> None:
347347

348348
# Now calculate a new "NPV", "IRR", "VIR", "Payback Period", and "MOIC"
349349
# Calculate more financial values using numpy financials
350-
self.ProjectNPV.value = npf.npv(self.FixedInternalRate.value / 100, self.ProjectCashFlow.value)
350+
self.ProjectNPV.value = Economics.calculate_npv(
351+
self.FixedInternalRate.value / 100,
352+
self.ProjectCashFlow.value.copy(),
353+
self.discount_initial_year_cashflow.value
354+
)
355+
351356
self.ProjectIRR.value = npf.irr(self.ProjectCashFlow.value)
352357
if math.isnan(self.ProjectIRR.value):
353358
self.ProjectIRR.value = 0.0

src/geophires_x/GeoPHIRESUtils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def density_water_kg_per_m3(Twater_degC: float, pressure: Optional[PlainQuantity
252252

253253
except (NotImplementedError, ValueError) as e:
254254
raise ValueError(f'Input temperature & pressure ({Twater_degC}, {pressure}) '
255-
f'are out of range or otherwise could not be used to calculate') from e
255+
f'are out of range or otherwise could not be used to calculate water density.') from e
256256

257257

258258
def celsius_to_kelvin(celsius: float) -> float:
@@ -298,7 +298,7 @@ def viscosity_water_Pa_sec(
298298

299299
except (NotImplementedError, ValueError) as e:
300300
raise ValueError(f'Input temperature & pressure ({Twater_degC}, {pressure}) '
301-
f'are out of range or otherwise could not be used to calculate') from e
301+
f'are out of range or otherwise could not be used to calculate water viscosity.') from e
302302

303303

304304
@lru_cache
@@ -334,7 +334,7 @@ def heat_capacity_water_J_per_kg_per_K(
334334

335335
except (NotImplementedError, ValueError) as e:
336336
raise ValueError(f'Input temperature & pressure ({Twater_degC}, {pressure}) '
337-
f'are out of range or otherwise could not be used to calculate') from e
337+
f'are out of range or otherwise could not be used to calculate heat capacity of water.') from e
338338

339339

340340
@lru_cache

0 commit comments

Comments
 (0)