diff --git a/setup.py b/setup.py index fd546b08..e3698b14 100755 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ def read(*names, **kwargs): ), author='NREL', author_email='Koenraad.Beckers@nrel.gov', - url='https://github.com/NREL/python-geophires-x', + url='https://github.com/NREL/GEOPHIRES-X', packages=find_packages('src'), package_dir={'': 'src'}, py_modules=[path.stem for path in Path('src').glob('*.py')], @@ -36,10 +36,10 @@ def read(*names, **kwargs): 'Operating System :: Unix', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', @@ -54,9 +54,9 @@ def read(*names, **kwargs): 'Private :: Do Not Upload', ], project_urls={ - 'Changelog': 'https://github.com/NREL/python-geophires-x/blob/master/CHANGELOG.rst', - 'Issue Tracker': 'https://github.com/NREL/python-geophires-x/issues', - 'Documentation': 'https://nrel.github.io/python-geophires-x-nrel/', + 'Changelog': 'https://github.com/NREL/GEOPHIRES-X/blob/master/CHANGELOG.rst', + 'Issue Tracker': 'https://github.com/NREL/GEOPHIRES-X/issues', + 'Documentation': 'https://nrel.github.io/GEOPHIRES-X/', }, keywords=['geothermal'], python_requires='>=3.8', diff --git a/src/geophires_x/AGSEconomics.py b/src/geophires_x/AGSEconomics.py index 1b0258ff..4db72ec5 100644 --- a/src/geophires_x/AGSEconomics.py +++ b/src/geophires_x/AGSEconomics.py @@ -110,8 +110,8 @@ def read_parameters(self, model: Model) -> None: # including the ones that are specific to this class # inputs we already have - needs to be set at ReadParameter time so values set at the latest possible time - self.Discount_rate = model.economics.discountrate.value # same units are GEOPHIRES - self.Electricity_rate = model.surfaceplant.electricity_cost_to_buy.value # same units are GEOPHIRES + self.Discount_rate = model.economics.discountrate.value # same units as GEOPHIRES + self.Electricity_rate = model.surfaceplant.electricity_cost_to_buy.value # same units as GEOPHIRES model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}') diff --git a/src/geophires_x/AGSOutputs.py b/src/geophires_x/AGSOutputs.py index 01ad1e44..c06e06e8 100644 --- a/src/geophires_x/AGSOutputs.py +++ b/src/geophires_x/AGSOutputs.py @@ -5,13 +5,14 @@ import geophires_x -from .Parameter import ConvertUnitsBack, ConvertOutputUnits -from .OptionList import EndUseOptions, EconomicModel -from .Units import * +from geophires_x.OptionList import EndUseOptions, EconomicModel + import geophires_x.Model as Model import geophires_x.Outputs as Outputs import numpy as np +from geophires_x.Units import TemperatureUnit + NL = "\n" @@ -28,27 +29,8 @@ def PrintOutputs(self, model: Model): :return: None """ model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}') - # Deal with converting Units back to PreferredUnits, if required. - # before we write the outputs, we go thru all the parameters for all of the objects and set the values - # back to the units that the user entered the data in - # We do this because the value may be displayed in the output, and we want the user to recognize their value, - # not some converted value - for obj in [model.reserv, model.wellbores, model.surfaceplant, model.economics]: - for key in obj.ParameterDict: - param = obj.ParameterDict[key] - if not param.UnitsMatch: - ConvertUnitsBack(param, model) - - # now we need to loop thru all the output parameters to update their units to whatever units the user has specified. - # i.e., they may have specified that all LENGTH results must be in feet, so we need to convert - # those from whatever LENGTH unit they are to feet. - # same for all the other classes of units (TEMPERATURE, DENSITY, etc). - - for obj in [model.reserv, model.wellbores, model.surfaceplant, model.economics]: - for key in obj.OutputParameterDict: - if key in self.ParameterDict: - if self.ParameterDict[key] != obj.OutputParameterDict[key].CurrentUnits: - ConvertOutputUnits(obj.OutputParameterDict[key], self.ParameterDict[key], model) + + self._convert_units(model) # --------------------------------------- # write results to output file and screen diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index 75b80963..ecc09f60 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -837,15 +837,17 @@ def __init__(self, model: Model): ErrMessage="assume default fixed charge rate (0.1)", ToolTipText="Fixed charge rate (FCR) used in the Fixed Charge Rate Model" ) + + discount_rate_default_val = 0.0625 self.discountrate = self.ParameterDict[self.discountrate.Name] = floatParameter( "Discount Rate", - DefaultValue=0.07, + DefaultValue=discount_rate_default_val, Min=0.0, Max=1.0, UnitType=Units.PERCENT, - PreferredUnits=PercentUnit.TENTH, + PreferredUnits=PercentUnit.PERCENT, CurrentUnits=PercentUnit.TENTH, - ErrMessage="assume default discount rate (0.07)", + ErrMessage=f'assume default discount rate ({discount_rate_default_val})', ToolTipText="Discount rate used in the Standard Levelized Cost Model" ) self.FIB = self.ParameterDict[self.FIB.Name] = floatParameter( @@ -1379,15 +1381,18 @@ def __init__(self, model: Model): PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS ) + + fir_default_unit = PercentUnit.PERCENT + fir_default_val = self.discountrate.quantity().to(convertible_unit(fir_default_unit)).magnitude self.FixedInternalRate = self.ParameterDict[self.FixedInternalRate.Name] = floatParameter( "Fixed Internal Rate", - DefaultValue=6.25, + DefaultValue=fir_default_val, Min=0.0, Max=100.0, UnitType=Units.PERCENT, PreferredUnits=PercentUnit.PERCENT, - CurrentUnits=PercentUnit.PERCENT, - ErrMessage="assume default for fixed internal rate (6.25%)", + CurrentUnits=fir_default_unit, + ErrMessage=f'assume default for fixed internal rate ({fir_default_val}%)', ToolTipText="Fixed Internal Rate (used in NPV calculation)" ) self.CAPEX_heat_electricity_plant_ratio = self.ParameterDict[self.CAPEX_heat_electricity_plant_ratio.Name] = floatParameter( diff --git a/src/geophires_x/GEOPHIRES3_newunits.txt b/src/geophires_x/GEOPHIRES3_newunits.txt index e8afe4c4..d5f52780 100644 --- a/src/geophires_x/GEOPHIRES3_newunits.txt +++ b/src/geophires_x/GEOPHIRES3_newunits.txt @@ -1,2 +1,4 @@ USD = [currency] cents = USD / 100 +KUSD = USD * 1000 +MMBTU = BTU * 1_000_000 diff --git a/src/geophires_x/GeoPHIRESUtils.py b/src/geophires_x/GeoPHIRESUtils.py index 762edbde..e660c3fd 100644 --- a/src/geophires_x/GeoPHIRESUtils.py +++ b/src/geophires_x/GeoPHIRESUtils.py @@ -18,7 +18,7 @@ import CoolProp.CoolProp as CP from geophires_x.Parameter import ParameterEntry, Parameter -from geophires_x.Units import get_unit_registry +from geophires_x.Units import get_unit_registry, convertible_unit _logger = logging.getLogger('root') # TODO use __name__ instead of root @@ -224,7 +224,7 @@ def quantity(value: float, unit: str) -> PlainQuantity: :rtype: pint.registry.Quantity - note type annotation uses PlainQuantity due to issues with python 3.8 failing to import the Quantity TypeAlias """ - return _ureg.Quantity(value, unit) + return _ureg.Quantity(value, convertible_unit(unit)) @lru_cache diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 271c7bac..ddbd8021 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -744,15 +744,7 @@ def read_parameters(self, model: Model, default_output_path: Path = None) -> Non model.logger.info(f'Complete {__class__!s}: {__name__}') - def PrintOutputs(self, model: Model): - """ - PrintOutputs writes the standard outputs to the output file. - :param model: The container class of the application, giving access to everything else, including the logger - :type model: :class:`~geophires_x.Model.Model` - :return: None - """ - model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}') - + def _convert_units(self, model: Model): # Deal with converting Units back to PreferredUnits, if required. # before we write the outputs, we go thru all the parameters for all of the objects and set the values back # to the units that the user entered the data in @@ -779,6 +771,17 @@ def PrintOutputs(self, model: Model): elif not output_param.UnitsMatch: obj.OutputParameterDict[key] = output_param.with_preferred_units() + def PrintOutputs(self, model: Model): + """ + PrintOutputs writes the standard outputs to the output file. + :param model: The container class of the application, giving access to everything else, including the logger + :type model: :class:`~geophires_x.Model.Model` + :return: None + """ + model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}') + + self._convert_units(model) + #data structures and assignments for HTML and Improved Text Output formats simulation_metadata = [] summary = [] @@ -1634,7 +1637,11 @@ 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) - f.write(f' Interest Rate: {model.economics.discountrate.value*100.0:10.2f} ' + model.economics.discountrate.CurrentUnits.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') + elif model.economics.econmodel.value == EconomicModel.BICYCLE: f.write(' Economic Model = ' + model.economics.econmodel.value.value + NL) f.write(f' Accrued financing during construction: {model.economics.inflrateconstruction.value*100:10.2f} ' + model.economics.inflrateconstruction.CurrentUnits.value + NL) diff --git a/src/geophires_x/OutputsAddOns.py b/src/geophires_x/OutputsAddOns.py index e9364bac..bab710f8 100644 --- a/src/geophires_x/OutputsAddOns.py +++ b/src/geophires_x/OutputsAddOns.py @@ -20,6 +20,8 @@ def PrintOutputs(self, model) -> tuple: """ model.logger.info(f'Init {str(__class__)}: {__name__}') + self._convert_units(model) + # now do AddOn output, which will append to the original output # write results to output file and screen try: diff --git a/src/geophires_x/OutputsCCUS.py b/src/geophires_x/OutputsCCUS.py index 0cb2cd00..e187c5a4 100644 --- a/src/geophires_x/OutputsCCUS.py +++ b/src/geophires_x/OutputsCCUS.py @@ -16,7 +16,9 @@ def PrintOutputs(self, model): :type model: :class:`~geophires_x.Model.Model` :return: Nothing """ - model.logger.info("Init " + str(__class__) + ": " + sys._getframe().f_code.co_name) + model.logger.info(f'Init {__class__!s}: sys._getframe().f_code.co_name') + + self._convert_units(model) if np.sum(model.ccuseconomics.CCUSRevenue.value) == 0: return # don't bother if we have nothing to report. diff --git a/src/geophires_x/OutputsS_DAC_GT.py b/src/geophires_x/OutputsS_DAC_GT.py index f9b20b31..b92d83e8 100644 --- a/src/geophires_x/OutputsS_DAC_GT.py +++ b/src/geophires_x/OutputsS_DAC_GT.py @@ -18,6 +18,8 @@ def PrintOutputs(self, model) -> tuple: """ model.logger.info(f'Init {str(__class__)}: {__name__}') + self._convert_units(model) + # now do S_DAC_GT output, which will append to the original output # write results to output file and screen try: diff --git a/src/geophires_x/Parameter.py b/src/geophires_x/Parameter.py index 9e3669c8..f986c870 100644 --- a/src/geophires_x/Parameter.py +++ b/src/geophires_x/Parameter.py @@ -76,7 +76,7 @@ def UnitsMatch(self) -> str: def with_preferred_units(self) -> Any: # Any is a proxy for Self ret: OutputParameter = dataclasses.replace(self) - ret.value = ret.quantity().to(ret.PreferredUnits).magnitude + ret.value = ret.quantity().to(convertible_unit(ret.PreferredUnits)).magnitude ret.CurrentUnits = ret.PreferredUnits return ret @@ -609,7 +609,7 @@ def ConvertUnitsBack(ParamToModify: Parameter, model): model.logger.info(f'Init {str(__name__)}: {sys._getframe().f_code.co_name} for {ParamToModify.Name}') try: - ParamToModify.value = _ureg.Quantity(ParamToModify.value, ParamToModify.CurrentUnits.value).to(ParamToModify.PreferredUnits.value).magnitude + ParamToModify.value = _ureg.Quantity(ParamToModify.value, convertible_unit(ParamToModify.CurrentUnits)).to(convertible_unit(ParamToModify.PreferredUnits)).magnitude ParamToModify.CurrentUnits = ParamToModify.PreferredUnits except AttributeError as ae: # TODO refactor to check for/convert currency instead of relying on try/except once currency conversion is @@ -848,7 +848,7 @@ def ConvertOutputUnits(oparam: OutputParameter, newUnit: Units, model): """ try: - oparam.value = _ureg.Quantity(oparam.value, oparam.CurrentUnits.value).to(newUnit.value).magnitude + oparam.value = _ureg.Quantity(oparam.value, oparam.CurrentUnits.value).to(convertible_unit(newUnit.value)).magnitude oparam.CurrentUnits = newUnit return except AttributeError as ae: diff --git a/src/geophires_x/SUTRAEconomics.py b/src/geophires_x/SUTRAEconomics.py index 89537243..f1a00cb0 100644 --- a/src/geophires_x/SUTRAEconomics.py +++ b/src/geophires_x/SUTRAEconomics.py @@ -111,19 +111,6 @@ def __init__(self, model: Model): ToolTipText="Multiplier for built-in surface plant capital cost correlation", ) - self.discountrate = self.ParameterDict[self.discountrate.Name] = floatParameter( - "Discount Rate", - value=0.07, - DefaultValue=0.07, - Min=0.0, - Max=1.0, - UnitType=Units.PERCENT, - PreferredUnits=PercentUnit.PERCENT, - CurrentUnits=PercentUnit.TENTH, - ErrMessage="assume default discount rate (0.07)", - ToolTipText="Discount rate used in the Standard Levelized Cost Model", - ) - self.inflrateconstruction = self.ParameterDict[self.inflrateconstruction.Name] = floatParameter( "Inflation Rate During Construction", value=0.0, diff --git a/src/geophires_x/SUTRAOutputs.py b/src/geophires_x/SUTRAOutputs.py index b6c2a15c..7e98d6d8 100644 --- a/src/geophires_x/SUTRAOutputs.py +++ b/src/geophires_x/SUTRAOutputs.py @@ -3,10 +3,11 @@ import sys import geophires_x -import numpy as np import geophires_x.Model as Model from geophires_x.Outputs import Outputs -from .OptionList import EconomicModel +from geophires_x.OptionList import EconomicModel + +import numpy as np NL="\n" @@ -47,6 +48,7 @@ def PrintOutputs(self, model: Model): """ model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}') + self._convert_units(model) # write results to output file and screen try: @@ -83,7 +85,11 @@ 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) - f.write(f" Interest Rate: {model.economics.discountrate.value*100.0:10.2f} " + model.economics.discountrate.PreferredUnits.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") + elif model.economics.econmodel.value == EconomicModel.BICYCLE: f.write(" Economic Model = " + model.economics.econmodel.value.value + NL) f.write(f" Accrued financing during construction: {model.economics.inflrateconstruction.value*100:10.2f} " + model.economics.inflrateconstruction.PreferredUnits.value + NL) diff --git a/src/geophires_x/Units.py b/src/geophires_x/Units.py index 7e8bf2d3..50509c73 100644 --- a/src/geophires_x/Units.py +++ b/src/geophires_x/Units.py @@ -1,11 +1,13 @@ # copyright, 2023, Malcolm I Ross from enum import IntEnum, Enum, auto +from typing import Any import pint import os - _UREG = None + + def get_unit_registry(): global _UREG if _UREG is None: @@ -14,6 +16,19 @@ def get_unit_registry(): return _UREG + +def convertible_unit(unit: Any) -> Any: + """ + pint can't handle '%' as a unit in python 3.8, so use this method when constructing quantities + + :type unit: str|Enum + """ + if unit == Units.PERCENT or unit == PercentUnit.PERCENT or unit == Units.PERCENT.value: + return 'percent' + + return unit + + class Units(IntEnum): """All possible systems of measure""" NONE = auto() @@ -59,8 +74,8 @@ class Units(IntEnum): POWERPERUNITAREA = auto() HEATPERUNITVOLUME = auto() POWERPERUNITVOLUME = auto() - DECAY_RATE=auto() - INFLATION_RATE=auto() + DECAY_RATE = auto() + INFLATION_RATE = auto() DYNAMIC_VISCOSITY = auto() @@ -158,6 +173,7 @@ class EnergyFrequencyUnit(str, Enum): MWhPERYEAR = "MWh/year" GWhPERYEAR = "GWh/year" + class CurrencyUnit(str, Enum): """Currency Units""" MDOLLARS = "MUSD" @@ -327,41 +343,41 @@ class MassUnit(str, Enum): OZ = "ounce" -class PopDensityUnit(str,Enum): +class PopDensityUnit(str, Enum): """Population Density Units""" perkm2 = "Population per square km" -class HeatPerUnitAreaUnit(str,Enum): +class HeatPerUnitAreaUnit(str, Enum): """Population Density Units""" KJPERSQKM = "kJ/km**2" -class PowerPerUnitAreaUnit(str,Enum): +class PowerPerUnitAreaUnit(str, Enum): """Population Density Units""" MWPERSQKM = "MW/km**2" -class HeatPerUnitVolumeUnit(str,Enum): +class HeatPerUnitVolumeUnit(str, Enum): """Population Density Units""" KJPERCUBICKM = "kJ/km**3" -class PowerPerUnitVolumeUnit(str,Enum): +class PowerPerUnitVolumeUnit(str, Enum): """Population Density Units""" MWPERCUBICKM = "MW/km**3" -class Decay_RateUnit(str,Enum): +class Decay_RateUnit(str, Enum): """Decay rate Units""" PERCENTPERYEAR = "%/yr" -class Inflation_RateUnit(str,Enum): +class Inflation_RateUnit(str, Enum): """Decay rate Units""" KPASCALPERYEAR = "kPa/yr" -class Dynamic_ViscosityUnit(str,Enum): +class Dynamic_ViscosityUnit(str, Enum): """Dynamic Viscosity Units""" PASCALSEC = "PaSec" diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_sCO2_heat.out b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_sCO2_heat.out index 601ca766..11fef764 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_sCO2_heat.out +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_sCO2_heat.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.4 - Simulation Date: 2024-03-02 - Simulation Time: 14:11 - Calculation Time: 1.178 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.730 sec ***AGS/CLGS STYLE OUTPUT*** @@ -19,7 +19,7 @@ Simulation Metadata Flow rate: 40.0 kg/sec Lateral Length: 1000 meter Vertical Depth: 3 kilometer - Geothermal Gradient: 0.0600 degC/km + Geothermal Gradient: 60.0000 degC/km Wellbore Diameter: 8.5000 in Injection Temperature: 60.0 degC Thermal Conductivity: 3.00 W/m/K @@ -33,7 +33,7 @@ Simulation Metadata Drilling Cost: 7.0 MUSD Surface Plant Cost: 0.1 MUSD OPEX: 2.1 KUSD/yr - LCOH: 54.1 USD/MWh + LCOH: 50.1 USD/MWh ****************************** * POWER GENERATION PROFILE * diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.out b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.out index deb133e3..890d0307 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.out +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Coaxial_water_heat.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.4 - Simulation Date: 2024-03-02 - Simulation Time: 14:12 - Calculation Time: 1.004 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.662 sec ***AGS/CLGS STYLE OUTPUT*** @@ -19,7 +19,7 @@ Simulation Metadata Flow rate: 20.0 kg/sec Lateral Length: 9000 meter Vertical Depth: 3 kilometer - Geothermal Gradient: 0.0600 degC/m + Geothermal Gradient: 60.0000 degC/km Wellbore Diameter: 8.5000 in Injection Temperature: 60.0 degC Thermal Conductivity: 3.00 W/m/K @@ -33,7 +33,7 @@ Simulation Metadata Drilling Cost: 12.0 MUSD Surface Plant Cost: 0.5 MUSD OPEX: 142.2 KUSD/yr - LCOH: 29.7 USD/MWh + LCOH: 27.8 USD/MWh ****************************** * POWER GENERATION PROFILE * diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.out b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.out index a3f80275..f4c64fc0 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.out +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_elec.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.4 - Simulation Date: 2024-03-02 - Simulation Time: 14:13 - Calculation Time: 1.085 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:55 + Calculation Time: 0.790 sec ***AGS/CLGS STYLE OUTPUT*** @@ -19,7 +19,7 @@ Simulation Metadata Flow rate: 40.0 kg/sec Lateral Length: 9000 meter Vertical Depth: 3 kilometer - Geothermal Gradient: 0.0600 degC/m + Geothermal Gradient: 60.0000 degC/km Wellbore Diameter: 8.5000 in Injection Temperature: 60.0 degC Thermal Conductivity: 3.00 W/m/K @@ -35,7 +35,7 @@ Simulation Metadata Drilling Cost: 15.0 MUSD Surface Plant Cost: 0.9 MUSD OPEX: 13.2 KUSD/yr - LCOE: 601.4 USD/MWh + LCOE: 557.6 USD/MWh ****************************** * POWER GENERATION PROFILE * diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.out b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.out index 9f8b837f..d0d9a524 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.out +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_sCO2_heat.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.4 - Simulation Date: 2024-03-02 - Simulation Time: 14:13 - Calculation Time: 1.093 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.724 sec ***AGS/CLGS STYLE OUTPUT*** @@ -19,7 +19,7 @@ Simulation Metadata Flow rate: 40.0 kg/sec Lateral Length: 9000 meter Vertical Depth: 3 kilometer - Geothermal Gradient: 0.0600 degC/m + Geothermal Gradient: 60.0000 degC/km Wellbore Diameter: 8.5000 in Injection Temperature: 60.0 degC Thermal Conductivity: 3.00 W/m/K @@ -33,7 +33,7 @@ Simulation Metadata Drilling Cost: 15.0 MUSD Surface Plant Cost: 0.4 MUSD OPEX: 6.0 KUSD/yr - LCOH: 38.8 USD/MWh + LCOH: 35.9 USD/MWh ****************************** * POWER GENERATION PROFILE * diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.out b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.out index 337917d6..84ac47c2 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.out +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_elec.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.4 - Simulation Date: 2024-03-02 - Simulation Time: 14:14 - Calculation Time: 1.073 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.741 sec ***AGS/CLGS STYLE OUTPUT*** @@ -19,7 +19,7 @@ Simulation Metadata Flow rate: 20.0 kg/sec Lateral Length: 9000 meter Vertical Depth: 3 kilometer - Geothermal Gradient: 0.0600 degC/m + Geothermal Gradient: 60.0000 degC/km Wellbore Diameter: 8.5000 in Injection Temperature: 60.0 degC Thermal Conductivity: 3.00 W/m/K @@ -35,7 +35,7 @@ Simulation Metadata Drilling Cost: 15.0 MUSD Surface Plant Cost: 1.2 MUSD OPEX: 18.6 KUSD/yr - LCOE: 437.5 USD/MWh + LCOE: 405.7 USD/MWh ****************************** * POWER GENERATION PROFILE * diff --git a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.out b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.out index 48deb5ee..da264613 100644 --- a/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.out +++ b/tests/examples/Beckers_et_al_2023_Tabulated_Database_Uloop_water_heat.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.4 - Simulation Date: 2024-03-02 - Simulation Time: 14:14 - Calculation Time: 1.026 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:55 + Calculation Time: 0.838 sec ***AGS/CLGS STYLE OUTPUT*** @@ -19,7 +19,7 @@ Simulation Metadata Flow rate: 20.0 kg/sec Lateral Length: 9000 meter Vertical Depth: 3 kilometer - Geothermal Gradient: 0.0600 degC/m + Geothermal Gradient: 60.0000 degC/km Wellbore Diameter: 8.5000 in Injection Temperature: 60.0 degC Thermal Conductivity: 3.00 W/m/K @@ -33,7 +33,7 @@ Simulation Metadata Drilling Cost: 15.0 MUSD Surface Plant Cost: 0.5 MUSD OPEX: 7.8 KUSD/yr - LCOH: 30.5 USD/MWh + LCOH: 28.3 USD/MWh ****************************** * POWER GENERATION PROFILE * diff --git a/tests/examples/SUTRAExample1.out b/tests/examples/SUTRAExample1.out index 848a42c6..dc2b4aff 100644 --- a/tests/examples/SUTRAExample1.out +++ b/tests/examples/SUTRAExample1.out @@ -4,26 +4,26 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.0 - Simulation Date: 2023-11-06 - Simulation Time: 17:26 - Calculation Time: 1.566 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 12:23 + Calculation Time: 0.515 sec ***SUMMARY OF RESULTS*** End-Use Option: Direct-Use Heat Reservoir Model = SUTRA Model - Direct-Use heat breakeven price: 1730.66 cents/kWh + Direct-Use heat breakeven price: 1703.38 cents/kWh Number of Production Wells: 1 Number of Injection Wells: 1 Lifetime Average Well Flow Rate: 16.2 kg/sec - Well depth: 600.0 meter + Well depth: 0.6 kilometer ***ECONOMIC PARAMETERS*** Economic Model = Standard Levelized Cost - Interest Rate: 7.00 % + Interest Rate: 6.25 Accrued financing during construction: 0.00 % Project lifetime: 30 yr @@ -31,11 +31,11 @@ Simulation Metadata Number of Production Wells: 1 Number of Injection Wells: 1 - Well Depth: 600.0 meter + Well Depth: 0.6 kilometer Pump efficiency: 0.8 Lifetime Average Well Flow Rate: 16.2 kg/sec - Injection well casing ID: 0.198 meter - Production well casing ID: 0.198 meter + Injection well casing ID: 7.800 in + Production well casing ID: 7.800 in ***RESERVOIR SIMULATION RESULTS*** diff --git a/tests/examples/example10_HP.out b/tests/examples/example10_HP.out index eb006d71..4ea9e015 100644 --- a/tests/examples/example10_HP.out +++ b/tests/examples/example10_HP.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.34 - Simulation Date: 2024-06-19 - Simulation Time: 07:42 - Calculation Time: 0.104 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.101 sec ***SUMMARY OF RESULTS*** @@ -25,7 +25,7 @@ Simulation Metadata ***ECONOMIC PARAMETERS*** Economic Model = Standard Levelized Cost - Interest Rate: 5.00 + Interest Rate: 5.00 % Accrued financing during construction: 5.00 Project lifetime: 30 yr Capacity factor: 90.0 % diff --git a/tests/examples/example11_AC.out b/tests/examples/example11_AC.out index 822c3601..7c607485 100644 --- a/tests/examples/example11_AC.out +++ b/tests/examples/example11_AC.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.34 - Simulation Date: 2024-06-19 - Simulation Time: 07:42 - Calculation Time: 0.106 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.100 sec ***SUMMARY OF RESULTS*** @@ -26,7 +26,7 @@ Simulation Metadata ***ECONOMIC PARAMETERS*** Economic Model = Standard Levelized Cost - Interest Rate: 5.00 + Interest Rate: 5.00 % Accrued financing during construction: 5.00 Project lifetime: 30 yr Capacity factor: 90.0 % diff --git a/tests/examples/example12_DH.out b/tests/examples/example12_DH.out index 340e952b..e4665283 100644 --- a/tests/examples/example12_DH.out +++ b/tests/examples/example12_DH.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.42 - Simulation Date: 2024-07-16 - Simulation Time: 17:55 - Calculation Time: 0.453 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.606 sec ***SUMMARY OF RESULTS*** @@ -28,7 +28,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: 20 yr Capacity factor: 96.3 % diff --git a/tests/examples/example13.out b/tests/examples/example13.out index a5acf112..b2e66a77 100644 --- a/tests/examples/example13.out +++ b/tests/examples/example13.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.37 - Simulation Date: 2024-06-24 - Simulation Time: 11:25 - Calculation Time: 0.037 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.034 sec ***SUMMARY OF RESULTS*** @@ -26,7 +26,7 @@ Simulation Metadata ***ECONOMIC PARAMETERS*** Economic Model = Standard Levelized Cost - Interest Rate: 5.00 + Interest Rate: 5.00 % Accrued financing during construction: 0.00 Project lifetime: 30 yr Capacity factor: 80.0 % diff --git a/tests/examples/example2.out b/tests/examples/example2.out index 1b9b377c..2d0f1af8 100644 --- a/tests/examples/example2.out +++ b/tests/examples/example2.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.4.34 - Simulation Date: 2024-06-19 - Simulation Time: 07:43 - Calculation Time: 0.210 sec + GEOPHIRES Version: 3.6.0 + Simulation Date: 2024-10-10 + Simulation Time: 11:54 + Calculation Time: 0.212 sec ***SUMMARY OF RESULTS*** @@ -24,7 +24,7 @@ Simulation Metadata ***ECONOMIC PARAMETERS*** Economic Model = Standard Levelized Cost - Interest Rate: 5.00 + Interest Rate: 5.00 % Accrued financing during construction: 0.00 Project lifetime: 25 yr Capacity factor: 90.0 % diff --git a/tests/regenerate-example-result.sh b/tests/regenerate-example-result.sh index e19152a6..55b05dba 100755 --- a/tests/regenerate-example-result.sh +++ b/tests/regenerate-example-result.sh @@ -6,7 +6,7 @@ # ./tests/regenerate-example-result.sh SUTRAExample1 # See https://github.com/NREL/GEOPHIRES-X/issues/107 -# Note: make sure your virtualenv is activated before runnning or this script will fail +# Note: make sure your virtualenv is activated before running or this script will fail # or generate incorrect results. cd "$(dirname "$0")" diff --git a/tests/test_geophires_x.py b/tests/test_geophires_x.py index 9108993b..f8cd9d03 100644 --- a/tests/test_geophires_x.py +++ b/tests/test_geophires_x.py @@ -1,3 +1,4 @@ +import os import tempfile import uuid from pathlib import Path @@ -166,6 +167,7 @@ def get_output_file_for_example(example_file: str): ) assert len(example_files) > 0 # test integrity check - no files means something is misconfigured + regenerate_cmds = [] for example_file_path in example_files: with self.subTest(msg=example_file_path): print(f'Running example test {example_file_path}') @@ -193,6 +195,15 @@ def get_output_file_for_example(example_file: str): 'Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.txt', ] allow_almost_equal = example_file_path in cases_to_allow_almost_equal + + cmd_script = ( + './tests/regenerate-example-result.sh' + if os.name != 'nt' + else './tests/regenerate-example-result.ps1' + ) + regenerate_cmd = f'{cmd_script} {example_file_path.split(".")[0]}' + regenerate_cmds.append(regenerate_cmd) + if allow_almost_equal: log.warning( f"Results aren't exactly equal in {example_file_path}, falling back to almostEqual..." @@ -203,21 +214,24 @@ def get_output_file_for_example(example_file: str): places=1, msg=f'Example test: {example_file_path}', ) + regenerate_cmds.pop() else: - msg = 'Results are not approximately equal within any percentage <100' + + msg = 'Results are not approximately equal within any percentage <100.' percent_diff = self._get_unequal_dicts_approximate_percent_difference( expected_result.result, geophires_result.result ) if percent_diff is not None: - msg = ( - f'Results are approximately equal within {percent_diff}%. ' - f'(Run `./tests/regenerate-example-result.sh {example_file_path.split(".")[0]}` ' - f'if this difference is expected due to calculation updates)' - ) + msg = f'Results are approximately equal within {percent_diff}%.' + + msg += f' (Run `{regenerate_cmd}` if this is expected due to calculation updates)' raise AssertionError(msg) from ae + if len(regenerate_cmds) > 0: + print(f'Command to regenerate {len(regenerate_cmds)} failed examples:\n{" && ".join(regenerate_cmds)}') + def _get_unequal_dicts_approximate_percent_difference(self, d1: dict, d2: dict) -> Optional[float]: for i in range(99): try: