diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2f2c0523c..66889ef0a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.7.5 +current_version = 3.7.6 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 363e0c23d..bfdb99380 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.5 + version: 3.7.6 version_manager: "bump2version" website: "https://github.com/NREL" year_from: "2023" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 494f83552..ea005d509 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ GEOPHIRES-X (2023-2025) 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.7.6: Fixes bugs pertaining to specifying non-CLGS laterals and display of per-lateral costs. 3.6 ^^^ diff --git a/README.rst b/README.rst index 76623fa97..e0ccfc06f 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.5.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/softwareengineerprogrammer/GEOPHIRES-X/v3.7.6.svg :alt: Commits since latest release - :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.5...main + :target: https://github.com/softwareengineerprogrammer/GEOPHIRES-X/compare/v3.7.6...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 ec55fbf73..8db4aeda0 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.5' +version = release = '3.7.6' pygments_style = 'trac' templates_path = ['./templates'] diff --git a/setup.py b/setup.py index a8be14de1..b8910f0fb 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ def read(*names, **kwargs): setup( name='geophires-x', - version='3.7.5', + version='3.7.6', license='MIT', description='GEOPHIRES is a free and open-source geothermal techno-economic simulator.', long_description='{}\n{}'.format( diff --git a/src/geophires_x/AGSEconomics.py b/src/geophires_x/AGSEconomics.py index b4d994241..d72f8e7f0 100644 --- a/src/geophires_x/AGSEconomics.py +++ b/src/geophires_x/AGSEconomics.py @@ -259,5 +259,6 @@ def Calculate(self, model: Model) -> None: self.Coam.value = self.AverageOPEX_Plant * 1000 self.Coam.CurrentUnits = CurrencyFrequencyUnit.KDOLLARSPERYEAR + self._calculate_derived_outputs(model) model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}') diff --git a/src/geophires_x/AGSWellBores.py b/src/geophires_x/AGSWellBores.py index 7604a8ae0..c43d0cecf 100644 --- a/src/geophires_x/AGSWellBores.py +++ b/src/geophires_x/AGSWellBores.py @@ -515,6 +515,11 @@ def __init__(self, model: Model): # NB: inputs we already have ("already have it") need to be set at ReadParameter time so values are set at the # last possible time + # Assume CLGS has 1 lateral by default (Non-CLGS default value is 0) + self.numnonverticalsections.value = 1 + self.numnonverticalsections.ErrMessage = (f'assume default for Number of Nonvertical Wellbore Sections ' + f'({self.numnonverticalsections.value})') + self.time_operation = self.ParameterDict[self.time_operation.Name] = floatParameter( "Closed Loop Calculation Start Year", DefaultValue=0.01, @@ -561,39 +566,28 @@ def read_parameters(self, model: Model) -> None: model.logger.info(f'Init {str(__class__)}: {sys._getframe().f_code.co_name}') super().read_parameters(model) # read the default parameters # if we call super, we don't need to deal with setting the parameters here, just deal with the special cases - # for the variables in this class because the call to the super.readparameters will set all the variables, + # for the variables in this class because the call to the super.read_parameters will set all the variables, # including the ones that are specific to this class - if len(model.InputParameters) > 0: - # loop through all the parameters that the user wishes to set, looking for parameters that match this object - for item in self.ParameterDict.items(): - ParameterToModify = item[1] - key = ParameterToModify.Name.strip() - if key in model.InputParameters: - ParameterReadIn = model.InputParameters[key] - # just handle special cases for this class - the call to super set all the values, - # including the value unique to this class - else: - model.logger.info("No parameters read because no content provided") - # handle error checking and special cases: + if model.reserv.numseg.value > 1: - msg = ('Warning: CLGS model can only handle a single layer gradient segment. ' + msg = ('CLGS model can only handle a single layer gradient segment. ' 'Number of Segments set to 1, Gradient set to Gradient[0], and Depth set to Reservoir Depth.') - print(msg) + print(f'Warning: {msg}') model.logger.warning(msg) model.reserv.numseg.value = 1 if self.ninj.value > 0: - msg = ('Warning: CLGS model considers the only the production wellbore parameters. ' + msg = ('CLGS model considers the only the production wellbore parameters. ' 'Anything related to the injection wellbore is ignored.') - print(msg) + print(f'Warning: {msg}') model.logger.warning(msg) if self.nprod.value != 1: - msg = ('Warning: CLGS model considers the only a single production wellbore (coaxial or uloop). ' - 'Number of production wellboreset set 1.') - print(msg) + msg = ('CLGS model considers the only a single production wellbore (coaxial or uloop). ' + 'Number of production wellbores set to 1.') + print(f'Warning: {msg}') model.logger.warning(msg) # inputs we already have - needs to be set at ReadParameter time so values set at the latest possible time diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index adffbf6c0..da21f8ffb 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -120,10 +120,8 @@ def calculate_cost_of_non_vertical_section(model: Model, length_m: float, well_c f'{fixed_well_cost_name} (fixed cost per well) instead.' ) - casing_factor = 1.0 - if not NonverticalsCased: - # assume that casing & cementing costs 50% of drilling costs - casing_factor = 0.5 + # assume that casing & cementing costs 50% of drilling costs + casing_factor = 1.0 if NonverticalsCased else 0.5 if model.economics.Nonvertical_drilling_cost_per_m.Provided or well_correlation is WellDrillingCostCorrelation.SIMPLE: cost_of_non_vertical_section = casing_factor * ((num_nonvertical_sections * nonvertical_drilling_cost_per_m * length_per_section_m)) * 1E-6 @@ -1554,11 +1552,16 @@ def __init__(self, model: Model): PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS ) + self.Cwell = self.OutputParameterDict[self.Cwell.Name] = OutputParameter( Name="Wellfield cost", UnitType=Units.CURRENCY, PreferredUnits=CurrencyUnit.MDOLLARS, - CurrentUnits=CurrencyUnit.MDOLLARS + CurrentUnits=CurrencyUnit.MDOLLARS, + + # See TODO re:parameterizing indirect costs at src/geophires_x/Economics.py:652 + ToolTipText="Includes total drilling and completion cost of all injection and production wells and " + "laterals, plus 5% indirect costs." ) self.Coamwell = self.OutputParameterDict[self.Coamwell.Name] = OutputParameter( Name="O&M Wellfield cost", @@ -1797,6 +1800,12 @@ def __init__(self, model: Model): PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS ) + self.cost_per_lateral_section = self.OutputParameterDict[self.cost_per_lateral_section.Name] = OutputParameter( + Name='Drilling and completion costs per non-vertical section', + UnitType=Units.CURRENCY, + PreferredUnits=CurrencyUnit.MDOLLARS, + CurrentUnits=CurrencyUnit.MDOLLARS + ) self.cost_to_junction_section = self.OutputParameterDict[self.cost_to_junction_section.Name] = OutputParameter( Name="Cost of the entire section of a well from bottom of vertical to junction with laterals", UnitType=Units.CURRENCY, @@ -2286,13 +2295,16 @@ def Calculate(self, model: Model) -> None: self.injection_well_cost_adjustment_factor.value) if hasattr(model.wellbores, 'numnonverticalsections') and model.wellbores.numnonverticalsections.Provided: - self.cost_lateral_section.value = calculate_cost_of_non_vertical_section(model, tot_horiz_m, - self.wellcorrelation.value, - self.Nonvertical_drilling_cost_per_m.value, - model.wellbores.numnonverticalsections.value, - self.per_injection_well_cost.Name, - model.wellbores.NonverticalsCased.value, - self.production_well_cost_adjustment_factor.value) + self.cost_lateral_section.value = calculate_cost_of_non_vertical_section( + model, + tot_horiz_m, + self.wellcorrelation.value, + self.Nonvertical_drilling_cost_per_m.value, + model.wellbores.numnonverticalsections.value, + self.Nonvertical_drilling_cost_per_m.Name, + model.wellbores.NonverticalsCased.value, + self.production_well_cost_adjustment_factor.value + ) else: self.cost_lateral_section.value = 0.0 # cost of the well field @@ -2881,7 +2893,20 @@ def Calculate(self, model: Model) -> None: np.average(model.surfaceplant.ElectricityProduced.quantity().to( 'MW').magnitude * self.jobs_created_per_MW_electricity.value)) + self._calculate_derived_outputs(model) model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}') + def _calculate_derived_outputs(self, model: Model) -> None: + """ + Subclasses should call _calculate_derived_outputs at the end of their Calculate methods to populate output + values that are derived from subclass-calculated outputs. + """ + + if hasattr(self, 'cost_lateral_section') and self.cost_lateral_section.value != 0: + self.cost_per_lateral_section.value = ( + self.cost_lateral_section.quantity().to(self.cost_per_lateral_section.CurrentUnits).magnitude + / model.wellbores.numnonverticalsections.value + ) + def __str__(self): return "Economics" diff --git a/src/geophires_x/EconomicsAddOns.py b/src/geophires_x/EconomicsAddOns.py index 70830650e..64fff9898 100644 --- a/src/geophires_x/EconomicsAddOns.py +++ b/src/geophires_x/EconomicsAddOns.py @@ -373,6 +373,7 @@ def Calculate(self, model: Model) -> None: # recalculate LCOE/LCOH self.LCOE.value, self.LCOH.value, LCOC = Economics.CalculateLCOELCOHLCOC(self, model) + self._calculate_derived_outputs(model) model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}') def __str__(self): diff --git a/src/geophires_x/EconomicsCCUS.py b/src/geophires_x/EconomicsCCUS.py index e2aab1831..cb7178f99 100644 --- a/src/geophires_x/EconomicsCCUS.py +++ b/src/geophires_x/EconomicsCCUS.py @@ -421,7 +421,8 @@ def Calculate(self, model) -> None: self.ProjectMOIC.value = self.ProjectCummCashFlow.value[len(self.ProjectCummCashFlow.value) - 1] / ( model.economics.CCap.value + (model.economics.Coam.value * model.surfaceplant.plant_lifetime.value)) - model.logger.info("complete " + str(__class__) + ": " + sys._getframe().f_code.co_name) + self._calculate_derived_outputs(model) + model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}') def __str__(self): return "EconomicsCCUS" diff --git a/src/geophires_x/EconomicsS_DAC_GT.py b/src/geophires_x/EconomicsS_DAC_GT.py index 909a58492..3aab8d591 100644 --- a/src/geophires_x/EconomicsS_DAC_GT.py +++ b/src/geophires_x/EconomicsS_DAC_GT.py @@ -676,4 +676,6 @@ def Calculate(self, model: Model) -> None: # (self.CarbonExtractedAnnually.value[i] * model.economics.CarbonPrice.value[i])) # if i > 0: # model.economics.CarbonCummCashFlow.value[i] = model.economics.CarbonCummCashFlow.value[i - 1] + model.economics.CarbonRevenue.value[i] - model.logger.info("Complete " + str(__class__) + ": " + sys._getframe().f_code.co_name) + + self._calculate_derived_outputs(model) + model.logger.info(f'Complete {str(__class__)}: {sys._getframe().f_code.co_name}') diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index ff4e90557..e51472a95 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -1810,7 +1810,7 @@ def PrintOutputs(self, model: Model): elif econ.cost_lateral_section.value > 0.0: f.write(f' Drilling and completion costs per vertical production well: {econ.cost_one_production_well.value:10.2f} ' + econ.cost_one_production_well.CurrentUnits.value + NL) f.write(f' Drilling and completion costs per vertical injection well: {econ.cost_one_injection_well.value:10.2f} ' + econ.cost_one_injection_well.CurrentUnits.value + NL) - f.write(f' Drilling and completion costs per non-vertical section: {econ.cost_lateral_section.value:10.2f} ' + econ.cost_lateral_section.CurrentUnits.value + NL) + f.write(f' {econ.cost_per_lateral_section.Name}: {econ.cost_per_lateral_section.value:10.2f} {econ.cost_lateral_section.CurrentUnits.value}\n') else: f.write(f' Drilling and completion costs per well: {model.economics.Cwell.value/(model.wellbores.nprod.value+model.wellbores.ninj.value):10.2f} ' + model.economics.Cwell.CurrentUnits.value + NL) f.write(f' Stimulation costs: {model.economics.Cstim.value:10.2f} ' + model.economics.Cstim.CurrentUnits.value + NL) diff --git a/src/geophires_x/SBTEconomics.py b/src/geophires_x/SBTEconomics.py index 60721b99e..7c7104444 100644 --- a/src/geophires_x/SBTEconomics.py +++ b/src/geophires_x/SBTEconomics.py @@ -845,5 +845,6 @@ def Calculate(self, model: Model) -> None: # Calculate LCOE/LCOH self.LCOE.value, self.LCOH.value, self.LCOC.value = CalculateLCOELCOHLCOC(self, model) + self._calculate_derived_outputs(model) model.logger.info(f'complete {__class__!s}: {sys._getframe().f_code.co_name}') diff --git a/src/geophires_x/SBTWellbores.py b/src/geophires_x/SBTWellbores.py index 6873597b0..86082a5df 100644 --- a/src/geophires_x/SBTWellbores.py +++ b/src/geophires_x/SBTWellbores.py @@ -52,6 +52,11 @@ def __init__(self, model: Model): # NB: inputs we already have ("already have it") need to be set at ReadParameter time so values are set at the # last possible time + # Assume CLGS has 1 lateral by default (Non-CLGS default value is 0) + self.numnonverticalsections.value = 1 + self.numnonverticalsections.ErrMessage = (f'assume default for Number of Nonvertical Wellbore Sections ' + f'({self.numnonverticalsections.value})') + self.vertical_section_length = self.ParameterDict[self.vertical_section_length.Name] = floatParameter( 'Vertical Section Length', DefaultValue=2000.0, diff --git a/src/geophires_x/SUTRAEconomics.py b/src/geophires_x/SUTRAEconomics.py index d6d4f98e0..f2211cf49 100644 --- a/src/geophires_x/SUTRAEconomics.py +++ b/src/geophires_x/SUTRAEconomics.py @@ -417,7 +417,8 @@ def Calculate(self, model: Model) -> None: * 1e8 ) # cents/kWh - model.logger.info("complete " + str(__class__) + ": " + sys._getframe().f_code.co_name) + self._calculate_derived_outputs(model) + model.logger.info(f'complete {str(__class__)}: {sys._getframe().f_code.co_name}') def __str__(self): return "Economics" diff --git a/src/geophires_x/WellBores.py b/src/geophires_x/WellBores.py index 8f4930492..4a7b24446 100644 --- a/src/geophires_x/WellBores.py +++ b/src/geophires_x/WellBores.py @@ -1030,10 +1030,10 @@ def __init__(self, model: Model): ) self.numnonverticalsections = self.ParameterDict[self.numnonverticalsections.Name] = intParameter( "Number of Multilateral Sections", - DefaultValue=1, + DefaultValue=0, AllowableRange=list(range(0, 101, 1)), UnitType=Units.NONE, - ErrMessage="assume default for Number of Nonvertical Wellbore Sections (1)", + ErrMessage="assume default for Number of Nonvertical Wellbore Sections (0)", ToolTipText="Number of Nonvertical Wellbore Sections" ) self.NonverticalsCased = self.ParameterDict[self.NonverticalsCased.Name] = boolParameter( @@ -1042,7 +1042,9 @@ def __init__(self, model: Model): Required=False, Provided=False, Valid=True, - ErrMessage="assume default value (False)" + ErrMessage="assume default value (False)", + ToolTipText="If set to True, casing & cementing are assumed to comprise 50% of drilling costs " + "(doubling cost compared to uncased)." ) # local variable initiation diff --git a/src/geophires_x/__init__.py b/src/geophires_x/__init__.py index 68a571381..044fb790d 100644 --- a/src/geophires_x/__init__.py +++ b/src/geophires_x/__init__.py @@ -1 +1 @@ -__version__ = '3.7.5' +__version__ = '3.7.6' diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index f660ae1fe..a8d0d026e 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -916,12 +916,12 @@ "type": "integer", "units": null, "category": "Well Bores", - "default": 1, + "default": 0, "minimum": 0, "maximum": 100 }, "Multilaterals Cased": { - "description": "", + "description": "If set to True, casing & cementing are assumed to comprise 50% of drilling costs (doubling cost compared to uncased).", "type": "boolean", "units": null, "category": "Well Bores", diff --git a/tests/examples/Fervo_Norbeck_Latimer_2023.out b/tests/examples/Fervo_Norbeck_Latimer_2023.out index 3f6607690..4d8f51520 100644 --- a/tests/examples/Fervo_Norbeck_Latimer_2023.out +++ b/tests/examples/Fervo_Norbeck_Latimer_2023.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.7.1 - Simulation Date: 2025-01-22 - Simulation Time: 10:48 - Calculation Time: 0.443 sec + GEOPHIRES Version: 3.7.5 + Simulation Date: 2025-01-31 + Simulation Time: 11:14 + Calculation Time: 0.437 sec ***SUMMARY OF RESULTS*** @@ -97,7 +97,7 @@ Simulation Metadata Drilling and completion costs: 10.13 MUSD Drilling and completion costs per vertical production well: 3.02 MUSD Drilling and completion costs per vertical injection well: 3.02 MUSD - Drilling and completion costs per non-vertical section: 3.61 MUSD + Drilling and completion costs per non-vertical section: 1.80 MUSD Stimulation costs: 1.51 MUSD Surface power plant costs: 11.34 MUSD Field gathering system costs: 1.52 MUSD diff --git a/tests/examples/Fervo_Norbeck_Latimer_2023.txt b/tests/examples/Fervo_Norbeck_Latimer_2023.txt index 486950ad9..dbab23aac 100644 --- a/tests/examples/Fervo_Norbeck_Latimer_2023.txt +++ b/tests/examples/Fervo_Norbeck_Latimer_2023.txt @@ -31,7 +31,7 @@ Well Geometry Configuration, 4 Has Nonvertical Section, True Multilaterals Cased, True Number of Multilateral Sections, 2, -- Two parallel horizontal sections -Total Nonvertical Length, 3250 feet, -- per the paper +Nonvertical Length per Multilateral Section, 3250 feet, -- per the paper Well Drilling Cost Correlation, 10, -- per the drill cost paper - works out to $400/ft Horizontal Well Drilling Cost Correlation, 10, -- per the drill cost paper - works out to $400/ft Production Flow Rate per Well, 41, -- =650 gpm per the paper - per the paper the maximum flow rate was 63 L/s but the range was 550-750 gpm diff --git a/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.out b/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.out index 71da797a5..81f58d91b 100644 --- a/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.out +++ b/tests/examples/Wanju_Yuan_Closed-Loop_Geothermal_Energy_Recovery.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.7.1 - Simulation Date: 2025-01-22 - Simulation Time: 10:48 - Calculation Time: 1.658 sec + GEOPHIRES Version: 3.7.5 + Simulation Date: 2025-01-31 + Simulation Time: 11:36 + Calculation Time: 1.595 sec ***SUMMARY OF RESULTS*** @@ -77,7 +77,7 @@ The AGS models contain an intrinsic reservoir model that doesn't expose values t Drilling and completion costs: 65.80 MUSD Drilling and completion costs per vertical production well: 10.24 MUSD Drilling and completion costs per vertical injection well: 10.24 MUSD - Drilling and completion costs per non-vertical section: 42.18 MUSD + Drilling and completion costs per non-vertical section: 14.06 MUSD Stimulation costs: 0.00 MUSD Surface power plant costs: 6.74 MUSD Field gathering system costs: 0.99 MUSD diff --git a/tests/examples/example_SBT_Hi_T.out b/tests/examples/example_SBT_Hi_T.out index bab06b216..d862c8c30 100644 --- a/tests/examples/example_SBT_Hi_T.out +++ b/tests/examples/example_SBT_Hi_T.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.7.1 - Simulation Date: 2025-01-22 - Simulation Time: 10:48 - Calculation Time: 9.906 sec + GEOPHIRES Version: 3.7.5 + Simulation Date: 2025-01-31 + Simulation Time: 11:36 + Calculation Time: 8.431 sec ***SUMMARY OF RESULTS*** @@ -77,7 +77,7 @@ The AGS models contain an intrinsic reservoir model that doesn't expose values t Drilling and completion costs: 104.87 MUSD Drilling and completion costs per vertical production well: 6.19 MUSD Drilling and completion costs per vertical injection well: 6.19 MUSD - Drilling and completion costs per non-vertical section: 92.48 MUSD + Drilling and completion costs per non-vertical section: 7.71 MUSD Stimulation costs: 0.00 MUSD Surface power plant costs: 52.76 MUSD Field gathering system costs: 0.97 MUSD diff --git a/tests/examples/example_SBT_Lo_T.out b/tests/examples/example_SBT_Lo_T.out index 9dc050a5f..4f1504639 100644 --- a/tests/examples/example_SBT_Lo_T.out +++ b/tests/examples/example_SBT_Lo_T.out @@ -4,10 +4,10 @@ Simulation Metadata ---------------------- - GEOPHIRES Version: 3.7.1 - Simulation Date: 2025-01-22 - Simulation Time: 10:47 - Calculation Time: 1.508 sec + GEOPHIRES Version: 3.7.5 + Simulation Date: 2025-01-31 + Simulation Time: 11:36 + Calculation Time: 1.399 sec ***SUMMARY OF RESULTS*** @@ -77,7 +77,7 @@ The AGS models contain an intrinsic reservoir model that doesn't expose values t Drilling and completion costs: 37.58 MUSD Drilling and completion costs per vertical production well: 3.28 MUSD Drilling and completion costs per vertical injection well: 3.28 MUSD - Drilling and completion costs per non-vertical section: 31.02 MUSD + Drilling and completion costs per non-vertical section: 15.51 MUSD Stimulation costs: 0.00 MUSD Surface power plant costs: 0.03 MUSD Field gathering system costs: 0.97 MUSD diff --git a/tests/geophires_x_tests/cylindrical_reservoir_input_depth_kilometers.txt b/tests/geophires_x_tests/cylindrical_reservoir_input_depth_kilometers.txt index c76985166..98017dd22 100644 --- a/tests/geophires_x_tests/cylindrical_reservoir_input_depth_kilometers.txt +++ b/tests/geophires_x_tests/cylindrical_reservoir_input_depth_kilometers.txt @@ -9,6 +9,7 @@ All-in Nonvertical Drilling Costs, 1000.0 Production Flow Rate per Well, 40, ---- kg/s for water / 40 kg/s for sCO2 Cylindrical Reservoir Input Depth, 3.0, -----kilometers Gradient 1, 60.0, ----deg.c/km +Number of Multilateral Sections, 1 Total Nonvertical Length, 9000 Production Well Diameter,8.5, --- [inch] Injection Temperature, 60.0, -----deg.C diff --git a/tests/geophires_x_tests/cylindrical_reservoir_input_depth_meters.txt b/tests/geophires_x_tests/cylindrical_reservoir_input_depth_meters.txt index 1005a8c46..b7da83aae 100644 --- a/tests/geophires_x_tests/cylindrical_reservoir_input_depth_meters.txt +++ b/tests/geophires_x_tests/cylindrical_reservoir_input_depth_meters.txt @@ -9,6 +9,7 @@ All-in Nonvertical Drilling Costs, 1000.0 Production Flow Rate per Well, 40, ---- kg/s for water / 40 kg/s for sCO2 Cylindrical Reservoir Input Depth, 3000 meter, Gradient 1, 60.0, ----deg.c/km +Number of Multilateral Sections, 1 Total Nonvertical Length, 9000 Production Well Diameter,8.5, --- [inch] Injection Temperature, 60.0, -----deg.C diff --git a/tests/error-code-5500.txt b/tests/geophires_x_tests/error-code-5500.txt similarity index 94% rename from tests/error-code-5500.txt rename to tests/geophires_x_tests/error-code-5500.txt index cbb7915de..6a8123065 100644 --- a/tests/error-code-5500.txt +++ b/tests/geophires_x_tests/error-code-5500.txt @@ -6,6 +6,7 @@ Number of Production Wells, 1 Number of Injection Wells, 0 All-in Vertical Drilling Costs, 1000 All-in Nonvertical Drilling Costs, 1000 +Number of Multilateral Sections, 1 Production Flow Rate per Well, 40 Gradient 1, 60 Total Nonvertical Length, 1000 diff --git a/tests/geophires_x_tests/drilling-adjustment-factor.txt b/tests/geophires_x_tests/generic-egs-case.txt similarity index 100% rename from tests/geophires_x_tests/drilling-adjustment-factor.txt rename to tests/geophires_x_tests/generic-egs-case.txt diff --git a/tests/test_geophires_x.py b/tests/test_geophires_x.py index adec09846..f36638ac0 100644 --- a/tests/test_geophires_x.py +++ b/tests/test_geophires_x.py @@ -290,7 +290,7 @@ def test_runtime_error_with_error_code(self): # https://github.com/NREL/python-geophires-x/issues/13), then error-code-5500.txt should be updated with # different input that is still expected to result in error code 5500. input_params = GeophiresInputParameters( - from_file_path=self._get_test_file_path(Path('error-code-5500.txt')) + from_file_path=self._get_test_file_path(Path('geophires_x_tests/error-code-5500.txt')) ) client.get_geophires_result(input_params) @@ -597,7 +597,7 @@ def test_transmission_pipeline_cost(self): ) 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') + base_file = self._get_test_file_path('geophires_x_tests/generic-egs-case.txt') r_no_adj = GeophiresXClient().get_geophires_result(GeophiresInputParameters(from_file_path=base_file)) r_noop_adj = GeophiresXClient().get_geophires_result( @@ -648,3 +648,25 @@ def c_well(r, prod: bool = False, inj: bool = False): 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) + + def test_egs_laterals(self): + def _get_result(num_laterals: int) -> GeophiresXResult: + return GeophiresXClient().get_geophires_result( + GeophiresInputParameters( + from_file_path=self._get_test_file_path('geophires_x_tests/generic-egs-case.txt'), + params={ + 'Well Geometry Configuration': 4, + 'Number of Multilateral Sections': num_laterals, + }, + ) + ) + + def _c_non_vert(r: GeophiresXResult) -> dict: + return r.result['CAPITAL COSTS (M$)']['Drilling and completion costs per non-vertical section'] + + self.assertIsNone(_c_non_vert(_get_result(0))) + + r_1 = _get_result(1) + self.assertIsNotNone(_c_non_vert(r_1)['value']) + + self.assertEqual(_c_non_vert(r_1)['value'], _c_non_vert(_get_result(2))['value'])