completor 0.1.2__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/wells.py ADDED
@@ -0,0 +1,273 @@
1
+ """Classes to keep track of Well objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ import numpy.typing as npt
7
+ import pandas as pd
8
+
9
+ from completor import completion, read_schedule
10
+ from completor.constants import Content, Headers, Method, WellData
11
+ from completor.read_casefile import ReadCasefile
12
+
13
+
14
+ class Well:
15
+ """A well containing one or more laterals.
16
+
17
+ Attributes:
18
+ well_name: The name of the well.
19
+ well_number: The number of the well.
20
+ lateral_numbers: Numbers for each lateral.
21
+ active_laterals: List of laterals.
22
+ df_well_all_laterals: DataFrame containing all the laterals' well-layer data.
23
+ df_reservoir_all_laterals: DataFrame containing all the laterals' reservoir-layer data.
24
+ df_welsegs_header_all_laterals: DataFrame containing all the laterals' well-segments header data.
25
+ df_welsegs_content_all_laterals: DataFrame containing all the laterals' well-segments content.
26
+ """
27
+
28
+ well_name: str
29
+ well_number: int
30
+ df_well_all_laterals: pd.DataFrame
31
+ df_reservoir_all_laterals: pd.DataFrame
32
+ lateral_numbers: npt.NDArray[np.int64]
33
+ active_laterals: list[Lateral]
34
+ df_welsegs_header_all_laterals: pd.DataFrame
35
+ df_welsegs_content_all_laterals: pd.DataFrame
36
+
37
+ def __init__(self, well_name: str, well_number: int, case: ReadCasefile, well_data: WellData):
38
+ """Create well.
39
+
40
+ Args:
41
+ well_name: Well name.
42
+ case: Data from the case file.
43
+ well_data: Data from the schedule file.
44
+ """
45
+ # Note: Important to run this check before creating wells as it fixes potential problems with cases.
46
+ case.check_input(well_name, well_data)
47
+ self.well_name = well_name
48
+ self.well_number = well_number
49
+
50
+ lateral_numbers = self._get_active_laterals(well_name, case.completion_table)
51
+ self.active_laterals = [Lateral(num, well_name, case, well_data) for num in lateral_numbers]
52
+
53
+ self.df_well_all_laterals = pd.DataFrame()
54
+ self.df_reservoir_all_laterals = pd.DataFrame()
55
+ self.df_well_all_laterals = pd.concat([lateral.df_well for lateral in self.active_laterals], sort=False)
56
+ self.df_reservoir_all_laterals = pd.concat(
57
+ [lateral.df_reservoir for lateral in self.active_laterals], sort=False
58
+ )
59
+ self.df_welsegs_header_all_laterals = pd.concat(
60
+ [lateral.df_welsegs_header for lateral in self.active_laterals], sort=False
61
+ )
62
+ self.df_welsegs_content_all_laterals = pd.concat(
63
+ [lateral.df_welsegs_content for lateral in self.active_laterals], sort=False
64
+ )
65
+
66
+ @staticmethod
67
+ def _get_active_laterals(well_name: str, df_completion: pd.DataFrame) -> npt.NDArray[np.int_]:
68
+ """Get a list of lateral numbers for the well.
69
+
70
+ Args:
71
+ well_name: The well name.
72
+ df_completion: The completion information from case data.
73
+
74
+ Returns:
75
+ The active laterals.
76
+ """
77
+ return np.array(df_completion[df_completion[Headers.WELL] == well_name][Headers.BRANCH].unique())
78
+
79
+
80
+ class Lateral:
81
+ """Lateral containing data related to a specific well's branch.
82
+
83
+ Attributes:
84
+ lateral_number: Current lateral number.
85
+ df_completion: Completion data.
86
+ df_welsegs_header: Header for welsegs.
87
+ df_welsegs_content: Content for welsegs.
88
+ df_reservoir_header: Reservoir header data.
89
+ df_measured_true_vertical_depth: Data for measured and true vertical depths.
90
+ df_tubing: Data for tubing segments.
91
+ df_well: Data for well-layer.
92
+ df_reservoir: Data for reservoir-layer.
93
+ df_tubing: Tubing data.
94
+ df_device: Device data.
95
+ """
96
+
97
+ lateral_number: int
98
+ df_completion: pd.DataFrame
99
+ df_welsegs_header: pd.DataFrame
100
+ df_welsegs_content: pd.DataFrame
101
+ df_reservoir_header: pd.DataFrame
102
+ df_measured_true_vertical_depth: pd.DataFrame
103
+ df_well: pd.DataFrame
104
+ df_reservoir: pd.DataFrame
105
+ df_tubing: pd.DataFrame
106
+ df_device: pd.DataFrame
107
+
108
+ def __init__(self, lateral_number: int, well_name: str, case: ReadCasefile, well_data: WellData):
109
+ """Create Lateral.
110
+
111
+ Args:
112
+ lateral_number: Number of the current lateral/branch.
113
+ well_name: The well's name.
114
+ case: The case data.
115
+ well_data: This wells' schedule data.
116
+ """
117
+ self.lateral_number = lateral_number
118
+ self.df_completion = case.get_completion(well_name, lateral_number)
119
+ self.df_welsegs_header, self.df_welsegs_content = read_schedule.get_well_segments(well_data, lateral_number)
120
+
121
+ self.df_device = pd.DataFrame()
122
+
123
+ self.df_reservoir = self._select_well(well_name, well_data, lateral_number)
124
+ if self.df_reservoir.empty:
125
+ print("")
126
+ self.df_measured_true_vertical_depth = completion.well_trajectory(
127
+ self.df_welsegs_header, self.df_welsegs_content
128
+ )
129
+ self.df_completion = completion.define_annulus_zone(self.df_completion)
130
+ self.df_tubing = self._create_tubing_segments(
131
+ self.df_reservoir, self.df_completion, self.df_measured_true_vertical_depth, case
132
+ )
133
+ self.df_tubing = completion.insert_missing_segments(self.df_tubing, well_name)
134
+ self.df_well = completion.complete_the_well(self.df_tubing, self.df_completion, case.joint_length)
135
+ self.df_well = self._get_devices(self.df_completion, self.df_well, case)
136
+ self.df_well = completion.correct_annulus_zone(self.df_well)
137
+ self.df_reservoir = self._connect_cells_to_segments(
138
+ self.df_reservoir, self.df_well, self.df_tubing, case.method
139
+ )
140
+ self.df_well[Headers.WELL] = well_name
141
+ self.df_reservoir[Headers.WELL] = well_name
142
+ self.df_well[Headers.LATERAL] = lateral_number
143
+ self.df_reservoir[Headers.LATERAL] = lateral_number
144
+
145
+ @staticmethod
146
+ def _select_well(well_name: str, well_data: WellData, lateral: int) -> pd.DataFrame:
147
+ """Filter the reservoir data for this well and its laterals.
148
+
149
+ Args:
150
+ well_name: The name of the well.
151
+ well_data: Multisegmented well segment data.
152
+ lateral: The lateral number.
153
+
154
+ Returns:
155
+ Filtered reservoir data.
156
+ """
157
+ df_compsegs = read_schedule.get_completion_segments(well_data, well_name, lateral)
158
+ df_compdat = read_schedule.get_completion_data(well_data)
159
+ df_reservoir = pd.merge(df_compsegs, df_compdat, how="inner", on=[Headers.I, Headers.J, Headers.K])
160
+
161
+ # Remove WELL column in the df_reservoir.
162
+ df_reservoir = df_reservoir.drop([Headers.WELL], axis=1)
163
+ # If multiple occurrences of same IJK in compdat/compsegs --> keep the last one.
164
+ df_reservoir = df_reservoir.drop_duplicates(subset=Headers.START_MEASURED_DEPTH, keep="last")
165
+ return df_reservoir.reset_index()
166
+
167
+ @staticmethod
168
+ def _connect_cells_to_segments(
169
+ df_reservoir: pd.DataFrame, df_well: pd.DataFrame, df_tubing_segments: pd.DataFrame, method: Method
170
+ ) -> pd.DataFrame:
171
+ """Connect cells to the well.
172
+
173
+ Notes:
174
+ Only some columns from well DataFrame are needed: MEASURED_DEPTH, NDEVICES, DEVICETYPE, and ANNULUS_ZONE.
175
+ ICV placement forces different methods in segment creation as USER defined.
176
+
177
+ Args:
178
+ df_reservoir: Reservoir data.
179
+ df_well: Well data.
180
+ df_tubing_segments: Tubing information.
181
+ method: The method to use for creating segments.
182
+
183
+ Returns:
184
+ Reservoir data with additional info on connected cells.
185
+ """
186
+ # drop BRANCH column, not needed
187
+ df_reservoir = df_reservoir.drop([Headers.BRANCH], axis=1)
188
+ icv_device = (
189
+ df_well[Headers.DEVICE_TYPE].nunique() > 1
190
+ and (df_well[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE).any()
191
+ and not df_well[Headers.NUMBER_OF_DEVICES].empty
192
+ )
193
+ method = Method.USER if icv_device else method
194
+ df_well = df_well[
195
+ [Headers.TUBING_MEASURED_DEPTH, Headers.NUMBER_OF_DEVICES, Headers.DEVICE_TYPE, Headers.ANNULUS_ZONE]
196
+ ]
197
+ return completion.connect_cells_to_segments(df_well, df_reservoir, df_tubing_segments, method)
198
+
199
+ @staticmethod
200
+ def _get_devices(df_completion: pd.DataFrame, df_well: pd.DataFrame, case: ReadCasefile) -> pd.DataFrame:
201
+ """Complete the well with the device information.
202
+
203
+ Args:
204
+ df_completion: Completion information.
205
+ df_well: Well data.
206
+ case: Case data.
207
+
208
+ Returns:
209
+ Well data with device information.
210
+ """
211
+ if not case.completion_icv_tubing.empty:
212
+ active_devices = pd.concat(
213
+ [df_completion[Headers.DEVICE_TYPE], case.completion_icv_tubing[Headers.DEVICE_TYPE]]
214
+ ).unique()
215
+ else:
216
+ active_devices = df_completion[Headers.DEVICE_TYPE].unique()
217
+ if Content.VALVE in active_devices:
218
+ df_well = completion.get_device(df_well, case.wsegvalv_table, Content.VALVE)
219
+ if Content.INFLOW_CONTROL_DEVICE in active_devices:
220
+ df_well = completion.get_device(df_well, case.wsegsicd_table, Content.INFLOW_CONTROL_DEVICE)
221
+ if Content.AUTONOMOUS_INFLOW_CONTROL_DEVICE in active_devices:
222
+ df_well = completion.get_device(df_well, case.wsegaicd_table, Content.AUTONOMOUS_INFLOW_CONTROL_DEVICE)
223
+ if Content.DENSITY_ACTIVATED_RECOVERY in active_devices:
224
+ df_well = completion.get_device(df_well, case.wsegdar_table, Content.DENSITY_ACTIVATED_RECOVERY)
225
+ if Content.AUTONOMOUS_INFLOW_CONTROL_VALVE in active_devices:
226
+ df_well = completion.get_device(df_well, case.wsegaicv_table, Content.AUTONOMOUS_INFLOW_CONTROL_VALVE)
227
+ if Content.INFLOW_CONTROL_VALVE in active_devices:
228
+ df_well = completion.get_device(df_well, case.wsegicv_table, Content.INFLOW_CONTROL_VALVE)
229
+ return df_well
230
+
231
+ @staticmethod
232
+ def _create_tubing_segments(
233
+ df_reservoir: pd.DataFrame, df_completion: pd.DataFrame, df_mdtvd: pd.DataFrame, case: ReadCasefile
234
+ ) -> pd.DataFrame:
235
+ """Create tubing segments based on the method and presence of Inflow Control Valves (ICVs).
236
+
237
+ The behavior of the df_tubing_segments will vary depending on the existence of the ICV keyword.
238
+ When the ICV keyword is present, it always creates a lumped tubing segment on its interval,
239
+ whereas other types of devices follow the default input.
240
+ If there is a combination of ICV and other devices (with devicetype > 1),
241
+ it results in a combination of ICV segment length with segment lumping,
242
+ and default segment length on other devices.
243
+
244
+ Args:
245
+ df_reservoir: Reservoir data.
246
+ df_completion: Completion information.
247
+ df_mdtvd: Measured and true vertical depths.
248
+ case: Case data, including the Method used to create segments.
249
+
250
+ Returns:
251
+ Tubing data.
252
+ """
253
+ df_tubing_segments_cells = completion.create_tubing_segments(
254
+ df_reservoir, df_completion, df_mdtvd, case.method, case.segment_length, case.minimum_segment_length
255
+ )
256
+
257
+ df_tubing_segments_user = completion.create_tubing_segments(
258
+ df_reservoir, df_completion, df_mdtvd, Method.USER, case.segment_length, case.minimum_segment_length
259
+ )
260
+
261
+ if (pd.unique(df_completion[Headers.DEVICE_TYPE]).size > 1) & (
262
+ (df_completion[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE)
263
+ & (df_completion[Headers.VALVES_PER_JOINT] > 0)
264
+ ).any():
265
+ return read_schedule.fix_compsegs_by_priority(
266
+ df_completion, df_tubing_segments_cells, df_tubing_segments_user
267
+ )
268
+
269
+ # If all the devices are ICVs, lump the segments.
270
+ if (df_completion[Headers.DEVICE_TYPE] == Content.INFLOW_CONTROL_VALVE).all():
271
+ return df_tubing_segments_user
272
+ # If none of the devices are ICVs use defined method.
273
+ return df_tubing_segments_cells
@@ -1,29 +1,28 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: completor
3
- Version: 0.1.2
3
+ Version: 1.0.0
4
4
  Summary: Advanced mulit-segmented well completion tool.
5
5
  Home-page: https://github.com/equinor/completor
6
6
  License: LGPL-3.0-only
7
7
  Author: Equinor ASA
8
8
  Author-email: opensource@equinor.com
9
- Requires-Python: >=3.8.1,<3.12
9
+ Requires-Python: >=3.11,<3.12
10
10
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
11
11
  Classifier: Natural Language :: English
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
- Classifier: Programming Language :: Python :: 3.10
16
14
  Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.8
18
15
  Provides-Extra: ert
19
16
  Requires-Dist: docutils (==0.20.1) ; extra == "ert"
20
17
  Requires-Dist: ert (>=10.1.2,<11.0.0) ; extra == "ert"
21
- Requires-Dist: matplotlib (>=3.7,<4.0)
22
- Requires-Dist: numpy (>=1.24,<2.0)
23
- Requires-Dist: pandas (>=2.0,<3.0)
18
+ Requires-Dist: matplotlib (>=3.9.2,<4.0.0)
19
+ Requires-Dist: numpy (>=1.26.4,<2.0.0)
20
+ Requires-Dist: pandas (>=2.2.2,<3.0.0)
24
21
  Requires-Dist: pyqt5-qt5 (==5.15.2) ; extra == "ert"
22
+ Requires-Dist: pytest-env (>=1.1.4,<2.0.0)
25
23
  Requires-Dist: rstcheck-core (>=1.2.1,<2.0.0) ; extra == "ert"
26
- Requires-Dist: scipy (>=1.10,<2.0)
24
+ Requires-Dist: scipy (>=1.14.1,<2.0.0)
25
+ Requires-Dist: tqdm (>=4.66.5,<5.0.0)
27
26
  Project-URL: Bug Tracker, https://github.com/equinor/completor/issues
28
27
  Project-URL: Documentation, https://equinor.github.io/completor
29
28
  Project-URL: Repository, https://github.com/equinor/completor
@@ -43,7 +42,7 @@ Detailed documentation for usage of completor can be found at https://equinor.gi
43
42
  ## Getting started as a user
44
43
 
45
44
  ### Prerequisites
46
- * [Python](https://www.python.org/), version 3.8 to 3.11
45
+ * [Python](https://www.python.org/), version 3.11
47
46
  * [ERT](https://github.com/equinor/ert) (optional, and only available on Linux.)
48
47
 
49
48
  ### Installation
@@ -84,7 +83,7 @@ and making a pull request.
84
83
  See [Contribution Document](documentation/docs/contribution_guide.mdx) on how to contribute.
85
84
 
86
85
  ### Install completor as dev
87
- In order to run Completor® you need to have versions of [Python 3.8 to 3.11](https://www.python.org/downloads/) installed.
86
+ In order to run Completor® you need to have versions of [Python 3.11](https://www.python.org/downloads/) installed.
88
87
  #### Source code
89
88
  Clone the [Completor® repository](https://github.com/equinor/completor) to get the source code.
90
89
  ```bash
@@ -0,0 +1,27 @@
1
+ completor/__init__.py,sha256=k6amf7jhp7KkBIlaw93-NZITxyZjPSzA5McFAZh8yv8,76
2
+ completor/completion.py,sha256=aNraZwUQs0jJX080ryPsiaTQUeYbtBP4vLlL0oaqUUs,30829
3
+ completor/config_jobs/run_completor,sha256=XePKj2xocfGF0XFRqr7sqfpZGrjgWcfaZLHIhVvGFCQ,600
4
+ completor/constants.py,sha256=Th7fzID5NPUhyV2RRc5h9qRQ963cKGT3TVCX_bsAtHA,12802
5
+ completor/create_output.py,sha256=ZYFIJCawb51m_7FuCStvs2fv1FTmz02-_QrjOD9cFBo,23174
6
+ completor/exceptions/__init__.py,sha256=Aj_N17UnldYBA-UgxYSYwIh5ib_fZmqqvzf4yFz_DzY,186
7
+ completor/exceptions/clean_exceptions.py,sha256=uQ2jgGfvUDAoS56JBWh-8ZCPIMB4hvYxLF-snA-_-pg,373
8
+ completor/exceptions/exceptions.py,sha256=eS00mVzmC4gQ7MUyGSknvKotmo2XzNGyp007mhjLMy0,5081
9
+ completor/get_version.py,sha256=LMzybu7m-O203lUSZvyu86J4bMobbjE9A23fWLDlxaM,168
10
+ completor/hook_implementations/jobs.py,sha256=7yJg-OD5_JwUPfsvn15suWKhY8XAsCLlR6zBTZckDJY,2120
11
+ completor/input_validation.py,sha256=Jsk96_r61so8q-c-aCPrqivdl0nxBRDGS5NUmpembGs,13478
12
+ completor/launch_args_parser.py,sha256=gb3FcyufZlRnKS3BZkFmgVH1VoSxMD0MbCLsHZKmz4c,1413
13
+ completor/logger.py,sha256=gYDbPL8ca5qT_MqYlDKEMKcSfIXW_59QklX8Gss5b2U,4784
14
+ completor/main.py,sha256=5rDozysrKRD98eq8VD5ZM3oi64tisujCT0A145x0pbI,9020
15
+ completor/parse.py,sha256=EGlt9CgkrXPqa7woyWQ5t_nh6OWsFrc2SJr8aFr_KsQ,20133
16
+ completor/prepare_outputs.py,sha256=8xE7ph2VKllRCQdE5xAbIfBFSfENm7JvFb5VKaqjar0,72064
17
+ completor/read_casefile.py,sha256=0e7zE9QeQnT63oCin9BgtS2vItXUhfp760RIfl1cpYI,34021
18
+ completor/read_schedule.py,sha256=IYyCubOggFGg664h1flTl7MUJhJWyibr6JsptnURjUA,18101
19
+ completor/utils.py,sha256=oiUyYKxFFHpUYOiwsXQCY_e6uQHwsUBzImD3wYbJPDg,14538
20
+ completor/visualization.py,sha256=ObxThqIyW3fsvVIupxVsgySFI_54n_Di-HTzFtDMSgo,4580
21
+ completor/visualize_well.py,sha256=I2kp2rnM2eFV4RUWnTTysPTqQKNEBicF2S9Z3rrAsNA,8672
22
+ completor/wells.py,sha256=G8hjBpV4paUrjlM3aUWREqtdXwGlnm614qvE3V6YtNM,12182
23
+ completor-1.0.0.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
24
+ completor-1.0.0.dist-info/METADATA,sha256=w9aQgLvvcESvHJe6b6Jzrv-eqQK5_W2dH4Df1IMA5pQ,7922
25
+ completor-1.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
26
+ completor-1.0.0.dist-info/entry_points.txt,sha256=co5L2_CC2QQWVdEALeMp-NIC4mx4nRpcLcvpVXMYdeI,106
27
+ completor-1.0.0.dist-info/RECORD,,
completor/create_wells.py DELETED
@@ -1,314 +0,0 @@
1
- """Module for creating completion structure."""
2
-
3
- from __future__ import annotations
4
-
5
- import numpy as np
6
- import pandas as pd
7
-
8
- from completor import completion
9
- from completor.constants import Headers, Method
10
- from completor.logger import logger
11
- from completor.read_casefile import ReadCasefile
12
- from completor.read_schedule import fix_compsegs_by_priority
13
-
14
- try:
15
- import numpy.typing as npt
16
- except ImportError:
17
- pass
18
-
19
-
20
- class CreateWells:
21
- """Class for creating well completion structure.
22
-
23
- Args:
24
- case: ReadCasefile class.
25
-
26
- Attributes:
27
- active_wells: Active wells defined in the case file.
28
- method: Method for segment creation.
29
- well_name: Well name (in loop).
30
- laterals: List of lateral number of the well in loop.
31
- df_completion: Completion data frame in loop.
32
- df_reservoir: COMPDAT and COMPSEGS data frame fusion in loop.
33
- df_welsegs_header: WELSEGS first record.
34
- df_welsegs_content: WELSEGS second record.
35
- df_mdtvd: Data frame of MD and TVD relationship.
36
- df_tubing_segments: Tubing segment data frame.
37
- df_well: Data frame after completion.
38
- df_well_all: Data frame (df_well) for all laterals after completion.
39
- df_reservoir_all: df_reservoir for all laterals.
40
- """
41
-
42
- def __init__(self, case: ReadCasefile):
43
- """Initialize CreateWells."""
44
- self.well_name: str | None = None
45
- self.df_reservoir = pd.DataFrame()
46
- self.df_mdtvd = pd.DataFrame()
47
- self.df_completion = pd.DataFrame()
48
- self.df_tubing_segments = pd.DataFrame()
49
- self.df_well = pd.DataFrame()
50
- self.df_compdat = pd.DataFrame()
51
- self.df_well_all = pd.DataFrame()
52
- self.df_reservoir_all = pd.DataFrame()
53
- self.df_welsegs_header = pd.DataFrame()
54
- self.df_welsegs_content = pd.DataFrame()
55
- self.laterals: list[int] = []
56
-
57
- self.case: ReadCasefile = case
58
- self.active_wells = self._active_wells()
59
- self.method = self._method()
60
-
61
- def update(self, well_name: str, schedule: completion.WellSchedule) -> None:
62
- """Update class variables in CreateWells.
63
-
64
- Args:
65
- well_name: Well name.
66
- schedule: ReadSchedule object.
67
- """
68
- self.well_name = well_name
69
- self._active_laterals()
70
- for lateral in self.laterals:
71
- self.select_well(schedule, lateral)
72
- self.well_trajectory()
73
- self.define_annulus_zone()
74
- self.create_tubing_segments()
75
- self.insert_missing_segments()
76
- self.complete_the_well()
77
- self.get_devices()
78
- self.correct_annulus_zone()
79
- self.connect_cells_to_segments()
80
- self.add_well_lateral_column(lateral)
81
- self.combine_df(lateral)
82
-
83
- def _active_wells(self) -> npt.NDArray[np.unicode_]:
84
- """Get a list of active wells specified by users.
85
-
86
- If the well has annulus content with gravel pack and the well is perforated,
87
- Completor will not add a device layer.
88
- Completor does nothing to gravel-packed perforated wells by default.
89
- This behavior can be changed by setting the GP_PERF_DEVICELAYER keyword in the case file to true.
90
-
91
- Returns:
92
- Active wells.
93
- """
94
- # Need to check completion of all wells in completion table to remove GP-PERF type wells
95
- active_wells = list(set(self.case.completion_table[Headers.WELL]))
96
- # We cannot update a list while iterating of it
97
- for well_name in active_wells.copy():
98
- # Annulus content of each well
99
- ann_series = self.case.completion_table[self.case.completion_table[Headers.WELL] == well_name][
100
- Headers.ANNULUS
101
- ]
102
- type_series = self.case.completion_table[self.case.completion_table[Headers.WELL] == well_name][
103
- Headers.DEVICE_TYPE
104
- ]
105
- gp_check = not ann_series.isin(["OA"]).any()
106
- perf_check = not type_series.isin(["AICD", "AICV", "DAR", "ICD", "VALVE", "ICV"]).any()
107
- if gp_check and perf_check and not self.case.gp_perf_devicelayer:
108
- # De-activate wells with GP_PERF if instructed to do so:
109
- active_wells.remove(well_name)
110
- if not active_wells:
111
- logger.warning(
112
- "There are no active wells for Completor to work on. E.g. all wells are defined with Gravel Pack "
113
- "(GP) and valve type PERF. If you want these wells to be active set GP_PERF_DEVICELAYER to TRUE."
114
- )
115
- return np.array([])
116
- return np.array(active_wells)
117
-
118
- def _method(self) -> Method:
119
- """Define how the user wants to create segments.
120
-
121
- Returns:
122
- Creation method enum.
123
-
124
- Raises:
125
- ValueError: If method is not one of the defined methods.
126
- """
127
- if isinstance(self.case.segment_length, float):
128
- if float(self.case.segment_length) > 0.0:
129
- return Method.FIX
130
- if float(self.case.segment_length) == 0.0:
131
- return Method.CELLS
132
- if self.case.segment_length < 0.0:
133
- return Method.USER
134
- else:
135
- raise ValueError(
136
- f"Unrecognized method '{self.case.segment_length}' in SEGMENTLENGTH keyword. "
137
- "The value should be one of: 'WELSEGS', 'CELLS', 'USER', or a number: -1 for 'USER', "
138
- "0 for 'CELLS', or a positive number for 'FIX'."
139
- )
140
-
141
- elif isinstance(self.case.segment_length, str):
142
- if "welsegs" in self.case.segment_length.lower() or "infill" in self.case.segment_length.lower():
143
- return Method.WELSEGS
144
- if "cell" in self.case.segment_length.lower():
145
- return Method.CELLS
146
- if "user" in self.case.segment_length.lower():
147
- return Method.USER
148
- else:
149
- raise ValueError(
150
- f"Unrecognized method '{self.case.segment_length}' in SEGMENTLENGTH keyword. "
151
- "The value should be one of: "
152
- "'WELSEGS', 'CELLS', 'USER', or a number: -1 for 'USER', 0 for 'CELLS', positive number for 'FIX'."
153
- )
154
- else:
155
- raise ValueError(
156
- f"Unrecognized type of '{self.case.segment_length}' in SEGMENTLENGTH keyword. "
157
- "The keyword must either be float or string."
158
- )
159
-
160
- def _active_laterals(self) -> None:
161
- """Get a list of lateral numbers for the well.
162
-
163
- ``get_active_laterals`` uses the case class DataFrame property
164
- ``completion_table`` with a format as shown in the function
165
- ``read_casefile.ReadCasefile.read_completion``.
166
- """
167
- self.laterals = list(
168
- self.case.completion_table[self.case.completion_table[Headers.WELL] == self.well_name][
169
- Headers.BRANCH
170
- ].unique()
171
- )
172
-
173
- def select_well(self, schedule: completion.WellSchedule, lateral: int) -> None:
174
- """Filter all the required DataFrames for this well and its laterals."""
175
- if self.well_name is None:
176
- raise ValueError("No well name given")
177
-
178
- self.df_completion = self.case.get_completion(self.well_name, lateral)
179
- self.df_welsegs_header, self.df_welsegs_content = schedule.get_well_segments(self.well_name, lateral)
180
- df_compsegs = schedule.get_compsegs(self.well_name, lateral)
181
- df_compdat = schedule.get_compdat(self.well_name)
182
- self.df_reservoir = pd.merge(df_compsegs, df_compdat, how="inner", on=[Headers.I, Headers.J, Headers.K])
183
-
184
- # Remove WELL column in the df_reservoir.
185
- self.df_reservoir.drop([Headers.WELL], inplace=True, axis=1)
186
- # If multiple occurrences of same IJK in compdat/compsegs --> keep the last one.
187
- self.df_reservoir.drop_duplicates(subset=Headers.START_MEASURED_DEPTH, keep="last", inplace=True)
188
- self.df_reservoir.reset_index(inplace=True)
189
-
190
- def well_trajectory(self) -> None:
191
- """Create trajectory DataFrame relations between measured depth and true vertical depth."""
192
- self.df_mdtvd = completion.well_trajectory(self.df_welsegs_header, self.df_welsegs_content)
193
-
194
- def define_annulus_zone(self) -> None:
195
- """Define an annulus zone if specified."""
196
- self.df_completion = completion.define_annulus_zone(self.df_completion)
197
-
198
- def create_tubing_segments(self) -> None:
199
- """Create tubing segments as the basis.
200
-
201
- The function creates a class property DataFrame df_tubing_segments
202
- from the class property DataFrames df_reservoir, df_completion, and df_mdtvd.
203
-
204
- The behavior of the df_tubing_segments will vary depending on the existence of the ICV keyword.
205
- When the ICV keyword is present, it always creates a lumped tubing segment on its interval,
206
- whereas other types of devices follow the default input.
207
- If there is a combination of an ICV and other devices (with devicetype >1),
208
- this results in a combination of ICV segment length with segment lumping,
209
- and default segment length on other devices.
210
- """
211
- df_tubing_cells = completion.create_tubing_segments(
212
- self.df_reservoir,
213
- self.df_completion,
214
- self.df_mdtvd,
215
- self.method,
216
- self.case.segment_length,
217
- self.case.minimum_segment_length,
218
- )
219
-
220
- df_tubing_user = completion.create_tubing_segments(
221
- self.df_reservoir,
222
- self.df_completion,
223
- self.df_mdtvd,
224
- Method.USER,
225
- self.case.segment_length,
226
- self.case.minimum_segment_length,
227
- )
228
-
229
- if (len(self.df_completion[Headers.DEVICE_TYPE].unique()) > 1) & (
230
- (self.df_completion[Headers.DEVICE_TYPE] == "ICV") & (self.df_completion[Headers.VALVES_PER_JOINT] > 0)
231
- ).any():
232
- self.df_tubing_segments = fix_compsegs_by_priority(self.df_completion, df_tubing_cells, df_tubing_user)
233
-
234
- # If all the devices are ICVs, lump the segments.
235
- elif (self.df_completion[Headers.DEVICE_TYPE] == "ICV").all():
236
- self.df_tubing_segments = df_tubing_user
237
- # If none of the devices are ICVs use defined method.
238
- else:
239
- self.df_tubing_segments = df_tubing_cells
240
-
241
- def insert_missing_segments(self) -> None:
242
- """Create a placeholder segment for inactive cells."""
243
- self.df_tubing_segments = completion.insert_missing_segments(self.df_tubing_segments, self.well_name)
244
-
245
- def complete_the_well(self) -> None:
246
- """Complete the well with users' completion design."""
247
- self.df_well = completion.complete_the_well(self.df_tubing_segments, self.df_completion, self.case.joint_length)
248
- self.df_well[Headers.ROUGHNESS] = self.df_well[Headers.ROUGHNESS].apply(lambda x: f"{x:.3E}")
249
-
250
- def get_devices(self) -> None:
251
- """Complete the well with the device information.
252
-
253
- Updates the class property DataFrame df_well described in ``complete_the_well``.
254
- """
255
- if not self.case.completion_icv_tubing.empty:
256
- active_devices = pd.concat(
257
- [self.df_completion[Headers.DEVICE_TYPE], self.case.completion_icv_tubing[Headers.DEVICE_TYPE]]
258
- ).unique()
259
- else:
260
- active_devices = self.df_completion[Headers.DEVICE_TYPE].unique()
261
- if "VALVE" in active_devices:
262
- self.df_well = completion.get_device(self.df_well, self.case.wsegvalv_table, "VALVE")
263
- if "ICD" in active_devices:
264
- self.df_well = completion.get_device(self.df_well, self.case.wsegsicd_table, "ICD")
265
- if "AICD" in active_devices:
266
- self.df_well = completion.get_device(self.df_well, self.case.wsegaicd_table, "AICD")
267
- if "DAR" in active_devices:
268
- self.df_well = completion.get_device(self.df_well, self.case.wsegdar_table, "DAR")
269
- if "AICV" in active_devices:
270
- self.df_well = completion.get_device(self.df_well, self.case.wsegaicv_table, "AICV")
271
- if "ICV" in active_devices:
272
- self.df_well = completion.get_device(self.df_well, self.case.wsegicv_table, "ICV")
273
-
274
- def correct_annulus_zone(self) -> None:
275
- """Remove the annulus zone if there is no connection to the tubing."""
276
- self.df_well = completion.correct_annulus_zone(self.df_well)
277
-
278
- def connect_cells_to_segments(self) -> None:
279
- """Connect cells to the well.
280
-
281
- We only need the following columns from the well DataFrame: MD, NDEVICES, DEVICETYPE, and ANNULUS_ZONE.
282
-
283
- ICV placement forces different methods in segment creation as USER defined.
284
- """
285
- # drop BRANCH column, not needed
286
- self.df_reservoir.drop([Headers.BRANCH], axis=1, inplace=True)
287
- icv_device = (
288
- self.df_well[Headers.DEVICE_TYPE].nunique() > 1
289
- and (self.df_well[Headers.DEVICE_TYPE] == "ICV").any()
290
- and not self.df_well[Headers.NUMBER_OF_DEVICES].empty
291
- )
292
- method = Method.USER if icv_device else self.method
293
- self.df_reservoir = completion.connect_cells_to_segments(
294
- self.df_well[[Headers.TUB_MD, Headers.NUMBER_OF_DEVICES, Headers.DEVICE_TYPE, Headers.ANNULUS_ZONE]],
295
- self.df_reservoir,
296
- self.df_tubing_segments,
297
- method,
298
- )
299
-
300
- def add_well_lateral_column(self, lateral: int) -> None:
301
- """Add well and lateral column in df_well and df_compsegs."""
302
- self.df_well[Headers.WELL] = self.well_name
303
- self.df_reservoir[Headers.WELL] = self.well_name
304
- self.df_well[Headers.LATERAL] = lateral
305
- self.df_reservoir[Headers.LATERAL] = lateral
306
-
307
- def combine_df(self, lateral: int) -> None:
308
- """Combine all DataFrames for this well."""
309
- if lateral == self.laterals[0]:
310
- self.df_well_all = self.df_well.copy(deep=True)
311
- self.df_reservoir_all = self.df_reservoir.copy(deep=True)
312
- else:
313
- self.df_well_all = pd.concat([self.df_well_all, self.df_well], sort=False)
314
- self.df_reservoir_all = pd.concat([self.df_reservoir_all, self.df_reservoir], sort=False)