completor 1.3.0__py3-none-any.whl → 1.5.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.
@@ -0,0 +1,563 @@
1
+ """Class to import user data and initialize the icv-control algorithm"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from datetime import datetime
7
+
8
+ import pandas as pd
9
+
10
+ from completor.constants import ICVMethod
11
+ from completor.logger import logger
12
+ from completor.read_casefile import ICVReadCasefile
13
+ from completor.utils import insert_comment_custom_content, reduce_newlines, remove_duplicates
14
+
15
+ FUTSTP_INIT = 0
16
+ FUTO_INIT = 0
17
+ FUTC_INIT = 0
18
+ FUP_INIT = 2
19
+
20
+
21
+ class Initialization:
22
+ """Initializes dicts for easy access. Creates the INPUT and UDQDEFINE files."""
23
+
24
+ def __init__(self, case_object: ICVReadCasefile, schedule_content: str | None = None):
25
+ self.case = case_object
26
+
27
+ if schedule_content is None:
28
+ schedule_content = ""
29
+
30
+ self.schedule_content = schedule_content
31
+ self.icv_control_table = case_object.icv_control_table
32
+
33
+ self.custom_conditions = case_object.custom_conditions
34
+ self.find_icv_names()
35
+ self.find_well_names()
36
+ self.find_segments()
37
+ self.find_opening_init()
38
+ self.find_areas()
39
+ self.find_steps()
40
+ self.find_frequency()
41
+ self.find_icv_dates()
42
+ self.check_dates_in_wells()
43
+ self.find_icvs_per_well()
44
+ self.create_init_icvcontrol()
45
+ self.create_input_icvcontrol()
46
+ self.create_summary_content()
47
+
48
+ def get_props(self, icv_date: datetime, icv_name: str, property: str) -> str | int | float:
49
+ """Get function for properties in the ICVCONTROL table.
50
+
51
+ Args:
52
+ icv_date: Date for an update in icv properties.
53
+ icv_name: Icv name. A letter from A through Z.
54
+ property: Property to be obtained from the ICVCONTROL table.
55
+
56
+ Returns:
57
+ An ICVCONTROL table property at a given date and for a given icv_name.
58
+ """
59
+ temp1 = self.icv_control_table[self.icv_control_table["ICVDATE"] == icv_date]
60
+ temp2 = temp1[temp1["ICV"] == icv_name]
61
+ return temp2[property].iloc[0]
62
+
63
+ def find_icv_names(self):
64
+ """Find unique icv names in the case file ICVCONTROL keyword."""
65
+ self.icv_names = self.icv_control_table["ICV"].unique()
66
+
67
+ def find_well_names(self):
68
+ """Find unique active well names in the case file ICVCONTROL keyword."""
69
+
70
+ self.well_names = {}
71
+ for icv_name in self.icv_names:
72
+ self.well_names[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["WELL"].iloc[
73
+ 0
74
+ ]
75
+
76
+ self.well_names_unique = list(set(self.well_names.values()))
77
+
78
+ def find_icvs_per_well(self):
79
+ """Find the number of unique valves pr well."""
80
+
81
+ self.icvs_per_well = {}
82
+ counted_icv_names = []
83
+ icv_well_combo = {}
84
+ for icv_name in self.icv_names:
85
+ if self.well_names[icv_name] not in self.icvs_per_well.keys():
86
+ self.icvs_per_well[self.well_names[icv_name]] = 1
87
+ counted_icv_names.append(icv_name)
88
+ icv_well_combo.update({icv_name: self.icvs_per_well[self.well_names[icv_name]]})
89
+ if icv_name not in counted_icv_names:
90
+ self.icvs_per_well[self.well_names[icv_name]] += 1
91
+ counted_icv_names.append(icv_name)
92
+ icv_well_combo.update({icv_name: self.icvs_per_well[self.well_names[icv_name]]})
93
+ self.icv_well_combo = icv_well_combo
94
+
95
+ def find_segments(self):
96
+ """Find segment number associated with each icv."""
97
+
98
+ self.segments = {}
99
+ for icv_name in self.icv_names:
100
+ self.segments[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["SEGMENT"].iloc[
101
+ 0
102
+ ]
103
+
104
+ def find_areas(self):
105
+ """Find unique icv areas in the case file ICVCONTROL keyword."""
106
+ self.areas = {}
107
+ for icv_name in self.icv_names:
108
+ self.areas[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["AC-TABLE"].iloc[0]
109
+
110
+ def find_steps(self):
111
+ """Find opersteps and waitsteps in the case file ICVCONTROL keyword."""
112
+
113
+ self.operation_step = {}
114
+ self.wait_step = {}
115
+ for icv_name in self.icv_names:
116
+ self.operation_step[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name][
117
+ "OPERSTEP"
118
+ ].iloc[0]
119
+ self.wait_step[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name][
120
+ "WAITSTEP"
121
+ ].iloc[0]
122
+
123
+ def find_opening_init(self):
124
+ self.init_opening = {}
125
+ # Add another column for table name`?`
126
+ for icv_name in self.icv_names:
127
+ self.init_opening[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name][
128
+ "OPENING"
129
+ ].iloc[0]
130
+
131
+ def find_frequency(self):
132
+ """Find unique icv frequency in the case file ICVCONTROL keyword."""
133
+
134
+ self.frequency = {}
135
+ for icv_name in self.icv_names:
136
+ self.frequency[icv_name] = self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["FREQ"].iloc[0]
137
+
138
+ def find_icv_dates(self):
139
+ """Find icv_dates in the case file ICVCONTROL keyword.
140
+
141
+ Note that this approach reserves icv names such as A and A2 for the first and
142
+ second appearance of icv A at dates number 1 and 2 in the ICVCONTROL table.
143
+ Similarly, B, B2 and B3 are reserved for the first, second and third appearance
144
+ of the icv B. All three appearances have distinct icv dates.
145
+ """
146
+
147
+ self.icv_dates = {}
148
+ for icv_name in self.icv_names:
149
+ # Get the ICVDATE for the current icv name
150
+ tmp_date = self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["ICVDATE"]
151
+ # Convert the date string to a datetime object
152
+ tmp = datetime.strptime(tmp_date.iloc[0], "%d.%b.%Y").date()
153
+ # Format the datetime object as a string in the desired format: 01.JAN.1970
154
+ tmp = tmp.strftime("%d %b %Y").upper()
155
+ # Replace the first occurrence of the original date with the formatted date
156
+ # It is replaced and not set to avoid pandas warnings.
157
+ tmp_date = tmp_date.replace([0, tmp_date.iloc[0]], [0, tmp])
158
+ if len(tmp_date) == 1: # If there is only one date for the icv name
159
+ self.icv_dates[icv_name] = tmp_date.iloc[0]
160
+ else: # If there are multiple dates for the icv name
161
+ self.icv_dates[icv_name] = tmp_date.iloc[0]
162
+ for idx in range(1, len(tmp_date)):
163
+ self.icv_dates[icv_name + str(idx + 1)] = tmp_date.iloc[idx]
164
+
165
+ def check_dates_in_wells(self) -> None:
166
+ """
167
+ Check if different ICV dates have been entered for ICVs placed on the same well.
168
+
169
+ This method iterates through the well names and ICV names in the
170
+ `well_names` dictionary and checks if different ICV dates have been
171
+ assigned to ICVs placed on the same well.
172
+ """
173
+ well_dates: dict[str, dict[str, str]] = {}
174
+ for well in set(self.well_names.values()):
175
+ well_dates[well] = {}
176
+ for icv_name in self.well_names:
177
+ if well == self.well_names[icv_name]:
178
+ well_dates[well][icv_name] = self.icv_dates[icv_name]
179
+ if len(set(well_dates[well].values())) > 1:
180
+ logger.warning(
181
+ "Different ICVDATE has been entered for ICVs placed on the same "
182
+ f"well. Well {well} got several dates. See {well_dates[well]}."
183
+ )
184
+
185
+ def number_of_icvs(self, icv_name: str) -> int:
186
+ """Finds the number of icvs in the well from an input icv_name.
187
+
188
+ Args:
189
+ icv_name: Letter from A through ZZ
190
+
191
+ Returns:
192
+ The number of icvs in the current well.
193
+
194
+ """
195
+ number_of_icvs = 0
196
+ for icv in self.icv_names:
197
+ if self.well_names[icv] == self.well_names[icv_name]:
198
+ number_of_icvs += 1
199
+ if number_of_icvs > 26:
200
+ raise ValueError("Not more than twenty-six valves per well")
201
+ return number_of_icvs
202
+
203
+ def create_init_icvcontrol(self):
204
+ """Create content of the init_icvcontrol.udq file.
205
+
206
+ Returns:
207
+ Content of the init_icvcontrol.udq icv-control file.
208
+
209
+ """
210
+ init_icvcontrol = "-- User input, specific for this input file\n\nUDQ\n\n" f"{60 * '-'}\n-- Time-stepping:\n\n"
211
+ for icv_name in self.icv_names:
212
+ table = {
213
+ "FUD": self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["FUD"].iloc[0],
214
+ "FUH": self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["FUH"].iloc[0],
215
+ "FUL": self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["FUL"].iloc[0],
216
+ }
217
+ for tstepping in ["FUD", "FUH", "FUL"]:
218
+ init_icvcontrol += f" ASSIGN {tstepping}_{icv_name} {table[tstepping]} /\n"
219
+
220
+ init_icvcontrol += "\n"
221
+ init_icvcontrol += f"\n{60 * '-'}\n-- Balance criteria\n\n"
222
+ sub_table = {}
223
+ for icv_name, icv_date in self.icv_dates.items():
224
+ if "na" in icv_name.lower():
225
+ raise ValueError("Python reads NA as NaN, thus ICV name cannot be NA!")
226
+ if icv_date not in sub_table:
227
+ sub_table[icv_date] = {}
228
+ if icv_name not in sub_table[icv_date]:
229
+ sub_table[icv_date][icv_name] = []
230
+ for _ in sub_table:
231
+ for icv_name in self.icv_names:
232
+ init_icvcontrol += (
233
+ f"\n ASSIGN FUTC_{icv_name} {FUTC_INIT} /\n"
234
+ f" ASSIGN FUTO_{icv_name} {FUTO_INIT} /\n"
235
+ f" ASSIGN FUP_{icv_name} {FUP_INIT} /\n"
236
+ )
237
+ init_icvcontrol += "/\n"
238
+
239
+ if self.case.icv_table:
240
+ init_icvcontrol += self.input_icv_opening_table(self.case.icv_table)
241
+ else:
242
+ logger.info("No ICVTABLES found, skipping writing table to init_icvcontrol.udq!")
243
+
244
+ self.init_icvcontrol = init_icvcontrol
245
+
246
+ def input_icv_opening_table(self, icv_tables: dict[str, pd.DataFrame]) -> str:
247
+ """Create the opening position tables content for init_icvcontrol.udq for
248
+ icv-control.
249
+
250
+ Args:
251
+ Icv opening tables.
252
+
253
+ Returns:
254
+ Updated contents of init_icvcontrol.udq.
255
+
256
+ """
257
+ icv_table_text = "\n\n" + 60 * "-" + "\n-- ICV opening position tables\n"
258
+ for key, value in icv_tables.items():
259
+ icv_table_text += "UDT\n-- ICV"
260
+ for icv, table in self.areas.items():
261
+ if key == table:
262
+ icv_table_text += f" {icv}"
263
+ icv_table_text += f"\n 'TU_{key}' 1 /\n"
264
+ if len(key) > 5:
265
+ logger.warning(
266
+ f"The table name '{key}' is longer than 5 characters. "
267
+ "This will create a UDT name longer than 8 characters which causes error in Eclipse."
268
+ )
269
+
270
+ position = " ".join(str(x) for x in value["POSITION"])
271
+ icv_table_text += f" 'NV' {position} /\n"
272
+ area = value["CV"] * value["AREA"]
273
+ formatted_area = [
274
+ f"{float(array_value):.3e}" for array_value in area
275
+ ] # put everything as scientific with 3 decimals
276
+ icv_table_text += f" {' '.join(formatted_area)} /\n /\n/\n\n"
277
+ return icv_table_text
278
+
279
+ def create_input_icvcontrol(self):
280
+ """Create the content of the input_icvcontrol.udq file for icv-control.
281
+
282
+ Returns:
283
+ Content of the input_icvcontrol.udq icv-control file.
284
+
285
+ """
286
+
287
+ futstp_init = 0
288
+ udq_define = "UDQ\n\n-- Initialization\n\n"
289
+ custom_fu_lines = "\n"
290
+ input_lines = "\n"
291
+ fufrq_lines = ""
292
+ fut_lines = "\n"
293
+ custom_content = None
294
+ icv_function = ICVMethod.UDQ
295
+ futstp_line = f" ASSIGN FUTSTP {futstp_init} /\n"
296
+ for icv_name in self.icv_names:
297
+ try:
298
+ fully_choked = self.case.min_table[icv_name][0]
299
+ fully_opened = self.case.max_table[icv_name][0]
300
+ except KeyError:
301
+ fully_choked = self.case.min_table[icv_name].iloc[0][0]
302
+ fully_opened = self.case.max_table[icv_name].iloc[0][0]
303
+ input_lines += f" ASSIGN FUCH_{icv_name} {fully_choked} /\n"
304
+ input_lines += f" ASSIGN FUOP_{icv_name} {fully_opened} /\n\n"
305
+ fufrq_lines += f" ASSIGN FUFRQ_{icv_name} {self.frequency[icv_name]} /\n"
306
+ fut_lines += f" ASSIGN FUT_{icv_name} {self.frequency[icv_name]} /\n"
307
+ custom_assignments = self._find_and_assign(icv_name, icv_function)
308
+ if custom_assignments:
309
+ custom_fu_lines += custom_assignments + "\n"
310
+
311
+ define_lines = "-- Definition of parameters,\n-- continuously updated:\n\n DEFINE FUTSTP TIMESTEP /\n"
312
+ for icv_name in self.icv_names:
313
+ define_lines += f" DEFINE FUT_{icv_name} FUT_{icv_name} + TIMESTEP /\n"
314
+ define_lines += "\n"
315
+ for icv_name in self.icv_names:
316
+ custom_content = self.get_custom_content(icv_name, icv_function, 1, False)
317
+
318
+ if custom_content is None:
319
+ define_lines += ""
320
+ logger.debug(f"No ICVALGORITHM given for icv {icv_name}.")
321
+ else:
322
+ define_lines += custom_content
323
+ if custom_content is not None:
324
+ if self.custom_conditions.get(icv_function).get("UDQ") is not None:
325
+ content = self.custom_conditions.get(icv_function).get("UDQ").get("1")
326
+ content = [f" {line.strip()}\n" for line in content.splitlines()]
327
+ for index, line in enumerate(content):
328
+ content[index] = re.sub(r"[^\w\)]*$", "", line) + " /\n"
329
+ content = "".join(content)
330
+ define_lines += content
331
+ if "ASSIGN" not in content:
332
+ logger.warning(
333
+ "When you define a custom UDQ without an ICV remember "
334
+ f"to assign every values. See ICVALGORITHM UDQ {content}"
335
+ )
336
+ area_lines = "\n"
337
+ if self.case.icv_table:
338
+ fu_pos, fu_area = self.assign_fupos_from_opening_table()
339
+ area_lines = fu_pos + fu_area
340
+
341
+ for icv_name in self.icv_names:
342
+ number = re.sub(r"[^A-Za-z_-]", "", self.areas[icv_name])
343
+ if number in ["", "E", "E-", "E+"]:
344
+ try:
345
+ init_opening_value = self.case.init_opening_table[icv_name][0]
346
+ if init_opening_value[0] == "T":
347
+ raise ValueError(
348
+ f"Table was reference in the case file for ICV {icv_name}, but no table was found."
349
+ )
350
+ except KeyError:
351
+ pass
352
+ if self.areas[icv_name] != "0":
353
+ area = self.areas[icv_name]
354
+ else:
355
+ area = self.icv_control_table[self.icv_control_table["ICV"] == icv_name]["AREA"].iloc[0]
356
+ area_lines += f" ASSIGN FUARE_{icv_name} {area} /\n"
357
+ udq_define += (
358
+ fufrq_lines + fut_lines + input_lines + futstp_line + custom_fu_lines + define_lines + area_lines + "/"
359
+ )
360
+ udq_define = reduce_newlines(udq_define)
361
+
362
+ self.input_icvcontrol = udq_define
363
+
364
+ def assign_fupos_from_opening_table(self) -> tuple[str, str]:
365
+ """Assign FUPOS_ICV to the max position of that ICV.
366
+
367
+ Returns:
368
+
369
+ Updated UDQDEFINE withst
370
+ FUPOS_ICV,
371
+ DEFINE _FUARE_ICV
372
+ """
373
+ fu_pos = "\n"
374
+ fu_area = "\n"
375
+ for icv, value in self.areas.items():
376
+ table_name = re.sub(r"[^A-Za-z_-]", "", value)
377
+ init_opening_value = self.init_opening[icv]
378
+ if init_opening_value[0] == "T":
379
+ position = re.sub("[^0-9]", "", init_opening_value)
380
+ elif float(init_opening_value) == 0 and table_name not in ["", "E"]:
381
+ position = self.case.icv_table[value]["POSITION"].max()
382
+ elif float(init_opening_value) != 0 and table_name not in ["", "E"]:
383
+ raise ValueError(
384
+ "Seems like you refer to a table, but have an opening value. "
385
+ f"See ICV '{icv}' with AC-Table '{table_name}' and opening "
386
+ f"value {init_opening_value}."
387
+ )
388
+
389
+ else:
390
+ continue
391
+ fu_pos += f" DEFINE FUPOS_{icv} {position} /\n"
392
+ fu_area += f" DEFINE FUARE_{icv} TU_{value}[FUPOS_{icv}] /\n"
393
+ return fu_pos, fu_area
394
+
395
+ def create_summary_content(self):
396
+ """Create the content of the summary file for icv-control.
397
+
398
+ Returns:
399
+ Content of the summary icv-control file.
400
+
401
+ """
402
+ fu_lines = ""
403
+ fuarea_lines = ""
404
+ fut_lines = ""
405
+
406
+ for icv_name in self.icv_names:
407
+ fus = ["FUP", "FUTC", "FUTO"]
408
+ for fu in fus:
409
+ fu_lines += f"{fu}_{icv_name}\n\n"
410
+ fuarea_lines += f"FUARE_{icv_name}\n\n"
411
+ fut_lines += f"FUT_{icv_name}\n\n"
412
+ # Positive look-behind to check word is preceded by ASSIGN/DEFINE
413
+ assigns = re.findall(r"(?<=ASSIGN\s)\w+", self.input_icvcontrol)
414
+ defines = re.findall(r"(?<=DEFINE\s)\w+", self.input_icvcontrol)
415
+ summary_values = assigns + defines + fu_lines.splitlines()
416
+ summary_values = remove_duplicates(summary_values)
417
+ summary = ""
418
+ for value in summary_values:
419
+ if len(value) > 8:
420
+ raise ValueError(
421
+ f"The UDQ parameter '{value}' is longer than 8 characters. This will cause errors in Eclipse."
422
+ )
423
+ summary += value + "\n\n"
424
+ sfopn_lines = "SFOPN\n"
425
+ well_segment = ""
426
+ for icv, well in self.well_names.items():
427
+ well_segment += f"'{well}' {self.segments[icv]} /\n"
428
+ well_segment += "/\n\n"
429
+ sfopn_lines += well_segment
430
+ self.summary = summary + sfopn_lines
431
+
432
+ def parse_custom_content(self, current_icv: str, custom_data: dict, content: str, is_end_of_records=True) -> str:
433
+ """Replace variables in custom content with their respective values.
434
+
435
+ E.g.
436
+ WELL(x) -> WellName
437
+ FURATE_x > FURATE_y -> FURATE_A > FURATE_B
438
+ FU_x1, FU_x2, FU_z -> FU_E, FU_F, FU_G
439
+
440
+ Args:
441
+ current_icv: Letter denoting the current icv name.
442
+ content: The content where unknowns
443
+ should be replaced with correct info.
444
+ is_end_of_records: If true add extra slash newline to
445
+ end the records.
446
+
447
+ Returns:
448
+ The content with replaced icvs/wells/segments.
449
+
450
+ """
451
+ if content is None:
452
+ return ""
453
+ for criteria in custom_data[current_icv]["map"]:
454
+ if criteria == "map":
455
+ pass
456
+ if isinstance(content, dict):
457
+ content = custom_data[current_icv][criteria]
458
+ if re.search(r"\d|\w", content) is None:
459
+ raise ValueError("Missing content in CONTROL_CRITERIA keyword!")
460
+ if current_icv != "UDQ":
461
+ for x_value, icv in custom_data[current_icv]["map"][criteria].items():
462
+ content = re.sub(rf"WELL\({x_value}\)", f"'{self.well_names[icv]}'", content)
463
+ content = re.sub(rf"SEG\({x_value}\)", str(self.segments[icv]), content)
464
+ content = re.sub(rf"\_{x_value}\b", f"_{icv}", content)
465
+ if current_icv != "UDQ":
466
+ for x_value, icv in custom_data[current_icv]["map"][criteria].items():
467
+ content = re.sub(rf"WELL\({x_value}\)", f"'{self.well_names[icv]}'", content)
468
+ content = re.sub(rf"SEG\({x_value}\)", str(self.segments[icv]), content)
469
+ content = re.sub(rf"\_{x_value}\b", f"_{icv}", content)
470
+ # Replace inconsistent end records and space with consistent version
471
+ content_lines = [f" {line.strip()}\n" for line in content.splitlines()]
472
+ for index, line in enumerate(content_lines):
473
+ if not line.isspace():
474
+ # Remove trailing non-word characters unless they are ) or ' at word boundary
475
+ content_lines[index] = re.sub(r"(?:\B'|[^\w\)'])*$", "", line) + " /\n"
476
+ else:
477
+ content_lines[index] = ""
478
+ content = "".join(content_lines)
479
+ content_variables = re.findall(r"[x]\d", content)
480
+ if content_variables != []:
481
+ logger.info(
482
+ f"ICVALGORITHM ICV {current_icv} criteria {criteria} "
483
+ f"contains an x value '{content_variables}' that did not "
484
+ f"get translated into an ICV-name.\nCustom criteria is: {content}."
485
+ )
486
+ if is_end_of_records:
487
+ return f"{insert_comment_custom_content()}{content}/\n"
488
+ return f"{insert_comment_custom_content()}{content}"
489
+
490
+ def get_custom_content(
491
+ self, icv_name: str, icv_function: ICVMethod, criteria: int | str | None, is_end_of_records: bool = True
492
+ ) -> str | None:
493
+ """Helper method to get correct custom content.
494
+
495
+ Args:
496
+ icv_name: Current ICV.
497
+ icv_function: The ICVMethod type calling this function.
498
+
499
+ Returns:
500
+ The custom content for the current ICV.
501
+
502
+ """
503
+ custom_content = None
504
+ if self.custom_conditions is not None:
505
+ custom_data = self.custom_conditions.get(icv_function)
506
+ if custom_data is not None:
507
+ if icv_function != ICVMethod.UDQ:
508
+ custom_content = ""
509
+ for icv in custom_data:
510
+ data = custom_data[icv].get(str(criteria))
511
+ if icv_name == icv.split()[0]:
512
+ custom_content += self.parse_custom_content(icv, custom_data, data, is_end_of_records)
513
+ if icv_name in custom_data:
514
+ data = custom_data[icv_name].get(str(criteria))
515
+ if data is None:
516
+ return None
517
+ else:
518
+ custom_content = self.parse_custom_content(icv_name, custom_data, data, is_end_of_records)
519
+ if icv_function == ICVMethod.UDQ:
520
+ custom_content = ""
521
+ for icv in custom_data:
522
+ if icv == "UDQ":
523
+ data = custom_data["UDQ"]
524
+ else:
525
+ data = custom_data[icv].get(str(criteria))
526
+ if icv_name == icv.split()[0]:
527
+ custom_content += self.parse_custom_content(icv, custom_data, data, is_end_of_records)
528
+ return custom_content
529
+
530
+ def _find_and_assign(self, icv_name: str, icv_function: ICVMethod = ICVMethod.UDQ) -> str | None:
531
+ """Helper method to find all define statements in custom content to
532
+ replace the 'DEFINE' statement with 'ASSIGN'.
533
+
534
+ Args:
535
+ icv_name: Current icv name.
536
+ icv_function: The icv method (typically ICVMethod.UDQ).
537
+
538
+ Returns:
539
+ All statements that are defined processed into assign-statements.
540
+
541
+ """
542
+ custom_content = self.get_custom_content(icv_name, icv_function, 1, False)
543
+ if custom_content is None:
544
+ return None
545
+ # Find all DEFINE some_word matches in text
546
+ defines = [match for match, _ in re.findall(r"(\b(DEFINE)\s+\b\w+\b)", custom_content)]
547
+ # Replace DEFINE with ASSIGN, and prepare format for writing to UDQDEFINE file.
548
+ init_value = {}
549
+ icvs_in_define = [line.split("_")[1] for line in defines]
550
+ assigns = ""
551
+ for current_icv in icvs_in_define:
552
+ try:
553
+ init_value[current_icv] = self.case.init_table[current_icv][0]
554
+ except KeyError:
555
+ init_value[current_icv] = self.case.init_table[current_icv].iloc[0][0]
556
+
557
+ for match in defines:
558
+ icv = icv_name
559
+ if match[-1] in icvs_in_define:
560
+ icv = match[-1]
561
+ assigns += f" {re.sub(r'(DEFINE)', 'ASSIGN', match)} {init_value[icv]} /\n"
562
+
563
+ return assigns
@@ -7,6 +7,9 @@ import pandas as pd
7
7
 
8
8
  from completor.constants import Content, Headers
9
9
  from completor.exceptions.clean_exceptions import CompletorError
10
+ from completor.exceptions.exceptions import CaseReaderFormatError
11
+
12
+ pd.set_option("future.no_silent_downcasting", True)
10
13
 
11
14
 
12
15
  def set_default_packer_section(df_comp: pd.DataFrame) -> pd.DataFrame:
@@ -399,3 +402,63 @@ def validate_minimum_segment_length(minimum_segment_length: str | float) -> floa
399
402
  if minimum_segment_length < 0.0:
400
403
  raise CompletorError(f"The MINIMUM_SEGMENT_LENGTH {minimum_segment_length} cannot be less than 0.0.")
401
404
  return minimum_segment_length
405
+
406
+
407
+ def set_format_icvcontrol(df_temp: pd.DataFrame) -> pd.DataFrame:
408
+ """Format the ICVCONTROL table.
409
+
410
+ Args:
411
+ df_temp: ICVCONTROL table.
412
+
413
+ Returns:
414
+ Updated ICVCONTROL formats.
415
+
416
+ The format of the ICVCONTROL table DataFrame is shown in
417
+ ``read_casefile.ReadCasefile.read_icv_control``.
418
+ """
419
+
420
+ config = {
421
+ "WELL": str,
422
+ "ICV": str,
423
+ "SEGMENT": int,
424
+ "AC-TABLE": str,
425
+ "STEPS": int,
426
+ "ICVDATE": str,
427
+ "FREQ": int,
428
+ "MAX": float,
429
+ "MIN": float,
430
+ "OPENING": str,
431
+ }
432
+ extended_config = {"FUD": float, "FUH": float, "FUL": float, "OPERSTEP": float, "WAITSTEP": float, "INIT": float}
433
+
434
+ try:
435
+ df_temp = df_temp.astype(extended_config)
436
+ except KeyError:
437
+ # Not using advanced mode, this is fine.
438
+ pass
439
+
440
+ try:
441
+ return df_temp.astype(config)
442
+ except Exception:
443
+ raise CaseReaderFormatError(
444
+ "ICVCONTROL table is formatted incorrectly. Note, the ordering of columns have changed."
445
+ )
446
+
447
+
448
+ def set_format_icv_table(df_temp: pd.DataFrame) -> pd.DataFrame:
449
+ """Format the ICVTABLE table.
450
+
451
+ Args:
452
+ df_temp: ICVTABLE table.
453
+
454
+ Returns:
455
+ Updated ICVTABLE formats.
456
+
457
+ The format of the ICVTABLE table DataFrame is shown in
458
+ ``read_casefile.ReadCasefile.read_icv_table``.
459
+
460
+ """
461
+ try:
462
+ return df_temp.astype({"POSITION": int, "CV": float, "AREA": float})
463
+ except Exception:
464
+ raise CompletorError("Keyword ICVTABLE has data with erroneous format.")
completor/logger.py CHANGED
@@ -59,7 +59,7 @@ def handle_error_messages(func):
59
59
  @wraps(func)
60
60
  def wrapper(*args, **kwargs):
61
61
  try:
62
- func(*args, **kwargs)
62
+ return func(*args, **kwargs)
63
63
  except (Exception, SystemExit) as ex:
64
64
  # SystemExit does not inherit from Exception
65
65
  if isinstance(ex, SystemExit):