Skip to content

Commit 270e162

Browse files
Merge pull request NREL#284 from malcolm-dsider/sbt-integration
SBT integration
2 parents 95e2122 + b7e70d4 commit 270e162

15 files changed

+3767
-107
lines changed

src/geophires_x/Economics.py

Lines changed: 20 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,13 @@
11
import math
22
import sys
3-
import os
43
import numpy as np
54
import numpy_financial as npf
65
import geophires_x.Model as Model
76
from geophires_x.OptionList import Configuration, WellDrillingCostCorrelation, EconomicModel, EndUseOptions, PlantType
87
from geophires_x.Parameter import intParameter, floatParameter, OutputParameter, ReadParameter, boolParameter, \
98
coerce_int_params_to_enum_values
109
from geophires_x.Units import *
11-
12-
13-
def calculate_total_drilling_lengths_m(Configuration, numnonverticalsections: int, nonvertical_length_km: float,
14-
InputDepth_km: float, OutputDepth_km: float, nprod:int, ninj:int) -> tuple:
15-
"""
16-
returns the total length, vertical length, and non-vertical lengths, depending on the configuration
17-
:param Configuration: Configuration of the well
18-
:type Configuration: :class:`~geophires
19-
:param numnonverticalsections: number of non-vertical sections
20-
:type numnonverticalsections: int
21-
:param nonvertical_length_km: length of non-vertical sections in km
22-
:type nonvertical_length_km: float
23-
:param InputDepth_km: depth of the well in km
24-
:type InputDepth_km: float
25-
:param OutputDepth_km: depth of the output end of the well in km, if U shaped, and not horizontal
26-
:type OutputDepth_km: float
27-
:param nprod: number of production wells
28-
:type nprod: int
29-
:param ninj: number of injection wells
30-
:return: total length, vertical length, and horizontal lengths in meters
31-
:rtype: tuple
32-
"""
33-
if Configuration == Configuration.ULOOP:
34-
# Total drilling depth of both wells and laterals in U-loop [m]
35-
vertical_pipe_length_m = (nprod * InputDepth_km * 1000.0) + (ninj * OutputDepth_km * 1000.0)
36-
nonvertical_pipe_length_m = numnonverticalsections * nonvertical_length_km * 1000.0
37-
elif Configuration == Configuration.COAXIAL:
38-
# Total drilling depth of well and lateral in co-axial case [m]
39-
vertical_pipe_length_m = (nprod + ninj) * InputDepth_km * 1000.0
40-
nonvertical_pipe_length_m = numnonverticalsections * nonvertical_length_km * 1000.0
41-
elif Configuration == Configuration.VERTICAL:
42-
# Total drilling depth of well in vertical case [m]
43-
vertical_pipe_length_m = (nprod + ninj) * InputDepth_km * 1000.0
44-
nonvertical_pipe_length_m = 0.0
45-
elif Configuration == Configuration.L:
46-
# Total drilling depth of well in L case [m]
47-
vertical_pipe_length_m = (nprod + ninj) * InputDepth_km * 1000.0
48-
nonvertical_pipe_length_m = numnonverticalsections * nonvertical_length_km * 1000.0
49-
else:
50-
raise ValueError(f'Invalid Configuration: {Configuration}')
51-
52-
tot_pipe_length_m = vertical_pipe_length_m + nonvertical_pipe_length_m
53-
return tot_pipe_length_m, vertical_pipe_length_m, nonvertical_pipe_length_m
10+
from geophires_x.WellBores import calculate_total_drilling_lengths_m
5411

5512

5613
def calculate_cost_of_one_vertical_well(model: Model, depth_m: float, well_correlation: int,
@@ -1809,8 +1766,14 @@ def __init__(self, model: Model):
18091766
PreferredUnits=CurrencyUnit.MDOLLARS,
18101767
CurrentUnits=CurrencyUnit.MDOLLARS
18111768
)
1812-
self.cost_nonvertical_section = self.OutputParameterDict[self.cost_nonvertical_section.Name] = OutputParameter(
1813-
Name="Cost of the non-vertical section of a well",
1769+
self.cost_lateral_section = self.OutputParameterDict[self.cost_lateral_section.Name] = OutputParameter(
1770+
Name="Cost of the entire (multi-) lateral section of a well",
1771+
UnitType=Units.CURRENCY,
1772+
PreferredUnits=CurrencyUnit.MDOLLARS,
1773+
CurrentUnits=CurrencyUnit.MDOLLARS
1774+
)
1775+
self.cost_to_junction_section = self.OutputParameterDict[self.cost_to_junction_section.Name] = OutputParameter(
1776+
Name="Cost of the entire section of a well from bottom of vertical to junction with laterals",
18141777
UnitType=Units.CURRENCY,
18151778
PreferredUnits=CurrencyUnit.MDOLLARS,
18161779
CurrentUnits=CurrencyUnit.MDOLLARS
@@ -2220,7 +2183,7 @@ def Calculate(self, model: Model) -> None:
22202183
(self.cost_one_injection_well.value * model.wellbores.ninj.value))
22212184
else:
22222185
if hasattr(model.wellbores, 'numnonverticalsections') and model.wellbores.numnonverticalsections.Provided:
2223-
self.cost_nonvertical_section.value = 0.0
2186+
self.cost_lateral_section.value = 0.0
22242187
if not model.wellbores.IsAGS.value:
22252188
input_vert_depth_km = model.reserv.depth.quantity().to('km').magnitude
22262189
output_vert_depth_km = 0.0
@@ -2229,13 +2192,13 @@ def Calculate(self, model: Model) -> None:
22292192
output_vert_depth_km = model.reserv.OutputDepth.quantity().to('km').magnitude
22302193
model.wellbores.injection_reservoir_depth.value = input_vert_depth_km
22312194

2232-
tot_m, tot_vert_m, tot_horiz_m = calculate_total_drilling_lengths_m(model.wellbores.Configuration.value,
2233-
model.wellbores.numnonverticalsections.value,
2234-
model.wellbores.Nonvertical_length.value / 1000.0,
2235-
input_vert_depth_km,
2236-
output_vert_depth_km,
2237-
model.wellbores.nprod.value,
2238-
model.wellbores.ninj.value)
2195+
tot_m, tot_vert_m, tot_horiz_m, _ = calculate_total_drilling_lengths_m(model.wellbores.Configuration.value,
2196+
model.wellbores.numnonverticalsections.value,
2197+
model.wellbores.Nonvertical_length.value / 1000.0,
2198+
input_vert_depth_km,
2199+
output_vert_depth_km,
2200+
model.wellbores.nprod.value,
2201+
model.wellbores.ninj.value)
22392202

22402203
else:
22412204
tot_m = tot_vert_m = model.reserv.depth.quantity().to('km').magnitude
@@ -2261,20 +2224,20 @@ def Calculate(self, model: Model) -> None:
22612224
self.injection_well_cost_adjustment_factor.value)
22622225

22632226
if hasattr(model.wellbores, 'numnonverticalsections') and model.wellbores.numnonverticalsections.Provided:
2264-
self.cost_nonvertical_section.value = calculate_cost_of_non_vertical_section(model, tot_horiz_m,
2227+
self.cost_lateral_section.value = calculate_cost_of_non_vertical_section(model, tot_horiz_m,
22652228
self.wellcorrelation.value,
22662229
self.Nonvertical_drilling_cost_per_m.value,
22672230
model.wellbores.numnonverticalsections.value,
22682231
self.per_injection_well_cost.Name,
22692232
model.wellbores.NonverticalsCased.value,
22702233
self.production_well_cost_adjustment_factor.value)
22712234
else:
2272-
self.cost_nonvertical_section.value = 0.0
2235+
self.cost_lateral_section.value = 0.0
22732236
# cost of the well field
22742237
# 1.05 for 5% indirect costs
22752238
self.Cwell.value = 1.05 * ((self.cost_one_production_well.value * model.wellbores.nprod.value) +
22762239
(self.cost_one_injection_well.value * model.wellbores.ninj.value) +
2277-
self.cost_nonvertical_section.value)
2240+
self.cost_lateral_section.value)
22782241

22792242
# reservoir stimulation costs (M$/injection well). These are calculated whether totalcapcost.Valid = 1
22802243
if self.ccstimfixed.Valid:

src/geophires_x/Model.py

Lines changed: 84 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
from geophires_x.TDPReservoir import TDPReservoir
1212
from geophires_x.WellBores import WellBores
1313
from geophires_x.SurfacePlant import SurfacePlant
14+
from geophires_x.SBTEconomics import SBTEconomics
15+
from geophires_x.SBTWellbores import SBTWellbores
16+
from geophires_x.SBTReservoir import SBTReservoir
1417
from geophires_x.SurfacePlantIndustrialHeat import SurfacePlantIndustrialHeat
1518
from geophires_x.SurfacePlantSubcriticalORC import SurfacePlantSubcriticalOrc
1619
from geophires_x.SurfacePlantSupercriticalORC import SurfacePlantSupercriticalOrc
@@ -72,68 +75,107 @@ def __init__(self, enable_geophires_logging_config=True, input_file=None):
7275
if input_file is None and len(sys.argv) > 1:
7376
input_file = sys.argv[1]
7477

78+
# Key step - read the entire provided input file
7579
read_input_file(self.InputParameters, logger=self.logger, input_file_name=input_file)
7680

81+
# initiate the outputs object
82+
output_file = 'HDR.out'
83+
if len(sys.argv) > 2:
84+
output_file = sys.argv[2]
85+
self.outputs = Outputs(self, output_file=output_file)
86+
87+
# Initiate the elements of the Model object
88+
# this is where you can change what class get initiated - the superclass, or one of the subclasses
89+
self.logger.info("Initiate the elements of the Model")
90+
91+
# Assume that SDAC and add-ons are not used
7792
self.sdacgtoutputs = None
7893
self.sdacgteconomics = None
7994
self.addoutputs = None
8095
self.addeconomics = None
8196

82-
# Initiate the elements of the Model
83-
# this is where you can change what class get initiated - the superclass, or one of the subclasses
84-
self.logger.info("Initiate the elements of the Model")
85-
# we need to decide which reservoir to instantiate based on the user input (InputParameters),
86-
# which we just read above for the first time
87-
# Default is Thermal drawdown percentage model (GETEM)
88-
self.reserv: TDPReservoir = TDPReservoir(self)
89-
if 'Reservoir Model' in self.InputParameters:
90-
if self.InputParameters['Reservoir Model'].sValue == '0':
91-
self.reserv: CylindricalReservoir = CylindricalReservoir(self) # Simple Cylindrical Reservoir
92-
elif self.InputParameters['Reservoir Model'].sValue == '1':
93-
self.reserv: MPFReservoir = MPFReservoir(self) # Multiple parallel fractures model (LANL)
94-
elif self.InputParameters['Reservoir Model'].sValue == '2':
95-
self.reserv: LHSReservoir = LHSReservoir(self) # Multiple parallel fractures model (LANL)
96-
elif self.InputParameters['Reservoir Model'].sValue == '3':
97-
self.reserv: SFReservoir = SFReservoir(self) # Drawdown parameter model (Tester)
98-
elif self.InputParameters['Reservoir Model'].sValue == '5':
99-
self.reserv: UPPReservoir = UPPReservoir(self) # Generic user-provided temperature profile
100-
elif self.InputParameters['Reservoir Model'].sValue == '6':
101-
self.reserv: TOUGH2Reservoir = TOUGH2Reservoir(self) # Tough2 is called
102-
elif self.InputParameters['Reservoir Model'].sValue == '7':
103-
self.reserv: SUTRAReservoir = SUTRAReservoir(self) # SUTRA output is created
104-
10597
# initialize the default objects
98+
self.reserv: TDPReservoir = TDPReservoir(self)
10699
self.wellbores: WellBores = WellBores(self)
107-
self.surfaceplant: SurfacePlant = SurfacePlant(self)
100+
self.surfaceplant = SurfacePlantIndustrialHeat(self) # default is Industrial Heat
108101
self.economics: Economics = Economics(self)
109102

110-
output_file = 'HDR.out'
111-
if len(sys.argv) > 2:
112-
output_file = sys.argv[2]
113-
114-
self.outputs = Outputs(self, output_file=output_file)
103+
# Now we need to handle the creation of all the special case objects based on the user settings
104+
# We have to access the user setting from the overall master table because the read_parameters functions
105+
# have not been called on the specific objects yet, so the instantiated objects only contain default values
106+
# not user set values. The user set value can only come from the master dictionary "InputParameters"
107+
# based on the user input, which we just read above for the first time
115108

109+
# First, we need to decide which reservoir to instantiate
110+
# For reservoirs, the default is Thermal drawdown percentage model (GETEM); see above where it is initialized.
111+
# The user can change this in the input file, so check the values and initiate the appropriate reservoir object
116112
if 'Reservoir Model' in self.InputParameters:
117-
if self.InputParameters['Reservoir Model'].sValue == '7':
118-
# if we use SUTRA output for simulating reservoir thermal energy storage, we use a special wellbore object that can handle SUTRA data
113+
if self.InputParameters['Reservoir Model'].sValue in ['0', 'Simple cylindrical']:
114+
self.reserv: CylindricalReservoir = CylindricalReservoir(self)
115+
elif self.InputParameters['Reservoir Model'].sValue in ['1', 'Multiple Parallel Fractures']:
116+
self.reserv: MPFReservoir = MPFReservoir(self)
117+
elif self.InputParameters['Reservoir Model'].sValue in ['2', '1-D Linear Heat Sweep']:
118+
self.reserv: LHSReservoir = LHSReservoir(self)
119+
elif self.InputParameters['Reservoir Model'].sValue in ['3', 'Single Fracture m/A Thermal Drawdown']:
120+
self.reserv: SFReservoir = SFReservoir(self)
121+
elif self.InputParameters['Reservoir Model'].sValue in ['5', 'User-Provided Temperature Profile']:
122+
self.reserv: UPPReservoir = UPPReservoir(self)
123+
elif self.InputParameters['Reservoir Model'].sValue in ['6', 'TOUGH2 Simulator']:
124+
self.reserv: TOUGH2Reservoir = TOUGH2Reservoir(self)
125+
elif self.InputParameters['Reservoir Model'].sValue in ['7', 'SUTRA']:
126+
# if we use SUTRA output for simulating reservoir thermal energy storage,
127+
# we use a special wellbore object that handles SUTRA data, and special Economics and Outputs objects
128+
self.logger.info('Setup the SUTRA elements of the Model and instantiate new attributes as needed')
129+
self.reserv: SUTRAReservoir = SUTRAReservoir(self)
119130
self.wellbores: WellBores = SUTRAWellBores(self)
120131
self.surfaceplant: SurfacePlantSUTRA = SurfacePlantSUTRA(self)
121132
self.economics: SUTRAEconomics = SUTRAEconomics(self)
122133
self.outputs: SUTRAOutputs = SUTRAOutputs(self, output_file=output_file)
134+
elif self.InputParameters['Reservoir Model'].sValue in ['8', 'SBT']:
135+
self.logger.info('Setup the SBT elements of the Model and instantiate new attributes as needed')
136+
self.reserv: SBTReservoir = SBTReservoir(self)
137+
self.wellbores: SBTWellBores = SBTWellbores(self)
138+
self.economics: SBTEconomics = SBTEconomics(self)
123139

140+
# Now handle the special cases for all AGS cases (CLGS, SBT, or CLGS)
124141
if 'Is AGS' in self.InputParameters:
125142
if self.InputParameters['Is AGS'].sValue in ['True', 'true', 'TRUE', 'T', '1']:
126-
self.logger.info("Setup the AGS elements of the Model and instantiate new attributes as needed")
127-
# If we are doing AGS, we need to replace the various objects we with versions of the objects
128-
# that have AGS functionality.
129-
# that means importing them, initializing them, then reading their parameters
130-
# use the simple cylindrical reservoir for all AGS systems.
131-
self.reserv: CylindricalReservoir = CylindricalReservoir(self)
132-
self.wellbores: WellBores = AGSWellBores(self)
133-
self.surfaceplant: SurfacePlantAGS = SurfacePlantAGS(self)
134-
self.economics: AGSEconomics = AGSEconomics(self)
135-
self.outputs: AGSOutputs = AGSOutputs(self, output_file=output_file)
143+
self.logger.info('Setup the AGS elements of the Model and instantiate new attributes as needed')
136144
self.wellbores.IsAGS.value = True
145+
if not isinstance(self.reserv, SBTReservoir):
146+
if self.InputParameters['Economic Model'].sValue not in ['4', 'Simple (CLGS)']:
147+
# must be doing wangju approach, # so go back to using default objects
148+
self.surfaceplant = SurfacePlant(self)
149+
self.economics = Economics(self)
150+
# Must be doing CLGS, so we need to instantiate the right objects
151+
self.reserv: CylindricalReservoir = CylindricalReservoir(self)
152+
self.wellbores: WellBores = AGSWellBores(self)
153+
self.surfaceplant: SurfacePlantAGS = SurfacePlantAGS(self)
154+
self.economics: AGSEconomics = AGSEconomics(self)
155+
self.outputs: AGSOutputs = AGSOutputs(self, output_file=output_file)
156+
157+
# initialize the right Power Plant Type
158+
if 'Power Plant Type' in self.InputParameters:
159+
# electricity
160+
if self.InputParameters['Power Plant Type'].sValue in ['1', 'Subcritical ORC']:
161+
self.surfaceplant = SurfacePlantSubcriticalOrc(self)
162+
elif self.InputParameters['Power Plant Type'].sValue in ['2', 'Supercritical ORC']:
163+
self.surfaceplant = SurfacePlantSupercriticalOrc(self)
164+
elif self.InputParameters['Power Plant Type'].sValue in ['3', 'Single-Flash']:
165+
self.surfaceplant = SurfacePlantSingleFlash(self)
166+
elif self.InputParameters['Power Plant Type'].sValue in ['4', 'Double-Flash']:
167+
self.surfaceplant = SurfacePlantDoubleFlash(self)
168+
# Heat applications
169+
elif self.InputParameters['Power Plant Type'].sValue in ['5', 'Absorption Chiller']:
170+
self.surfaceplant = SurfacePlantAbsorptionChiller(self)
171+
elif self.InputParameters['Power Plant Type'].sValue in ['6', 'Heat Pump']:
172+
self.surfaceplant = SurfacePlantHeatPump(self)
173+
elif self.InputParameters['Power Plant Type'].sValue in ['7', 'District Heating']:
174+
self.surfaceplant = SurfacePlantDistrictHeating(self)
175+
elif self.InputParameters['Power Plant Type'].sValue in ['8', 'Reservoir Thermal Energy Storage']:
176+
self.surfaceplant = SurfacePlantSUTRA(self)
177+
elif self.InputParameters['Power Plant Type'].sValue in ['9', 'Industrial']:
178+
self.surfaceplant = SurfacePlantIndustrialHeat(self)
137179

138180
# if we find out we have an add-ons, we need to instantiate it, then read for the parameters
139181
if 'AddOn Nickname 1' in self.InputParameters:
@@ -150,6 +192,7 @@ def __init__(self, enable_geophires_logging_config=True, input_file=None):
150192

151193
self.logger.info(f'Complete {__class__}: {__name__}')
152194

195+
153196
def __str__(self):
154197
return "Model"
155198

0 commit comments

Comments
 (0)