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.
@@ -3,14 +3,14 @@ from __future__ import annotations
3
3
  import re
4
4
  from collections.abc import Mapping
5
5
  from io import StringIO
6
+ from typing import Any
6
7
 
7
8
  import numpy as np
9
+ import numpy.typing as npt
8
10
  import pandas as pd
9
11
 
10
- from completor import input_validation as val
11
- from completor import parse
12
- from completor.completion import WellSchedule
13
- from completor.constants import Headers, Keywords
12
+ from completor import input_validation, parse
13
+ from completor.constants import Content, Headers, Keywords, Method, WellData
14
14
  from completor.exceptions import CaseReaderFormatError, CompletorError
15
15
  from completor.logger import logger
16
16
  from completor.utils import clean_file_lines
@@ -46,8 +46,8 @@ class ReadCasefile:
46
46
 
47
47
  This class reads the case/input file of the Completor program.
48
48
  It reads the following keywords:
49
- SCHFILE, OUTFILE, COMPLETION, SEGMENTLENGTH, JOINTLENGTH
50
- WSEGAICD, WSEGVALV, WSEGSICD, WSEGDAR, WSEGAICV, WSEGICV, PVTFILE, PVTTABLE.
49
+ SCHEDULE_FILE, OUT_FILE, COMPLETION, SEGMENTLENGTH, JOINTLENGTH AUTONOMOUS_INFLOW_CONTROL_DEVICE, WELL_SEGMENTS_VALVE,
50
+ INFLOW_CONTROL_DEVICE, DENSITY_ACTIVATED_RECOVERY, AUTONOMOUS_INFLOW_CONTROL_VALVE, INFLOW_CONTROL_VALVE, PVTFILE, PVTTABLE.
51
51
  In the absence of some keywords, the program uses the default values.
52
52
 
53
53
  Attributes:
@@ -58,15 +58,15 @@ class ReadCasefile:
58
58
  pvt_file (str): The pvt file content.
59
59
  pvt_file_name (str): The pvt file name.
60
60
  completion_table (pd.DataFrame): ....
61
- wsegaicd_table (pd.DataFrame): WSEGAICD.
62
- wsegsicd_table (pd.DataFrame): WSEGSICD.
63
- wsegvalv_table (pd.DataFrame): WSEGVALV.
64
- wsegicv_table (pd.DataFrame): WSEGICV.
65
- wsegdar_table (pd.DataFrame): WSEGDAR.
66
- wsegaicv_table (pd.DataFrame): WSEGAICV.
61
+ wsegaicd_table (pd.DataFrame): AUTONOMOUS_INFLOW_CONTROL_DEVICE.
62
+ wsegsicd_table (pd.DataFrame): INFLOW_CONTROL_DEVICE.
63
+ wsegvalv_table (pd.DataFrame): WELL_SEGMENTS_VALVE.
64
+ wsegicv_table (pd.DataFrame): INFLOW_CONTROL_VALVE.
65
+ wsegdar_table (pd.DataFrame): DENSITY_ACTIVATED_RECOVERY.
66
+ wsegaicv_table (pd.DataFrame): AUTONOMOUS_INFLOW_CONTROL_VALVE.
67
67
  strict (bool): USE_STRICT. If TRUE it will exit if any lateral is not defined in the case-file. Default to TRUE.
68
68
  lat2device (pd.DataFrame): LATERAL_TO_DEVICE.
69
- gp_perf_devicelayer (bool): GP_PERF_DEVICELAYER. If TRUE all wells with
69
+ gp_perf_devicelayer (bool): GRAVEL_PACKED_PERFORATED_DEVICELAYER. If TRUE all wells with
70
70
  gravel pack and perforation completion are given a device layer.
71
71
  If FALSE (default) all wells with this type of completions are untouched by Completor.
72
72
  """
@@ -86,7 +86,6 @@ class ReadCasefile:
86
86
 
87
87
  # assign default values
88
88
  self.joint_length = 12.0
89
- self.segment_length: float | str = 0.0
90
89
  self.minimum_segment_length: float = 0.0
91
90
  self.strict = True
92
91
  self.gp_perf_devicelayer = False
@@ -108,7 +107,8 @@ class ReadCasefile:
108
107
  # Run programs
109
108
  self.read_completion()
110
109
  self.read_joint_length()
111
- self.read_segment_length()
110
+ self.segment_length = self.read_segment_length()
111
+ self.method = self.segmentation_method(self.segment_length)
112
112
  self.read_strictness()
113
113
  self.read_gp_perf_devicelayer()
114
114
  self.read_mapfile()
@@ -127,7 +127,7 @@ class ReadCasefile:
127
127
  Raises:
128
128
  ValueError: If COMPLETION keyword is not defined in the case.
129
129
  """
130
- start_index, end_index = self.locate_keyword(Keywords.COMPLETION)
130
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.COMPLETION)
131
131
  if start_index == end_index:
132
132
  raise ValueError("No completion is defined in the case file.")
133
133
 
@@ -147,15 +147,15 @@ class ReadCasefile:
147
147
  ]
148
148
  df_temp = self._create_dataframe_with_columns(header, start_index, end_index)
149
149
  # Set default value for packer segment
150
- df_temp = val.set_default_packer_section(df_temp)
150
+ df_temp = input_validation.set_default_packer_section(df_temp)
151
151
  # Set default value for PERF segments
152
- df_temp = val.set_default_perf_section(df_temp)
152
+ df_temp = input_validation.set_default_perf_section(df_temp)
153
153
  # Give errors if 1* is found for non packer segments
154
- df_temp = val.check_default_non_packer(df_temp)
154
+ df_temp = input_validation.check_default_non_packer(df_temp)
155
155
  # Fix the data types format
156
- df_temp = val.set_format_completion(df_temp)
156
+ df_temp = input_validation.set_format_completion(df_temp)
157
157
  # Check overall user inputs on completion
158
- val.assess_completion(df_temp)
158
+ input_validation.assess_completion(df_temp)
159
159
  df_temp = self.read_icv_tubing(df_temp)
160
160
  self.completion_table = df_temp.copy(deep=True)
161
161
 
@@ -170,18 +170,18 @@ class ReadCasefile:
170
170
  """
171
171
  if not df_temp.loc[
172
172
  (df_temp[Headers.START_MEASURED_DEPTH] == df_temp[Headers.END_MEASURED_DEPTH])
173
- & (df_temp[Headers.DEVICE_TYPE] == "ICV")
173
+ & (df_temp[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE)
174
174
  ].empty:
175
175
  # take ICV tubing table
176
176
  self.completion_icv_tubing = df_temp.loc[
177
177
  (df_temp[Headers.START_MEASURED_DEPTH] == df_temp[Headers.END_MEASURED_DEPTH])
178
- & (df_temp[Headers.DEVICE_TYPE] == "ICV")
178
+ & (df_temp[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE)
179
179
  ].reset_index(drop=True)
180
180
  # drop its line
181
181
  df_temp = df_temp.drop(
182
182
  df_temp.loc[
183
183
  (df_temp[Headers.START_MEASURED_DEPTH] == df_temp[Headers.END_MEASURED_DEPTH])
184
- & (df_temp[Headers.DEVICE_TYPE] == "ICV")
184
+ & (df_temp[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE)
185
185
  ].index[:]
186
186
  ).reset_index(drop=True)
187
187
  return df_temp
@@ -203,19 +203,19 @@ class ReadCasefile:
203
203
  /
204
204
  """
205
205
  header = [Headers.WELL, Headers.BRANCH]
206
- start_index, end_index = self.locate_keyword("LATERAL_TO_DEVICE")
206
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.LATERAL_TO_DEVICE)
207
207
 
208
208
  if start_index == end_index:
209
209
  # set default behaviour (if keyword not in case file)
210
210
  self.lat2device = pd.DataFrame([], columns=header) # empty df
211
211
  return
212
212
  self.lat2device = self._create_dataframe_with_columns(header, start_index, end_index)
213
- val.validate_lateral_to_device(self.lat2device, self.completion_table)
213
+ input_validation.validate_lateral_to_device(self.lat2device, self.completion_table)
214
214
  self.lat2device[Headers.BRANCH] = self.lat2device[Headers.BRANCH].astype(np.int64)
215
215
 
216
216
  def read_joint_length(self) -> None:
217
217
  """Read the JOINTLENGTH keyword in the case file."""
218
- start_index, end_index = self.locate_keyword("JOINTLENGTH")
218
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.JOINT_LENGTH)
219
219
  if end_index == start_index + 2:
220
220
  self.joint_length = float(self.content[start_index + 1])
221
221
  if self.joint_length <= 0:
@@ -224,52 +224,75 @@ class ReadCasefile:
224
224
  else:
225
225
  logger.info("No joint length is defined. It is set to default 12.0 m")
226
226
 
227
- def read_segment_length(self) -> None:
227
+ def read_segment_length(self) -> float | str:
228
228
  """Read the SEGMENTLENGTH keyword in the case file.
229
229
 
230
230
  Raises:
231
231
  CompletorError: If SEGMENTLENGTH is not float or string.
232
232
  """
233
- start_index, end_index = self.locate_keyword("SEGMENTLENGTH")
233
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.SEGMENT_LENGTH)
234
234
  if end_index == start_index + 2:
235
235
  try:
236
- self.segment_length = float(self.content[start_index + 1])
237
- # 'Fix' method if value is positive.
238
- if self.segment_length > 0.0:
239
- logger.info("Segments are defined per %s meters.", self.segment_length)
240
- # 'User' method if value is negative.
241
- elif self.segment_length < 0.0:
242
- logger.info(
243
- "Segments are defined based on the COMPLETION keyword. "
244
- "Attempting to pick segments' measured depth from .case file."
245
- )
246
- # 'Cells' method if value is zero.
247
- elif self.segment_length == 0:
248
- logger.info("Segments are defined based on the grid dimensions.")
249
-
236
+ return float(self.content[start_index + 1])
250
237
  except ValueError:
251
- try:
252
- self.segment_length = str(self.content[start_index + 1])
253
- # 'Welsegs' method
254
- if "welsegs" in self.segment_length.lower() or "infill" in self.segment_length.lower():
255
- logger.info(
256
- "Segments are defined based on the WELSEGS keyword. "
257
- "Retaining the original tubing segment structure."
258
- )
259
- # 'User' method if value is negative
260
- elif "user" in self.segment_length.lower():
261
- logger.info(
262
- "Segments are defined based on the COMPLETION keyword. "
263
- "Attempting to pick segments' measured depth from casefile."
264
- )
265
- # 'Cells' method
266
- elif "cell" in self.segment_length.lower():
267
- logger.info("Segment lengths are created based on the grid dimensions.")
268
- except ValueError as err:
269
- raise CompletorError("SEGMENTLENGTH takes number or string") from err
238
+ return self.content[start_index + 1]
239
+
270
240
  else:
271
- # 'Cells' method if value is 0.0 or undefined
272
- logger.info("No segment length is defined. " "Segments are created based on the grid dimension.")
241
+ logger.info(
242
+ "SEGMENTLENGTH keyword undefined, using default strategy 'cells' "
243
+ "to create segments based on the grid dimensions."
244
+ )
245
+ return 0.0
246
+
247
+ @staticmethod
248
+ def segmentation_method(segment_length: float | str) -> Method:
249
+ """Determine the method of segmentation, and log the implication to info.
250
+
251
+ Args:
252
+ segment_length: The string or number value from the SEGMENTLENGTH keyword.
253
+
254
+ Returns:
255
+ The method used to create the segments.
256
+
257
+ Raises:
258
+ ValueError: If value of segment_length is invalid.
259
+ """
260
+ if isinstance(segment_length, float):
261
+ if segment_length > 0.0:
262
+ logger.info("Segments are defined per fixed %s meters.", segment_length)
263
+ return Method.FIX
264
+ if segment_length == 0.0:
265
+ logger.info("Segments are defined based on the grid dimensions.")
266
+ return Method.CELLS
267
+ if segment_length < 0.0:
268
+ logger.info(
269
+ "Segments are defined based on the COMPLETION keyword. "
270
+ "Attempting to pick segments' measured depth from case file."
271
+ )
272
+ return Method.USER
273
+
274
+ if isinstance(segment_length, str):
275
+ if "welsegs" in segment_length.lower() or "infill" in segment_length.lower():
276
+ logger.info(
277
+ "Segments are defined based on the WELL_SEGMENTS%s keyword. "
278
+ "Retaining the original tubing segment structure.",
279
+ Keywords.WELL_SEGMENTS,
280
+ )
281
+ return Method.WELSEGS
282
+ if "cell" in segment_length.lower():
283
+ logger.info("Segment lengths are created based on the grid dimensions.")
284
+ return Method.CELLS
285
+ if "user" in segment_length.lower():
286
+ logger.info(
287
+ "Segments are defined based on the COMPLETION keyword. "
288
+ "Attempting to pick segments' measured depth from casefile."
289
+ )
290
+ return Method.USER
291
+ raise CompletorError(
292
+ f"Unrecognized method for SEGMENTLENGTH keyword '{segment_length}'. The value should be one of: "
293
+ f"'{Keywords.WELL_SEGMENTS}', 'CELLS', 'USER'. "
294
+ "Alternatively a negative number for 'USER', zero for 'CELLS', or positive number for 'FIX'.",
295
+ )
273
296
 
274
297
  def read_strictness(self) -> None:
275
298
  """Read the USE_STRICT keyword in the case file.
@@ -282,7 +305,7 @@ class ReadCasefile:
282
305
 
283
306
  Best practice: All branches in all wells should be defined in the case file.
284
307
  """
285
- start_index, end_index = self.locate_keyword("USE_STRICT")
308
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.USE_STRICT)
286
309
  if end_index == start_index + 2:
287
310
  strict = self.content[start_index + 1]
288
311
  if strict.upper() == "FALSE":
@@ -290,14 +313,14 @@ class ReadCasefile:
290
313
  logger.info("case-strictness is set to %d", self.strict)
291
314
 
292
315
  def read_gp_perf_devicelayer(self) -> None:
293
- """Read the GP_PERF_DEVICELAYER keyword in the case file.
316
+ """Read the GRAVEL_PACKED_PERFORATED_DEVICELAYER keyword in the case file.
294
317
 
295
- If GP_PERF_DEVICELAYER = True the program assigns a device layer to
296
- wells with GP PERF type completions. If GP_PERF_DEVICELAYER = False, the
318
+ If GRAVEL_PACKED_PERFORATED_DEVICELAYER = True the program assigns a device layer to
319
+ wells with GP PERF type completions. If GRAVEL_PACKED_PERFORATED_DEVICELAYER = False, the
297
320
  program does not add a device layer to the well. I.e. the well is
298
321
  untouched by the program. The default value is False.
299
322
  """
300
- start_index, end_index = self.locate_keyword("GP_PERF_DEVICELAYER")
323
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.GRAVEL_PACKED_PERFORATED_DEVICELAYER)
301
324
  if end_index == start_index + 2:
302
325
  gp_perf_devicelayer = self.content[start_index + 1]
303
326
  self.gp_perf_devicelayer = gp_perf_devicelayer.upper() == "TRUE"
@@ -309,97 +332,108 @@ class ReadCasefile:
309
332
  The default value is 0.0, meaning that no segments are lumped by this keyword.
310
333
  The program will continue to coalesce segments until all segments are longer than the given minimum.
311
334
  """
312
- start_index, end_index = self.locate_keyword("MINIMUM_SEGMENT_LENGTH")
335
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.MINIMUM_SEGMENT_LENGTH)
313
336
  if end_index == start_index + 2:
314
337
  min_seg_len = self.content[start_index + 1]
315
- self.minimum_segment_length = val.validate_minimum_segment_length(min_seg_len)
338
+ self.minimum_segment_length = input_validation.validate_minimum_segment_length(min_seg_len)
316
339
  logger.info("minimum_segment_length is set to %s", self.minimum_segment_length)
317
340
 
318
341
  def read_mapfile(self) -> None:
319
- """Read the MAPFILE keyword in the case file (if any) into a mapper."""
320
- start_index, end_index = self.locate_keyword("MAPFILE")
342
+ """Read the MAP_FILE keyword in the case file (if any) into a mapper."""
343
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.MAP_FILE)
321
344
  if end_index == start_index + 2:
322
345
  # the content is in between the keyword and the /
323
346
  self.mapfile = parse.remove_string_characters(self.content[start_index + 1])
324
347
  self.mapper = _mapper(self.mapfile)
325
- else:
326
- self.mapfile = None
327
- self.mapper = None
328
348
 
329
349
  def read_wsegvalv(self) -> None:
330
- """Read the WSEGVALV keyword in the case file.
350
+ """Read the WELL_SEGMENTS_VALVE keyword in the case file.
331
351
 
332
352
  Raises:
333
353
  CompletorError: If WESEGVALV is not defined and VALVE is used in COMPLETION. If the device number is not found.
334
354
  """
335
- start_index, end_index = self.locate_keyword(Keywords.WSEGVALV)
355
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.WELL_SEGMENTS_VALVE)
336
356
  if start_index == end_index:
337
- if "VALVE" in self.completion_table[Headers.DEVICE_TYPE]:
338
- raise CompletorError("WSEGVALV keyword must be defined, if VALVE is used in the completion.")
357
+ if Content.VALVE in self.completion_table[Headers.DEVICE_TYPE]:
358
+ raise CompletorError("WELL_SEGMENTS_VALVE keyword must be defined, if VALVE is used in the completion.")
339
359
  else:
340
360
  # Table headers
341
- header = [Headers.DEVICE_NUMBER, Headers.CV, Headers.AC, Headers.L]
361
+ header = [
362
+ Headers.DEVICE_NUMBER,
363
+ Headers.FLOW_COEFFICIENT,
364
+ Headers.FLOW_CROSS_SECTIONAL_AREA,
365
+ Headers.ADDITIONAL_PIPE_LENGTH_FRICTION_PRESSURE_DROP,
366
+ ]
342
367
  try:
343
368
  df_temp = self._create_dataframe_with_columns(header, start_index, end_index)
344
- df_temp[Headers.AC_MAX] = np.nan
369
+ df_temp[Headers.MAX_FLOW_CROSS_SECTIONAL_AREA] = np.nan
345
370
  except CaseReaderFormatError:
346
- header += [Headers.AC_MAX]
371
+ header += [Headers.MAX_FLOW_CROSS_SECTIONAL_AREA]
347
372
  df_temp = self._create_dataframe_with_columns(header, start_index, end_index)
348
373
 
349
- self.wsegvalv_table = val.set_format_wsegvalv(df_temp)
350
- device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == "VALVE"][
374
+ self.wsegvalv_table = input_validation.set_format_wsegvalv(df_temp)
375
+ device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == Content.VALVE][
351
376
  Headers.DEVICE_NUMBER
352
377
  ].to_numpy()
353
378
  if not check_contents(device_checks, self.wsegvalv_table[Headers.DEVICE_NUMBER].to_numpy()):
354
- raise CompletorError("Not all device in COMPLETION is specified in WSEGVALV")
379
+ raise CompletorError(
380
+ f"Not all device in {Keywords.COMPLETION} is specified in {Keywords.WELL_SEGMENTS_VALVE}"
381
+ )
355
382
 
356
383
  def read_wsegsicd(self) -> None:
357
- """Read the WSEGSICD keyword in the case file.
384
+ """Read the INFLOW_CONTROL_DEVICE keyword in the case file.
358
385
 
359
386
  Raises:
360
- CompletorError: If WSEGSICD is not defined and ICD is used in COMPLETION, or if the device number is not found.
361
- If not all devices in COMPLETION are specified in WSEGSICD.
387
+ CompletorError: If INFLOW_CONTROL_DEVICE is not defined and ICD is used in COMPLETION,
388
+ or if the device number is not found.
389
+ If not all devices in COMPLETION are specified in INFLOW_CONTROL_DEVICE.
362
390
  """
363
- start_index, end_index = self.locate_keyword(Keywords.WSEGSICD)
391
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.INFLOW_CONTROL_DEVICE)
364
392
  if start_index == end_index:
365
- if "ICD" in self.completion_table[Headers.DEVICE_TYPE]:
366
- raise CompletorError("WSEGSICD keyword must be defined, if ICD is used in the completion.")
393
+ if Content.INFLOW_CONTROL_DEVICE in self.completion_table[Headers.DEVICE_TYPE]:
394
+ raise CompletorError(
395
+ f"{Keywords.INFLOW_CONTROL_DEVICE} keyword must be defined, if ICD is used in the completion."
396
+ )
367
397
  else:
368
398
  # Table headers
369
399
  header = [
370
400
  Headers.DEVICE_NUMBER,
371
401
  Headers.STRENGTH,
372
- Headers.RHOCAL_ICD,
373
- Headers.VISCAL_ICD,
402
+ Headers.CALIBRATION_FLUID_DENSITY,
403
+ Headers.CALIBRATION_FLUID_VISCOSITY,
374
404
  Headers.WATER_CUT,
375
405
  ]
376
- self.wsegsicd_table = val.set_format_wsegsicd(
406
+ self.wsegsicd_table = input_validation.set_format_wsegsicd(
377
407
  self._create_dataframe_with_columns(header, start_index, end_index)
378
408
  )
379
- # Check if the device in COMPLETION is exist in WSEGSICD
380
- device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == "ICD"][
381
- Headers.DEVICE_NUMBER
382
- ].to_numpy()
409
+ # Check if the device in COMPLETION is exist in INFLOW_CONTROL_DEVICE
410
+ device_checks = self.completion_table[
411
+ self.completion_table[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_DEVICE
412
+ ][Headers.DEVICE_NUMBER].to_numpy()
383
413
  if not check_contents(device_checks, self.wsegsicd_table[Headers.DEVICE_NUMBER].to_numpy()):
384
- raise CompletorError("Not all device in COMPLETION is specified in WSEGSICD")
414
+ raise CompletorError(f"Not all device in COMPLETION is specified in {Keywords.INFLOW_CONTROL_DEVICE}")
385
415
 
386
416
  def read_wsegaicd(self) -> None:
387
- """Read the WSEGAICD keyword in the case file.
417
+ """Read the AUTONOMOUS_INFLOW_CONTROL_DEVICE keyword in the case file.
388
418
 
389
419
  Raises:
390
- ValueError: If invalid entries in WSEGAICD.
391
- CompletorError: If WSEGAICD is not defined and AICD is used in COMPLETION, or if the device number is not found.
392
- If all devices in COMPLETION are not specified in WSEGAICD.
420
+ ValueError: If invalid entries in AUTONOMOUS_INFLOW_CONTROL_DEVICE.
421
+ CompletorError: If AUTONOMOUS_INFLOW_CONTROL_DEVICE is not defined, and AICD is used in COMPLETION,
422
+ or if the device number is not found.
423
+ If all devices in COMPLETION are not specified in AUTONOMOUS_INFLOW_CONTROL_DEVICE.
393
424
  """
394
- start_index, end_index = self.locate_keyword(Keywords.WSEGAICD)
425
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.AUTONOMOUS_INFLOW_CONTROL_DEVICE)
395
426
  if start_index == end_index:
396
- if "AICD" in self.completion_table[Headers.DEVICE_TYPE]:
397
- raise CompletorError("WSEGAICD keyword must be defined, if AICD is used in the completion.")
427
+ if Content.AUTONOMOUS_INFLOW_CONTROL_DEVICE in self.completion_table[Headers.DEVICE_TYPE]:
428
+ raise CompletorError(
429
+ f"{Keywords.AUTONOMOUS_INFLOW_CONTROL_DEVICE} keyword must be defined, "
430
+ "if AICD is used in the completion."
431
+ )
398
432
  else:
399
433
  # Table headers
400
434
  header = [
401
435
  Headers.DEVICE_NUMBER,
402
- Headers.ALPHA,
436
+ Headers.STRENGTH,
403
437
  Headers.X,
404
438
  Headers.Y,
405
439
  Headers.A,
@@ -408,75 +442,84 @@ class ReadCasefile:
408
442
  Headers.D,
409
443
  Headers.E,
410
444
  Headers.F,
411
- Headers.RHOCAL_AICD,
412
- Headers.VISCAL_AICD,
445
+ Headers.AICD_CALIBRATION_FLUID_DENSITY,
446
+ Headers.AICD_FLUID_VISCOSITY,
413
447
  ]
414
- self.wsegaicd_table = val.set_format_wsegaicd(
448
+ self.wsegaicd_table = input_validation.set_format_wsegaicd(
415
449
  self._create_dataframe_with_columns(header, start_index, end_index)
416
450
  )
417
- device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == "AICD"][
418
- Headers.DEVICE_NUMBER
419
- ].to_numpy()
451
+ device_checks = self.completion_table[
452
+ self.completion_table[Headers.DEVICE_TYPE] == Content.AUTONOMOUS_INFLOW_CONTROL_DEVICE
453
+ ][Headers.DEVICE_NUMBER].to_numpy()
420
454
  if not check_contents(device_checks, self.wsegaicd_table[Headers.DEVICE_NUMBER].to_numpy()):
421
- raise CompletorError("Not all device in COMPLETION is specified in WSEGAICD")
455
+ raise CompletorError(
456
+ f"Not all device in COMPLETION is specified in {Keywords.AUTONOMOUS_INFLOW_CONTROL_DEVICE}"
457
+ )
422
458
 
423
459
  def read_wsegdar(self) -> None:
424
- """Read the WSEGDAR keyword in the case file.
460
+ """Read the DENSITY_ACTIVATED_RECOVERY keyword in the case file.
425
461
 
426
462
  Raises:
427
- ValueError: If there are invalid entries in WSEGDAR.
428
- CompletorError: If not all device in COMPLETION is specified in WSEGDAR.
429
- If WSEGDAR keyword not defined, when DAR is used in the completion.
463
+ ValueError: If there are invalid entries in DENSITY_ACTIVATED_RECOVERY.
464
+ CompletorError: If not all device in COMPLETION is specified in DENSITY_ACTIVATED_RECOVERY.
465
+ If DENSITY_ACTIVATED_RECOVERY keyword not defined, when DAR is used in the completion.
430
466
  """
431
- start_index, end_index = self.locate_keyword(Keywords.WSEGDAR)
467
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.DENSITY_ACTIVATED_RECOVERY)
432
468
  if start_index == end_index:
433
- if "DAR" in self.completion_table[Headers.DEVICE_TYPE]:
434
- raise CompletorError("WSEGDAR keyword must be defined, if DAR is used in the completion")
469
+ if Content.DENSITY_ACTIVATED_RECOVERY in self.completion_table[Headers.DEVICE_TYPE]:
470
+ raise CompletorError(
471
+ f"{Keywords.DENSITY_ACTIVATED_RECOVERY} keyword must be defined, if DAR is used in the completion"
472
+ )
435
473
  else:
436
474
  # Table headers
437
475
  header = [
438
476
  Headers.DEVICE_NUMBER,
439
- Headers.CV_DAR,
440
- Headers.AC_OIL,
441
- Headers.AC_GAS,
442
- Headers.AC_WATER,
443
- Headers.WHF_LCF_DAR,
444
- Headers.WHF_HCF_DAR,
445
- Headers.GHF_LCF_DAR,
446
- Headers.GHF_HCF_DAR,
477
+ Headers.FLOW_COEFFICIENT,
478
+ Headers.OIL_FLOW_CROSS_SECTIONAL_AREA,
479
+ Headers.GAS_FLOW_CROSS_SECTIONAL_AREA,
480
+ Headers.WATER_FLOW_CROSS_SECTIONAL_AREA,
481
+ Headers.WATER_HOLDUP_FRACTION_LOW_CUTOFF,
482
+ Headers.WATER_HOLDUP_FRACTION_HIGH_CUTOFF,
483
+ Headers.GAS_HOLDUP_FRACTION_LOW_CUTOFF,
484
+ Headers.GAS_HOLDUP_FRACTION_HIGH_CUTOFF,
447
485
  ]
448
486
 
449
487
  # Fix table format
450
- if self.completion_table[Headers.DEVICE_TYPE].str.contains("DAR").any():
451
- self.wsegdar_table = val.set_format_wsegdar(
488
+ if self.completion_table[Headers.DEVICE_TYPE].str.contains(Content.DENSITY_ACTIVATED_RECOVERY).any():
489
+ self.wsegdar_table = input_validation.set_format_wsegdar(
452
490
  self._create_dataframe_with_columns(header, start_index, end_index)
453
491
  )
454
- device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == "DAR"][
455
- Headers.DEVICE_NUMBER
456
- ].to_numpy()
492
+ device_checks = self.completion_table[
493
+ self.completion_table[Headers.DEVICE_TYPE] == Content.DENSITY_ACTIVATED_RECOVERY
494
+ ][Headers.DEVICE_NUMBER].to_numpy()
457
495
  if not check_contents(device_checks, self.wsegdar_table[Headers.DEVICE_NUMBER].to_numpy()):
458
- raise CompletorError("Not all device in COMPLETION is specified in WSEGDAR")
496
+ raise CompletorError(
497
+ f"Not all device in COMPLETION is specified in {Keywords.DENSITY_ACTIVATED_RECOVERY}"
498
+ )
459
499
 
460
500
  def read_wsegaicv(self) -> None:
461
- """Read the WSEGAICV keyword in the case file.
501
+ """Read the AUTONOMOUS_INFLOW_CONTROL_VALVE keyword in the case file.
462
502
 
463
503
  Raises:
464
- ValueError: If invalid entries in WSEGAICV.
465
- CompletorError: WSEGAICV keyword not defined when AICV is used in completion.
466
- If all devices in COMPLETION are not specified in WSEGAICV.
504
+ ValueError: If invalid entries in AUTONOMOUS_INFLOW_CONTROL_VALVE.
505
+ CompletorError: AUTONOMOUS_INFLOW_CONTROL_VALVE keyword not defined when AICV is used in completion.
506
+ If all devices in COMPLETION are not specified in AUTONOMOUS_INFLOW_CONTROL_VALVE.
467
507
  """
468
- start_index, end_index = self.locate_keyword(Keywords.WSEGAICV)
508
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.AUTONOMOUS_INFLOW_CONTROL_VALVE)
469
509
  if start_index == end_index:
470
- if "AICV" in self.completion_table[Headers.DEVICE_TYPE]:
471
- raise CompletorError("WSEGAICV keyword must be defined, if AICV is used in the completion.")
510
+ if Content.AUTONOMOUS_INFLOW_CONTROL_VALVE in self.completion_table[Headers.DEVICE_TYPE]:
511
+ raise CompletorError(
512
+ f"{Keywords.AUTONOMOUS_INFLOW_CONTROL_VALVE} keyword must be defined, "
513
+ "if AICV is used in the completion."
514
+ )
472
515
  else:
473
516
  # Table headers
474
517
  header = [
475
518
  Headers.DEVICE_NUMBER,
476
- Headers.WCT_AICV,
477
- Headers.GHF_AICV,
478
- Headers.RHOCAL_AICV,
479
- Headers.VISCAL_AICV,
519
+ Headers.AICV_WATER_CUT,
520
+ Headers.AICV_GAS_HOLDUP_FRACTION,
521
+ Headers.AICV_CALIBRATION_FLUID_DENSITY,
522
+ Headers.AICV_FLUID_VISCOSITY,
480
523
  Headers.ALPHA_MAIN,
481
524
  Headers.X_MAIN,
482
525
  Headers.Y_MAIN,
@@ -497,45 +540,47 @@ class ReadCasefile:
497
540
  Headers.F_PILOT,
498
541
  ]
499
542
  # Fix table format
500
- self.wsegaicv_table = val.set_format_wsegaicv(
543
+ self.wsegaicv_table = input_validation.set_format_wsegaicv(
501
544
  self._create_dataframe_with_columns(header, start_index, end_index)
502
545
  )
503
- # Check if the device in COMPLETION is exist in WSEGAICV
504
- device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == "AICV"][
505
- Headers.DEVICE_NUMBER
506
- ].to_numpy()
546
+ # Check if the device in COMPLETION is exist in AUTONOMOUS_INFLOW_CONTROL_VALVE
547
+ device_checks = self.completion_table[
548
+ self.completion_table[Headers.DEVICE_TYPE] == Content.AUTONOMOUS_INFLOW_CONTROL_VALVE
549
+ ][Headers.DEVICE_NUMBER].to_numpy()
507
550
  if not check_contents(device_checks, self.wsegaicv_table[Headers.DEVICE_NUMBER].to_numpy()):
508
- raise CompletorError("Not all devices in COMPLETION are specified in WSEGAICV")
551
+ raise CompletorError(
552
+ f"Not all devices in COMPLETION are specified in {Keywords.AUTONOMOUS_INFLOW_CONTROL_VALVE}"
553
+ )
509
554
 
510
555
  def read_wsegicv(self) -> None:
511
- """Read WSEGICV keyword in the case file.
556
+ """Read INFLOW_CONTROL_VALVE keyword in the case file.
512
557
 
513
558
  Raises:
514
- ValueError: If invalid entries in WSEGICV.
515
- CompletorError: WSEGICV keyword not defined when ICV is used in completion.
559
+ ValueError: If invalid entries in INFLOW_CONTROL_VALVE.
560
+ CompletorError: INFLOW_CONTROL_VALVE keyword not defined when ICV is used in completion.
516
561
  """
517
562
 
518
- start_index, end_index = self.locate_keyword(Keywords.WSEGICV)
563
+ start_index, end_index = parse.locate_keyword(self.content, Keywords.INFLOW_CONTROL_VALVE)
519
564
  if start_index == end_index:
520
- if "ICV" in self.completion_table[Headers.DEVICE_TYPE]:
521
- raise CompletorError("WSEGICV keyword must be defined, if ICV is used in the completion")
565
+ if Content.INFLOW_CONTROL_VALVE in self.completion_table[Headers.DEVICE_TYPE]:
566
+ raise CompletorError("INFLOW_CONTROL_VALVE keyword must be defined, if ICV is used in the completion")
522
567
  else:
523
568
  # Table headers
524
- header = [Headers.DEVICE_NUMBER, Headers.CV, Headers.AC]
569
+ header = [Headers.DEVICE_NUMBER, Headers.FLOW_COEFFICIENT, Headers.FLOW_CROSS_SECTIONAL_AREA]
525
570
  try:
526
571
  df_temp = self._create_dataframe_with_columns(header, start_index, end_index)
527
- df_temp[Headers.AC_MAX] = np.nan
572
+ df_temp[Headers.MAX_FLOW_CROSS_SECTIONAL_AREA] = np.nan
528
573
  except CaseReaderFormatError:
529
- header += [Headers.AC_MAX]
574
+ header += [Headers.MAX_FLOW_CROSS_SECTIONAL_AREA]
530
575
  df_temp = self._create_dataframe_with_columns(header, start_index, end_index)
531
576
  # Fix format
532
- self.wsegicv_table = val.set_format_wsegicv(df_temp)
533
- # Check if the device in COMPLETION exists in WSEGICV
534
- device_checks = self.completion_table[self.completion_table[Headers.DEVICE_TYPE] == "ICV"][
535
- Headers.DEVICE_NUMBER
536
- ].to_numpy()
577
+ self.wsegicv_table = input_validation.set_format_wsegicv(df_temp)
578
+ # Check if the device in COMPLETION exists in INFLOW_CONTROL_VALVE
579
+ device_checks = self.completion_table[
580
+ self.completion_table[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE
581
+ ][Headers.DEVICE_NUMBER].to_numpy()
537
582
  if not check_contents(device_checks, self.wsegicv_table[Headers.DEVICE_NUMBER].to_numpy()):
538
- raise CompletorError("Not all device in COMPLETION is specified in WSEGICV")
583
+ raise CompletorError("Not all device in COMPLETION is specified in INFLOW_CONTROL_VALVE")
539
584
 
540
585
  def get_completion(self, well_name: str | None, branch: int) -> pd.DataFrame:
541
586
  """Create the COMPLETION table for the selected well and branch.
@@ -551,7 +596,7 @@ class ReadCasefile:
551
596
  df_temp = df_temp[df_temp[Headers.BRANCH] == branch]
552
597
  return df_temp
553
598
 
554
- def check_input(self, well_name: str, schedule: WellSchedule) -> None:
599
+ def check_input(self, well_name: str, well_data: WellData) -> None:
555
600
  """Ensure that the completion table (given in the case-file) is complete.
556
601
 
557
602
  If one branch is completed, all branches must be completed, unless not 'strict'.
@@ -561,35 +606,45 @@ class ReadCasefile:
561
606
 
562
607
  Args:
563
608
  well_name: Well name.
564
- schedule: Schedule object.
609
+ schedule_data: Schedule file data.
565
610
 
566
611
  Returns:
567
612
  COMPLETION for that well and branch.
613
+
614
+ Raises:
615
+ CompletorError: If strict is true and there are undefined branches.
568
616
  """
569
- msw = schedule.msws[well_name]
570
- compl = self.completion_table[self.completion_table.WELL == well_name]
617
+ if sorted(list(well_data.keys())) != Keywords.main_keywords:
618
+ found_keys = set(well_data.keys())
619
+ raise CompletorError(
620
+ f"Well '{well_name}' is missing keyword(s): '{', '.join(set(Keywords.main_keywords) - found_keys)}'!"
621
+ )
622
+ df_completion = self.completion_table[self.completion_table.WELL == well_name]
623
+ # Check that all branches are defined in the case-file.
571
624
 
572
- # check that all branches are defined in case-file
573
- branch_nos = set(msw[Keywords.COMPSEGS].BRANCH).difference(set(compl.BRANCH))
625
+ # TODO(#173): Use TypedDict for this, and remove the type: ignore.
626
+ branch_nos = set(well_data[Keywords.COMPLETION_SEGMENTS][Headers.BRANCH]).difference( # type: ignore
627
+ set(df_completion[Headers.BRANCH])
628
+ )
574
629
  if len(branch_nos):
575
630
  logger.warning("Well %s has branch(es) not defined in case-file", well_name)
576
631
  if self.strict:
577
632
  raise CompletorError("USE_STRICT True: Define all branches in case file.")
578
- else:
579
- for branch_no in branch_nos:
580
- logger.warning("Adding branch %s for Well %s", branch_no, well_name)
581
- # copy first entry
582
- lateral = pd.DataFrame(
583
- [self.completion_table.loc[self.completion_table.WELL == well_name].iloc[0]],
584
- columns=self.completion_table.columns,
585
- )
586
- lateral[Headers.START_MEASURED_DEPTH] = 0
587
- lateral[Headers.END_MEASURED_DEPTH] = 999999
588
- lateral[Headers.DEVICE_TYPE] = "PERF"
589
- lateral[Headers.ANNULUS] = "GP"
590
- lateral[Headers.BRANCH] = branch_no
591
- # add new entry
592
- self.completion_table = pd.concat([self.completion_table, lateral])
633
+
634
+ for branch_no in branch_nos:
635
+ logger.warning("Adding branch %s for Well %s", branch_no, well_name)
636
+ # copy first entry
637
+ lateral = pd.DataFrame(
638
+ [self.completion_table.loc[self.completion_table.WELL == well_name].iloc[0]],
639
+ columns=self.completion_table.columns,
640
+ )
641
+ lateral[Headers.START_MEASURED_DEPTH] = 0
642
+ lateral[Headers.END_MEASURED_DEPTH] = 999999
643
+ lateral[Headers.DEVICE_TYPE] = Content.PERFORATED
644
+ lateral[Headers.ANNULUS] = Content.GRAVEL_PACKED
645
+ lateral[Headers.BRANCH] = branch_no
646
+ # add new entry
647
+ self.completion_table = pd.concat([self.completion_table, lateral])
593
648
 
594
649
  def connect_to_tubing(self, well_name: str, lateral: int) -> bool:
595
650
  """Connect a branch to the tubing- or device-layer.
@@ -607,9 +662,6 @@ class ReadCasefile:
607
662
  return False
608
663
  return True
609
664
 
610
- def locate_keyword(self, keyword: str) -> tuple[int, int]:
611
- return parse.locate_keyword(self.content, keyword)
612
-
613
665
  def _create_dataframe_with_columns(
614
666
  self, header: list[str], start_index: int, end_index: int, keyword: str | None = None
615
667
  ) -> pd.DataFrame:
@@ -629,7 +681,6 @@ class ReadCasefile:
629
681
  if keyword is None:
630
682
  keyword = self.content[start_index]
631
683
  table_header = " ".join(header)
632
- table_content = ""
633
684
  # Handle weirdly formed keywords.
634
685
  if start_index + 1 == end_index or self.content[start_index + 1].endswith("/"):
635
686
  content_str = "\n".join(self.content[start_index + 1 :]) + "\n"
@@ -664,14 +715,14 @@ class ReadCasefile:
664
715
  return parse.remove_string_characters(df_temp)
665
716
 
666
717
 
667
- def check_contents(values: np.ndarray, reference: np.ndarray) -> bool:
718
+ def check_contents(values: npt.NDArray[Any], reference: npt.NDArray[Any]) -> bool:
668
719
  """Check if all members of a list is in another list.
669
720
 
670
721
  Args:
671
- val_array: Array to be evaluated.
672
- ref_array: Reference array.
722
+ values: Array to be evaluated.
723
+ reference: Reference array.
673
724
 
674
725
  Returns:
675
- True if members of val_array are present in ref_array, false otherwise.
726
+ True if members of values are present in reference, false otherwise.
676
727
  """
677
728
  return all(comp in reference for comp in values)