diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9f7d6b1d..8432e803 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.9.17 +current_version = 3.9.18 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 4755fac3..589f8796 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.17 + version: 3.9.18 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/README.rst b/README.rst index d61c7b0e..31fbdd29 100644 --- a/README.rst +++ b/README.rst @@ -56,9 +56,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.17.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.9.18.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.17...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.9.18...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 329e7f9f..6ebffab0 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.17' +version = release = '3.9.18' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 6239e30d..96af6aa9 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.9.17', + version='3.9.18', 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 af438173..0179027c 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -1111,6 +1111,18 @@ 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, + 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" + ) self.dhpipingcostrate = self.ParameterDict[self.dhpipingcostrate.Name] = floatParameter( "District Heating Piping Cost Rate", DefaultValue=1200, @@ -1689,13 +1701,18 @@ def __init__(self, model: Model): PreferredUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR, CurrentUnits=CurrencyFrequencyUnit.MDOLLARSPERYEAR ) + # district heating self.peakingboilercost = self.OutputParameterDict[self.peakingboilercost.Name] = OutputParameter( Name="Peaking boiler cost", UnitType=Units.CURRENCY, PreferredUnits=CurrencyUnit.MDOLLARS, - CurrentUnits=CurrencyUnit.MDOLLARS + CurrentUnits=CurrencyUnit.MDOLLARS, + 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.' ) + self.dhdistrictcost = self.OutputParameterDict[self.dhdistrictcost.Name] = OutputParameter( Name="District Heating System Cost", UnitType=Units.CURRENCY, @@ -2381,10 +2398,18 @@ def Calculate(self, model: Model) -> None: if self.ccplantfixed.Valid: self.Cplant.value = self.ccplantfixed.value else: + # 1.15 for 15% contingency and 1.12 for 12% indirect costs + # TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor self.Cplant.value = 1.12 * 1.15 * self.ccplantadjfactor.value * 250E-6 * np.max( - model.surfaceplant.HeatExtracted.value) * 1000. # 1.15 for 15% contingency and 1.12 for 12% indirect costs - self.peakingboilercost.value = 65 * model.surfaceplant.max_peaking_boiler_demand.value / 1000 # add 65$/KW for peaking boiler - self.Cplant.value += self.peakingboilercost.value # add peaking boiler cost to surface plant cost + model.surfaceplant.HeatExtracted.value) * 1000. + + # add 65$/KW for peaking boiler + self.peakingboilercost.value = (self.peaking_boiler_cost_per_kw.quantity() + .to('USD / kilowatt').magnitude + * model.surfaceplant.max_peaking_boiler_demand.value / 1000) + + # add peaking boiler cost to surface plant cost + self.Cplant.value += self.peakingboilercost.value else: # all other options have power plant diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 7126d4ba..868d8672 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.9.17' +__version__ = '3.9.18' diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index af6967e6..7fae74c1 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -1861,6 +1861,15 @@ "minimum": 0, "maximum": 1 }, + "Peaking Boiler Cost per KW": { + "description": "Peaking boiler cost per KW of maximum peaking boiler demand", + "type": "number", + "units": "USD/kW", + "category": "Economics", + "default": 65, + "minimum": 0, + "maximum": 1000 + }, "District Heating Piping Cost Rate": { "description": "District heating piping cost rate ($/m)", "type": "number", diff --git a/tests/geophires_x_tests/test_economics.py b/tests/geophires_x_tests/test_economics.py index 76a628a6..79689736 100644 --- a/tests/geophires_x_tests/test_economics.py +++ b/tests/geophires_x_tests/test_economics.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from pathlib import Path @@ -7,6 +9,7 @@ # ruff: noqa: I001 # Successful module initialization is dependent on this specific import order. from geophires_x.Model import Model from geophires_x.Economics import CalculateFinancialPerformance +from geophires_x_client import GeophiresXResult, GeophiresXClient, GeophiresInputParameters from tests.base_test_case import BaseTestCase @@ -115,3 +118,24 @@ def _new_model(self) -> Model: os.chdir(stash_cwd) return m + + def test_peaking_boiler_cost(self): + def _get_result(peaking_boiler_cost_: int) -> GeophiresXResult: + return GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=self._get_test_file_path('../examples/example12_DH.txt'), + params={ + 'Peaking Boiler Cost per KW': peaking_boiler_cost_, + }, + ) + ) + + def _lcoh_pbc(r: GeophiresXResult) -> tuple[float, float]: + return ( + r.result['SUMMARY OF RESULTS']['Direct-Use heat breakeven price (LCOH)']['value'], + r.result['CAPITAL COSTS (M$)']['of which Peaking Boiler Cost']['value'], + ) + + lcoh, peaking_boiler_cost = _lcoh_pbc(_get_result(0)) + self.assertLess(lcoh, 13.19) + self.assertEqual(0, peaking_boiler_cost)