diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 48d46dbb7..2f2c0523c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.7.4 +current_version = 3.7.5 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index f51d38f21..363e0c23d 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.7.4 + version: 3.7.5 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 418a4002a..494f83552 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ GEOPHIRES-X (2023-2025) 3.7.3: Heat to Power Conversion Efficiency output added. +3.7.5: Injection Well Drilling and Completion Capital Cost Adjustment Factor, if not provided, is now automatically set to the provided value of Well Drilling and Completion Capital Cost Adjustment Factor. + 3.6 ^^^ diff --git a/README.rst b/README.rst index 5fab975dc..76623fa97 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.7.4.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.7.5.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.4...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.5...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 16a44381d..ec55fbf73 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,7 @@ year = '2025' author = 'NREL' copyright = f'{year}, {author}' -version = release = '3.7.4' +version = release = '3.7.5' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index 7edb0a246..a8be14de1 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.7.4', + version='3.7.5', 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 7f1da0f9a..adffbf6c0 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -647,6 +647,10 @@ def __init__(self, model: Model): Valid=False, ToolTipText="Injection Well Drilling and Completion Capital Cost" ) + + # TODO parameterize/document default 5% indirect cost factor that is applied when neither of the well + # drilling/completion capital cost adjustment factors are provided + injection_well_cost_adjustment_factor_name = "Injection Well Drilling and Completion Capital Cost Adjustment Factor" self.production_well_cost_adjustment_factor = self.ParameterDict[self.production_well_cost_adjustment_factor.Name] = floatParameter( "Well Drilling and Completion Capital Cost Adjustment Factor", DefaultValue=1.0, @@ -657,19 +661,23 @@ def __init__(self, model: Model): CurrentUnits=PercentUnit.TENTH, Provided=False, Valid=True, - ToolTipText="Well Drilling and Completion Capital Cost Adjustment Factor" + ToolTipText="Well Drilling and Completion Capital Cost Adjustment Factor. Applies to production wells; " + f"also applies to injection wells unless a value is provided for " + f"{injection_well_cost_adjustment_factor_name}." ) self.injection_well_cost_adjustment_factor = self.ParameterDict[self.injection_well_cost_adjustment_factor.Name] = floatParameter( - "Injection Well Drilling and Completion Capital Cost Adjustment Factor", - DefaultValue=self.production_well_cost_adjustment_factor.value, - Min=0, - Max=10, + injection_well_cost_adjustment_factor_name, + DefaultValue=self.production_well_cost_adjustment_factor.DefaultValue, + Min=self.production_well_cost_adjustment_factor.Min, + Max=self.production_well_cost_adjustment_factor.Max, UnitType=Units.PERCENT, PreferredUnits=PercentUnit.TENTH, CurrentUnits=PercentUnit.TENTH, Provided=False, Valid=True, - ToolTipText="Injection Well Drilling and Completion Capital Cost Adjustment Factor" + ToolTipText="Injection Well Drilling and Completion Capital Cost Adjustment Factor. " + f"If not provided, this value will be set automatically to the same value as " + f"{self.production_well_cost_adjustment_factor.Name}." ) self.oamwellfixed = self.ParameterDict[self.oamwellfixed.Name] = floatParameter( "Wellfield O&M Cost", @@ -1753,7 +1761,8 @@ def __init__(self, model: Model): CurrentUnits=PercentUnit.TENTH ) self.ProjectMOIC = self.OutputParameterDict[self.ProjectMOIC.Name] = OutputParameter( - "Project Multiple of Invested Capital", + "Project MOIC", + ToolTipText="Project Multiple of Invested Capital", UnitType=Units.PERCENT, PreferredUnits=PercentUnit.TENTH, CurrentUnits=PercentUnit.TENTH @@ -1910,35 +1919,30 @@ def read_parameters(self, model: Model) -> None: ParameterToModify.value = 1.0 elif ParameterToModify.Name == "Well Drilling and Completion Capital Cost Adjustment Factor": if self.per_production_well_cost.Valid and ParameterToModify.Valid: - print("Warning: Provided well drilling and completion cost adjustment factor not" + - " considered because valid total well drilling and completion cost provided.") - model.logger.warning("Provided well drilling and completion cost adjustment factor not" + - " considered because valid total well drilling and completion cost provided.") + msg = ('Provided well drilling and completion cost adjustment factor not considered ' + 'because valid total well drilling and completion cost provided.') + print(f'Warning: {msg}') + model.logger.warning(msg) elif not self.per_production_well_cost.Provided and not self.production_well_cost_adjustment_factor.Provided: ParameterToModify.value = 1.0 - print("Warning: No valid well drilling and completion total cost or adjustment" + - " factor provided. GEOPHIRES will assume default built-in well drilling and" + - " completion cost correlation with adjustment factor = 1.") - model.logger.warning( - "No valid well drilling and completion total cost or adjustment factor" + - " provided. GEOPHIRES will assume default built-in well drilling and completion cost" + - " correlation with adjustment factor = 1.") + msg = ("No valid well drilling and completion total cost or adjustment factor provided. " + "GEOPHIRES will assume default built-in well drilling and completion cost " + "correlation with adjustment factor = 1.") + print(f'Warning: {msg}') + model.logger.warning(msg) elif self.per_production_well_cost.Provided and not self.per_production_well_cost.Valid: - print("Warning: Provided well drilling and completion cost outside of range 0-1000." + - " GEOPHIRES will assume default built-in well drilling and completion cost correlation" + - " with adjustment factor = 1.") - model.logger.warning("Provided well drilling and completion cost outside of range 0-1000." + - " GEOPHIRES will assume default built-in well drilling and completion cost correlation with" + - " adjustment factor = 1.") + msg = ("Provided well drilling and completion cost outside of range 0-1000. GEOPHIRES " + "will assume default built-in well drilling and completion cost correlation " + "with adjustment factor = 1.") + print(f'Warning: {msg}') + model.logger.warning(msg) self.production_well_cost_adjustment_factor.value = 1.0 elif not self.per_production_well_cost.Provided and self.production_well_cost_adjustment_factor.Provided and not self.production_well_cost_adjustment_factor.Valid: - print("Warning: Provided well drilling and completion cost adjustment factor outside" + - " of range 0-10. GEOPHIRES will assume default built-in well drilling and completion" + - " cost correlation with adjustment factor = 1.") - model.logger.warning( - "Provided well drilling and completion cost adjustment factor outside" + - " of range 0-10. GEOPHIRES will assume default built-in well drilling and completion" + - " cost correlation with adjustment factor = 1.") + msg = ("Provided well drilling and completion cost adjustment factor outside of range " + "0-10. GEOPHIRES will assume default built-in well drilling and completion cost " + "correlation with adjustment factor = 1.") + print(f'Warning: {msg}') + model.logger.warning(msg) self.production_well_cost_adjustment_factor.value = 1.0 elif ParameterToModify.Name == "Wellfield O&M Cost Adjustment Factor": if self.oamtotalfixed.Valid: @@ -2169,6 +2173,7 @@ def read_parameters(self, model: Model) -> None: coerce_int_params_to_enum_values(self.ParameterDict) self.sync_interest_rate(model) + self.sync_well_drilling_and_completion_capital_cost_adjustment_factor(model) model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}') @@ -2198,6 +2203,16 @@ def discount_rate_display() -> str: self.interest_rate.value = self.discountrate.quantity().to(convertible_unit(self.interest_rate.CurrentUnits)).magnitude + def sync_well_drilling_and_completion_capital_cost_adjustment_factor(self, model): + if (self.production_well_cost_adjustment_factor.Provided + and not self.injection_well_cost_adjustment_factor.Provided): + factor = self.production_well_cost_adjustment_factor.value + self.injection_well_cost_adjustment_factor.value = factor + model.logger.info( + f'Set {self.injection_well_cost_adjustment_factor.Name} to {factor} because ' + f'{self.production_well_cost_adjustment_factor.Name} was provided.') + + def Calculate(self, model: Model) -> None: """ The Calculate function is where all the calculations are done. @@ -2281,7 +2296,7 @@ def Calculate(self, model: Model) -> None: else: self.cost_lateral_section.value = 0.0 # cost of the well field - # 1.05 for 5% indirect costs + # 1.05 for 5% indirect costs - see TODO re:parameterizing at src/geophires_x/Economics.py:652 self.Cwell.value = 1.05 * ((self.cost_one_production_well.value * model.wellbores.nprod.value) + (self.cost_one_injection_well.value * model.wellbores.ninj.value) + self.cost_lateral_section.value) diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 1a5ce3afb..ff4e90557 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -1646,7 +1646,7 @@ def PrintOutputs(self, model: Model): f.write(f' Project NPV: {model.economics.ProjectNPV.value:10.2f} ' + model.economics.ProjectNPV.PreferredUnits.value + NL) f.write(f' Project IRR: {model.economics.ProjectIRR.value:10.2f} ' + model.economics.ProjectIRR.PreferredUnits.value + NL) f.write(f' Project VIR=PI=PIR: {model.economics.ProjectVIR.value:10.2f}' + NL) - f.write(f' Project MOIC: {model.economics.ProjectMOIC.value:10.2f}' + NL) + f.write(f' {model.economics.ProjectMOIC.Name}: {model.economics.ProjectMOIC.value:10.2f}' + NL) payback_period_val = model.economics.ProjectPaybackPeriod.value project_payback_period_display = f'{payback_period_val:10.2f} {model.economics.ProjectPaybackPeriod.PreferredUnits.value}' \ diff --git a/src/geophires_x/OutputsCCUS.py b/src/geophires_x/OutputsCCUS.py index e187c5a4f..60935240e 100644 --- a/src/geophires_x/OutputsCCUS.py +++ b/src/geophires_x/OutputsCCUS.py @@ -36,7 +36,7 @@ def PrintOutputs(self, model): f.write(f" Project NPV (including carbon credit): {model.ccuseconomics.ProjectNPV.value:10.2f} " + model.ccuseconomics.ProjectNPV.PreferredUnits.value + NL) f.write(f" Project IRR (including carbon credit): {model.ccuseconomics.ProjectIRR.value:10.2f} " + model.ccuseconomics.ProjectIRR.PreferredUnits.value + NL) f.write(f" Project VIR=IR=PIR (including carbon credit): {model.ccuseconomics.ProjectVIR.value:10.2f}" + NL) - f.write(f" Project MOIC (including carbon credit): {model.ccuseconomics.ProjectMOIC.value:10.2f}" + NL) + f.write(f" {model.economics.ProjectMOIC.Name} (including carbon credit): {model.ccuseconomics.ProjectMOIC.value:10.2f}" + NL) f.write(NL) f.write(NL) f.write(" ******************" + NL) diff --git a/src/geophires_x/Reservoir.py b/src/geophires_x/Reservoir.py index 8c33b4e7c..841e4546a 100644 --- a/src/geophires_x/Reservoir.py +++ b/src/geophires_x/Reservoir.py @@ -404,8 +404,8 @@ def __init__(self, model: Model): CurrentUnits=TemperatureUnit.CELSIUS, Required=True, ErrMessage="assume default surface temperature (15 deg.C)", - ToolTipText="Surface temperature used for calculating bottom-hole temperature \ - (with geothermal gradient and reservoir depth)" + ToolTipText="Surface temperature used for calculating bottom-hole temperature " + "(with geothermal gradient and reservoir depth)" ) self.usebuiltintough2model = False diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index e604a8e1e..68a571381 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.7.4' +__version__ = '3.7.5' diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index 395fa8d6e..f660ae1fe 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -396,7 +396,7 @@ "maximum": 0.99 }, "Surface Temperature": { - "description": "Surface temperature used for calculating bottom-hole temperature (with geothermal gradient and reservoir depth)", + "description": "Surface temperature used for calculating bottom-hole temperature (with geothermal gradient and reservoir depth)", "type": "number", "units": "degC", "category": "Reservoir", @@ -1392,7 +1392,7 @@ "maximum": 200 }, "Well Drilling and Completion Capital Cost Adjustment Factor": { - "description": "Well Drilling and Completion Capital Cost Adjustment Factor", + "description": "Well Drilling and Completion Capital Cost Adjustment Factor. Applies to production wells; also applies to injection wells unless a value is provided for Injection Well Drilling and Completion Capital Cost Adjustment Factor.", "type": "number", "units": "", "category": "Economics", @@ -1401,7 +1401,7 @@ "maximum": 10 }, "Injection Well Drilling and Completion Capital Cost Adjustment Factor": { - "description": "Injection Well Drilling and Completion Capital Cost Adjustment Factor", + "description": "Injection Well Drilling and Completion Capital Cost Adjustment Factor. If not provided, this value will be set automatically to the same value as Well Drilling and Completion Capital Cost Adjustment Factor.", "type": "number", "units": "", "category": "Economics", diff --git a/tests/geophires_x_tests/drilling-adjustment-factor.txt b/tests/geophires_x_tests/drilling-adjustment-factor.txt new file mode 100644 index 000000000..80f50240c --- /dev/null +++ b/tests/geophires_x_tests/drilling-adjustment-factor.txt @@ -0,0 +1,62 @@ +Reservoir Model, 1 +Reservoir Volume Option, 1 +Reservoir Density, 2800 +Reservoir Heat Capacity, 790 +Reservoir Thermal Conductivity, 3.05 +Reservoir Porosity, 0.0118 +Reservoir Impedance, 0.001 + +Number of Fractures, 149 +Fracture Shape, 4 +Fracture Height, 2000 +Fracture Width, 10000 +Fracture Separation, 30 + +Number of Segments, 1 + +Production Well Diameter, 7 +Injection Well Diameter, 7 +Well Separation, 365 feet +Injection Temperature, 60 degC +Injection Wellbore Temperature Gain, 3 +Plant Outlet Pressure, 1000 psi +Ramey Production Wellbore Model, 1 +Utilization Factor, .9 +Water Loss Fraction, 0.05 +Maximum Drawdown, 1 +Ambient Temperature, 10 degC +#Surface Temperature, 10 degC +End-Use Option, 1 + +Plant Lifetime, 25 + +Circulation Pump Efficiency, 0.80 + +Economic Model, 3 +Starting Electricity Sale Price, 0.15 +Ending Electricity Sale Price, 1.00 +Electricity Escalation Rate Per Year, 0.004053223 +Electricity Escalation Start Year, 1 +Fraction of Investment in Bonds, .5 +Combined Income Tax Rate, .3 +Gross Revenue Tax Rate, 0 +Inflated Bond Interest Rate, .05 +Inflated Equity Interest Rate, .08 +Inflation Rate, .02 +Investment Tax Credit Rate, .3, -- https://programs.dsireusa.org/system/program/detail/658 +Production Tax Credit Electricity, 0.0275, -- https://programs.dsireusa.org/system/program/detail/734 +Inflation Rate During Construction, 0.05 +Property Tax Rate, 0 +Time steps per year, 10 +Maximum Temperature, 500 + + +Print Output to Console, 0 +Surface Temperature, 12 +Reservoir Depth, 5.4 +Gradient 1, 36.7 +Power Plant Type, 4 + +Number of Injection Wells, 54 +Number of Production Wells, 54 +Production Flow Rate per Well, 80 diff --git a/tests/test_geophires_x.py b/tests/test_geophires_x.py index 75a4ae6a6..adec09846 100644 --- a/tests/test_geophires_x.py +++ b/tests/test_geophires_x.py @@ -595,3 +595,56 @@ def test_transmission_pipeline_cost(self): self.assertAlmostEqual( result.result['CAPITAL COSTS (M$)']['Transmission pipeline cost']['value'], 3.75, delta=0.5 ) + + def test_well_drilling_and_completion_capital_cost_adjustment_factor(self): + base_file = self._get_test_file_path('geophires_x_tests/drilling-adjustment-factor.txt') + r_no_adj = GeophiresXClient().get_geophires_result(GeophiresInputParameters(from_file_path=base_file)) + + r_noop_adj = GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=base_file, + params={'Well Drilling and Completion Capital Cost Adjustment Factor': 1.0}, + ) + ) + + r_adj = GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=base_file, + params={'Well Drilling and Completion Capital Cost Adjustment Factor': 1.175}, + ) + ) + + def c_well(r, prod: bool = False, inj: bool = False): + well_type = 'production ' if prod else 'injection ' if inj else '' + try: + c = r.result['CAPITAL COSTS (M$)'][f'Drilling and completion costs per {well_type}well']['value'] + + if not prod and not inj: + # indirect cost is not applied to prod/inj-specific per-well cost; + # see TODO re:parameterizing at src/geophires_x/Economics.py:652 + default_indirect_cost_factor = 1.05 + c = c / default_indirect_cost_factor + + return c + except TypeError: + return None + + self.assertEqual(c_well(r_no_adj), c_well(r_noop_adj)) + + self.assertAlmostEqual(1.175 * c_well(r_no_adj), c_well(r_adj), delta=0.1) + + r_adj_diff_prod_inj = GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=base_file, + params={ + 'Well Drilling and Completion Capital Cost Adjustment Factor': 1.175, + 'Injection Well Drilling and Completion Capital Cost Adjustment Factor': 3, + }, + ) + ) + + c_well_no_adj = c_well(r_no_adj) + c_prod_well_adj = c_well(r_adj_diff_prod_inj, prod=True) + c_inj_well_adj = c_well(r_adj_diff_prod_inj, inj=True) + self.assertAlmostEqual(1.175 * c_well_no_adj, c_prod_well_adj, delta=0.1) + self.assertAlmostEqual(3 * c_well_no_adj, c_inj_well_adj, delta=0.1)