completor 0.1.3__py3-none-any.whl → 1.0.0__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 +152 -542
- completor/constants.py +223 -150
- completor/create_output.py +559 -431
- completor/exceptions/exceptions.py +6 -6
- completor/get_version.py +8 -0
- completor/hook_implementations/jobs.py +2 -3
- completor/input_validation.py +53 -41
- completor/launch_args_parser.py +7 -12
- completor/logger.py +3 -3
- completor/main.py +102 -360
- completor/parse.py +104 -93
- completor/prepare_outputs.py +593 -457
- completor/read_casefile.py +248 -197
- completor/read_schedule.py +317 -14
- completor/utils.py +256 -25
- completor/visualization.py +1 -14
- completor/visualize_well.py +29 -27
- completor/wells.py +273 -0
- {completor-0.1.3.dist-info → completor-1.0.0.dist-info}/METADATA +10 -11
- completor-1.0.0.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.0.dist-info}/LICENSE +0 -0
- {completor-0.1.3.dist-info → completor-1.0.0.dist-info}/WHEEL +0 -0
- {completor-0.1.3.dist-info → completor-1.0.0.dist-info}/entry_points.txt +0 -0
completor/main.py
CHANGED
|
@@ -6,165 +6,28 @@ import logging
|
|
|
6
6
|
import os
|
|
7
7
|
import re
|
|
8
8
|
import time
|
|
9
|
-
from collections.abc import Mapping
|
|
10
|
-
from typing import overload
|
|
11
9
|
|
|
12
|
-
import
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from tqdm import tqdm
|
|
13
12
|
|
|
14
|
-
import
|
|
15
|
-
from completor import
|
|
16
|
-
from completor.completion import WellSchedule
|
|
17
|
-
from completor.constants import Keywords
|
|
18
|
-
from completor.create_output import CreateOutput
|
|
19
|
-
from completor.create_wells import CreateWells
|
|
13
|
+
from completor import create_output, parse, read_schedule, utils
|
|
14
|
+
from completor.constants import Keywords, ScheduleData
|
|
20
15
|
from completor.exceptions import CompletorError
|
|
16
|
+
from completor.get_version import get_version
|
|
21
17
|
from completor.launch_args_parser import get_parser
|
|
22
18
|
from completor.logger import handle_error_messages, logger
|
|
23
19
|
from completor.read_casefile import ReadCasefile
|
|
24
|
-
from completor.utils import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class FileWriter:
|
|
34
|
-
"""Functionality for writing a new schedule file."""
|
|
35
|
-
|
|
36
|
-
def __init__(self, file: str, mapper: Mapping[str, str] | None):
|
|
37
|
-
"""Initialize the FileWriter.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
file: Name of file to be written. Does not check if it already exists.
|
|
41
|
-
mapper: A dictionary for mapping strings.
|
|
42
|
-
Typically used for mapping pre-processor reservoir modelling tools to reservoir simulator well names.
|
|
43
|
-
"""
|
|
44
|
-
self.fh = open(file, "w", encoding="utf-8") # create new output file
|
|
45
|
-
self.mapper = mapper
|
|
46
|
-
|
|
47
|
-
@overload
|
|
48
|
-
def write(self, keyword: Literal[None], content: str, chunk: bool = True, end_of_record: bool = False) -> None: ...
|
|
49
|
-
|
|
50
|
-
@overload
|
|
51
|
-
def write(
|
|
52
|
-
self, keyword: str, content: list[list[str]], chunk: Literal[True] = True, end_of_record: bool = False
|
|
53
|
-
) -> None: ...
|
|
54
|
-
|
|
55
|
-
@overload
|
|
56
|
-
def write(
|
|
57
|
-
self, keyword: str, content: list[str] | str, chunk: Literal[False] = False, end_of_record: bool = False
|
|
58
|
-
) -> None: ...
|
|
59
|
-
|
|
60
|
-
@overload
|
|
61
|
-
def write(
|
|
62
|
-
self, keyword: str, content: list[list[str]] | list[str] | str, chunk: bool = True, end_of_record: bool = False
|
|
63
|
-
) -> None: ...
|
|
64
|
-
|
|
65
|
-
def write(
|
|
66
|
-
self,
|
|
67
|
-
keyword: str | None,
|
|
68
|
-
content: list[list[str]] | list[str] | str,
|
|
69
|
-
chunk: bool = True,
|
|
70
|
-
end_of_record: bool = False,
|
|
71
|
-
) -> None:
|
|
72
|
-
"""Write the content of a keyword to the output file.
|
|
73
|
-
|
|
74
|
-
Args:
|
|
75
|
-
keyword: Reservoir simulator keyword.
|
|
76
|
-
content: Text to be written.
|
|
77
|
-
chunk: Flag for indicating this is a list of records.
|
|
78
|
-
end_of_record: Flag for adding end-of-record ('/').
|
|
79
|
-
"""
|
|
80
|
-
txt = ""
|
|
81
|
-
|
|
82
|
-
if keyword is None:
|
|
83
|
-
txt = content # type: ignore # it's really a formatted string
|
|
84
|
-
else:
|
|
85
|
-
self.fh.write(f"{keyword:s}\n")
|
|
86
|
-
if chunk:
|
|
87
|
-
for recs in content:
|
|
88
|
-
txt += " " + " ".join(recs) + " /\n"
|
|
89
|
-
else:
|
|
90
|
-
for line in content:
|
|
91
|
-
if isinstance(line, list):
|
|
92
|
-
logger.warning(
|
|
93
|
-
"Chunk is False, but content contains lists of lists, "
|
|
94
|
-
"instead of a list of strings the lines will be concatenated."
|
|
95
|
-
)
|
|
96
|
-
line = " ".join(line)
|
|
97
|
-
txt += line + "\n"
|
|
98
|
-
if self.mapper:
|
|
99
|
-
txt = self._replace_preprocessing_names(txt)
|
|
100
|
-
if end_of_record:
|
|
101
|
-
txt += "/\n"
|
|
102
|
-
self.fh.write(txt)
|
|
103
|
-
|
|
104
|
-
def _replace_preprocessing_names(self, text: str) -> str:
|
|
105
|
-
"""Expand start and end marker pairs for well pattern recognition as needed.
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
text: Text with pre-processor reservoir modelling well names.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
Text with reservoir simulator well names.
|
|
112
|
-
"""
|
|
113
|
-
if self.mapper is None:
|
|
114
|
-
raise ValueError(
|
|
115
|
-
f"{self._replace_preprocessing_names.__name__} requires a file containing two "
|
|
116
|
-
"columns with input and output names given by the MAPFILE keyword in "
|
|
117
|
-
f"case file to be set when creating {self.__class__.__name__}."
|
|
118
|
-
)
|
|
119
|
-
start_marks = ["'", " ", "\n", "\t"]
|
|
120
|
-
end_marks = ["'", " ", " ", " "]
|
|
121
|
-
for key, value in self.mapper.items():
|
|
122
|
-
for start, end in zip(start_marks, end_marks):
|
|
123
|
-
my_key = start + str(key) + start
|
|
124
|
-
if my_key in text:
|
|
125
|
-
my_value = start + str(value) + end
|
|
126
|
-
text = text.replace(my_key, my_value)
|
|
127
|
-
return text
|
|
128
|
-
|
|
129
|
-
def close(self) -> None:
|
|
130
|
-
"""Close FileWriter."""
|
|
131
|
-
self.fh.close()
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
class ProgressStatus:
|
|
135
|
-
"""Bookmark the reading progress of a schedule file.
|
|
136
|
-
|
|
137
|
-
See https://stackoverflow.com/questions/3173320/text-progress-bar-in-the-console
|
|
138
|
-
for improved functionality.
|
|
139
|
-
"""
|
|
140
|
-
|
|
141
|
-
def __init__(self, num_lines: int, percent: float):
|
|
142
|
-
"""Initialize ProgressStatus.
|
|
20
|
+
from completor.utils import (
|
|
21
|
+
abort,
|
|
22
|
+
clean_file_lines,
|
|
23
|
+
clean_raw_data,
|
|
24
|
+
find_keyword_data,
|
|
25
|
+
find_well_keyword_data,
|
|
26
|
+
replace_preprocessing_names,
|
|
27
|
+
)
|
|
28
|
+
from completor.wells import Well
|
|
143
29
|
|
|
144
|
-
|
|
145
|
-
num_lines: Number of lines in schedule file.
|
|
146
|
-
percent: Indicates schedule file processing progress (in percent).
|
|
147
|
-
"""
|
|
148
|
-
self.percent = percent
|
|
149
|
-
self.nlines = num_lines
|
|
150
|
-
self.prev_n = 0
|
|
151
|
-
|
|
152
|
-
def update(self, line_number: int) -> None:
|
|
153
|
-
"""Update logger information.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
line_number: Input schedule file line number.
|
|
157
|
-
|
|
158
|
-
Returns:
|
|
159
|
-
Logger info message.
|
|
160
|
-
"""
|
|
161
|
-
# If the divisor, or numerator is a float, the integer division gives a float
|
|
162
|
-
n = int((line_number / self.nlines * 100) // self.percent)
|
|
163
|
-
if n > self.prev_n:
|
|
164
|
-
logger.info("=" * 80)
|
|
165
|
-
logger.info("Done processing %i %% of schedule/data file", n * self.percent)
|
|
166
|
-
logger.info("=" * 80)
|
|
167
|
-
self.prev_n = n
|
|
30
|
+
pd.set_option("future.no_silent_downcasting", True)
|
|
168
31
|
|
|
169
32
|
|
|
170
33
|
def get_content_and_path(case_content: str, file_path: str | None, keyword: str) -> tuple[str | None, str | None]:
|
|
@@ -181,8 +44,7 @@ def get_content_and_path(case_content: str, file_path: str | None, keyword: str)
|
|
|
181
44
|
File content, file path.
|
|
182
45
|
|
|
183
46
|
Raises:
|
|
184
|
-
CompletorError: If the keyword cannot be found.
|
|
185
|
-
CompletorError: If the file cannot be found.
|
|
47
|
+
CompletorError: If the keyword or file cannot be found.
|
|
186
48
|
"""
|
|
187
49
|
if file_path is None:
|
|
188
50
|
# Find the path/name of file from case file
|
|
@@ -195,186 +57,112 @@ def get_content_and_path(case_content: str, file_path: str | None, keyword: str)
|
|
|
195
57
|
file_path = re.sub("[\"']+", "", file_path)
|
|
196
58
|
|
|
197
59
|
else:
|
|
198
|
-
#
|
|
199
|
-
if keyword ==
|
|
60
|
+
# OUT_FILE is optional, if it's needed but not supplied the error is caught in ReadCasefile:check_pvt_file()
|
|
61
|
+
if keyword == Keywords.OUT_FILE:
|
|
200
62
|
return None, None
|
|
201
63
|
raise CompletorError(f"The keyword {keyword} is not defined correctly in the casefile")
|
|
202
|
-
if keyword !=
|
|
64
|
+
if keyword != Keywords.OUT_FILE:
|
|
203
65
|
try:
|
|
204
66
|
with open(file_path, encoding="utf-8") as file:
|
|
205
67
|
file_content = file.read()
|
|
206
68
|
except FileNotFoundError as e:
|
|
207
69
|
raise CompletorError(f"Could not find the file: '{file_path}'!") from e
|
|
208
70
|
except (PermissionError, IsADirectoryError) as e:
|
|
209
|
-
raise CompletorError(
|
|
71
|
+
raise CompletorError(
|
|
72
|
+
f"Could not read {Keywords.SCHEDULE_FILE}, this is likely because the path is missing quotes."
|
|
73
|
+
) from e
|
|
210
74
|
return file_content, file_path
|
|
211
75
|
return None, file_path
|
|
212
76
|
|
|
213
77
|
|
|
214
|
-
# noinspection TimingAttack
|
|
215
|
-
# caused by `if token == '...'` and token is interpreted as a security token / JWT
|
|
216
|
-
# or otherwise sensitive, but in this context, `token` refers to a token of parsed
|
|
217
|
-
# text / semantic token
|
|
218
78
|
def create(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
show_fig: bool = False,
|
|
223
|
-
percent: float = 5.0,
|
|
224
|
-
paths: tuple[str, str] | None = None,
|
|
225
|
-
) -> (
|
|
226
|
-
tuple[list[tuple[str, list[list[str]]]], ReadCasefile, WellSchedule, CreateWells, CreateOutput]
|
|
227
|
-
| tuple[list[tuple[str, list[list[str]]]], ReadCasefile, WellSchedule, CreateWells]
|
|
228
|
-
):
|
|
229
|
-
"""Create a new Completor schedule file from input case- and schedule files.
|
|
79
|
+
case_file: str, schedule: str, new_file: str, show_fig: bool = False, paths: tuple[str, str] | None = None
|
|
80
|
+
) -> tuple[ReadCasefile, Well | None]:
|
|
81
|
+
"""Create and write the advanced schedule file from input case- and schedule files.
|
|
230
82
|
|
|
231
83
|
Args:
|
|
232
|
-
|
|
233
|
-
|
|
84
|
+
case_file: Input case file.
|
|
85
|
+
schedule: Input schedule file.
|
|
234
86
|
new_file: Output schedule file.
|
|
235
87
|
show_fig: Flag indicating if a figure is to be shown.
|
|
236
|
-
percent: ProgressStatus percentage steps to be shown (in percent, %).
|
|
237
88
|
paths: Optional additional paths.
|
|
238
89
|
|
|
239
90
|
Returns:
|
|
240
|
-
|
|
91
|
+
The case and schedule file, the well and output object.
|
|
241
92
|
"""
|
|
242
|
-
case = ReadCasefile(case_file=
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
lines = schedule_file.splitlines()
|
|
247
|
-
|
|
248
|
-
clean_lines_map = {}
|
|
249
|
-
for line_number, line in enumerate(lines):
|
|
250
|
-
line = clean_file_line(line, remove_quotation_marks=True)
|
|
251
|
-
if line:
|
|
252
|
-
clean_lines_map[line_number] = line
|
|
253
|
-
|
|
254
|
-
outfile = FileWriter(new_file, case.mapper)
|
|
255
|
-
chunks = [] # for debug..
|
|
256
|
-
figno = 0
|
|
257
|
-
written = set() # Keep track of which MSW's has been written
|
|
258
|
-
line_number = 0
|
|
259
|
-
progress_status = ProgressStatus(len(lines), percent)
|
|
260
|
-
|
|
261
|
-
pdf_file = None
|
|
93
|
+
case = ReadCasefile(case_file=case_file, schedule_file=schedule, output_file=new_file)
|
|
94
|
+
active_wells = utils.get_active_wells(case.completion_table, case.gp_perf_devicelayer)
|
|
95
|
+
|
|
96
|
+
figure_name = None
|
|
262
97
|
if show_fig:
|
|
263
98
|
figure_no = 1
|
|
264
|
-
|
|
265
|
-
while os.path.isfile(
|
|
99
|
+
figure_name = f"Well_schematic_{figure_no:03d}.pdf"
|
|
100
|
+
while os.path.isfile(figure_name):
|
|
266
101
|
figure_no += 1
|
|
267
|
-
|
|
268
|
-
pdf_file = create_pdfpages(fnm)
|
|
269
|
-
# loop lines
|
|
270
|
-
while line_number < len(lines):
|
|
271
|
-
progress_status.update(line_number)
|
|
272
|
-
line = lines[line_number]
|
|
273
|
-
keyword = line[:8].rstrip() # look for keywords
|
|
274
|
-
|
|
275
|
-
# most lines will just be duplicated
|
|
276
|
-
if keyword not in Keywords.main_keywords:
|
|
277
|
-
outfile.write(None, f"{line}\n")
|
|
278
|
-
else:
|
|
279
|
-
# This is a (potential) MSW keyword.
|
|
280
|
-
logger.debug(keyword)
|
|
281
|
-
|
|
282
|
-
well_name = _get_well_name(clean_lines_map, line_number)
|
|
283
|
-
if keyword in Keywords.segments: # check if it is an active well
|
|
284
|
-
logger.debug(well_name)
|
|
285
|
-
if well_name not in list(schedule.active_wells):
|
|
286
|
-
outfile.write(keyword, "")
|
|
287
|
-
line_number += 1
|
|
288
|
-
continue # not an active well
|
|
289
|
-
|
|
290
|
-
# first, collect data for this keyword into a 'chunk'
|
|
291
|
-
chunk_str = ""
|
|
292
|
-
raw = [] # only used for WELSPECS which we dont modify
|
|
293
|
-
# concatenate and look for 'end of records' => //
|
|
294
|
-
while not re.search(r"/\s*/$", chunk_str):
|
|
295
|
-
line_number += 1
|
|
296
|
-
raw.append(lines[line_number])
|
|
297
|
-
if line_number in clean_lines_map:
|
|
298
|
-
chunk_str += clean_lines_map[line_number]
|
|
299
|
-
chunk = _format_chunk(chunk_str)
|
|
300
|
-
chunks.append((keyword, chunk)) # for debug ...
|
|
301
|
-
|
|
302
|
-
# use data to update our schedule
|
|
303
|
-
if keyword == Keywords.WELSPECS:
|
|
304
|
-
schedule.set_welspecs(chunk) # update with new data
|
|
305
|
-
outfile.write(keyword, raw, chunk=False) # but write it back 'untouched'
|
|
306
|
-
line_number += 1 # ready for next line
|
|
307
|
-
continue
|
|
308
|
-
|
|
309
|
-
elif keyword == Keywords.COMPDAT:
|
|
310
|
-
remains = schedule.handle_compdat(chunk) # update with new data
|
|
311
|
-
if remains:
|
|
312
|
-
# Add single quotes to non-active well names
|
|
313
|
-
for remain in remains:
|
|
314
|
-
remain[0] = "'" + remain[0] + "'"
|
|
315
|
-
outfile.write(keyword, remains, end_of_record=True) # write any 'none-active' wells here
|
|
316
|
-
line_number += 1 # ready for next line
|
|
317
|
-
continue
|
|
318
|
-
|
|
319
|
-
elif keyword == Keywords.WELSEGS:
|
|
320
|
-
schedule.set_welsegs(chunk) # update with new data
|
|
321
|
-
|
|
322
|
-
elif keyword == Keywords.COMPSEGS:
|
|
323
|
-
# this is COMPSEGS'. will now update and write out new data
|
|
324
|
-
schedule.set_compsegs(chunk)
|
|
102
|
+
figure_name = f"Well_schematic_{figure_no:03d}.pdf"
|
|
325
103
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
f"token ({keyword} is one of "
|
|
334
|
-
f"{', '.join(Keywords.segments)})"
|
|
335
|
-
) from err
|
|
336
|
-
|
|
337
|
-
if well_name not in written:
|
|
338
|
-
write_welsegs = True # will only write WELSEGS once
|
|
339
|
-
written.add(well_name)
|
|
340
|
-
else:
|
|
341
|
-
write_welsegs = False
|
|
342
|
-
figno += 1
|
|
343
|
-
logger.debug("Writing new MSW info for well %s", well_name)
|
|
344
|
-
wells.update(well_name, schedule)
|
|
345
|
-
output = CreateOutput(
|
|
346
|
-
case,
|
|
347
|
-
schedule,
|
|
348
|
-
wells,
|
|
349
|
-
well_name,
|
|
350
|
-
schedule.get_well_number(well_name),
|
|
351
|
-
completor.__version__,
|
|
352
|
-
show_fig,
|
|
353
|
-
pdf_file,
|
|
354
|
-
write_welsegs,
|
|
355
|
-
paths,
|
|
356
|
-
)
|
|
357
|
-
outfile.write(None, output.finalprint)
|
|
358
|
-
else:
|
|
359
|
-
raise ValueError(f"The keyword '{keyword}' has not been implemented in Completor, but should have been")
|
|
360
|
-
|
|
361
|
-
line_number += 1 # ready for next line
|
|
362
|
-
logger.debug(line_number)
|
|
363
|
-
outfile.close()
|
|
364
|
-
close_figure()
|
|
365
|
-
if pdf_file is not None:
|
|
366
|
-
pdf_file.close()
|
|
104
|
+
err: Exception | None = None
|
|
105
|
+
well = None
|
|
106
|
+
# Add banner.
|
|
107
|
+
schedule = create_output.metadata_banner(paths) + schedule
|
|
108
|
+
# Strip trailing whitespace.
|
|
109
|
+
schedule = re.sub(r"[^\S\r\n]+$", "", schedule, flags=re.MULTILINE)
|
|
110
|
+
meaningful_data: ScheduleData = {}
|
|
367
111
|
|
|
368
112
|
try:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
113
|
+
# Find the old data for each of the four main keywords.
|
|
114
|
+
for chunk in find_keyword_data(Keywords.WELL_SPECIFICATION, schedule):
|
|
115
|
+
clean_data = clean_raw_data(chunk, Keywords.WELL_SPECIFICATION)
|
|
116
|
+
meaningful_data = read_schedule.set_welspecs(meaningful_data, clean_data)
|
|
117
|
+
|
|
118
|
+
for chunk in find_keyword_data(Keywords.COMPLETION_DATA, schedule):
|
|
119
|
+
clean_data = clean_raw_data(chunk, Keywords.COMPLETION_DATA)
|
|
120
|
+
meaningful_data = read_schedule.set_compdat(meaningful_data, clean_data)
|
|
121
|
+
|
|
122
|
+
for chunk in find_keyword_data(Keywords.WELL_SEGMENTS, schedule):
|
|
123
|
+
clean_data = clean_raw_data(chunk, Keywords.WELL_SEGMENTS)
|
|
124
|
+
meaningful_data = read_schedule.set_welsegs(meaningful_data, clean_data)
|
|
125
|
+
|
|
126
|
+
for chunk in find_keyword_data(Keywords.COMPLETION_SEGMENTS, schedule):
|
|
127
|
+
clean_data = clean_raw_data(chunk, Keywords.COMPLETION_SEGMENTS)
|
|
128
|
+
meaningful_data = read_schedule.set_compsegs(meaningful_data, clean_data)
|
|
129
|
+
|
|
130
|
+
for i, well_name in tqdm(enumerate(active_wells.tolist()), total=len(active_wells)):
|
|
131
|
+
well = Well(well_name, i, case, meaningful_data[well_name])
|
|
132
|
+
compdat, welsegs, compsegs, bonus = create_output.format_output(well, case, figure_name)
|
|
133
|
+
|
|
134
|
+
for keyword in [Keywords.COMPLETION_SEGMENTS, Keywords.WELL_SEGMENTS, Keywords.COMPLETION_DATA]:
|
|
135
|
+
old_data = find_well_keyword_data(well_name, keyword, schedule)
|
|
136
|
+
if not old_data:
|
|
137
|
+
raise CompletorError(
|
|
138
|
+
"Could not find the unmodified data in original schedule file. Please contact the team!"
|
|
139
|
+
)
|
|
140
|
+
try:
|
|
141
|
+
# Check that nothing is lost.
|
|
142
|
+
schedule.index(old_data)
|
|
143
|
+
except ValueError:
|
|
144
|
+
raise CompletorError("Could not match the old data to schedule file. Please contact the team!")
|
|
145
|
+
|
|
146
|
+
match keyword:
|
|
147
|
+
case Keywords.COMPLETION_DATA:
|
|
148
|
+
schedule = schedule.replace(old_data, compdat)
|
|
149
|
+
case Keywords.COMPLETION_SEGMENTS:
|
|
150
|
+
schedule = schedule.replace(old_data, compsegs + bonus)
|
|
151
|
+
case Keywords.WELL_SEGMENTS:
|
|
152
|
+
schedule = schedule.replace(old_data, welsegs)
|
|
153
|
+
|
|
154
|
+
except Exception as e_:
|
|
155
|
+
err = e_
|
|
156
|
+
finally:
|
|
157
|
+
# Make sure the output thus far is written, and figure files are closed.
|
|
158
|
+
schedule = replace_preprocessing_names(schedule, case.mapper)
|
|
159
|
+
with open(new_file, "w", encoding="utf-8") as file:
|
|
160
|
+
file.write(schedule)
|
|
161
|
+
|
|
162
|
+
if err is not None:
|
|
163
|
+
raise err
|
|
164
|
+
|
|
165
|
+
return case, well
|
|
378
166
|
|
|
379
167
|
|
|
380
168
|
def main() -> None:
|
|
@@ -401,26 +189,27 @@ def main() -> None:
|
|
|
401
189
|
if inputs.inputfile is not None:
|
|
402
190
|
with open(inputs.inputfile, encoding="utf-8") as file:
|
|
403
191
|
case_file_content = file.read()
|
|
404
|
-
else:
|
|
405
|
-
raise CompletorError("Need input case file to run Completor")
|
|
406
192
|
|
|
407
193
|
schedule_file_content, inputs.schedulefile = get_content_and_path(
|
|
408
|
-
case_file_content, inputs.schedulefile, Keywords.
|
|
194
|
+
case_file_content, inputs.schedulefile, Keywords.SCHEDULE_FILE
|
|
409
195
|
)
|
|
410
196
|
|
|
411
197
|
if isinstance(schedule_file_content, str):
|
|
412
198
|
parse.read_schedule_keywords(clean_file_lines(schedule_file_content.splitlines()), Keywords.main_keywords)
|
|
413
199
|
|
|
414
|
-
_, inputs.outputfile = get_content_and_path(case_file_content, inputs.outputfile, Keywords.
|
|
200
|
+
_, inputs.outputfile = get_content_and_path(case_file_content, inputs.outputfile, Keywords.OUT_FILE)
|
|
415
201
|
|
|
416
202
|
if inputs.outputfile is None:
|
|
417
203
|
if inputs.schedulefile is None:
|
|
418
|
-
raise ValueError(
|
|
204
|
+
raise ValueError(
|
|
205
|
+
"Could not find a path to schedule file. "
|
|
206
|
+
f"It must be provided as a input argument or within the case files keyword '{Keywords.SCHEDULE_FILE}'."
|
|
207
|
+
)
|
|
419
208
|
inputs.outputfile = inputs.schedulefile.split(".")[0] + "_advanced.wells"
|
|
420
209
|
|
|
421
210
|
paths_input_schedule = (inputs.inputfile, inputs.schedulefile)
|
|
422
211
|
|
|
423
|
-
logger.
|
|
212
|
+
logger.info("Running Completor version %s. An advanced well modelling tool.", get_version())
|
|
424
213
|
logger.debug("-" * 60)
|
|
425
214
|
start_a = time.time()
|
|
426
215
|
|
|
@@ -432,53 +221,6 @@ def main() -> None:
|
|
|
432
221
|
logger.debug("-" * 60)
|
|
433
222
|
|
|
434
223
|
|
|
435
|
-
def _get_well_name(schedule_lines: dict[int, str], i: int) -> str:
|
|
436
|
-
"""Get the well name from line number
|
|
437
|
-
|
|
438
|
-
Args:
|
|
439
|
-
schedule_lines: Dictionary of lines in schedule file.
|
|
440
|
-
i: Line index.
|
|
441
|
-
|
|
442
|
-
Returns:
|
|
443
|
-
Well name.
|
|
444
|
-
"""
|
|
445
|
-
keys = np.array(sorted(list(schedule_lines.keys())))
|
|
446
|
-
j = np.where(keys == i)[0][0]
|
|
447
|
-
next_line = schedule_lines[int(keys[j + 1])]
|
|
448
|
-
return next_line.split()[0]
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
def _format_chunk(chunk_str: str) -> list[list[str]]:
|
|
452
|
-
"""Format the data-records and resolve the repeat-mechanism.
|
|
453
|
-
|
|
454
|
-
E.g. 3* == 1* 1* 1*, 3*250 == 250 250 250.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
chunk_str: A chunk data-record.
|
|
458
|
-
|
|
459
|
-
Returns:
|
|
460
|
-
Expanded values.
|
|
461
|
-
"""
|
|
462
|
-
chunk = re.split(r"\s+/", chunk_str)[:-1]
|
|
463
|
-
expanded_data = []
|
|
464
|
-
for line in chunk:
|
|
465
|
-
new_record = ""
|
|
466
|
-
for record in line.split():
|
|
467
|
-
if not record[0].isdigit():
|
|
468
|
-
new_record += record + " "
|
|
469
|
-
continue
|
|
470
|
-
if "*" not in record:
|
|
471
|
-
new_record += record + " "
|
|
472
|
-
continue
|
|
473
|
-
|
|
474
|
-
# need to handle things like 3* or 3*250
|
|
475
|
-
multiplier, number = record.split("*")
|
|
476
|
-
new_record += f"{number if number else '1*'} " * int(multiplier)
|
|
477
|
-
if new_record:
|
|
478
|
-
expanded_data.append(new_record.split())
|
|
479
|
-
return expanded_data
|
|
480
|
-
|
|
481
|
-
|
|
482
224
|
if __name__ == "__main__":
|
|
483
225
|
try:
|
|
484
226
|
main()
|