Skip to content
Open
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.13.10
current_version = 3.13.12
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.13.10
version: 3.13.12
version_manager: "bump2version"
website: "https://github.com/NREL"
year_from: "2023"
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# pre-commit install --install-hooks
# To update the versions:
# pre-commit autoupdate
exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg|src/geophires_x(?!/(GEOPHIRESv3|EconomicsSam|EconomicsSamCashFlow|EconomicsUtils|EconomicsSamPreRevenue|EconomicsSamCalculations|SurfacePlantUtils|NumpyUtils|UPPReservoir)\.py))(/|$)'
exclude: '^(\.tox|ci/templates|\.bumpversion\.cfg|src/geophires_x(?!/(GEOPHIRESv3|EconomicsSam|EconomicsSamCashFlow|EconomicsUtils|EconomicsSamPreRevenue|EconomicsSamCalculations||ParameterUtils|SurfacePlantUtils|NumpyUtils|UPPReservoir)\.py))(/|$)'
# Note the order is intentional to avoid multiple passes of the hooks
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
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.13.10.svg
.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.13.12.svg
:alt: Commits since latest release
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.13.10...main
:target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.13.12...main

.. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat
:target: https://softwareengineerprogrammer.github.io/GEOPHIRES
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.13.10'
version = release = '3.13.12'

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.13.10',
version='3.13.12',
license='MIT',
description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.',
long_description='{}\n{}'.format(
Expand Down
4 changes: 2 additions & 2 deletions src/geophires_x/Economics.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
project_payback_period_parameter, inflation_cost_during_construction_output_parameter, \
interest_during_construction_output_parameter, total_capex_parameter_output_parameter, \
overnight_capital_cost_output_parameter, CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME, \
_YEAR_INDEX_VALUE_EXPLANATION_SNIPPET, investment_tax_credit_output_parameter, expand_schedule_dsl, \
lcoh_output_parameter, lcoc_output_parameter
_YEAR_INDEX_VALUE_EXPLANATION_SNIPPET, investment_tax_credit_output_parameter, lcoh_output_parameter, lcoc_output_parameter
from geophires_x.ParameterUtils import expand_schedule_dsl
from geophires_x.GeoPHIRESUtils import quantity
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType, \
_WellDrillingCostCorrelationCitation
Expand Down
92 changes: 5 additions & 87 deletions src/geophires_x/EconomicsUtils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

from geophires_x.GeoPHIRESUtils import is_float, is_int
from geophires_x.Parameter import OutputParameter, SCHEDULE_DSL_MULTIPLIER_SYMBOL
from geophires_x.Parameter import OutputParameter
from geophires_x.Units import Units, PercentUnit, TimeUnit, CurrencyUnit, CurrencyFrequencyUnit, EnergyCostUnit

CONSTRUCTION_CAPEX_SCHEDULE_PARAMETER_NAME = 'Construction CAPEX Schedule'
Expand Down Expand Up @@ -248,90 +247,9 @@ def investment_tax_credit_output_parameter() -> OutputParameter:

def expand_schedule_dsl(schedule_strings: list[str | float], total_years: int) -> list[float]:
"""
Parse a duration-based scheduling DSL and expand it into a fixed-length time-series array.

Syntax: `[Value] * [Years], [Value] * [Years], ..., [Terminal Value]`

The terminal (last) value is repeated to fill `total_years`. A bare scalar
(e.g. `['2.5']`) is treated as a terminal value and broadcast across all years.

Examples::

expand_schedule_dsl(['1.0 * 3', '0.1'], total_years=6)
# => [1.0, 1.0, 1.0, 0.1, 0.1, 0.1]

expand_schedule_dsl(['2.5'], total_years=4)
# => [2.5, 2.5, 2.5, 2.5]

:param schedule_strings: list of DSL segment strings. Each element is either
`"<value> * <years>"` (a run-length segment) or `"<value>"` (a scalar,
which becomes the terminal value when it is the last element, or a 1-year
segment otherwise).
:param total_years: The total number of years the expanded array must span
(typically `construction_years + plant_lifetime`).
:returns: A `list[float]` of length `total_years`.
:raises ValueError: On malformed DSL strings or when explicit segments exceed
`total_years`.
Deprecated, call ParameterUtils.expand_schedule_dsl
"""

if total_years <= 0:
return []

if not schedule_strings:
return [0.0] * total_years

segments: list[tuple[float, int | None]] = []
for raw in schedule_strings:
raw = str(raw).strip()
if SCHEDULE_DSL_MULTIPLIER_SYMBOL in raw:
parts = raw.split(SCHEDULE_DSL_MULTIPLIER_SYMBOL)
if len(parts) != 2:
raise ValueError(f'Invalid schedule segment "{raw}": expected "<value> * <years>".')

val_raw = parts[0].strip()
if not is_float(val_raw):
raise ValueError(f'Invalid schedule segment "{raw}": "{val_raw}" is not a float.')
value = float(val_raw)
if value < 0:
raise ValueError(f'Invalid schedule segment "{raw}": {val_raw} is negative.')

years_raw = parts[1].strip()
if not is_int(years_raw):
raise ValueError(f'Invalid schedule segment "{raw}": "{years_raw}" is not an int.')

years = int(years_raw)
if years < 0:
raise ValueError(f'Invalid schedule segment "{raw}": year count must be non-negative.')
segments.append((value, years))
else:
if not is_float(raw):
raise ValueError(f'Invalid schedule segment "{raw}": "{raw}" is not a float.')

value = float(raw)
segments.append((value, None))

result: list[float] = []
terminal_value = 0.0

for idx, (value, years) in enumerate(segments):
is_last = idx == len(segments) - 1
if years is not None:
result.extend([value] * years)
terminal_value = value
else:
if is_last:
terminal_value = value
else:
result.append(value)
terminal_value = value

if len(result) > total_years:
raise ValueError(
f'Invalid schedule: Schedule expands to {len(result)} years ' f'which exceeds total_years={total_years}.'
)

remaining = total_years - len(result)
if remaining > 0:
result.extend([terminal_value] * remaining)

return result
from geophires_x.ParameterUtils import expand_schedule_dsl

return expand_schedule_dsl(schedule_strings, total_years)
112 changes: 112 additions & 0 deletions src/geophires_x/ParameterUtils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from __future__ import annotations

import copy
import logging

from geophires_x.GeoPHIRESUtils import is_float, is_int
from geophires_x.Parameter import SCHEDULE_DSL_MULTIPLIER_SYMBOL


_log = logging.getLogger(__name__)


def expand_schedule_dsl(
schedule_strings: list[str | float], total_years: int, allow_schedule_length_to_exceed_total_years: bool = False
) -> list[float]:
"""
Parse a duration-based scheduling DSL and expand it into a fixed-length time-series array.

Syntax: `[Value] * [Years], [Value] * [Years], ..., [Terminal Value]`

The terminal (last) value is repeated to fill `total_years`. A bare scalar
(e.g. `['2.5']`) is treated as a terminal value and broadcast across all years.

Examples::

expand_schedule_dsl(['1.0 * 3', '0.1'], total_years=6)
# => [1.0, 1.0, 1.0, 0.1, 0.1, 0.1]

expand_schedule_dsl(['2.5'], total_years=4)
# => [2.5, 2.5, 2.5, 2.5]

:param schedule_strings: list of DSL segment strings. Each element is either
`"<value> * <years>"` (a run-length segment) or `"<value>"` (a scalar,
which becomes the terminal value when it is the last element, or a 1-year
segment otherwise).
:param total_years: The total number of years the expanded array must span
(typically `construction_years + plant_lifetime`).
:returns: A `list[float]` of length `total_years`.
:raises ValueError: On malformed DSL strings or when explicit segments exceed
`total_years`.
"""

if total_years <= 0:
return []

if not schedule_strings:
return [0.0] * total_years

segments: list[tuple[float, int | None]] = []
for raw in schedule_strings:
raw = str(raw).strip()
if SCHEDULE_DSL_MULTIPLIER_SYMBOL in raw:
parts = raw.split(SCHEDULE_DSL_MULTIPLIER_SYMBOL)
if len(parts) != 2:
raise ValueError(f'Invalid schedule segment "{raw}": expected "<value> * <years>".')

val_raw = parts[0].strip()
if not is_float(val_raw):
raise ValueError(f'Invalid schedule segment "{raw}": "{val_raw}" is not a float.')
value = float(val_raw)
if value < 0:
raise ValueError(f'Invalid schedule segment "{raw}": {val_raw} is negative.')

years_raw = parts[1].strip()
if not is_int(years_raw):
raise ValueError(f'Invalid schedule segment "{raw}": "{years_raw}" is not an int.')

years = int(years_raw)
if years < 0:
raise ValueError(f'Invalid schedule segment "{raw}": year count must be non-negative.')
segments.append((value, years))
else:
if not is_float(raw):
raise ValueError(f'Invalid schedule segment "{raw}": "{raw}" is not a float.')

value = float(raw)
segments.append((value, None))

result: list[float] = []
terminal_value = 0.0

for idx, (value, years) in enumerate(segments):
is_last = idx == len(segments) - 1
if years is not None:
result.extend([value] * years)
terminal_value = value
else:
if is_last:
terminal_value = value
else:
result.append(value)
terminal_value = value

remaining = total_years - len(result)
if remaining > 0:
result.extend([terminal_value] * remaining)

if len(result) > total_years:
if not allow_schedule_length_to_exceed_total_years:
raise ValueError(
f'Invalid schedule: Schedule expands to {len(result)} years '
f'which exceeds total_years={total_years}.'
)
else:
pre_truncation_result = copy.copy(result)
result = result[:total_years]
_log.warning(
f'Schedule expands to {len(pre_truncation_result)} years, which exceeds total_years={total_years}. '
f'Schedule has been truncated to {total_years} years ({result}; from {pre_truncation_result}).'
)

return result
18 changes: 10 additions & 8 deletions src/geophires_x/SFReservoir.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class SFReservoir(Reservoir):
"""
This class models the Single Fracture Reservoir.
"""
def __init__(self, model:Model):
def __init__(self, model: Model):
"""
The __init__ function is called automatically when a class is instantiated.
It initializes the attributes of an object, and sets default values for certain arguments that can be
Expand All @@ -19,7 +19,7 @@ def __init__(self, model:Model):
:type model: :class:`~geophires_x.Model.Model`
:return: None
"""
model.logger.info("Init " + str(__class__) + ": " + sys._getframe().f_code.co_name)
model.logger.info(f'Init {__class__}: {sys._getframe().f_code.co_name}')
super().__init__(model) # initialize the parent parameters and variables
sclass = str(__class__).replace("<class \'", "")
self.MyClass = sclass.replace("\'>", "")
Expand All @@ -34,6 +34,8 @@ def __init__(self, model:Model):
# If you choose to subclass this master class, you can do so before or after you create your own parameters.
# If you do, you can also choose to call this method from you class, which will effectively add
# and set all these parameters to your class.

# noinspection SpellCheckingInspection
self.drawdp = self.ParameterDict[self.drawdp.Name] = floatParameter(
"Drawdown Parameter",
DefaultValue=0.005,
Expand All @@ -46,7 +48,7 @@ def __init__(self, model:Model):
ToolTipText="specify the thermal drawdown for reservoir model 3 and 4"
)

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

def __str__(self):
return 'SFReservoir'
Expand All @@ -62,15 +64,15 @@ def read_parameters(self, model: Model) -> None:
:type model: :class:`~geophires_x.Model.Model`
:return: None
"""
model.logger.info("Init " + str(__class__) + ": " + sys._getframe().f_code.co_name)
model.logger.info(f'Init {__class__}: {sys._getframe().f_code.co_name}')
super().read_parameters(model) # read the parameters for the parent.
# 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,
# including the ones that are specific to this class
model.logger.info("Complete " + str(__class__) + ": " + sys._getframe().f_code.co_name)
model.logger.info(f'Complete {__class__}: {sys._getframe().f_code.co_name}')

def Calculate(self, model:Model):
def Calculate(self, model: Model):
"""
The Calculate function calculates the values of all the parameters that are calculated by this object.
It calls the Calculate function of the parent object to calculate the values of the parameters that are
Expand All @@ -80,7 +82,7 @@ def Calculate(self, model:Model):
:type model: :class:`~geophires_x.Model.Model`
:return: None
"""
model.logger.info("Init " + str(__class__) + ": " + sys._getframe().f_code.co_name)
model.logger.info(f'Init {__class__}: {sys._getframe().f_code.co_name}')
super().Calculate(model) # run calculation for the parent.

model.reserv.Tresoutput.value[0] = model.reserv.Trock.value
Expand All @@ -92,4 +94,4 @@ def Calculate(self, model:Model):
(model.reserv.Trock.value - model.wellbores.Tinj.value) +\
model.wellbores.Tinj.value

model.logger.info("Complete " + str(__class__) + ": " + sys._getframe().f_code.co_name)
model.logger.info(f'Complete {__class__}: {sys._getframe().f_code.co_name}')
Loading
Loading