From b4d7130c5d541fc48e8890f6abd0b0eaf65da716 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:04:54 -0700 Subject: [PATCH 1/4] stimulation costs for redrilling output code cleanup --- src/geophires_x/Outputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geophires_x/Outputs.py b/src/geophires_x/Outputs.py index 50dc13c1..d1c45300 100644 --- a/src/geophires_x/Outputs.py +++ b/src/geophires_x/Outputs.py @@ -478,7 +478,7 @@ def PrintOutputs(self, model: Model): if model.economics.totalcapcost.Valid and model.wellbores.redrill.value > 0: f.write(f' Drilling and completion costs (for redrilling):{model.economics.Cwell.value:10.2f} ' + model.economics.Cwell.CurrentUnits.value + NL) f.write(f' Drilling and completion costs per redrilled 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 (for redrilling): {model.economics.Cstim.value:10.2f} ' + model.economics.Cstim.CurrentUnits.value + NL) + f.write(f' Stimulation costs (for redrilling): {econ.Cstim.value:10.2f} {econ.Cstim.CurrentUnits.value}\n') if model.economics.RITCValue.value: f.write(f' {model.economics.RITCValue.display_name}: {-1*model.economics.RITCValue.value:10.2f} {model.economics.RITCValue.CurrentUnits.value}\n') From cfbcb9fd8ab604b9686ed88975956df261c5cfe3 Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:36:20 -0700 Subject: [PATCH 2/4] Parameterize Reservoir Stimulation Capital Cost per Injection Well --- src/geophires_x/Economics.py | 22 ++++++++++++++++--- .../geophires-request.json | 9 ++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index 06de9ae2..3b43c3ae 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -572,6 +572,7 @@ def __init__(self, model: Model): ToolTipText="Specify the economic model to calculate the levelized cost of energy. " + '; '.join([f'{it.int_value}: {it.value}' for it in EconomicModel]) ) + self.ccstimfixed = self.ParameterDict[self.ccstimfixed.Name] = floatParameter( "Reservoir Stimulation Capital Cost", DefaultValue=-1.0, @@ -584,6 +585,18 @@ def __init__(self, model: Model): Valid=False, ToolTipText="Total reservoir stimulation capital cost" ) + self.stimulation_cost_per_injection_well = \ + self.ParameterDict[self.stimulation_cost_per_injection_well.Name] = floatParameter( + 'Reservoir Stimulation Capital Cost per Injection Well', + DefaultValue=1.25, + Min=0, + Max=100, + UnitType=Units.CURRENCY, + PreferredUnits=CurrencyUnit.MDOLLARS, + CurrentUnits=CurrencyUnit.MDOLLARS, + Provided=False, + ToolTipText='Reservoir stimulation capital cost per injection well' + ) self.ccstimadjfactor = self.ParameterDict[self.ccstimadjfactor.Name] = floatParameter( "Reservoir Stimulation Capital Cost Adjustment Factor", DefaultValue=1.0, @@ -1614,7 +1627,8 @@ def __init__(self, model: Model): UnitType=Units.CURRENCY, PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS, - ToolTipText=f'Default correlation: $1.25M per injection well {contingency_and_indirect_costs_tooltip}. ' + ToolTipText=f'Default correlation: ${self.stimulation_cost_per_injection_well.value}M ' + f'per injection well {contingency_and_indirect_costs_tooltip}. ' f'Provide {self.ccstimadjfactor.Name} to multiply the default correlation. ' f'Provide {self.ccstimfixed.Name} to override the default correlation and set your own cost.' ) @@ -2336,12 +2350,14 @@ def Calculate(self, model: Model) -> None: if self.ccstimfixed.Valid: self.Cstim.value = self.ccstimfixed.value else: - base_stimulation_cost_MUSD_per_injection_well = 1.25 # TODO parameterize + stim_cost_per_injection_well = self.stimulation_cost_per_injection_well.quantity().to( + self.Cstim.CurrentUnits).magnitude # 1.15 for 15% contingency and 1.05 for 5% indirect costs # TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor - self.Cstim.value = (base_stimulation_cost_MUSD_per_injection_well * self.ccstimadjfactor.value + self.Cstim.value = (stim_cost_per_injection_well * model.wellbores.ninj.value + * self.ccstimadjfactor.value * 1.05 * 1.15) # field gathering system costs (M$) diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index 61e3b937..93aa1b7a 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -1395,6 +1395,15 @@ "minimum": 0, "maximum": 1000 }, + "Reservoir Stimulation Capital Cost per Injection Well": { + "description": "Reservoir stimulation capital cost per injection well", + "type": "number", + "units": "MUSD", + "category": "Economics", + "default": 1.25, + "minimum": 0, + "maximum": 100 + }, "Reservoir Stimulation Capital Cost Adjustment Factor": { "description": "Multiplier for built-in reservoir stimulation capital cost correlation", "type": "number", From 59b28d088f19f6c25b2bbcef0e65afd8e573ad6f Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:43:40 -0700 Subject: [PATCH 3/4] Fix stimulation cost tooltip text incorrect indirect cost - actual value is 5%, not 15%. Update tooltip text for clarity and to reference cost per injection well parameter. --- src/geophires_x/Economics.py | 14 ++++++++++---- .../geophires-result.json | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index 3b43c3ae..e1633bb0 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -1619,7 +1619,7 @@ def __init__(self, model: Model): ) # TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor - contingency_and_indirect_costs_tooltip = 'plus 15% contingency plus 12% indirect costs' + stimulation_contingency_and_indirect_costs_tooltip = 'plus 15% contingency plus 5% indirect costs' # noinspection SpellCheckingInspection self.Cstim = self.OutputParameterDict[self.Cstim.Name] = OutputParameter( @@ -1628,11 +1628,17 @@ def __init__(self, model: Model): PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS, ToolTipText=f'Default correlation: ${self.stimulation_cost_per_injection_well.value}M ' - f'per injection well {contingency_and_indirect_costs_tooltip}. ' - f'Provide {self.ccstimadjfactor.Name} to multiply the default correlation. ' - f'Provide {self.ccstimfixed.Name} to override the default correlation and set your own cost.' + f'per injection well {stimulation_contingency_and_indirect_costs_tooltip}. ' + f'Provide {self.stimulation_cost_per_injection_well.Name} to set the correlation ' + f'cost per injection well. ' + f'Provide {self.ccstimadjfactor.Name} to multiply the correlation-calculated cost. ' + f'Provide {self.ccstimfixed.Name} to override the correlation and set your own ' + f'total stimulation cost.' ) + # TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor + contingency_and_indirect_costs_tooltip = 'plus 15% contingency plus 12% indirect costs' + # See TODO re:parameterizing indirect costs at src/geophires_x/Economics.py:652 # (https://github.com/NREL/GEOPHIRES-X/issues/383) self.Cexpl = self.OutputParameterDict[self.Cexpl.Name] = OutputParameter( diff --git a/src/geophires_x_schema_generator/geophires-result.json b/src/geophires_x_schema_generator/geophires-result.json index 52fcb5af..0acee961 100644 --- a/src/geophires_x_schema_generator/geophires-result.json +++ b/src/geophires_x_schema_generator/geophires-result.json @@ -351,7 +351,7 @@ "Drilling and completion costs per redrilled well": {}, "Stimulation costs": { "type": "number", - "description": "Default correlation: $1.25M per injection well plus 15% contingency plus 12% indirect costs. Provide Reservoir Stimulation Capital Cost Adjustment Factor to multiply the default correlation. Provide Reservoir Stimulation Capital Cost to override the default correlation and set your own cost.", + "description": "Default correlation: $1.25M per injection well plus 15% contingency plus 5% indirect costs. Provide Reservoir Stimulation Capital Cost per Injection Well to set the correlation cost per injection well. Provide Reservoir Stimulation Capital Cost Adjustment Factor to multiply the correlation-calculated cost. Provide Reservoir Stimulation Capital Cost to override the correlation and set your own total stimulation cost.", "units": "MUSD" }, "Stimulation costs (for redrilling)": {}, From feefbf336b2b9f5efdbdf8ba6f9a8edd739289ab Mon Sep 17 00:00:00 2001 From: softwareengineerprogrammer <4056124+softwareengineerprogrammer@users.noreply.github.com> Date: Mon, 21 Jul 2025 12:07:22 -0700 Subject: [PATCH 4/4] Add Reservoir Stimulation Capital Cost per Production Well parameter (default value = $0) --- src/geophires_x/Economics.py | 37 ++++++++++++++++--- .../geophires-request.json | 11 +++++- .../geophires-result.json | 2 +- tests/test_geophires_x.py | 34 +++++++++++++++++ 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/geophires_x/Economics.py b/src/geophires_x/Economics.py index e1633bb0..3d62e47b 100644 --- a/src/geophires_x/Economics.py +++ b/src/geophires_x/Economics.py @@ -585,18 +585,38 @@ def __init__(self, model: Model): Valid=False, ToolTipText="Total reservoir stimulation capital cost" ) + + max_stimulation_cost_per_well_MUSD = 100 self.stimulation_cost_per_injection_well = \ self.ParameterDict[self.stimulation_cost_per_injection_well.Name] = floatParameter( 'Reservoir Stimulation Capital Cost per Injection Well', DefaultValue=1.25, Min=0, - Max=100, + Max=max_stimulation_cost_per_well_MUSD, UnitType=Units.CURRENCY, PreferredUnits=CurrencyUnit.MDOLLARS, CurrentUnits=CurrencyUnit.MDOLLARS, Provided=False, ToolTipText='Reservoir stimulation capital cost per injection well' ) + + stimulation_cost_per_production_well_default_value_MUSD = 0 + stimulation_cost_per_production_well_default_value_note = \ + '. By default, only the injection wells are assumed to be stimulated unless this parameter is provided.' \ + if stimulation_cost_per_production_well_default_value_MUSD == 0 else '' + self.stimulation_cost_per_production_well = \ + self.ParameterDict[self.stimulation_cost_per_production_well.Name] = floatParameter( + 'Reservoir Stimulation Capital Cost per Production Well', + DefaultValue=stimulation_cost_per_production_well_default_value_MUSD, + Min=0, + Max=max_stimulation_cost_per_well_MUSD, + UnitType=Units.CURRENCY, + PreferredUnits=CurrencyUnit.MDOLLARS, + CurrentUnits=CurrencyUnit.MDOLLARS, + ToolTipText=f'Reservoir stimulation capital cost per production well' + f'{stimulation_cost_per_production_well_default_value_note}' + ) + self.ccstimadjfactor = self.ParameterDict[self.ccstimadjfactor.Name] = floatParameter( "Reservoir Stimulation Capital Cost Adjustment Factor", DefaultValue=1.0, @@ -607,7 +627,7 @@ def __init__(self, model: Model): CurrentUnits=PercentUnit.TENTH, Provided=False, Valid=True, - ToolTipText="Multiplier for built-in reservoir stimulation capital cost correlation" + ToolTipText="Multiplier for reservoir stimulation capital cost correlation" ) self.ccexplfixed = self.ParameterDict[self.ccexplfixed.Name] = floatParameter( "Exploration Capital Cost", @@ -1629,8 +1649,9 @@ def __init__(self, model: Model): CurrentUnits=CurrencyUnit.MDOLLARS, ToolTipText=f'Default correlation: ${self.stimulation_cost_per_injection_well.value}M ' f'per injection well {stimulation_contingency_and_indirect_costs_tooltip}. ' - f'Provide {self.stimulation_cost_per_injection_well.Name} to set the correlation ' - f'cost per injection well. ' + f'Provide {self.stimulation_cost_per_injection_well.Name} and ' + f'{self.stimulation_cost_per_production_well.Name} to set the correlation ' + f'costs per well. ' f'Provide {self.ccstimadjfactor.Name} to multiply the correlation-calculated cost. ' f'Provide {self.ccstimfixed.Name} to override the correlation and set your own ' f'total stimulation cost.' @@ -2358,11 +2379,15 @@ def Calculate(self, model: Model) -> None: else: stim_cost_per_injection_well = self.stimulation_cost_per_injection_well.quantity().to( self.Cstim.CurrentUnits).magnitude + stim_cost_per_production_well = self.stimulation_cost_per_production_well.quantity().to( + self.Cstim.CurrentUnits).magnitude # 1.15 for 15% contingency and 1.05 for 5% indirect costs # TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor - self.Cstim.value = (stim_cost_per_injection_well - * model.wellbores.ninj.value + self.Cstim.value = (( + stim_cost_per_injection_well * model.wellbores.ninj.value + + stim_cost_per_production_well * model.wellbores.nprod.value + ) * self.ccstimadjfactor.value * 1.05 * 1.15) diff --git a/src/geophires_x_schema_generator/geophires-request.json b/src/geophires_x_schema_generator/geophires-request.json index 93aa1b7a..779604ea 100644 --- a/src/geophires_x_schema_generator/geophires-request.json +++ b/src/geophires_x_schema_generator/geophires-request.json @@ -1404,8 +1404,17 @@ "minimum": 0, "maximum": 100 }, + "Reservoir Stimulation Capital Cost per Production Well": { + "description": "Reservoir stimulation capital cost per production well. By default, only the injection wells are assumed to be stimulated unless this parameter is provided.", + "type": "number", + "units": "MUSD", + "category": "Economics", + "default": 0, + "minimum": 0, + "maximum": 100 + }, "Reservoir Stimulation Capital Cost Adjustment Factor": { - "description": "Multiplier for built-in reservoir stimulation capital cost correlation", + "description": "Multiplier for reservoir stimulation capital cost correlation", "type": "number", "units": "", "category": "Economics", diff --git a/src/geophires_x_schema_generator/geophires-result.json b/src/geophires_x_schema_generator/geophires-result.json index 0acee961..9cba6618 100644 --- a/src/geophires_x_schema_generator/geophires-result.json +++ b/src/geophires_x_schema_generator/geophires-result.json @@ -351,7 +351,7 @@ "Drilling and completion costs per redrilled well": {}, "Stimulation costs": { "type": "number", - "description": "Default correlation: $1.25M per injection well plus 15% contingency plus 5% indirect costs. Provide Reservoir Stimulation Capital Cost per Injection Well to set the correlation cost per injection well. Provide Reservoir Stimulation Capital Cost Adjustment Factor to multiply the correlation-calculated cost. Provide Reservoir Stimulation Capital Cost to override the correlation and set your own total stimulation cost.", + "description": "Default correlation: $1.25M per injection well plus 15% contingency plus 5% indirect costs. Provide Reservoir Stimulation Capital Cost per Injection Well and Reservoir Stimulation Capital Cost per Production Well to set the correlation costs per well. Provide Reservoir Stimulation Capital Cost Adjustment Factor to multiply the correlation-calculated cost. Provide Reservoir Stimulation Capital Cost to override the correlation and set your own total stimulation cost.", "units": "MUSD" }, "Stimulation costs (for redrilling)": {}, diff --git a/tests/test_geophires_x.py b/tests/test_geophires_x.py index 7ded3f88..eefec5b8 100644 --- a/tests/test_geophires_x.py +++ b/tests/test_geophires_x.py @@ -12,6 +12,7 @@ from geophires_x_client import _get_logger from geophires_x_client.geophires_input_parameters import EndUseOption from geophires_x_client.geophires_input_parameters import GeophiresInputParameters +from geophires_x_client.geophires_input_parameters import ImmutableGeophiresInputParameters from geophires_x_tests.test_options_list import WellDrillingCostCorrelationTestCase from tests.base_test_case import BaseTestCase @@ -941,3 +942,36 @@ def test_sbt_coaxial_raises_error(self): ) client.get_geophires_result(params) self.assertIn('SBT with coaxial configuration is not implemented', str(e.exception)) + + def test_production_well_stimulation_cost(self): + def _get_result(prod_well_stim_MUSD: Optional[int] = None) -> GeophiresXResult: + p = {} + if prod_well_stim_MUSD is not None: + p['Reservoir Stimulation Capital Cost per Production Well'] = prod_well_stim_MUSD + + return GeophiresXClient().get_geophires_result( + ImmutableGeophiresInputParameters( + from_file_path=self._get_test_file_path('geophires_x_tests/generic-egs-case.txt'), + params=p, + ) + ) + + result_no_prod_stim: GeophiresXResult = _get_result() + + result_prod_stim: GeophiresXResult = _get_result(1.25) + + # TODO https://github.com/NREL/GEOPHIRES-X/issues/383?title=Parameterize+indirect+cost+factor + indirect_and_contingency = 1.05 * 1.15 + + self.assertAlmostEqual( + ( + 2 + * ( + result_no_prod_stim.result['CAPITAL COSTS (M$)']['Stimulation costs']['value'] + / (indirect_and_contingency) + ) + ) + * indirect_and_contingency, + result_prod_stim.result['CAPITAL COSTS (M$)']['Stimulation costs']['value'], + places=1, + )