completor 0.1.3__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- completor/completion.py +149 -545
- completor/constants.py +223 -150
- completor/create_output.py +559 -431
- completor/exceptions/__init__.py +0 -4
- completor/exceptions/exceptions.py +6 -6
- completor/get_version.py +8 -0
- completor/hook_implementations/jobs.py +21 -13
- completor/input_validation.py +54 -42
- completor/launch_args_parser.py +7 -12
- completor/logger.py +3 -3
- completor/main.py +105 -363
- completor/parse.py +104 -93
- completor/prepare_outputs.py +594 -458
- completor/read_casefile.py +250 -198
- completor/read_schedule.py +317 -14
- completor/utils.py +256 -54
- completor/visualization.py +1 -14
- completor/visualize_well.py +29 -27
- completor/wells.py +271 -0
- {completor-0.1.3.dist-info → completor-1.0.1.dist-info}/METADATA +11 -12
- completor-1.0.1.dist-info/RECORD +27 -0
- completor/create_wells.py +0 -314
- completor/pvt_model.py +0 -14
- completor-0.1.3.dist-info/RECORD +0 -27
- {completor-0.1.3.dist-info → completor-1.0.1.dist-info}/LICENSE +0 -0
- {completor-0.1.3.dist-info → completor-1.0.1.dist-info}/WHEEL +0 -0
- {completor-0.1.3.dist-info → completor-1.0.1.dist-info}/entry_points.txt +0 -0
completor/create_output.py
CHANGED
|
@@ -5,458 +5,586 @@ from __future__ import annotations
|
|
|
5
5
|
import getpass
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from matplotlib.backends.backend_pdf import PdfPages # type: ignore
|
|
9
12
|
|
|
10
|
-
from completor import prepare_outputs
|
|
11
|
-
from completor.completion import WellSchedule
|
|
13
|
+
from completor import prepare_outputs
|
|
12
14
|
from completor.constants import Headers, Keywords
|
|
13
|
-
from completor.
|
|
15
|
+
from completor.exceptions.clean_exceptions import CompletorError
|
|
16
|
+
from completor.get_version import get_version
|
|
14
17
|
from completor.logger import logger
|
|
15
|
-
from completor.pvt_model import CORRELATION_UDQ
|
|
16
18
|
from completor.read_casefile import ReadCasefile
|
|
17
19
|
from completor.visualize_well import visualize_well
|
|
20
|
+
from completor.wells import Lateral, Well
|
|
18
21
|
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
There are two output files from completor:
|
|
24
|
-
1. Well schedule file (text file) for input to reservoir simulator.
|
|
25
|
-
2. Well diagram (pdf file), i.e. a well completion schematic.
|
|
23
|
+
def format_output(well: Well, case: ReadCasefile, figure_name: str | None = None) -> tuple[str, str, str, str]:
|
|
24
|
+
"""Formats the finished output string to be written to a file.
|
|
26
25
|
|
|
27
26
|
Args:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
show_figure: Flag for pdf export of well completion schematic.
|
|
35
|
-
figure_no: Figure number.
|
|
36
|
-
write_welsegs: Flag to write WELSEGS.
|
|
27
|
+
well: Well data.
|
|
28
|
+
case: Case data.
|
|
29
|
+
figure_name: The name of the figure, if None, no figure is printed. Defaults to None.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Properly formatted output data for completion data, well segments, completion segments, and bonus.
|
|
37
33
|
"""
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
self.schedule = schedule
|
|
64
|
-
self.case_path: str | None
|
|
65
|
-
self.schedule_path: str | None
|
|
66
|
-
if paths:
|
|
67
|
-
self.case_path, self.schedule_path = paths
|
|
68
|
-
else:
|
|
69
|
-
self.case_path = None
|
|
70
|
-
self.schedule_path = None
|
|
71
|
-
self.wells = wells
|
|
72
|
-
self.well_name = well_name
|
|
73
|
-
self.iwell = iwell
|
|
74
|
-
self.version = completor_version
|
|
75
|
-
self.write_welsegs = write_welsegs
|
|
76
|
-
self.show_figure = show_figure
|
|
77
|
-
|
|
78
|
-
# different line connection
|
|
79
|
-
self.newline1 = "\n\n\n"
|
|
80
|
-
self.newline2 = "\n/" + self.newline1
|
|
81
|
-
self.newline3 = "/" + self.newline1
|
|
82
|
-
|
|
83
|
-
# create final print
|
|
84
|
-
self.header = self.make_completor_header()
|
|
85
|
-
|
|
86
|
-
# Prints the UDQ statement if a PVT file and
|
|
87
|
-
# PVT table are specified in the case file.
|
|
88
|
-
self.print_udq = False
|
|
89
|
-
self.udq_correlation = ""
|
|
90
|
-
self.udq_parameter: dict[str, str] = {}
|
|
91
|
-
if self.case.completion_table[Headers.DEVICE_TYPE].isin(["AICV"]).any():
|
|
92
|
-
self.print_udq = True
|
|
93
|
-
self.udq_correlation = CORRELATION_UDQ
|
|
94
|
-
|
|
95
|
-
# Start printing all wells
|
|
96
|
-
self.finalprint = self.header
|
|
97
|
-
# print udq equation if relevant
|
|
98
|
-
if self.print_udq:
|
|
99
|
-
self.finalprint += self.udq_correlation
|
|
100
|
-
|
|
101
|
-
self.df_reservoir = wells.df_reservoir_all[wells.df_reservoir_all[Headers.WELL] == self.well_name]
|
|
102
|
-
self.df_well = wells.df_well_all[wells.df_well_all[Headers.WELL] == self.well_name]
|
|
103
|
-
self.laterals = self.df_well[self.df_well[Headers.WELL] == self.well_name][Headers.LATERAL].unique()
|
|
104
|
-
|
|
105
|
-
# Start printing per well.
|
|
106
|
-
self.welsegs_header, _ = self.schedule.get_well_segments(self.well_name, branch=1)
|
|
107
|
-
self.check_welsegs1()
|
|
108
|
-
self.print_welsegs = f"{Keywords.WELSEGS}\n{po.dataframe_tostring(self.welsegs_header, True)}\n"
|
|
109
|
-
self.print_welsegsinit = self.print_welsegs
|
|
110
|
-
self.print_wseglink = f"{Keywords.WSEGLINK}\n"
|
|
111
|
-
self.print_wseglinkinit = self.print_wseglink
|
|
112
|
-
self.print_compsegs = f"{Keywords.COMPSEGS}\n'{self.well_name}' /\n"
|
|
113
|
-
self.print_compsegsinit = self.print_compsegs
|
|
114
|
-
self.print_compdat = f"{Keywords.COMPDAT}\n"
|
|
115
|
-
self.print_compdatinit = self.print_compdat
|
|
116
|
-
self.print_wsegvalv = f"{Keywords.WSEGVALV}\n"
|
|
117
|
-
self.print_wsegvalvinit = self.print_wsegvalv
|
|
118
|
-
self.print_wsegicv = f"{Keywords.WSEGVALV}\n"
|
|
119
|
-
self.print_wsegicvinit = self.print_wsegicv
|
|
120
|
-
self.print_wsegaicd = f"{Keywords.WSEGAICD}\n"
|
|
121
|
-
self.print_wsegaicdinit = self.print_wsegaicd
|
|
122
|
-
self.print_wsegsicd = f"{Keywords.WSEGSICD}\n"
|
|
123
|
-
self.print_wsegsicdinit = self.print_wsegsicd
|
|
124
|
-
self.print_wsegdar = f"""\
|
|
125
|
-
{'-' * 100}
|
|
126
|
-
-- This is how we model DAR technology using sets of ACTIONX keywords.
|
|
127
|
-
-- The segment dP curves changes according to the segment water-
|
|
128
|
-
-- and gas volume fractions at downhole condition.
|
|
129
|
-
-- The value of Cv is adjusted according to the segment length and the number of
|
|
130
|
-
-- devices per joint. The constriction area varies according to values of
|
|
131
|
-
-- volume fractions.
|
|
132
|
-
{"-" * 100}{self.newline1}"""
|
|
133
|
-
|
|
134
|
-
self.print_wsegdarinit = self.print_wsegdar
|
|
135
|
-
self.print_wsegaicv = f"""\
|
|
136
|
-
{"-" * 100}
|
|
137
|
-
-- This is how we model AICV technology using sets of ACTIONX keyword
|
|
138
|
-
-- the DP parameters change according to the segment water cut (at downhole condition )
|
|
139
|
-
-- and gas volume fraction (at downhole condition)
|
|
140
|
-
{"-" * 100}{self.newline1}"""
|
|
141
|
-
|
|
142
|
-
self.print_wsegaicvinit = self.print_wsegaicv
|
|
143
|
-
self.start_segment = 2
|
|
144
|
-
self.start_branch = 1
|
|
145
|
-
# pre-preparations
|
|
146
|
-
data = {} # just a container. need to to loop twice to make connect_lateral work
|
|
147
|
-
for lateral in self.laterals:
|
|
148
|
-
self.df_tubing, top = po.prepare_tubing_layer(
|
|
149
|
-
self.schedule,
|
|
150
|
-
self.well_name,
|
|
151
|
-
lateral,
|
|
152
|
-
self.df_well,
|
|
153
|
-
self.start_segment,
|
|
154
|
-
self.start_branch,
|
|
155
|
-
self.case.completion_table,
|
|
156
|
-
)
|
|
157
|
-
self.df_device = po.prepare_device_layer(self.well_name, lateral, self.df_well, self.df_tubing)
|
|
158
|
-
self.df_annulus, self.df_wseglink = po.prepare_annulus_layer(
|
|
159
|
-
self.well_name, lateral, self.df_well, self.df_device
|
|
160
|
-
)
|
|
161
|
-
self.update_segmentbranch()
|
|
162
|
-
self.check_segments(lateral)
|
|
163
|
-
data[lateral] = (self.df_tubing, self.df_device, self.df_annulus, self.df_wseglink, top)
|
|
164
|
-
# attach lateral to their proper segments (in overburden, potentially)
|
|
165
|
-
for lateral in data:
|
|
166
|
-
po.connect_lateral(self.well_name, lateral, data, self.case)
|
|
167
|
-
# main preparations
|
|
168
|
-
for lateral in self.laterals:
|
|
169
|
-
self.df_tubing, self.df_device, self.df_annulus, self.df_wseglink = data[lateral][:4]
|
|
170
|
-
|
|
171
|
-
self.branch_revision(lateral)
|
|
172
|
-
|
|
173
|
-
completion_table_well = case.completion_table[case.completion_table[Headers.WELL] == self.well_name]
|
|
174
|
-
completion_table_lateral = completion_table_well[completion_table_well[Headers.BRANCH] == lateral]
|
|
175
|
-
self.df_compsegs = po.prepare_compsegs(
|
|
176
|
-
self.well_name,
|
|
177
|
-
lateral,
|
|
178
|
-
self.df_reservoir,
|
|
179
|
-
self.df_device,
|
|
180
|
-
self.df_annulus,
|
|
181
|
-
completion_table_lateral,
|
|
182
|
-
self.case.segment_length,
|
|
35
|
+
completion_data_list = []
|
|
36
|
+
print_well_segments = ""
|
|
37
|
+
print_well_segments_link = ""
|
|
38
|
+
print_completion_segments = ""
|
|
39
|
+
print_valve = ""
|
|
40
|
+
print_inflow_control_valve = ""
|
|
41
|
+
print_autonomous_inflow_control_device = ""
|
|
42
|
+
print_inflow_control_device = ""
|
|
43
|
+
print_density_activated_recovery = ""
|
|
44
|
+
print_autonomous_inflow_control_valve = ""
|
|
45
|
+
|
|
46
|
+
start_segment = 2
|
|
47
|
+
start_branch = 1
|
|
48
|
+
|
|
49
|
+
header_written = False
|
|
50
|
+
first = True
|
|
51
|
+
for lateral in well.active_laterals:
|
|
52
|
+
_check_well_segments_header(
|
|
53
|
+
lateral.df_welsegs_header, well.df_reservoir_all_laterals[Headers.START_MEASURED_DEPTH].iloc[0]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if not header_written:
|
|
57
|
+
print_well_segments += (
|
|
58
|
+
f"{Keywords.WELL_SEGMENTS}\n{prepare_outputs.dataframe_tostring(lateral.df_welsegs_header, True)}"
|
|
183
59
|
)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
self.case.completion_icv_tubing,
|
|
197
|
-
self.case.wsegicv_table,
|
|
60
|
+
header_written = True
|
|
61
|
+
|
|
62
|
+
lateral.df_tubing, top = prepare_outputs.prepare_tubing_layer(
|
|
63
|
+
well, lateral, start_segment, start_branch, case.completion_table
|
|
64
|
+
)
|
|
65
|
+
lateral.df_device = prepare_outputs.prepare_device_layer(lateral.df_well, lateral.df_tubing)
|
|
66
|
+
|
|
67
|
+
if lateral.df_device.empty:
|
|
68
|
+
logger.warning(
|
|
69
|
+
"No connection from reservoir to tubing in Well : %s Lateral : %d",
|
|
70
|
+
well.well_name,
|
|
71
|
+
lateral.lateral_number,
|
|
198
72
|
)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
73
|
+
df_annulus, df_well_segments_link = prepare_outputs.prepare_annulus_layer(
|
|
74
|
+
well.well_name, lateral.df_well, lateral.df_device
|
|
75
|
+
)
|
|
76
|
+
if df_annulus.empty:
|
|
77
|
+
logger.info("No annular flow in Well : %s Lateral : %d", well.well_name, lateral.lateral_number)
|
|
78
|
+
|
|
79
|
+
start_segment, start_branch = _update_segmentbranch(lateral.df_device, df_annulus)
|
|
80
|
+
|
|
81
|
+
lateral.df_tubing = _connect_lateral(well.well_name, lateral, top, well, case)
|
|
82
|
+
|
|
83
|
+
lateral.df_tubing[Headers.BRANCH] = lateral.lateral_number
|
|
84
|
+
active_laterals = [lateral.lateral_number for lateral in well.active_laterals]
|
|
85
|
+
lateral.df_device, df_annulus = _branch_revision(
|
|
86
|
+
lateral.lateral_number, active_laterals, lateral.df_device, df_annulus
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
completion_table_well = case.completion_table[case.completion_table[Headers.WELL] == well.well_name]
|
|
90
|
+
completion_table_lateral = completion_table_well[
|
|
91
|
+
completion_table_well[Headers.BRANCH] == lateral.lateral_number
|
|
92
|
+
]
|
|
93
|
+
df_completion_segments = prepare_outputs.prepare_completion_segments(
|
|
94
|
+
well.well_name,
|
|
95
|
+
lateral.lateral_number,
|
|
96
|
+
well.df_reservoir_all_laterals,
|
|
97
|
+
lateral.df_device,
|
|
98
|
+
df_annulus,
|
|
99
|
+
completion_table_lateral,
|
|
100
|
+
case.segment_length,
|
|
101
|
+
)
|
|
102
|
+
df_completion_data = prepare_outputs.prepare_completion_data(
|
|
103
|
+
well.well_name, lateral.lateral_number, well.df_reservoir_all_laterals, completion_table_lateral
|
|
104
|
+
)
|
|
105
|
+
df_valve = prepare_outputs.prepare_valve(well.well_name, lateral.df_well, lateral.df_device)
|
|
106
|
+
df_inflow_control_device = prepare_outputs.prepare_inflow_control_device(
|
|
107
|
+
well.well_name, lateral.df_well, lateral.df_device
|
|
108
|
+
)
|
|
109
|
+
df_autonomous_inflow_control_device = prepare_outputs.prepare_autonomous_inflow_control_device(
|
|
110
|
+
well.well_name, lateral.df_well, lateral.df_device
|
|
111
|
+
)
|
|
112
|
+
df_density_activated_recovery = prepare_outputs.prepare_density_activated_recovery(
|
|
113
|
+
well.well_name, lateral.df_well, lateral.df_device
|
|
114
|
+
)
|
|
115
|
+
df_autonomous_inflow_control_valve = prepare_outputs.prepare_autonomous_inflow_control_valve(
|
|
116
|
+
well.well_name, lateral.df_well, lateral.df_device
|
|
117
|
+
)
|
|
118
|
+
df_inflow_control_valve = prepare_outputs.prepare_inflow_control_valve(
|
|
119
|
+
well.well_name,
|
|
120
|
+
lateral.lateral_number,
|
|
121
|
+
well.df_well_all_laterals,
|
|
122
|
+
lateral.df_device,
|
|
123
|
+
lateral.df_tubing,
|
|
124
|
+
case.completion_icv_tubing,
|
|
125
|
+
case.wsegicv_table,
|
|
126
|
+
)
|
|
127
|
+
completion_data_list.append(
|
|
128
|
+
_format_completion_data(well.well_name, lateral.lateral_number, df_completion_data, first)
|
|
129
|
+
)
|
|
130
|
+
print_well_segments += _format_well_segments(
|
|
131
|
+
well.well_name, lateral.lateral_number, lateral.df_tubing, lateral.df_device, df_annulus, first
|
|
132
|
+
)
|
|
133
|
+
print_well_segments_link += _format_well_segments_link(
|
|
134
|
+
well.well_name, lateral.lateral_number, df_well_segments_link, first
|
|
135
|
+
)
|
|
136
|
+
print_completion_segments += _format_completion_segments(
|
|
137
|
+
well.well_name, lateral.lateral_number, df_completion_segments, first
|
|
138
|
+
)
|
|
139
|
+
print_valve += _format_valve(well.well_name, lateral.lateral_number, df_valve, first)
|
|
140
|
+
print_inflow_control_device += _format_inflow_control_device(
|
|
141
|
+
well.well_name, lateral.lateral_number, df_inflow_control_device, first
|
|
142
|
+
)
|
|
143
|
+
print_autonomous_inflow_control_device += _format_autonomous_inflow_control_device(
|
|
144
|
+
well.well_name, lateral.lateral_number, df_autonomous_inflow_control_device, first
|
|
145
|
+
)
|
|
146
|
+
print_inflow_control_valve += _format_inflow_control_valve(
|
|
147
|
+
well.well_name, lateral.lateral_number, df_inflow_control_valve, first
|
|
148
|
+
)
|
|
149
|
+
print_density_activated_recovery += _format_density_activated_recovery(
|
|
150
|
+
well.well_number, df_density_activated_recovery
|
|
151
|
+
)
|
|
152
|
+
print_autonomous_inflow_control_valve += _format_autonomous_inflow_control_valve(
|
|
153
|
+
well.well_number, df_autonomous_inflow_control_valve
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if figure_name is not None:
|
|
157
|
+
logger.info(f"Creating figure for lateral {lateral.lateral_number}.")
|
|
158
|
+
with PdfPages(figure_name) as figure:
|
|
159
|
+
figure.savefig(
|
|
160
|
+
visualize_well(
|
|
161
|
+
well.well_name, well.df_well_all_laterals, well.df_reservoir_all_laterals, case.segment_length
|
|
162
|
+
),
|
|
214
163
|
orientation="landscape",
|
|
215
164
|
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
logger.warning("No connection from reservoir to tubing in Well : %s Lateral : %d", self.well_name, lateral)
|
|
258
|
-
|
|
259
|
-
def update_segmentbranch(self) -> None:
|
|
260
|
-
"""Update the numbering of the tubing segment and branch."""
|
|
261
|
-
|
|
262
|
-
if self.df_annulus.shape[0] == 0 and self.df_device.shape[0] > 0:
|
|
263
|
-
self.start_segment = max(self.df_device[Headers.SEG].to_numpy()) + 1
|
|
264
|
-
self.start_branch = max(self.df_device[Headers.BRANCH].to_numpy()) + 1
|
|
265
|
-
elif self.df_annulus.shape[0] > 0:
|
|
266
|
-
self.start_segment = max(self.df_annulus[Headers.SEG].to_numpy()) + 1
|
|
267
|
-
self.start_branch = max(self.df_annulus[Headers.BRANCH].to_numpy()) + 1
|
|
268
|
-
|
|
269
|
-
def make_compdat(self, lateral: int) -> None:
|
|
270
|
-
"""Print completion data to file."""
|
|
271
|
-
nchar = po.get_number_of_characters(self.df_compdat)
|
|
272
|
-
if self.df_compdat.shape[0] > 0:
|
|
273
|
-
self.print_compdat += (
|
|
274
|
-
po.get_header(self.well_name, Keywords.COMPDAT, lateral, "", nchar)
|
|
275
|
-
+ po.dataframe_tostring(self.df_compdat, True)
|
|
276
|
-
+ "\n"
|
|
277
|
-
)
|
|
165
|
+
logger.info("Creating schematics: %s.pdf", figure_name)
|
|
166
|
+
first = False
|
|
167
|
+
print_completion_data = "\n".join(completion_data_list)
|
|
168
|
+
if print_well_segments:
|
|
169
|
+
print_well_segments = f"{print_well_segments}\n/\n\n"
|
|
170
|
+
if print_completion_segments:
|
|
171
|
+
print_completion_segments = (
|
|
172
|
+
f"{Keywords.COMPLETION_SEGMENTS}\n'{well.well_name}' /{print_completion_segments}\n/\n\n\n"
|
|
173
|
+
)
|
|
174
|
+
bonus = []
|
|
175
|
+
if print_well_segments_link:
|
|
176
|
+
bonus.append(f"{Keywords.WELL_SEGMENTS_LINK}{print_well_segments_link}\n/\n\n\n")
|
|
177
|
+
if print_valve:
|
|
178
|
+
bonus.append(f"{Keywords.WELL_SEGMENTS_VALVE}{print_valve}\n/\n\n\n")
|
|
179
|
+
if print_inflow_control_device:
|
|
180
|
+
bonus.append(f"{Keywords.INFLOW_CONTROL_DEVICE}{print_inflow_control_device}\n/\n\n\n")
|
|
181
|
+
if print_autonomous_inflow_control_device:
|
|
182
|
+
bonus.append(f"{Keywords.AUTONOMOUS_INFLOW_CONTROL_DEVICE}{print_autonomous_inflow_control_device}\n/\n\n\n")
|
|
183
|
+
if print_inflow_control_valve:
|
|
184
|
+
bonus.append(f"{Keywords.WELL_SEGMENTS_VALVE}{print_inflow_control_valve}\n/\n\n\n")
|
|
185
|
+
if print_density_activated_recovery:
|
|
186
|
+
metadata = (
|
|
187
|
+
f"{'-' * 100}\n"
|
|
188
|
+
"-- This is how we model DAR technology using sets of ACTIONX keywords.\n"
|
|
189
|
+
"-- The segment dP curves changes according to the segment water-\n"
|
|
190
|
+
"-- and gas volume fractions at downhole condition.\n"
|
|
191
|
+
"-- The value of Cv is adjusted according to the segment length and the number of\n"
|
|
192
|
+
"-- devices per joint. The constriction area varies according to values of\n"
|
|
193
|
+
"-- volume fractions.\n"
|
|
194
|
+
f"{'-' * 100}\n\n\n"
|
|
195
|
+
)
|
|
196
|
+
bonus.append(metadata + print_density_activated_recovery + "\n\n\n\n")
|
|
197
|
+
if print_autonomous_inflow_control_valve:
|
|
198
|
+
metadata = (
|
|
199
|
+
f"{'-' * 100}\n"
|
|
200
|
+
"-- This is how we model AICV technology using sets of ACTIONX keyword\n"
|
|
201
|
+
"-- the DP parameters change according to the segment water cut (at downhole condition )\n"
|
|
202
|
+
"-- and gas volume fraction (at downhole condition)\n"
|
|
203
|
+
f"{'-' * 100}\n\n\n"
|
|
204
|
+
)
|
|
205
|
+
bonus.append(metadata + print_autonomous_inflow_control_valve + "\n\n\n\n")
|
|
278
206
|
|
|
279
|
-
|
|
280
|
-
"""Print well segments to file."""
|
|
281
|
-
nchar = po.get_number_of_characters(self.df_tubing)
|
|
282
|
-
if self.df_device.shape[0] > 0:
|
|
283
|
-
self.print_welsegs += (
|
|
284
|
-
po.get_header(self.well_name, Keywords.WELSEGS, lateral, "Tubing", nchar)
|
|
285
|
-
+ po.dataframe_tostring(self.df_tubing, True)
|
|
286
|
-
+ "\n"
|
|
287
|
-
)
|
|
288
|
-
if self.df_device.shape[0] > 0:
|
|
289
|
-
nchar = po.get_number_of_characters(self.df_tubing)
|
|
290
|
-
self.print_welsegs += (
|
|
291
|
-
po.get_header(self.well_name, Keywords.WELSEGS, lateral, "Device", nchar)
|
|
292
|
-
+ po.dataframe_tostring(self.df_device, True)
|
|
293
|
-
+ "\n"
|
|
294
|
-
)
|
|
295
|
-
if self.df_annulus.shape[0] > 0:
|
|
296
|
-
nchar = po.get_number_of_characters(self.df_tubing)
|
|
297
|
-
self.print_welsegs += (
|
|
298
|
-
po.get_header(self.well_name, Keywords.WELSEGS, lateral, "Annulus", nchar)
|
|
299
|
-
+ po.dataframe_tostring(self.df_annulus, True)
|
|
300
|
-
+ "\n"
|
|
301
|
-
)
|
|
207
|
+
return print_completion_data, print_well_segments, print_completion_segments, "".join(bonus)
|
|
302
208
|
|
|
303
|
-
def make_wseglink(self, lateral: int) -> None:
|
|
304
|
-
"""Print WSEGLINK to file."""
|
|
305
|
-
if self.df_wseglink.shape[0] > 0:
|
|
306
|
-
nchar = po.get_number_of_characters(self.df_wseglink)
|
|
307
|
-
self.print_wseglink += (
|
|
308
|
-
po.get_header(self.well_name, Keywords.WSEGLINK, lateral, "", nchar)
|
|
309
|
-
+ po.dataframe_tostring(self.df_wseglink, True)
|
|
310
|
-
+ "\n"
|
|
311
|
-
)
|
|
312
209
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
nchar = po.get_number_of_characters(self.df_compsegs)
|
|
316
|
-
if self.df_compsegs.shape[0] > 0:
|
|
317
|
-
self.print_compsegs += (
|
|
318
|
-
po.get_header(self.well_name, Keywords.COMPSEGS, lateral, "", nchar)
|
|
319
|
-
+ po.dataframe_tostring(self.df_compsegs, True)
|
|
320
|
-
+ "\n"
|
|
321
|
-
)
|
|
210
|
+
def _check_well_segments_header(welsegs_header: pd.DataFrame, start_measured_depths: pd.Series) -> pd.DataFrame:
|
|
211
|
+
"""Check whether the measured depth of the first segment is deeper than the first cells start measured depth.
|
|
322
212
|
|
|
323
|
-
|
|
324
|
-
"""Print WSEGAICD to file."""
|
|
325
|
-
if self.df_wsegaicd.shape[0] > 0:
|
|
326
|
-
nchar = po.get_number_of_characters(self.df_wsegaicd)
|
|
327
|
-
self.print_wsegaicd += (
|
|
328
|
-
po.get_header(self.well_name, Keywords.WSEGAICD, lateral, "", nchar)
|
|
329
|
-
+ po.dataframe_tostring(self.df_wsegaicd, True)
|
|
330
|
-
+ "\n"
|
|
331
|
-
)
|
|
213
|
+
In this case, adjust segments measured depth to be 1 meter shallower.
|
|
332
214
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
nchar = po.get_number_of_characters(self.df_wsegsicd)
|
|
337
|
-
self.print_wsegsicd += (
|
|
338
|
-
po.get_header(self.well_name, Keywords.WSEGSICD, lateral, "", nchar)
|
|
339
|
-
+ po.dataframe_tostring(self.df_wsegsicd, True)
|
|
340
|
-
+ "\n"
|
|
341
|
-
)
|
|
215
|
+
Args:
|
|
216
|
+
welsegs_header: The header for well segments.
|
|
217
|
+
start_measured_depths: The measured depths of the first cells from the reservoir.
|
|
342
218
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
+ po.dataframe_tostring(self.df_wsegvalv, True)
|
|
350
|
-
+ "\n"
|
|
351
|
-
)
|
|
219
|
+
Returns:
|
|
220
|
+
Corrected measured depths if well segments header.
|
|
221
|
+
"""
|
|
222
|
+
if welsegs_header[Headers.MEASURED_DEPTH].iloc[0] > start_measured_depths:
|
|
223
|
+
welsegs_header[Headers.MEASURED_DEPTH] = start_measured_depths - 1.0
|
|
224
|
+
return welsegs_header
|
|
352
225
|
|
|
353
|
-
def make_wsegicv(self, lateral: int) -> None:
|
|
354
|
-
"""Print WSEGICV to file."""
|
|
355
|
-
if self.df_wsegicv.shape[0] > 0:
|
|
356
|
-
nchar = po.get_number_of_characters(self.df_wsegicv)
|
|
357
|
-
self.print_wsegicv += (
|
|
358
|
-
po.get_header(self.well_name, Keywords.WSEGVALV, lateral, "", nchar)
|
|
359
|
-
+ po.dataframe_tostring(self.df_wsegicv, True)
|
|
360
|
-
+ "\n"
|
|
361
|
-
)
|
|
362
226
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
+ self.print_wsegsicd
|
|
441
|
-
+ self.print_wsegaicd
|
|
442
|
-
+ self.print_wsegdar
|
|
443
|
-
+ self.print_wsegaicv
|
|
444
|
-
+ self.print_wsegicv
|
|
227
|
+
def _update_segmentbranch(df_device: pd.DataFrame, df_annulus: pd.DataFrame) -> tuple[int, int]:
|
|
228
|
+
"""Update the numbering of the tubing segment and branch.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
df_device: Device data.
|
|
232
|
+
df_annulus: Annulus data.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The numbers for starting segment and branch.
|
|
236
|
+
|
|
237
|
+
Raises:
|
|
238
|
+
ValueError: If there is neither device nor annulus data.
|
|
239
|
+
"""
|
|
240
|
+
if df_annulus.empty and df_device.empty:
|
|
241
|
+
raise ValueError("Cannot determine starting segment and branch without device and annulus data.")
|
|
242
|
+
if df_annulus.empty and not df_device.empty:
|
|
243
|
+
start_segment = max(df_device[Headers.START_SEGMENT_NUMBER].to_numpy()) + 1
|
|
244
|
+
start_branch = max(df_device[Headers.BRANCH].to_numpy()) + 1
|
|
245
|
+
else:
|
|
246
|
+
start_segment = max(df_annulus[Headers.START_SEGMENT_NUMBER].to_numpy()) + 1
|
|
247
|
+
start_branch = max(df_annulus[Headers.BRANCH].to_numpy()) + 1
|
|
248
|
+
return start_segment, start_branch
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _format_completion_data(well_name: str, lateral_number: int, df_compdat: pd.DataFrame, first: bool) -> str:
|
|
252
|
+
"""Print completion data to file.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
well_name: Name of well.
|
|
256
|
+
lateral_number: Current laterals number.
|
|
257
|
+
df_compdat: Completion data.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Formatted string.
|
|
261
|
+
"""
|
|
262
|
+
if df_compdat.empty:
|
|
263
|
+
return ""
|
|
264
|
+
nchar = prepare_outputs.get_number_of_characters(df_compdat)
|
|
265
|
+
result = prepare_outputs.get_header(well_name, Keywords.COMPLETION_DATA, lateral_number, "", nchar)
|
|
266
|
+
result += prepare_outputs.dataframe_tostring(df_compdat, True, keep_header=first)
|
|
267
|
+
return result
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _format_well_segments(
|
|
271
|
+
well_name: str,
|
|
272
|
+
lateral_number: int,
|
|
273
|
+
df_tubing: pd.DataFrame,
|
|
274
|
+
df_device: pd.DataFrame,
|
|
275
|
+
df_annulus: pd.DataFrame,
|
|
276
|
+
header: bool,
|
|
277
|
+
) -> str:
|
|
278
|
+
"""Print well segments to file.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
well_name: Name of well.
|
|
282
|
+
lateral_number: Current lateral number.
|
|
283
|
+
df_tubing: Tubing data.
|
|
284
|
+
df_device: Device data.
|
|
285
|
+
df_annulus: Annulus data.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Formatted string.
|
|
289
|
+
"""
|
|
290
|
+
print_welsegs = ""
|
|
291
|
+
nchar = prepare_outputs.get_number_of_characters(df_tubing)
|
|
292
|
+
if not df_device.empty:
|
|
293
|
+
# Tubing layer.
|
|
294
|
+
print_welsegs += (
|
|
295
|
+
"\n"
|
|
296
|
+
+ prepare_outputs.get_header(well_name, Keywords.WELL_SEGMENTS, lateral_number, "Tubing", nchar)
|
|
297
|
+
+ prepare_outputs.dataframe_tostring(df_tubing, True, keep_header=header)
|
|
298
|
+
)
|
|
299
|
+
# Device layer.
|
|
300
|
+
print_welsegs += (
|
|
301
|
+
"\n"
|
|
302
|
+
+ prepare_outputs.get_header(well_name, Keywords.WELL_SEGMENTS, lateral_number, "Device", nchar)
|
|
303
|
+
+ prepare_outputs.dataframe_tostring(df_device, True, keep_header=False)
|
|
445
304
|
)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
305
|
+
if not df_annulus.empty:
|
|
306
|
+
# Annulus layer.
|
|
307
|
+
print_welsegs += (
|
|
308
|
+
"\n"
|
|
309
|
+
+ prepare_outputs.get_header(well_name, Keywords.WELL_SEGMENTS, lateral_number, "Annulus", nchar)
|
|
310
|
+
+ prepare_outputs.dataframe_tostring(df_annulus, True, keep_header=False)
|
|
311
|
+
)
|
|
312
|
+
return print_welsegs
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _format_well_segments_link(
|
|
316
|
+
well_name: str, lateral_number: int, df_well_segments_link: pd.DataFrame, header: bool
|
|
317
|
+
) -> str:
|
|
318
|
+
"""Formats well-segments for links.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
well_name: Name of well.
|
|
322
|
+
lateral_number: Current lateral number.
|
|
323
|
+
df_well_segments_link: Well-segmentation data with links.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Formatted string.
|
|
327
|
+
"""
|
|
328
|
+
if df_well_segments_link.empty:
|
|
329
|
+
return ""
|
|
330
|
+
nchar = prepare_outputs.get_number_of_characters(df_well_segments_link)
|
|
331
|
+
return (
|
|
332
|
+
"\n"
|
|
333
|
+
+ prepare_outputs.get_header(well_name, Keywords.WELL_SEGMENTS_LINK, lateral_number, "", nchar)
|
|
334
|
+
+ prepare_outputs.dataframe_tostring(df_well_segments_link, True, keep_header=header)
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _format_completion_segments(well_name: str, lateral_number: int, df_compsegs: pd.DataFrame, header: bool) -> str:
|
|
339
|
+
"""Formats completion segments.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
well_name: Name of well.
|
|
343
|
+
lateral_number: Current lateral number.
|
|
344
|
+
df_compsegs: Completion data.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Formatted string.
|
|
348
|
+
"""
|
|
349
|
+
if df_compsegs.empty:
|
|
350
|
+
return ""
|
|
351
|
+
nchar = prepare_outputs.get_number_of_characters(df_compsegs)
|
|
352
|
+
return (
|
|
353
|
+
"\n"
|
|
354
|
+
+ prepare_outputs.get_header(well_name, Keywords.COMPLETION_SEGMENTS, lateral_number, "", nchar)
|
|
355
|
+
+ prepare_outputs.dataframe_tostring(df_compsegs, True, keep_header=header)
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _format_autonomous_inflow_control_device(
|
|
360
|
+
well_name: str, lateral_number: int, df_wsegaicd: pd.DataFrame, header: bool
|
|
361
|
+
) -> str:
|
|
362
|
+
"""Formats well-segments for autonomous inflow control devices.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
well_name: Name of well.
|
|
366
|
+
lateral_number: Current lateral number.
|
|
367
|
+
df_wsegaicd: Well-segments data for autonomous inflow control devices.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Formatted string.
|
|
371
|
+
"""
|
|
372
|
+
if df_wsegaicd.empty:
|
|
373
|
+
return ""
|
|
374
|
+
nchar = prepare_outputs.get_number_of_characters(df_wsegaicd)
|
|
375
|
+
return (
|
|
376
|
+
"\n"
|
|
377
|
+
+ prepare_outputs.get_header(well_name, Keywords.INFLOW_CONTROL_DEVICE, lateral_number, "", nchar)
|
|
378
|
+
+ prepare_outputs.dataframe_tostring(df_wsegaicd, True, keep_header=header)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _format_inflow_control_device(well_name: str, lateral_number: int, df_wsegsicd: pd.DataFrame, header: bool) -> str:
|
|
383
|
+
"""Formats well-segments for inflow control devices.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
well_name: Name of well.
|
|
387
|
+
lateral_number: Current lateral number.
|
|
388
|
+
df_wsegsicd: Well-segment data for inflow control devices.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Formatted string.
|
|
392
|
+
"""
|
|
393
|
+
if df_wsegsicd.empty:
|
|
394
|
+
return ""
|
|
395
|
+
nchar = prepare_outputs.get_number_of_characters(df_wsegsicd)
|
|
396
|
+
return (
|
|
397
|
+
"\n"
|
|
398
|
+
+ prepare_outputs.get_header(well_name, Keywords.INFLOW_CONTROL_DEVICE, lateral_number, "", nchar)
|
|
399
|
+
+ prepare_outputs.dataframe_tostring(df_wsegsicd, True, keep_header=header)
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _format_valve(well_name: str, lateral_number: int, df_wsegvalv, header: bool) -> str:
|
|
404
|
+
"""Formats well-segments for valves.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
well_name: Name of well.
|
|
408
|
+
lateral_number: Current lateral number.
|
|
409
|
+
df_wsegvalv: Well-segment data for valves.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Formatted string.
|
|
413
|
+
"""
|
|
414
|
+
if df_wsegvalv.empty:
|
|
415
|
+
return ""
|
|
416
|
+
nchar = prepare_outputs.get_number_of_characters(df_wsegvalv)
|
|
417
|
+
return (
|
|
418
|
+
"\n"
|
|
419
|
+
+ prepare_outputs.get_header(well_name, Keywords.WELL_SEGMENTS_VALVE, lateral_number, "", nchar)
|
|
420
|
+
+ prepare_outputs.dataframe_tostring(df_wsegvalv, True, keep_header=header)
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _format_inflow_control_valve(well_name: str, lateral_number: int, df_wsegicv: pd.DataFrame, header: bool) -> str:
|
|
425
|
+
"""Formats well-segments for inflow control valve.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
well_name: Name of well.
|
|
429
|
+
lateral_number: Current lateral number.
|
|
430
|
+
df_wsegicv: Well-segment data for inflow control valves.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Formatted string.
|
|
434
|
+
"""
|
|
435
|
+
if df_wsegicv.empty:
|
|
436
|
+
return ""
|
|
437
|
+
nchar = prepare_outputs.get_number_of_characters(df_wsegicv)
|
|
438
|
+
return (
|
|
439
|
+
"\n"
|
|
440
|
+
+ prepare_outputs.get_header(well_name, Keywords.WELL_SEGMENTS_VALVE, lateral_number, "", nchar)
|
|
441
|
+
+ prepare_outputs.dataframe_tostring(df_wsegicv, True, keep_header=header)
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _format_density_activated_recovery(well_number: int, df_wsegdar: pd.DataFrame) -> str:
|
|
446
|
+
"""Formats well-segments for density activated recovery valve.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
well_number: The well's number
|
|
450
|
+
df_wsegdar: Data to print.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Formatted string.
|
|
454
|
+
"""
|
|
455
|
+
if df_wsegdar.empty:
|
|
456
|
+
return ""
|
|
457
|
+
return prepare_outputs.print_wsegdar(df_wsegdar, well_number + 1)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _format_autonomous_inflow_control_valve(well_number: int, df_wsegaicv: pd.DataFrame) -> str:
|
|
461
|
+
"""Formats the AICV section.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
well_number: The well's number
|
|
465
|
+
df_wsegaicv: Data to print.
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
Formatted string.
|
|
469
|
+
"""
|
|
470
|
+
if df_wsegaicv.empty:
|
|
471
|
+
return ""
|
|
472
|
+
return prepare_outputs.print_wsegaicv(df_wsegaicv, well_number + 1)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _branch_revision(
|
|
476
|
+
lateral_number: int,
|
|
477
|
+
active_laterals: list[int] | npt.NDArray[np.int64],
|
|
478
|
+
df_device: pd.DataFrame,
|
|
479
|
+
df_annulus: pd.DataFrame,
|
|
480
|
+
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
481
|
+
"""Revises the order of branch numbers to be in agreement with common practice.
|
|
482
|
+
|
|
483
|
+
This means that tubing layers will get branch numbers from 1 to the number of laterals.
|
|
484
|
+
Device and lateral branch numbers are changed accordingly if they exist.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
lateral_number: The lateral number being worked on.
|
|
488
|
+
active_laterals: List of active lateral numbers.
|
|
489
|
+
df_device: Dataframe containing device data.
|
|
490
|
+
df_annulus: Dataframe containing annular data.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Corrected device.
|
|
494
|
+
"""
|
|
495
|
+
correction = max(active_laterals) - lateral_number
|
|
496
|
+
if df_device.get(Headers.BRANCH) is not None:
|
|
497
|
+
df_device[Headers.BRANCH] += correction
|
|
498
|
+
if df_annulus.get(Headers.BRANCH) is not None:
|
|
499
|
+
df_annulus[Headers.BRANCH] += correction
|
|
500
|
+
return df_device, df_annulus
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def _connect_lateral(
|
|
504
|
+
well_name: str, lateral: Lateral, top: pd.DataFrame, well: Well, case: ReadCasefile
|
|
505
|
+
) -> pd.DataFrame:
|
|
506
|
+
"""Connect lateral to main wellbore/branch.
|
|
507
|
+
|
|
508
|
+
The main branch can either have a tubing- or device-layer connected.
|
|
509
|
+
By default, the lateral will be connected to tubing-layer, but if connect_to_tubing is False,
|
|
510
|
+
it will be connected to device-layer.
|
|
511
|
+
Abort if it cannot find device layer at junction depth.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
well_name: Well name.
|
|
515
|
+
lateral: Current lateral to connect.
|
|
516
|
+
top: DataFrame of first connection.
|
|
517
|
+
well: Well object containing data from whole well.
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
Tubing data with modified outsegment.
|
|
521
|
+
|
|
522
|
+
Raises:
|
|
523
|
+
CompletorError: If there is no device layer at junction of lateral.
|
|
524
|
+
"""
|
|
525
|
+
if top.empty:
|
|
526
|
+
lateral.df_tubing.at[0, Headers.OUT] = 1 # Default out segment.
|
|
527
|
+
return lateral.df_tubing
|
|
528
|
+
|
|
529
|
+
first_lateral_in_top = top[Headers.TUBING_BRANCH].to_numpy()[0]
|
|
530
|
+
top_lateral = [lateral for lateral in well.active_laterals if lateral.lateral_number == first_lateral_in_top][0]
|
|
531
|
+
junction_measured_depth = float(top[Headers.TUBING_MEASURED_DEPTH].to_numpy()[0])
|
|
532
|
+
if junction_measured_depth > lateral.df_tubing[Headers.MEASURED_DEPTH][0]:
|
|
533
|
+
logger.warning(
|
|
534
|
+
"Found a junction above the start of the tubing layer, well %s, branch %s. "
|
|
535
|
+
"Check the depth of segments pointing at the main stem in schedulefile.",
|
|
536
|
+
well_name,
|
|
537
|
+
lateral.lateral_number,
|
|
538
|
+
)
|
|
539
|
+
if case.connect_to_tubing(well_name, lateral.lateral_number):
|
|
540
|
+
layer_to_connect = top_lateral.df_tubing
|
|
541
|
+
measured_depths = top_lateral.df_tubing[Headers.MEASURED_DEPTH]
|
|
542
|
+
else:
|
|
543
|
+
layer_to_connect = top_lateral.df_device
|
|
544
|
+
measured_depths = top_lateral.df_device[Headers.MEASURED_DEPTH]
|
|
545
|
+
try:
|
|
546
|
+
if case.connect_to_tubing(well_name, lateral.lateral_number):
|
|
547
|
+
# Since the junction_measured_depth has segment tops and layer_to_connect has grid block midpoints,
|
|
548
|
+
# a junction at the top of the well may not be found. Therefore, we try the following:
|
|
549
|
+
if (np.array(~(measured_depths <= junction_measured_depth))).all():
|
|
550
|
+
junction_measured_depth = measured_depths.iloc[0]
|
|
551
|
+
idx = np.where(measured_depths <= junction_measured_depth)[0][-1]
|
|
552
|
+
|
|
553
|
+
else:
|
|
554
|
+
idx = np.where(measured_depths <= junction_measured_depth)[0][-1]
|
|
555
|
+
else:
|
|
556
|
+
# Add 0.1 to junction measured depth since it refers to the tubing layer junction measured depth,
|
|
557
|
+
# and the device layer measured depth is shifted 0.1 m to the tubing layer.
|
|
558
|
+
idx = np.where(measured_depths <= junction_measured_depth + 0.1)[0][-1]
|
|
559
|
+
except IndexError as err:
|
|
560
|
+
raise CompletorError(
|
|
561
|
+
f"Cannot find a device layer at junction of lateral {lateral.lateral_number} in {well_name}"
|
|
562
|
+
) from err
|
|
563
|
+
out_segment = layer_to_connect.at[idx, Headers.START_SEGMENT_NUMBER]
|
|
564
|
+
lateral.df_tubing.at[0, Headers.OUT] = out_segment
|
|
565
|
+
return lateral.df_tubing
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def metadata_banner(paths: tuple[str, str] | None) -> str:
|
|
569
|
+
"""Formats the header banner, with metadata.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
paths: The paths to case and schedule files.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
Formatted header.
|
|
576
|
+
"""
|
|
577
|
+
header = f"{'-' * 100}\n-- Output from Completor {get_version()}\n"
|
|
578
|
+
|
|
579
|
+
if paths is not None:
|
|
580
|
+
header += f"-- Case file: {paths[0]}\n-- Schedule file: {paths[1]}\n"
|
|
581
|
+
else:
|
|
582
|
+
logger.warning("Could not resolve case-file path to output file.")
|
|
583
|
+
header += "-- Case file: No path found\n-- Schedule file: No path found\n"
|
|
584
|
+
|
|
585
|
+
header += (
|
|
586
|
+
f"-- Created by : {(getpass.getuser()).upper()}\n"
|
|
587
|
+
f"-- Created at : {datetime.now().strftime('%Y %B %d %H:%M')}\n"
|
|
588
|
+
f"{'-' * 100}\n\n"
|
|
589
|
+
)
|
|
590
|
+
return header
|