diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2da642071..5e89dded1 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.9.53 +current_version = 3.9.54 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 072d540c8..20b327d68 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -54,7 +54,7 @@ default_context: sphinx_doctest: "no" sphinx_theme: "sphinx-py3doc-enhanced-theme" test_matrix_separate_coverage: "no" - version: 3.9.53 + version: 3.9.54 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/README.rst b/README.rst index 896bec847..2fbc926f5 100644 --- a/README.rst +++ b/README.rst @@ -58,9 +58,9 @@ Free software: `MIT 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.53.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.54.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.53...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.54...main .. |docs| image:: https://readthedocs.org/projects/GEOPHIRES-X/badge/?style=flat :target: https://nrel.github.io/GEOPHIRES-X diff --git a/docs/conf.py b/docs/conf.py index 90dcfdb35..091ee8507 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2025' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.9.53' +version = release = '3.9.54' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 4ae1c8515..399416965 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.9.53', + version='3.9.54', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_x/Parameter.py b/src/geophires_x/Parameter.py index e3e51a219..135df6725 100644 --- a/src/geophires_x/Parameter.py +++ b/src/geophires_x/Parameter.py @@ -281,7 +281,7 @@ def __post_init__(self): json_parameter_type: str = _JSON_PARAMETER_TYPE_ARRAY -def ReadParameter(ParameterReadIn: ParameterEntry, ParamToModify, model): +def ReadParameter(ParameterReadIn: ParameterEntry, ParamToModify, model) -> None: """ ReadParameter: A method to take a single ParameterEntry object and use it to update the associated Parameter. Does validation as well as Unit and Currency conversion @@ -393,35 +393,8 @@ def default_parameter_value_message(new_val: Any, param_to_modify_name: str, def ParamToModify.Provided = True # set provided to true because we are using a user provide value now ParamToModify.Valid = True # set Valid to true because it passed the validation tests elif isinstance(ParamToModify, listParameter): - New_val = float(ParameterReadIn.sValue) - if (New_val < float(ParamToModify.Min)) or (New_val > float(ParamToModify.Max)): - # user provided value is out of range, so announce it, leave set to whatever it was set to (default value) - if len(ParamToModify.ErrMessage) > 0: - msg = ( - f'Parameter given ({str(New_val)}) for {ParamToModify.Name} outside of valid range.' - f'GEOPHIRES will {ParamToModify.ErrMessage}' - ) - print(f'Warning: {msg}') - model.logger.warning(msg) - model.logger.info(f'Complete {str(__name__)}: {sys._getframe().f_code.co_name}') - return - else: - if ' ' in ParamToModify.Name: - # Some list parameters are read in with enumerated parameter names; in these cases we use the last - # character of the description to get the position i.e., "Gradient 1" is position 0. - parts = ParameterReadIn.Name.split(' ') - position = int(parts[1]) - 1 - if position >= len(ParamToModify.value): - ParamToModify.value.append(New_val) # we are adding to the list, so use append - else: # we are replacing a value, so pop the value we want to replace, then insert a new one - ParamToModify.value.pop(position) - ParamToModify.value.insert(position, New_val) - else: - # In an ideal world this would be handled in ParameterEntry such that its sValue and Comment are - # correct; however that would only be practical if ParameterEntry had typing information to know - # whether to treat text after a second comma as a comment or list entry. - ParamToModify.value = [float(x.strip()) for x in ParameterReadIn.raw_entry.split('--')[0].split(',')[1:] - if x.strip() != ''] + _read_list_parameter(ParameterReadIn, ParamToModify, model) + elif isinstance(ParamToModify, boolParameter): if ParameterReadIn.sValue == "0": New_val = False @@ -449,6 +422,47 @@ def default_parameter_value_message(new_val: Any, param_to_modify_name: str, def model.logger.info(f'Complete {str(__name__)}: {sys._getframe().f_code.co_name}') +def _read_list_parameter(ParameterReadIn: ParameterEntry, ParamToModify, model) -> None: + """ + :type ParamToModify: :class:`~geophires_x.Parameter.Parameter` + :type model: :class:`~geophires_x.Model.Model` + """ + + if ' ' in ParamToModify.Name: + New_val = float(ParameterReadIn.sValue) + # Some list parameters are read in with enumerated parameter names; in these cases we use the last + # character of the description to get the position i.e., "Gradient 1" is position 0. + parts = ParameterReadIn.Name.split(' ') + position = int(parts[1]) - 1 + if position >= len(ParamToModify.value): + ParamToModify.value.append(New_val) # we are adding to the list, so use append + else: # we are replacing a value, so pop the value we want to replace, then insert a new one + ParamToModify.value.pop(position) + ParamToModify.value.insert(position, New_val) + else: + # In an ideal world this would be handled in ParameterEntry such that its sValue and Comment are + # correct; however that would only be practical if ParameterEntry had typing information to know + # whether to treat text after a second comma as a comment or list entry. + ParamToModify.value = [float(x.strip()) for x in ParameterReadIn.raw_entry.split('--')[0].split(',')[1:] + if x.strip() != ''] + + ParamToModify.Provided = True + + valid = True + for i in range(len(ParamToModify.value)): + New_val = ParamToModify.value[i] + if (New_val < float(ParamToModify.Min)) or (New_val > float(ParamToModify.Max)): + msg = ( + f'Value given ({str(New_val)}) for {ParamToModify.Name} outside of valid range ' + f'({ParamToModify.Min}–{ParamToModify.Max}).' + ) + print(f'Warning: {msg}') + model.logger.warning(msg) + valid = False + + ParamToModify.Valid = valid + + def ConvertUnits(ParamToModify, strUnit: str, model) -> str: """ ConvertUnits gets called if a unit version is needed: either currency or standard units like F to C or m to ft diff --git a/src/geophires_x/Reservoir.py b/src/geophires_x/Reservoir.py index c8013941c..b152b8625 100644 --- a/src/geophires_x/Reservoir.py +++ b/src/geophires_x/Reservoir.py @@ -606,7 +606,14 @@ def read_parameters(self, model: Model) -> None: elif ParameterToModify.Name.startswith('Thickness '): parts = ParameterReadIn.Name.split(' ') position = int(parts[1]) - 1 - model.reserv.layerthickness.value[position] = ParameterToModify.value + num_segments = len(model.reserv.layerthickness.value) + if position < num_segments: + model.reserv.layerthickness.value[position] = ParameterToModify.value + else: + model.logger.error(f'Cannot set {ParameterToModify.Name} to {ParameterToModify.value} ' + f'because only {num_segments} segments are defined.') + # Don't raise exception since we can ignore and the user will see the incorrectly configured + # segment(s) in the result. elif ParameterToModify.Name.startswith("Fracture Separation"): self.fracsepcalc.value = self.fracsep.value diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index fe3973af0..ba7552b02 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.9.53' +__version__ = '3.9.54' diff --git a/tests/geophires_x_tests/test_reservoir.py b/tests/geophires_x_tests/test_reservoir.py index 112804425..199bdf2e7 100644 --- a/tests/geophires_x_tests/test_reservoir.py +++ b/tests/geophires_x_tests/test_reservoir.py @@ -163,3 +163,68 @@ def _fractures_lcoe_net(r: GeophiresXResult) -> tuple[int, float, float]: self.assertGreater(net_production, 0) self.assertLess(net_production, 500) + + def test_thicknesses_param(self): + def _get_result() -> GeophiresXResult: + return GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=self._get_test_file_path('generic-egs-case.txt'), + params={ + 'Number of Segments': 3, + 'Gradient 2': '40', + 'Gradient 3': '40', + 'Thicknesses': '2.5,0.49,0.87', + }, + ) + ) + + r = _get_result() + summary = r.result['SUMMARY OF RESULTS'] + + expected = { + 'Segment 1 Geothermal gradient': {'unit': 'degC/km', 'value': 36.7}, + 'Segment 1 Thickness': {'unit': 'kilometer', 'value': 2.5}, + 'Segment 2 Geothermal gradient': {'unit': 'degC/km', 'value': 40}, + 'Segment 2 Thickness': {'unit': 'kilometer', 'value': 0.49}, + 'Segment 3 Geothermal gradient': {'unit': 'degC/km', 'value': 40}, + 'Segment 3 Thickness': None, + 'Segment 4 Geothermal gradient': None, + } + + for k, v in expected.items(): + self.assertEqual(summary[k], v) + + def test_number_of_segments(self): + def _get_result() -> GeophiresXResult: + return GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=self._get_test_file_path('generic-egs-case.txt'), + params={ + 'Number of Segments': 3, + 'Gradient 2': '40', + 'Gradient 3': '40', + 'Gradient 4': '40', + 'Thickness 1': '2.5', + 'Thickness 2': '0.49', + 'Thicknesses': '2.5,0.49,0.87', + 'Thickness 3': '0.65', + 'Thickness 4': '0.85', + }, + ) + ) + + r = _get_result() + summary = r.result['SUMMARY OF RESULTS'] + + expected = { + 'Segment 1 Geothermal gradient': {'unit': 'degC/km', 'value': 36.7}, + 'Segment 1 Thickness': {'unit': 'kilometer', 'value': 2.5}, + 'Segment 2 Geothermal gradient': {'unit': 'degC/km', 'value': 40}, + 'Segment 2 Thickness': {'unit': 'kilometer', 'value': 0.49}, + 'Segment 3 Geothermal gradient': {'unit': 'degC/km', 'value': 40}, + 'Segment 3 Thickness': None, + 'Segment 4 Geothermal gradient': None, + } + + for k, v in expected.items(): + self.assertEqual(summary[k], v)