diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d9e91924..bd645900 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.6.2 +current_version = 3.6.3 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index e6cb3326..6c9e24aa 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.6.2 + version: 3.6.3 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/README.rst b/README.rst index 2dc95c7a..a6a41491 100644 --- a/README.rst +++ b/README.rst @@ -51,9 +51,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.6.2.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.6.3.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.6.2...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.6.3...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 d3e9b7ae..d79876eb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2024' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.6.2' +version = release = '3.6.3' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 434bc54f..8d985a0d 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.6.2', + version='3.6.3', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index 91e2e01c..17aa3956 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -1,4 +1,5 @@ import math +import os import sys import numpy as np import numpy_financial as npf @@ -845,7 +846,7 @@ def __init__(self, model: Model): Min=0.0, Max=1.0, UnitType=Units.PERCENT, - PreferredUnits=PercentUnit.PERCENT, + PreferredUnits=PercentUnit.TENTH, CurrentUnits=PercentUnit.TENTH, ErrMessage=f'assume default discount rate ({discount_rate_default_val})', ToolTipText="Discount rate used in the Standard Levelized Cost Model" @@ -1711,6 +1712,12 @@ def __init__(self, model: Model): PreferredUnits=MassUnit.LB, CurrentUnits=MassUnit.LB ) + self.interest_rate = self.OutputParameterDict[self.interest_rate.Name] = OutputParameter( + Name='Interest Rate', + UnitType=Units.PERCENT, + PreferredUnits=PercentUnit.PERCENT, + CurrentUnits=PercentUnit.PERCENT + ) self.TotalRevenue = self.OutputParameterDict[self.TotalRevenue.Name] = OutputParameter( Name="Annual Revenue from Project", UnitType=Units.CURRENCYFREQUENCY, @@ -2147,15 +2154,43 @@ def read_parameters(self, model: Model) -> None: if key.startswith("AddOn"): self.DoAddOnCalculations.value = True break + for key in model.InputParameters.keys(): if key.startswith("S-DAC-GT"): self.DoSDACGTCalculations.value = True break coerce_int_params_to_enum_values(self.ParameterDict) + self.sync_interest_rate(model) model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}') + def sync_interest_rate(self, model): + def discount_rate_display() -> str: + return str(self.discountrate.quantity()).replace(' dimensionless', '') + + if self.discountrate.Provided ^ self.FixedInternalRate.Provided: + if self.discountrate.Provided: + self.FixedInternalRate.value = self.discountrate.quantity().to( + convertible_unit(self.FixedInternalRate.CurrentUnits)).magnitude + model.logger.info(f'Set {self.FixedInternalRate.Name} to {self.FixedInternalRate.quantity()} ' + f'because {self.discountrate.Name} was provided ({discount_rate_display()})') + else: + self.discountrate.value = self.FixedInternalRate.quantity().to( + convertible_unit(self.discountrate.CurrentUnits)).magnitude + model.logger.info( + f'Set {self.discountrate.Name} to {discount_rate_display()} because ' + f'{self.FixedInternalRate.Name} was provided ({self.FixedInternalRate.quantity()})') + + if self.discountrate.Provided and self.FixedInternalRate.Provided \ + and self.discountrate.quantity().to(convertible_unit(self.FixedInternalRate.CurrentUnits)).magnitude \ + != self.FixedInternalRate.value: + model.logger.warning(f'{self.discountrate.Name} and {self.FixedInternalRate.Name} provided with different ' + f'values ({discount_rate_display()}; {self.FixedInternalRate.quantity()}). ' + f'It is recommended to only provide one of these values.') + + self.interest_rate.value = self.discountrate.quantity().to(convertible_unit(self.interest_rate.CurrentUnits)).magnitude + def Calculate(self, model: Model) -> None: """ The Calculate function is where all the calculations are done. diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index ddbd8021..5a35a974 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -23,6 +23,7 @@ PlantType from geophires_x.GeoPHIRESUtils import UpgradeSymbologyOfUnits, render_default, InsertImagesIntoHTML from geophires_x.Parameter import Parameter +from geophires_x.Units import convertible_unit, Units, PercentUnit NL = '\n' validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits) @@ -1637,10 +1638,7 @@ def PrintOutputs(self, model: Model): f.write(f' Fixed Charge Rate (FCR): {model.economics.FCR.value*100.0:10.2f} ' + model.economics.FCR.CurrentUnits.value + NL) elif model.economics.econmodel.value == EconomicModel.STANDARDIZED_LEVELIZED_COST: f.write(' Economic Model = ' + model.economics.econmodel.value.value + NL) - - # FIXME discountrate should not be multiplied by 100 here - - # it appears to be incorrectly claiming its units are percent when the actual value is in tenths. - f.write(f' Interest Rate: {model.economics.discountrate.value*100.0:10.2f} {model.economics.discountrate.CurrentUnits.value}\n') + f.write(f' {model.economics.interest_rate.Name}: {model.economics.interest_rate.value:10.2f} {model.economics.interest_rate.CurrentUnits.value}\n') elif model.economics.econmodel.value == EconomicModel.BICYCLE: f.write(' Economic Model = ' + model.economics.econmodel.value.value + NL) diff --git a/src/geophires_x/SUTRAEconomics.py b/src/geophires_x/SUTRAEconomics.py index f1a00cb0..a4855f47 100644 --- a/src/geophires_x/SUTRAEconomics.py +++ b/src/geophires_x/SUTRAEconomics.py @@ -330,6 +330,8 @@ def read_parameters(self, model: Model) -> None: else: model.logger.info('No parameters read because no content provided') + self.sync_interest_rate(model) + model.logger.info(f'Complete {__class__!s}: {sys._getframe().f_code.co_name}') def Calculate(self, model: Model) -> None: diff --git a/src/geophires_x/SUTRAOutputs.py b/src/geophires_x/SUTRAOutputs.py index 7e98d6d8..ef7113e6 100644 --- a/src/geophires_x/SUTRAOutputs.py +++ b/src/geophires_x/SUTRAOutputs.py @@ -85,10 +85,7 @@ def PrintOutputs(self, model: Model): f.write(f" Fixed Charge Rate (FCR): {model.economics.FCR.value*100.0:10.2f} " + model.economics.FCR.CurrentUnits.value + NL) elif model.economics.econmodel.value == EconomicModel.STANDARDIZED_LEVELIZED_COST: f.write(" Economic Model = " + model.economics.econmodel.value.value + NL) - - # FIXME discountrate should not be multiplied by 100 here - - # it appears to be incorrectly claiming its units are percent when the actual value is in tenths. - f.write(f" Interest Rate: {model.economics.discountrate.value*100.0:10.2f} {model.economics.discountrate.CurrentUnits.value}\n") + f.write(f' {model.economics.interest_rate.Name}: {model.economics.interest_rate.value:10.2f} {model.economics.interest_rate.CurrentUnits.value}\n') elif model.economics.econmodel.value == EconomicModel.BICYCLE: f.write(" Economic Model = " + model.economics.econmodel.value.value + NL) diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 575236d7..74b87ca5 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.6.2' +__version__ = '3.6.3' diff --git a/tests/examples/SUTRAExample1.out b/tests/examples/SUTRAExample1.out index fd714035..456d6b84 100644 --- a/tests/examples/SUTRAExample1.out +++ b/tests/examples/SUTRAExample1.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.6.0 - Simulation Date: 2024-10-15 - Simulation Time: 13:07 - Calculation Time: 0.507 sec + GEOPHIRES Version: 3.6.2 + Simulation Date: 2024-10-18 + Simulation Time: 10:43 + Calculation Time: 0.516 sec ***SUMMARY OF RESULTS*** @@ -23,7 +23,7 @@ Simulation Metadata ***ECONOMIC PARAMETERS*** Economic Model = Standard Levelized Cost - Interest Rate: 7.00 + Interest Rate: 7.00 % Accrued financing during construction: 0.00 % Project lifetime: 30 yr diff --git a/tests/examples/example10_HP.txt b/tests/examples/example10_HP.txt index deb8ffb7..d1e7c5ee 100644 --- a/tests/examples/example10_HP.txt +++ b/tests/examples/example10_HP.txt @@ -51,7 +51,6 @@ Heat Pump COP, 2.8, --- [-] Plant Lifetime,30, ---[years] Economic Model,2, ---BICYCLE Levelized Cost Model Discount Rate, 0.05, --- [-] Required if Standard LCOE/LCOH model is selected. See manual for details. -Fixed Internal Rate, 5, -- matches Discount Rate Inflation Rate During Construction,0.05, ---[-] # ***Capital and O&M Cost Parameters*** diff --git a/tests/examples/example11_AC.txt b/tests/examples/example11_AC.txt index 434458b6..ceb9d1cd 100644 --- a/tests/examples/example11_AC.txt +++ b/tests/examples/example11_AC.txt @@ -46,7 +46,6 @@ Absorption Chiller COP, 0.72, --- [-] Plant Lifetime,30, ---[years] Economic Model,2, ---BICYCLE Levelized Cost Model Discount Rate, 0.05, --- [-] Required if Standard LCOE/LCOH model is selected. See manual for details. -Fixed Internal Rate, 5, -- matches Discount Rate Inflation Rate During Construction,0.05, ---[-] # ***Capital and O&M Cost Parameters*** diff --git a/tests/examples/example13.txt b/tests/examples/example13.txt index 7a027d01..bb4e8730 100644 --- a/tests/examples/example13.txt +++ b/tests/examples/example13.txt @@ -53,7 +53,6 @@ District Heating Road Length,3, ---[km] supersedes model option 2 if any val Plant Lifetime,30, --- [years] Economic Model,2, --- Should be 1 (FCR model), 2 (Standard LCOE/LCOH model), or 3 (Bicycle model). Discount Rate,0.05, -Fixed Internal Rate,5, -- matches Discount Rate Inflation Rate During Construction,0, --- [-] Well Drilling and Completion Capital Cost Adjustment Factor,1, --- [-] Use built-in well cost correlation as is Well Drilling Cost Correlation,1, --- [-] Use built-in well drilling cost correlation #1 diff --git a/tests/examples/example2.txt b/tests/examples/example2.txt index 644ba199..2d338951 100644 --- a/tests/examples/example2.txt +++ b/tests/examples/example2.txt @@ -43,7 +43,6 @@ Surface Temperature,15, ---[deg.C] Plant Lifetime,25, ---[years] Economic Model,2, ---Standard Levelized Cost Model Discount Rate,.05, ---[-] -Fixed Internal Rate,5, -- matches Discount Rate Inflation Rate During Construction,0, ---[-] # ***Capital and O&M Cost Parameters*** diff --git a/tests/examples/example_SHR-2.txt b/tests/examples/example_SHR-2.txt index 150b2b08..7da28a52 100644 --- a/tests/examples/example_SHR-2.txt +++ b/tests/examples/example_SHR-2.txt @@ -47,7 +47,6 @@ Tax Relief Per Year, 2.212 Do Carbon Price Calculations, True Time steps per year, 6 Discount Rate, 0.06 -Fixed Internal Rate, 6, -- matches Discount Rate Reservoir Stimulation Capital Cost, 18 Well Drilling and Completion Capital Cost Adjustment Factor, 2 Injection Well Drilling and Completion Capital Cost Adjustment Factor, 2 diff --git a/tests/test_geophires_x.py b/tests/test_geophires_x.py index f8cd9d03..140c64e6 100644 --- a/tests/test_geophires_x.py +++ b/tests/test_geophires_x.py @@ -537,3 +537,47 @@ def s(r): # deprecated is ignored if both are present. self.assertDictEqual(both_params.result, non_deprecated_param.result) + + def test_discount_rate_and_fixed_internal_rate(self): + def input_params(discount_rate=None, fixed_internal_rate=None): + params = { + 'End-Use Option': EndUseOption.ELECTRICITY.value, + 'Reservoir Model': 1, + 'Reservoir Depth': 3, + 'Gradient 1': 50, + } + + if discount_rate is not None: + params['Discount Rate'] = discount_rate + + if fixed_internal_rate is not None: + params['Fixed Internal Rate'] = fixed_internal_rate + + return GeophiresInputParameters(params) + + client = GeophiresXClient() + + # noinspection PyPep8Naming + def assertHasLogRecordWithMessage(logs_, message): + assert message in [record.message for record in logs_.records] + + with self.assertLogs(level='INFO') as logs: + result = client.get_geophires_result(input_params(discount_rate='0.042')) + + assert result is not None + assert result.result['ECONOMIC PARAMETERS']['Interest Rate']['value'] == 4.2 + assert result.result['ECONOMIC PARAMETERS']['Interest Rate']['unit'] == '%' + assertHasLogRecordWithMessage( + logs, 'Set Fixed Internal Rate to 4.2 percent because Discount Rate was provided (0.042)' + ) + + with self.assertLogs(level='INFO') as logs2: + result2 = client.get_geophires_result(input_params(fixed_internal_rate='4.2')) + + assert result2 is not None + assert result2.result['ECONOMIC PARAMETERS']['Interest Rate']['value'] == 4.2 + assert result2.result['ECONOMIC PARAMETERS']['Interest Rate']['unit'] == '%' + + assertHasLogRecordWithMessage( + logs2, 'Set Discount Rate to 0.042 because Fixed Internal Rate was provided (4.2 percent)' + )