completor 0.1.2__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/__init__.py +3 -0
- completor/completion.py +1042 -0
- completor/config_jobs/run_completor +18 -0
- completor/constants.py +256 -0
- completor/create_output.py +462 -0
- completor/create_wells.py +314 -0
- completor/exceptions/__init__.py +4 -0
- completor/exceptions/clean_exceptions.py +10 -0
- completor/exceptions/exceptions.py +134 -0
- completor/hook_implementations/jobs.py +63 -0
- completor/input_validation.py +340 -0
- completor/launch_args_parser.py +41 -0
- completor/logger.py +138 -0
- completor/main.py +486 -0
- completor/parse.py +581 -0
- completor/prepare_outputs.py +1473 -0
- completor/pvt_model.py +14 -0
- completor/read_casefile.py +677 -0
- completor/read_schedule.py +160 -0
- completor/utils.py +185 -0
- completor/visualization.py +164 -0
- completor/visualize_well.py +217 -0
- completor-0.1.2.dist-info/LICENSE +165 -0
- completor-0.1.2.dist-info/METADATA +205 -0
- completor-0.1.2.dist-info/RECORD +27 -0
- completor-0.1.2.dist-info/WHEEL +4 -0
- completor-0.1.2.dist-info/entry_points.txt +6 -0
completor/parse.py
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
"""Functions for reading files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from typing import Literal, overload
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from completor.constants import Headers, Keywords
|
|
14
|
+
from completor.exceptions.clean_exceptions import CompletorError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContentCollection(list):
|
|
18
|
+
"""A subclass of list that can accept additional attributes. To be used like a regular list."""
|
|
19
|
+
|
|
20
|
+
def __new__(cls, *args, **kwargs):
|
|
21
|
+
"""Override new method of list."""
|
|
22
|
+
return super().__new__(cls, args, kwargs)
|
|
23
|
+
|
|
24
|
+
def __init__(self, *args, name: str, well: pd.DataFrame | str | None = None):
|
|
25
|
+
"""Override init method of list."""
|
|
26
|
+
if len(args) == 1 and hasattr(args[0], "__iter__"):
|
|
27
|
+
list.__init__(self, args[0])
|
|
28
|
+
else:
|
|
29
|
+
list.__init__(self, args)
|
|
30
|
+
self.name = name
|
|
31
|
+
self.well = well
|
|
32
|
+
|
|
33
|
+
def __call__(self, **kwargs):
|
|
34
|
+
"""Override call method of list."""
|
|
35
|
+
self.__dict__.update(kwargs)
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def locate_keyword(
|
|
41
|
+
content: list[str], keyword: str, end_char: str = ..., take_first: Literal[True] = ...
|
|
42
|
+
) -> tuple[int, int]: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def locate_keyword(
|
|
47
|
+
content: list[str], keyword: str, end_char: str = ..., *, take_first: Literal[False]
|
|
48
|
+
) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@overload
|
|
52
|
+
def locate_keyword(
|
|
53
|
+
content: list[str], keyword: str, end_char: str, take_first: Literal[False]
|
|
54
|
+
) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]: ...
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def locate_keyword(
|
|
58
|
+
content: list[str], keyword: str, end_char: str = "/", take_first: bool = True
|
|
59
|
+
) -> tuple[int, int] | tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]:
|
|
60
|
+
"""Find the start and end of a keyword.
|
|
61
|
+
|
|
62
|
+
The start of the keyword is the keyword itself.
|
|
63
|
+
The end of the keyword is end_char if specified.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
content: List of strings.
|
|
67
|
+
keyword: Keyword name.
|
|
68
|
+
end_char: String which ends the keyword. Defaults to '/' Because it's the most used in this code base.
|
|
69
|
+
take_first: Flag to toggle whether to return the first elements of the arrays.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
start_index - array that located the keyword (or its first element).
|
|
73
|
+
end_index - array that locates the end of the keyword (or its first element).
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
CompletorError: If keyword had no end record.
|
|
77
|
+
ValueError: If keyword cannot be found in case file.
|
|
78
|
+
"""
|
|
79
|
+
content_length = len(content)
|
|
80
|
+
start_index: npt.NDArray[np.int64] = np.where(np.asarray(content) == keyword)[0]
|
|
81
|
+
if start_index.size == 0:
|
|
82
|
+
# the keyword is not found
|
|
83
|
+
return np.asarray([-1]), np.asarray([-1])
|
|
84
|
+
|
|
85
|
+
end_index: npt.NDArray[np.int64] = np.array([], dtype="int64")
|
|
86
|
+
idx = 0
|
|
87
|
+
for istart in start_index:
|
|
88
|
+
if end_char != "":
|
|
89
|
+
idx = istart + 1
|
|
90
|
+
for idx in range(istart + 1, content_length):
|
|
91
|
+
if content[idx] == end_char:
|
|
92
|
+
break
|
|
93
|
+
if (idx == content_length - 1) and content[idx] != end_char:
|
|
94
|
+
# error if until the last line the end char is not found
|
|
95
|
+
raise CompletorError(f"Keyword {keyword} has no end record")
|
|
96
|
+
else:
|
|
97
|
+
# if there is no end character is specified, then the end of a record is the next keyword or end of line
|
|
98
|
+
for idx in range(istart + 1, content_length):
|
|
99
|
+
first_char = content[idx][0]
|
|
100
|
+
if first_char.isalpha():
|
|
101
|
+
# end is before the new keyword
|
|
102
|
+
idx -= 1
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
end_index = np.append(end_index, idx)
|
|
107
|
+
except NameError as err:
|
|
108
|
+
raise ValueError(f"Cannot find keyword {keyword} in file") from err
|
|
109
|
+
# return all in a numpy array format
|
|
110
|
+
end_index = np.asarray(end_index)
|
|
111
|
+
if take_first:
|
|
112
|
+
return start_index[0], end_index[0]
|
|
113
|
+
return start_index, end_index
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def take_first_record(
|
|
117
|
+
start_index: list[float] | npt.NDArray[np.float64], end_index: list[float] | npt.NDArray[np.float64]
|
|
118
|
+
) -> tuple[float | int, float | int]:
|
|
119
|
+
"""Take the first record of a list.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
start_index:
|
|
123
|
+
end_index:
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Tuple of floats.
|
|
127
|
+
"""
|
|
128
|
+
return start_index[0], end_index[0]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def unpack_records(record: list[str]) -> list[str]:
|
|
132
|
+
"""Unpack the keyword content.
|
|
133
|
+
|
|
134
|
+
E.g. 3* --> 1* 1* 1*
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
record: List of strings.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Updated record of strings.
|
|
141
|
+
"""
|
|
142
|
+
record = deepcopy(record)
|
|
143
|
+
record_length = len(record)
|
|
144
|
+
i = -1
|
|
145
|
+
while i < record_length - 1:
|
|
146
|
+
# Loop and find if default records are found
|
|
147
|
+
i += 1
|
|
148
|
+
if "*" in str(record[i]):
|
|
149
|
+
# default is found and get the number before the star *
|
|
150
|
+
ndefaults = re.search(r"\d+", record[i])
|
|
151
|
+
record[i] = "1*"
|
|
152
|
+
if ndefaults:
|
|
153
|
+
_ndefaults = int(ndefaults.group())
|
|
154
|
+
idef = 0
|
|
155
|
+
while idef < _ndefaults - 1:
|
|
156
|
+
record.insert(i, "1*")
|
|
157
|
+
idef += 1
|
|
158
|
+
record_length = len(record)
|
|
159
|
+
return record
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def complete_records(record: list[str], keyword: str) -> list[str]:
|
|
163
|
+
"""Complete the record.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
record: List of strings.
|
|
167
|
+
keyword: Keyword name.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Completed list of strings.
|
|
171
|
+
"""
|
|
172
|
+
if keyword == Keywords.WSEGVALV:
|
|
173
|
+
return complete_wsegvalv_record(record)
|
|
174
|
+
|
|
175
|
+
dict_ncolumns = {
|
|
176
|
+
Keywords.WELSPECS: 17,
|
|
177
|
+
Keywords.COMPDAT: 14,
|
|
178
|
+
Keywords.WELSEGS_H: 12,
|
|
179
|
+
Keywords.WELSEGS: 15,
|
|
180
|
+
Keywords.COMPSEGS: 11,
|
|
181
|
+
}
|
|
182
|
+
max_column = dict_ncolumns[keyword]
|
|
183
|
+
ncolumn = len(record)
|
|
184
|
+
if ncolumn < max_column:
|
|
185
|
+
extension = ["1*"] * (max_column - ncolumn)
|
|
186
|
+
record.extend(extension)
|
|
187
|
+
elif ncolumn > max_column:
|
|
188
|
+
record = record[:max_column]
|
|
189
|
+
return record
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def complete_wsegvalv_record(record: list[str]) -> list[str]:
|
|
193
|
+
"""Complete the WSEGVALV record.
|
|
194
|
+
|
|
195
|
+
Columns DEFAULT_1 - DEFAULT_4, STATE and AC_MAX might not be provided and need to be filled in with default values.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
record: List of strings.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Completed list of strings.
|
|
202
|
+
"""
|
|
203
|
+
WSEGVALV_COLUMNS = 10
|
|
204
|
+
AC_INDEX = 3
|
|
205
|
+
DEFAULT_STATE = "OPEN"
|
|
206
|
+
|
|
207
|
+
if len(record) < 8:
|
|
208
|
+
# add defaults
|
|
209
|
+
record.extend(["1*"] * (8 - len(record)))
|
|
210
|
+
|
|
211
|
+
if len(record) < 9:
|
|
212
|
+
# append default state
|
|
213
|
+
record.append(DEFAULT_STATE)
|
|
214
|
+
|
|
215
|
+
if len(record) < WSEGVALV_COLUMNS:
|
|
216
|
+
# append default ac_max
|
|
217
|
+
record.append(record[AC_INDEX])
|
|
218
|
+
|
|
219
|
+
if len(record) > WSEGVALV_COLUMNS:
|
|
220
|
+
record = record[:WSEGVALV_COLUMNS]
|
|
221
|
+
|
|
222
|
+
return record
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def read_schedule_keywords(
|
|
226
|
+
content: list[str], keywords: list[str], optional_keywords: list[str] = []
|
|
227
|
+
) -> tuple[list[ContentCollection], npt.NDArray[np.str_]]:
|
|
228
|
+
"""Read schedule keywords or all keywords in table format.
|
|
229
|
+
|
|
230
|
+
E.g. WELSPECS, COMPDAT, WELSEGS, COMPSEGS, WSEGVALV.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
content: List of strings. Lines from the schedule file.
|
|
234
|
+
keywords: List of keywords to find data for.
|
|
235
|
+
optional_keywords: List of optional keywords. Will not raise error if not found.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
df_collection - Object collection (pd.DataFrame).
|
|
239
|
+
remaining_content - List of strings of un-listed keywords.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
CompletorError: If keyword is not found.
|
|
243
|
+
"""
|
|
244
|
+
content = deepcopy(content)
|
|
245
|
+
used_index = np.asarray([-1])
|
|
246
|
+
collections = []
|
|
247
|
+
# get the contents correspond to the list_keywords
|
|
248
|
+
for keyword in keywords + optional_keywords:
|
|
249
|
+
start_index, end_index = locate_keyword(content, keyword, take_first=False)
|
|
250
|
+
if start_index[0] == end_index[0] and keyword not in optional_keywords:
|
|
251
|
+
raise CompletorError(f"Keyword {keyword} is not found")
|
|
252
|
+
for idx, start in enumerate(start_index):
|
|
253
|
+
end = end_index[idx]
|
|
254
|
+
used_index = np.append(used_index, np.arange(start, end + 1))
|
|
255
|
+
keyword_content = [_create_record(content, keyword, irec, start) for irec in range(start + 1, end)]
|
|
256
|
+
collection = ContentCollection(keyword_content, name=keyword)
|
|
257
|
+
if keyword in [Keywords.WELSEGS, Keywords.COMPSEGS]:
|
|
258
|
+
# remove string characters
|
|
259
|
+
collection.well = remove_string_characters(keyword_content[0][0])
|
|
260
|
+
collections.append(collection)
|
|
261
|
+
# get anything that is not listed in the keywords
|
|
262
|
+
# ignore the first record -1
|
|
263
|
+
used_index = used_index[1:]
|
|
264
|
+
mask = np.full(len(content), True, dtype=bool)
|
|
265
|
+
mask[used_index] = False
|
|
266
|
+
return collections, np.asarray(content)[mask]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _create_record(content: list[str], keyword: str, irec: int, start: int) -> list[str]:
|
|
270
|
+
_record = content[irec]
|
|
271
|
+
# remove / sign at the end
|
|
272
|
+
_record = list(filter(None, _record.rsplit("/", 1)))[0]
|
|
273
|
+
# split each column
|
|
274
|
+
record = list(filter(None, _record.split(" ")))
|
|
275
|
+
# unpack records
|
|
276
|
+
record = unpack_records(record)
|
|
277
|
+
# complete records
|
|
278
|
+
record = complete_records(
|
|
279
|
+
record, Keywords.WELSEGS_H if keyword == Keywords.WELSEGS and irec == start + 1 else keyword
|
|
280
|
+
)
|
|
281
|
+
return record
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def get_welsegs_table(collections: list[ContentCollection]) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
285
|
+
"""Return dataframe table of WELSEGS.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
collections: ContentCollection class.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
header_table - The header of WELSEGS.
|
|
292
|
+
record_table - The record of WELSEGS.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If collection does not contain the 'WELSEGS' keyword.
|
|
296
|
+
"""
|
|
297
|
+
header_columns = [
|
|
298
|
+
Headers.WELL,
|
|
299
|
+
Headers.SEGMENTTVD,
|
|
300
|
+
Headers.SEGMENTMD,
|
|
301
|
+
Headers.WBVOLUME,
|
|
302
|
+
Headers.INFO_TYPE,
|
|
303
|
+
Headers.PDROPCOMP,
|
|
304
|
+
Headers.MPMODEL,
|
|
305
|
+
Headers.ITEM_8,
|
|
306
|
+
Headers.ITEM_9,
|
|
307
|
+
Headers.ITEM_10,
|
|
308
|
+
Headers.ITEM_11,
|
|
309
|
+
Headers.ITEM_12,
|
|
310
|
+
]
|
|
311
|
+
content_columns = [
|
|
312
|
+
Headers.WELL,
|
|
313
|
+
Headers.TUBING_SEGMENT,
|
|
314
|
+
Headers.TUBING_SEGMENT_2,
|
|
315
|
+
Headers.TUBINGBRANCH,
|
|
316
|
+
Headers.TUBING_OUTLET,
|
|
317
|
+
Headers.TUBINGMD,
|
|
318
|
+
Headers.TUBINGTVD,
|
|
319
|
+
Headers.TUBING_INNER_DIAMETER,
|
|
320
|
+
Headers.TUBING_ROUGHNESS,
|
|
321
|
+
Headers.CROSS,
|
|
322
|
+
Headers.VSEG,
|
|
323
|
+
Headers.ITEM_11,
|
|
324
|
+
Headers.ITEM_12,
|
|
325
|
+
Headers.ITEM_13,
|
|
326
|
+
Headers.ITEM_14,
|
|
327
|
+
Headers.ITEM_15,
|
|
328
|
+
]
|
|
329
|
+
for collection in collections:
|
|
330
|
+
if collection.name == Keywords.WELSEGS:
|
|
331
|
+
header_collection = np.asarray(collection[:1])
|
|
332
|
+
record_collection = np.asarray(collection[1:])
|
|
333
|
+
# add additional well column on the second collection
|
|
334
|
+
well_column = np.full(record_collection.shape[0], collection.well)
|
|
335
|
+
record_collection = np.column_stack((well_column, record_collection))
|
|
336
|
+
try:
|
|
337
|
+
header_table: npt.NDArray[np.unicode_] | pd.DataFrame
|
|
338
|
+
record_table: npt.NDArray[np.unicode_] | pd.DataFrame
|
|
339
|
+
header_table = np.row_stack((header_table, header_collection))
|
|
340
|
+
record_table = np.row_stack((record_table, record_collection))
|
|
341
|
+
except NameError:
|
|
342
|
+
# First iteration
|
|
343
|
+
header_table = np.asarray(header_collection)
|
|
344
|
+
record_table = np.asarray(record_collection)
|
|
345
|
+
try:
|
|
346
|
+
header_table = pd.DataFrame(header_table, columns=header_columns)
|
|
347
|
+
record_table = pd.DataFrame(record_table, columns=content_columns)
|
|
348
|
+
except NameError as err:
|
|
349
|
+
raise ValueError("Collection does not contain the 'WELSEGS' keyword") from err
|
|
350
|
+
|
|
351
|
+
# replace string component " or ' in the columns
|
|
352
|
+
header_table = remove_string_characters(header_table)
|
|
353
|
+
record_table = remove_string_characters(record_table)
|
|
354
|
+
return header_table, record_table
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def get_welspecs_table(collections: list[ContentCollection]) -> pd.DataFrame:
|
|
358
|
+
"""Return dataframe table of WELSPECS.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
collections: ContentCollection class.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
WELSPECS table.
|
|
365
|
+
|
|
366
|
+
Raises:
|
|
367
|
+
ValueError: If collection does not contain the 'WELSPECS' keyword.
|
|
368
|
+
"""
|
|
369
|
+
columns = [
|
|
370
|
+
Headers.WELL,
|
|
371
|
+
Headers.GROUP,
|
|
372
|
+
Headers.I,
|
|
373
|
+
Headers.J,
|
|
374
|
+
Headers.BHP_DEPTH,
|
|
375
|
+
Headers.PHASE,
|
|
376
|
+
Headers.DR,
|
|
377
|
+
Headers.FLAG,
|
|
378
|
+
Headers.SHUT,
|
|
379
|
+
Headers.CROSS,
|
|
380
|
+
Headers.PRESSURE_TABLE,
|
|
381
|
+
Headers.DENSCAL,
|
|
382
|
+
Headers.REGION,
|
|
383
|
+
Headers.ITEM_14,
|
|
384
|
+
Headers.ITEM_15,
|
|
385
|
+
Headers.ITEM_16,
|
|
386
|
+
Headers.ITEM_17,
|
|
387
|
+
]
|
|
388
|
+
welspecs_table = None
|
|
389
|
+
for collection in collections:
|
|
390
|
+
if collection.name == Keywords.WELSPECS:
|
|
391
|
+
the_collection = np.asarray(collection)
|
|
392
|
+
if welspecs_table is None:
|
|
393
|
+
welspecs_table = np.copy(the_collection)
|
|
394
|
+
else:
|
|
395
|
+
welspecs_table = np.row_stack((welspecs_table, the_collection))
|
|
396
|
+
|
|
397
|
+
if welspecs_table is None:
|
|
398
|
+
raise ValueError("Collection does not contain the 'WELSPECS' keyword")
|
|
399
|
+
|
|
400
|
+
welspecs_table = pd.DataFrame(welspecs_table, columns=columns)
|
|
401
|
+
# replace string component " or ' in the columns
|
|
402
|
+
welspecs_table = remove_string_characters(welspecs_table)
|
|
403
|
+
return welspecs_table
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def get_compdat_table(collections: list[ContentCollection]) -> pd.DataFrame:
|
|
407
|
+
"""Return dataframe table of COMPDAT.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
collections: ContentCollection class.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
COMPDAT table.
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
ValueError: If a collection does not contain the 'COMPDAT' keyword.
|
|
417
|
+
"""
|
|
418
|
+
compdat_table = None
|
|
419
|
+
for collection in collections:
|
|
420
|
+
if collection.name == Keywords.COMPDAT:
|
|
421
|
+
the_collection = np.asarray(collection)
|
|
422
|
+
if compdat_table is None:
|
|
423
|
+
compdat_table = np.copy(the_collection)
|
|
424
|
+
else:
|
|
425
|
+
compdat_table = np.row_stack((compdat_table, the_collection))
|
|
426
|
+
if compdat_table is None:
|
|
427
|
+
raise ValueError("Collection does not contain the 'COMPDAT' keyword")
|
|
428
|
+
compdat_table = pd.DataFrame(
|
|
429
|
+
compdat_table,
|
|
430
|
+
columns=[
|
|
431
|
+
Headers.WELL,
|
|
432
|
+
Headers.I,
|
|
433
|
+
Headers.J,
|
|
434
|
+
Headers.K,
|
|
435
|
+
Headers.K2,
|
|
436
|
+
Headers.STATUS,
|
|
437
|
+
Headers.SATURATION_FUNCTION_REGION_NUMBERS,
|
|
438
|
+
Headers.CONNECTION_FACTOR,
|
|
439
|
+
Headers.DIAMETER,
|
|
440
|
+
Headers.FORAMTION_PERMEABILITY_THICKNESS,
|
|
441
|
+
Headers.SKIN,
|
|
442
|
+
Headers.DFACT,
|
|
443
|
+
Headers.COMPDAT_DIRECTION,
|
|
444
|
+
Headers.RO,
|
|
445
|
+
],
|
|
446
|
+
)
|
|
447
|
+
# replace string component " or ' in the columns
|
|
448
|
+
compdat_table = remove_string_characters(compdat_table)
|
|
449
|
+
return compdat_table
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def get_compsegs_table(collections: list[ContentCollection]) -> pd.DataFrame:
|
|
453
|
+
"""Return data frame table of COMPSEGS.
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
collections: ContentCollection class.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
COMPSEGS table.
|
|
460
|
+
|
|
461
|
+
Raises:
|
|
462
|
+
ValueError: If collection does not contain the 'COMPSEGS' keyword.
|
|
463
|
+
|
|
464
|
+
"""
|
|
465
|
+
compsegs_table = None
|
|
466
|
+
for collection in collections:
|
|
467
|
+
if collection.name == Keywords.COMPSEGS:
|
|
468
|
+
the_collection = np.asarray(collection[1:])
|
|
469
|
+
# add additional well column
|
|
470
|
+
well_column = np.full(the_collection.shape[0], collection.well)
|
|
471
|
+
the_collection = np.column_stack((well_column, the_collection))
|
|
472
|
+
if compsegs_table is None:
|
|
473
|
+
compsegs_table = np.copy(the_collection)
|
|
474
|
+
else:
|
|
475
|
+
compsegs_table = np.row_stack((compsegs_table, the_collection))
|
|
476
|
+
|
|
477
|
+
if compsegs_table is None:
|
|
478
|
+
raise ValueError("Collection does not contain the 'COMPSEGS' keyword")
|
|
479
|
+
|
|
480
|
+
compsegs_table = pd.DataFrame(
|
|
481
|
+
compsegs_table,
|
|
482
|
+
columns=[
|
|
483
|
+
Headers.WELL,
|
|
484
|
+
Headers.I,
|
|
485
|
+
Headers.J,
|
|
486
|
+
Headers.K,
|
|
487
|
+
Headers.BRANCH,
|
|
488
|
+
Headers.START_MEASURED_DEPTH,
|
|
489
|
+
Headers.END_MEASURED_DEPTH,
|
|
490
|
+
Headers.COMPSEGS_DIRECTION,
|
|
491
|
+
Headers.ENDGRID,
|
|
492
|
+
Headers.PERFDEPTH,
|
|
493
|
+
Headers.THERM,
|
|
494
|
+
Headers.SEGMENT,
|
|
495
|
+
],
|
|
496
|
+
)
|
|
497
|
+
# replace string component " or ' in the columns
|
|
498
|
+
compsegs_table = remove_string_characters(compsegs_table)
|
|
499
|
+
return compsegs_table
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def get_wsegvalv_table(collections: list[ContentCollection]) -> pd.DataFrame:
|
|
503
|
+
"""Return a dataframe of WSEGVALV.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
collections: ContentCollection class.
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
WSEGVALV table.
|
|
510
|
+
"""
|
|
511
|
+
columns = ["WELL", "SEGMENT", "CD", "AC", "DEFAULT_1", "DEFAULT_2", "DEFAULT_3", "DEFAULT_4", "STATE", "AC_MAX"]
|
|
512
|
+
|
|
513
|
+
wsegvalv_collections = [np.asarray(collection) for collection in collections if collection.name == "WSEGVALV"]
|
|
514
|
+
wsegvalv_table = np.vstack(wsegvalv_collections)
|
|
515
|
+
|
|
516
|
+
if wsegvalv_table.size == 0:
|
|
517
|
+
return pd.DataFrame(columns=columns)
|
|
518
|
+
|
|
519
|
+
wsegvalv_table = pd.DataFrame(wsegvalv_table, columns=columns)
|
|
520
|
+
wsegvalv_table = wsegvalv_table.astype(
|
|
521
|
+
{
|
|
522
|
+
"WELL": "string",
|
|
523
|
+
"SEGMENT": "int",
|
|
524
|
+
"CD": "float",
|
|
525
|
+
"AC": "float",
|
|
526
|
+
"DEFAULT_1": "string",
|
|
527
|
+
"DEFAULT_2": "string",
|
|
528
|
+
"DEFAULT_3": "string",
|
|
529
|
+
"DEFAULT_4": "string",
|
|
530
|
+
"STATE": "string",
|
|
531
|
+
"AC_MAX": "float",
|
|
532
|
+
}
|
|
533
|
+
)
|
|
534
|
+
return remove_string_characters(wsegvalv_table)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
@overload
|
|
538
|
+
def remove_string_characters(df: pd.DataFrame, columns: list[str] | None = ...) -> pd.DataFrame: ...
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
@overload
|
|
542
|
+
def remove_string_characters(df: str, columns: list[str] | None = ...) -> str: ...
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def remove_string_characters(df: pd.DataFrame | str, columns: list[str] | None = None) -> pd.DataFrame | str:
|
|
546
|
+
"""Remove string characters `"` and `'`.
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
df: DataFrame or string.
|
|
550
|
+
columns: List of column names to be checked.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
DataFrame without string characters.
|
|
554
|
+
|
|
555
|
+
Raises:
|
|
556
|
+
Exception: If an unexpected error occurred.
|
|
557
|
+
"""
|
|
558
|
+
if columns is None:
|
|
559
|
+
columns = []
|
|
560
|
+
|
|
561
|
+
def remove_quotes(item: str):
|
|
562
|
+
return item.replace("'", "").replace('"', "")
|
|
563
|
+
|
|
564
|
+
if isinstance(df, str):
|
|
565
|
+
df = remove_quotes(df)
|
|
566
|
+
elif isinstance(df, pd.DataFrame):
|
|
567
|
+
if len(columns) == 0:
|
|
568
|
+
iterator: range | list[str] = range(df.shape[1])
|
|
569
|
+
else:
|
|
570
|
+
iterator = [] if columns is None else columns
|
|
571
|
+
for column in iterator:
|
|
572
|
+
try:
|
|
573
|
+
df.iloc[:, column] = remove_quotes(df.iloc[:, column].str)
|
|
574
|
+
except ValueError:
|
|
575
|
+
df[column] = remove_quotes(df[column].str)
|
|
576
|
+
except AttributeError:
|
|
577
|
+
# Some dataframes contains numeric data, which we ignore
|
|
578
|
+
pass
|
|
579
|
+
except Exception as err:
|
|
580
|
+
raise err
|
|
581
|
+
return df
|