xtgeo 4.8.0__cp313-cp313-win_amd64.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.

Potentially problematic release.


This version of xtgeo might be problematic. Click here for more details.

Files changed (117) hide show
  1. cxtgeo.py +582 -0
  2. cxtgeoPYTHON_wrap.c +20938 -0
  3. xtgeo/__init__.py +246 -0
  4. xtgeo/_cxtgeo.cp313-win_amd64.pyd +0 -0
  5. xtgeo/_internal.cp313-win_amd64.pyd +0 -0
  6. xtgeo/common/__init__.py +19 -0
  7. xtgeo/common/_angles.py +29 -0
  8. xtgeo/common/_xyz_enum.py +50 -0
  9. xtgeo/common/calc.py +396 -0
  10. xtgeo/common/constants.py +30 -0
  11. xtgeo/common/exceptions.py +42 -0
  12. xtgeo/common/log.py +93 -0
  13. xtgeo/common/sys.py +166 -0
  14. xtgeo/common/types.py +18 -0
  15. xtgeo/common/version.py +21 -0
  16. xtgeo/common/xtgeo_dialog.py +604 -0
  17. xtgeo/cube/__init__.py +9 -0
  18. xtgeo/cube/_cube_export.py +214 -0
  19. xtgeo/cube/_cube_import.py +532 -0
  20. xtgeo/cube/_cube_roxapi.py +180 -0
  21. xtgeo/cube/_cube_utils.py +287 -0
  22. xtgeo/cube/_cube_window_attributes.py +340 -0
  23. xtgeo/cube/cube1.py +1023 -0
  24. xtgeo/grid3d/__init__.py +15 -0
  25. xtgeo/grid3d/_ecl_grid.py +774 -0
  26. xtgeo/grid3d/_ecl_inte_head.py +148 -0
  27. xtgeo/grid3d/_ecl_logi_head.py +71 -0
  28. xtgeo/grid3d/_ecl_output_file.py +81 -0
  29. xtgeo/grid3d/_egrid.py +1004 -0
  30. xtgeo/grid3d/_find_gridprop_in_eclrun.py +625 -0
  31. xtgeo/grid3d/_grdecl_format.py +266 -0
  32. xtgeo/grid3d/_grdecl_grid.py +388 -0
  33. xtgeo/grid3d/_grid3d.py +29 -0
  34. xtgeo/grid3d/_grid3d_fence.py +181 -0
  35. xtgeo/grid3d/_grid3d_utils.py +228 -0
  36. xtgeo/grid3d/_grid_boundary.py +76 -0
  37. xtgeo/grid3d/_grid_etc1.py +1566 -0
  38. xtgeo/grid3d/_grid_export.py +221 -0
  39. xtgeo/grid3d/_grid_hybrid.py +66 -0
  40. xtgeo/grid3d/_grid_import.py +79 -0
  41. xtgeo/grid3d/_grid_import_ecl.py +101 -0
  42. xtgeo/grid3d/_grid_import_roff.py +135 -0
  43. xtgeo/grid3d/_grid_import_xtgcpgeom.py +375 -0
  44. xtgeo/grid3d/_grid_refine.py +125 -0
  45. xtgeo/grid3d/_grid_roxapi.py +292 -0
  46. xtgeo/grid3d/_grid_wellzone.py +165 -0
  47. xtgeo/grid3d/_gridprop_export.py +178 -0
  48. xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
  49. xtgeo/grid3d/_gridprop_import_grdecl.py +130 -0
  50. xtgeo/grid3d/_gridprop_import_roff.py +52 -0
  51. xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
  52. xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
  53. xtgeo/grid3d/_gridprop_op1.py +174 -0
  54. xtgeo/grid3d/_gridprop_roxapi.py +239 -0
  55. xtgeo/grid3d/_gridprop_value_init.py +140 -0
  56. xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
  57. xtgeo/grid3d/_gridprops_import_roff.py +83 -0
  58. xtgeo/grid3d/_roff_grid.py +469 -0
  59. xtgeo/grid3d/_roff_parameter.py +303 -0
  60. xtgeo/grid3d/grid.py +2537 -0
  61. xtgeo/grid3d/grid_properties.py +699 -0
  62. xtgeo/grid3d/grid_property.py +1341 -0
  63. xtgeo/grid3d/types.py +15 -0
  64. xtgeo/io/__init__.py +1 -0
  65. xtgeo/io/_file.py +592 -0
  66. xtgeo/metadata/__init__.py +17 -0
  67. xtgeo/metadata/metadata.py +431 -0
  68. xtgeo/roxutils/__init__.py +7 -0
  69. xtgeo/roxutils/_roxar_loader.py +54 -0
  70. xtgeo/roxutils/_roxutils_etc.py +122 -0
  71. xtgeo/roxutils/roxutils.py +207 -0
  72. xtgeo/surface/__init__.py +18 -0
  73. xtgeo/surface/_regsurf_boundary.py +26 -0
  74. xtgeo/surface/_regsurf_cube.py +210 -0
  75. xtgeo/surface/_regsurf_cube_window.py +391 -0
  76. xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
  77. xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
  78. xtgeo/surface/_regsurf_export.py +388 -0
  79. xtgeo/surface/_regsurf_grid3d.py +271 -0
  80. xtgeo/surface/_regsurf_gridding.py +347 -0
  81. xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
  82. xtgeo/surface/_regsurf_import.py +347 -0
  83. xtgeo/surface/_regsurf_lowlevel.py +122 -0
  84. xtgeo/surface/_regsurf_oper.py +631 -0
  85. xtgeo/surface/_regsurf_roxapi.py +241 -0
  86. xtgeo/surface/_regsurf_utils.py +81 -0
  87. xtgeo/surface/_surfs_import.py +43 -0
  88. xtgeo/surface/_zmap_parser.py +138 -0
  89. xtgeo/surface/regular_surface.py +2967 -0
  90. xtgeo/surface/surfaces.py +276 -0
  91. xtgeo/well/__init__.py +24 -0
  92. xtgeo/well/_blockedwell_roxapi.py +221 -0
  93. xtgeo/well/_blockedwells_roxapi.py +68 -0
  94. xtgeo/well/_well_aux.py +30 -0
  95. xtgeo/well/_well_io.py +327 -0
  96. xtgeo/well/_well_oper.py +574 -0
  97. xtgeo/well/_well_roxapi.py +304 -0
  98. xtgeo/well/_wellmarkers.py +486 -0
  99. xtgeo/well/_wells_utils.py +158 -0
  100. xtgeo/well/blocked_well.py +216 -0
  101. xtgeo/well/blocked_wells.py +122 -0
  102. xtgeo/well/well1.py +1514 -0
  103. xtgeo/well/wells.py +211 -0
  104. xtgeo/xyz/__init__.py +6 -0
  105. xtgeo/xyz/_polygons_oper.py +272 -0
  106. xtgeo/xyz/_xyz.py +741 -0
  107. xtgeo/xyz/_xyz_data.py +646 -0
  108. xtgeo/xyz/_xyz_io.py +490 -0
  109. xtgeo/xyz/_xyz_lowlevel.py +42 -0
  110. xtgeo/xyz/_xyz_oper.py +613 -0
  111. xtgeo/xyz/_xyz_roxapi.py +766 -0
  112. xtgeo/xyz/points.py +681 -0
  113. xtgeo/xyz/polygons.py +811 -0
  114. xtgeo-4.8.0.dist-info/METADATA +145 -0
  115. xtgeo-4.8.0.dist-info/RECORD +117 -0
  116. xtgeo-4.8.0.dist-info/WHEEL +5 -0
  117. xtgeo-4.8.0.dist-info/licenses/LICENSE.md +165 -0
@@ -0,0 +1,574 @@
1
+ """Operations along a well, private module."""
2
+
3
+ from copy import deepcopy
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ from xtgeo import _cxtgeo
9
+ from xtgeo.common._xyz_enum import _AttrType
10
+ from xtgeo.common.constants import UNDEF_INT, UNDEF_INT_LIMIT
11
+ from xtgeo.common.log import null_logger
12
+ from xtgeo.common.sys import _get_carray
13
+ from xtgeo.xyz.points import Points
14
+
15
+ logger = null_logger(__name__)
16
+
17
+
18
+ def rescale(self, delta=0.15, tvdrange=None):
19
+ """Rescale by using a new MD increment.
20
+
21
+ The rescaling is technically done by interpolation in the Pandas dataframe
22
+ """
23
+ pdrows = pd.options.display.max_rows
24
+ pd.options.display.max_rows = 999
25
+
26
+ dfrcolumns0 = self.get_dataframe(copy=False).columns
27
+
28
+ if self.mdlogname is None:
29
+ self.geometrics()
30
+
31
+ dfrcolumns1 = self.get_dataframe(copy=False).columns
32
+ columnsadded = list(set(dfrcolumns1) - set(dfrcolumns0)) # new tmp columns, if any
33
+
34
+ dfr = self.get_dataframe().set_index(self.mdlogname)
35
+
36
+ logger.debug("Initial dataframe\n %s", dfr)
37
+
38
+ start = dfr.index[0]
39
+ stop = dfr.index[-1]
40
+ startt = start
41
+ stopt = stop
42
+
43
+ if tvdrange and isinstance(tvdrange, tuple) and len(tvdrange) == 2:
44
+ tvd1, tvd2 = tvdrange
45
+
46
+ try:
47
+ startt = dfr.index[dfr[self._wdata.zname] >= tvd1][0]
48
+ except IndexError:
49
+ startt = start
50
+
51
+ try:
52
+ stopt = dfr.index[dfr[self._wdata.zname] >= tvd2][0]
53
+ except IndexError:
54
+ stopt = stop
55
+
56
+ dfr1 = dfr[start:startt]
57
+ dfr2 = dfr[stopt:stop]
58
+
59
+ nentry = int(round((stopt - startt) / delta))
60
+
61
+ dfr = dfr.reindex(dfr.index.union(np.linspace(startt, stopt, num=nentry)))
62
+ dfr = dfr.interpolate("index", limit_area="inside").loc[
63
+ np.linspace(startt, stopt, num=nentry)
64
+ ]
65
+
66
+ dfr = pd.concat([dfr1, dfr, dfr2], sort=False)
67
+
68
+ dfr.drop_duplicates(inplace=True)
69
+ dfr[self.mdlogname] = dfr.index
70
+ dfr.reset_index(inplace=True, drop=True)
71
+
72
+ for lname in dfr.columns:
73
+ if lname in self.wlogtypes:
74
+ ltype = self.wlogtypes[lname]
75
+ if ltype == _AttrType.DISC.value:
76
+ dfr = dfr.round({lname: 0})
77
+
78
+ logger.debug("Updated dataframe:\n%s", dfr)
79
+
80
+ pd.options.display.max_rows = pdrows # reset
81
+
82
+ self.set_dataframe(dfr)
83
+ if columnsadded:
84
+ self.delete_log(columnsadded)
85
+
86
+
87
+ def make_zone_qual_log(self, zqname):
88
+ """Make a flag log based on stratigraphic relations."""
89
+ if zqname in self.get_dataframe(copy=False):
90
+ logger.warning("Quality log %s exists, will be overwritten", zqname)
91
+
92
+ if not self.zonelogname or self.zonelogname not in self.get_dataframe(copy=False):
93
+ raise ValueError("Cannot find a zonelog")
94
+
95
+ dff = self.get_filled_dataframe()
96
+ dff["ztmp"] = dff[self.zonelogname].copy()
97
+ dff["ztmp"] = (dff.ztmp != dff.ztmp.shift()).cumsum()
98
+
99
+ sgrp = dff.groupby("ztmp")
100
+
101
+ dff[zqname] = dff[self.zonelogname] * 0
102
+
103
+ idlist = []
104
+ seq = []
105
+ for idx, grp in sgrp:
106
+ izns = int(grp[self.zonelogname].mean())
107
+ seq.append(izns)
108
+ idlist.append(idx)
109
+
110
+ codes = {
111
+ 0: "UNDETERMINED",
112
+ 1: "INCREASE",
113
+ 2: "DECREASE",
114
+ 3: "U_TURN",
115
+ 4: "INV_U_TURN",
116
+ 9: "INCOMPLETE",
117
+ }
118
+
119
+ code = []
120
+ for ind, iseq in enumerate(seq):
121
+ if ind in (0, len(seq) - 1):
122
+ code.append(0)
123
+ else:
124
+ prev_ = seq[ind - 1]
125
+ next_ = seq[ind + 1]
126
+ if prev_ > UNDEF_INT_LIMIT or next_ > UNDEF_INT_LIMIT:
127
+ code.append(9)
128
+ elif next_ > iseq > prev_:
129
+ code.append(1)
130
+ elif next_ < iseq < prev_:
131
+ code.append(2)
132
+ elif next_ < iseq > prev_:
133
+ code.append(3)
134
+ elif next_ > iseq < prev_:
135
+ code.append(4)
136
+ dcode = dict(zip(idlist, code))
137
+
138
+ # now create the new log
139
+ self.create_log(zqname, logtype=_AttrType.DISC.value, logrecord=codes)
140
+ dataframe = self.get_dataframe()
141
+ for key, val in dcode.items():
142
+ dataframe.loc[dff["ztmp"] == key, zqname] = val
143
+
144
+ self.set_dataframe(dataframe)
145
+ # set the metadata
146
+ self.set_logtype(zqname, _AttrType.DISC.value)
147
+ self.set_logrecord(zqname, codes)
148
+ self._ensure_consistency()
149
+
150
+ del dff
151
+
152
+
153
+ def make_ijk_from_grid(self, grid, grid_id="", algorithm=1, activeonly=True):
154
+ """Make an IJK log from grid indices."""
155
+ logger.debug("Using algorithm %s in %s", algorithm, __name__)
156
+
157
+ if algorithm == 1:
158
+ _make_ijk_from_grid_v1(self, grid, grid_id=grid_id)
159
+ else:
160
+ _make_ijk_from_grid_v2(self, grid, grid_id=grid_id, activeonly=activeonly)
161
+
162
+ logger.debug("Using algorithm %s in %s done", algorithm, __name__)
163
+
164
+
165
+ def _make_ijk_from_grid_v1(self, grid, grid_id=""):
166
+ """Getting IJK from a grid and make as well logs.
167
+
168
+ This is the first version, using _cxtgeo.grd3d_well_ijk from C
169
+ """
170
+ logger.debug("Using algorithm 1 in %s", __name__)
171
+
172
+ wxarr = _get_carray(self.get_dataframe(copy=False), self.wlogtypes, self.xname)
173
+ wyarr = _get_carray(self.get_dataframe(copy=False), self.wlogtypes, self.yname)
174
+ wzarr = _get_carray(self.get_dataframe(copy=False), self.wlogtypes, self.zname)
175
+
176
+ nlen = self.nrow
177
+ wivec = _cxtgeo.new_intarray(nlen)
178
+ wjvec = _cxtgeo.new_intarray(nlen)
179
+ wkvec = _cxtgeo.new_intarray(nlen)
180
+
181
+ onelayergrid = grid.copy()
182
+ onelayergrid.reduce_to_one_layer()
183
+
184
+ cstatus = _cxtgeo.grd3d_well_ijk(
185
+ grid.ncol,
186
+ grid.nrow,
187
+ grid.nlay,
188
+ grid._coordsv,
189
+ grid._zcornsv,
190
+ grid._actnumsv,
191
+ onelayergrid._zcornsv,
192
+ onelayergrid._actnumsv,
193
+ self.nrow,
194
+ wxarr,
195
+ wyarr,
196
+ wzarr,
197
+ wivec,
198
+ wjvec,
199
+ wkvec,
200
+ 0,
201
+ )
202
+
203
+ if cstatus != 0:
204
+ raise RuntimeError(f"Error from C routine, code is {cstatus}")
205
+
206
+ indarray = _cxtgeo.swig_carr_to_numpy_i1d(nlen, wivec).astype("float")
207
+ jndarray = _cxtgeo.swig_carr_to_numpy_i1d(nlen, wjvec).astype("float")
208
+ kndarray = _cxtgeo.swig_carr_to_numpy_i1d(nlen, wkvec).astype("float")
209
+
210
+ indarray[indarray == 0] = np.nan
211
+ jndarray[jndarray == 0] = np.nan
212
+ kndarray[kndarray == 0] = np.nan
213
+
214
+ icellname = "ICELL" + grid_id
215
+ jcellname = "JCELL" + grid_id
216
+ kcellname = "KCELL" + grid_id
217
+
218
+ self._wdata.data[icellname] = indarray
219
+ self._wdata.data[jcellname] = jndarray
220
+ self._wdata.data[kcellname] = kndarray
221
+
222
+ for cellname in [icellname, jcellname, kcellname]:
223
+ self.set_logtype(cellname, _AttrType.DISC.value)
224
+
225
+ self.set_logrecord(icellname, {ncel: str(ncel) for ncel in range(1, grid.ncol + 1)})
226
+ self.set_logrecord(jcellname, {ncel: str(ncel) for ncel in range(1, grid.nrow + 1)})
227
+ self.set_logrecord(kcellname, {ncel: str(ncel) for ncel in range(1, grid.nlay + 1)})
228
+
229
+ _cxtgeo.delete_intarray(wivec)
230
+ _cxtgeo.delete_intarray(wjvec)
231
+ _cxtgeo.delete_intarray(wkvec)
232
+ _cxtgeo.delete_doublearray(wxarr)
233
+ _cxtgeo.delete_doublearray(wyarr)
234
+ _cxtgeo.delete_doublearray(wzarr)
235
+
236
+ del onelayergrid
237
+
238
+
239
+ def _make_ijk_from_grid_v2(self, grid, grid_id="", activeonly=True):
240
+ """Getting IJK from a grid and make as well logs.
241
+
242
+ This is a newer version using grid.get_ijk_from_points. This one
243
+ is believed to be more precise!
244
+ """
245
+ # establish a Points instance and make points dataframe from well trajectory X Y Z
246
+ wpoints = Points()
247
+ wpdf = self.get_dataframe().loc[:, [self.xname, self.yname, self.zname]]
248
+ wpdf.reset_index(inplace=True, drop=True)
249
+ wpoints.set_dataframe(wpdf)
250
+
251
+ # column names
252
+ cna = ("ICELL" + grid_id, "JCELL" + grid_id, "KCELL" + grid_id)
253
+
254
+ df = grid.get_ijk_from_points(
255
+ wpoints,
256
+ activeonly=activeonly,
257
+ zerobased=False,
258
+ dataframe=True,
259
+ includepoints=False,
260
+ columnnames=cna,
261
+ fmt="float",
262
+ undef=np.nan,
263
+ )
264
+
265
+ # The resulting df shall have same length as the well's dataframe,
266
+ # but the well index may not start from one. So first ignore index, then
267
+ # re-establish
268
+ wellindex = self.get_dataframe(copy=False).index
269
+
270
+ newdf = pd.concat([self.get_dataframe().reset_index(drop=True), df], axis=1)
271
+ newdf.index = wellindex
272
+
273
+ self.set_dataframe(newdf)
274
+
275
+
276
+ def get_gridproperties(self, gridprops, grid=("ICELL", "JCELL", "KCELL"), prop_id=""):
277
+ """Getting gridproperties as logs.
278
+
279
+ The routine will make grid_coordinates from grid with make_ijk_from_grid(), or reuse
280
+ existing vectors if grid is a tuple (much faster).
281
+ """
282
+ from xtgeo.grid3d.grid import Grid
283
+ from xtgeo.grid3d.grid_properties import GridProperties
284
+ from xtgeo.grid3d.grid_property import GridProperty
285
+
286
+ if not isinstance(gridprops, (GridProperty, GridProperties)):
287
+ raise ValueError('"gridprops" not a GridProperties or GridProperty instance')
288
+
289
+ if isinstance(gridprops, GridProperty):
290
+ gprops = GridProperties()
291
+ gprops.append_props([gridprops])
292
+ else:
293
+ gprops = gridprops
294
+
295
+ ijk_logs_created_tmp = False
296
+ if isinstance(grid, tuple):
297
+ icl, jcl, kcl = grid
298
+ elif isinstance(grid, Grid):
299
+ self.make_ijk_from_grid(grid, grid_id="_tmp", algorithm=2)
300
+ icl, jcl, kcl = ("ICELL_tmp", "JCELL_tmp", "KCELL_tmp")
301
+ ijk_logs_created_tmp = True
302
+ else:
303
+ raise ValueError("The 'grid' is of wrong type, must be a tuple or a Grid")
304
+
305
+ # let grid values have base 1 when looking up cells for gridprops
306
+ iind = self.get_dataframe(copy=False)[icl].to_numpy(copy=True) - 1
307
+ jind = self.get_dataframe(copy=False)[jcl].to_numpy(copy=True) - 1
308
+ kind = self.get_dataframe(copy=False)[kcl].to_numpy(copy=True) - 1
309
+
310
+ xind = iind.copy()
311
+
312
+ iind[np.isnan(iind)] = 0
313
+ jind[np.isnan(jind)] = 0
314
+ kind[np.isnan(kind)] = 0
315
+
316
+ iind = iind.astype("int")
317
+ jind = jind.astype("int")
318
+ kind = kind.astype("int")
319
+ dfr = self.get_dataframe()
320
+
321
+ pnames = {}
322
+ for prop in gprops.props:
323
+ arr = prop.values[iind, jind, kind].astype("float")
324
+ arr = np.ma.filled(arr, fill_value=np.nan)
325
+ arr[np.isnan(xind)] = np.nan
326
+ pname = prop.name + prop_id
327
+ dfr[pname] = arr
328
+ pnames[pname] = (prop.isdiscrete, deepcopy(prop.codes))
329
+
330
+ self.set_dataframe(dfr)
331
+ for pname, isdiscrete_codes in pnames.items():
332
+ isdiscrete, codes = isdiscrete_codes
333
+ if isdiscrete:
334
+ self.set_logtype(pname, _AttrType.DISC.value)
335
+ self.set_logrecord(pname, codes)
336
+ else:
337
+ self.set_logtype(pname, _AttrType.CONT.value)
338
+ self.set_logrecord(pname, ("", ""))
339
+
340
+ if ijk_logs_created_tmp:
341
+ self.delete_logs(["ICELL_tmp", "JCELL_tmp", "KCELL_tmp"])
342
+
343
+
344
+ def report_zonation_holes(self, threshold=5):
345
+ """Reports if well has holes in zonation, less or equal to N samples."""
346
+
347
+ if self.zonelogname is None:
348
+ raise RuntimeError("No zonelog present for well")
349
+
350
+ wellreport = []
351
+
352
+ zlog = self._wdata.data[self.zonelogname].values.copy()
353
+
354
+ mdlog = None
355
+ if self.mdlogname:
356
+ mdlog = self._wdata.data[self.mdlogname].values
357
+
358
+ xvv = self._wdata.data[self.xname].values
359
+ yvv = self._wdata.data[self.yname].values
360
+ zvv = self._wdata.data[self.zname].values
361
+ zlog[np.isnan(zlog)] = UNDEF_INT
362
+
363
+ ncv = 0
364
+ first = True
365
+ hole = False
366
+ for ind, zone in np.ndenumerate(zlog):
367
+ ino = ind[0]
368
+ if zone > UNDEF_INT_LIMIT and first:
369
+ continue
370
+
371
+ if zone < UNDEF_INT_LIMIT and first:
372
+ first = False
373
+ continue
374
+
375
+ if zone > UNDEF_INT_LIMIT:
376
+ ncv += 1
377
+ hole = True
378
+
379
+ if zone > UNDEF_INT_LIMIT and ncv > threshold:
380
+ logger.debug("Restart first (bigger hole)")
381
+ hole = False
382
+ first = True
383
+ ncv = 0
384
+ continue
385
+
386
+ if hole and zone < UNDEF_INT_LIMIT and ncv <= threshold:
387
+ # here we have a hole that fits criteria
388
+ if mdlog is not None:
389
+ entry = (
390
+ ino,
391
+ xvv[ino],
392
+ yvv[ino],
393
+ zvv[ino],
394
+ int(zone),
395
+ self.xwellname,
396
+ mdlog[ino],
397
+ )
398
+ else:
399
+ entry = (ino, xvv[ino], yvv[ino], zvv[ino], int(zone), self.xwellname)
400
+
401
+ wellreport.append(entry)
402
+
403
+ # restart count
404
+ hole = False
405
+ ncv = 0
406
+
407
+ if hole and zone < UNDEF_INT_LIMIT and ncv > threshold:
408
+ hole = False
409
+ ncv = 0
410
+
411
+ if not wellreport: # ie length is 0
412
+ return None
413
+
414
+ if mdlog is not None:
415
+ clm = ["INDEX", self.xname, self.yname, self.zname, "Zone", "Well", "MD"]
416
+ else:
417
+ clm = ["INDEX", self.xname, self.yname, self.zname, "Zone", "Well"]
418
+
419
+ return pd.DataFrame(wellreport, columns=clm)
420
+
421
+
422
+ def mask_shoulderbeds(self, inputlogs, targetlogs, nsamples, strict):
423
+ """Mask targetlogs around discrete boundaries.
424
+
425
+ Returns True if inputlog(s) and targetlog(s) are present; otherwise False.
426
+ """
427
+ logger.debug("Mask shoulderbeds for some logs...")
428
+
429
+ useinputs, usetargets, use_numeric = _mask_shoulderbeds_checks(
430
+ self, inputlogs, targetlogs, nsamples, strict
431
+ )
432
+
433
+ if not useinputs or not usetargets:
434
+ logger.debug("Mask shoulderbeds for some logs... nothing done")
435
+ return False
436
+
437
+ for inlog in useinputs:
438
+ inseries = self._wdata.data[inlog]
439
+ if use_numeric:
440
+ bseries = _get_bseries(inseries, nsamples)
441
+ else:
442
+ mode, value = list(nsamples.items())[0]
443
+
444
+ depth = self._wdata.data[self.zname]
445
+ if mode == "md" and self.mdlogname is not None:
446
+ depth = self._wdata.data[self.mdlogname]
447
+ elif mode == "md" and self.mdlogname is None:
448
+ raise ValueError("There is no mdlogname attribute present.")
449
+
450
+ bseries = _get_bseries_by_distance(depth, inseries, value)
451
+
452
+ for target in usetargets:
453
+ self._wdata.data.loc[bseries, target] = np.nan
454
+
455
+ logger.debug("Mask shoulderbeds for some logs... done")
456
+ return True
457
+
458
+
459
+ def _mask_shoulderbeds_checks(self, inputlogs, targetlogs, nsamples, strict):
460
+ """Checks/validates input for mask targetlogs around discrete boundaries."""
461
+ # check that inputlogs exists and that they are discrete, and targetlogs
462
+ useinputs = []
463
+ for inlog in inputlogs:
464
+ if inlog not in self.wlogtypes and strict is True:
465
+ raise ValueError(f"Input log {inlog} is missing and strict=True")
466
+ if inlog in self.wlogtypes and self.wlogtypes[inlog] != _AttrType.DISC.value:
467
+ raise ValueError(f"Input log {inlog} is not of type DISC")
468
+ if inlog in self.wlogtypes:
469
+ useinputs.append(inlog)
470
+
471
+ usetargets = []
472
+ for target in targetlogs:
473
+ if target not in self.wlogtypes and strict is True:
474
+ raise ValueError(f"Target log {target} is missing and strict=True")
475
+ if target in self.wlogtypes:
476
+ usetargets.append(target)
477
+
478
+ use_numeric = True
479
+ if isinstance(nsamples, int):
480
+ maxlen = self.nrow // 2
481
+ if nsamples < 1 or nsamples > maxlen:
482
+ raise ValueError(f"Keyword nsamples must be an int > 1 and < {maxlen}")
483
+ elif isinstance(nsamples, dict):
484
+ if len(nsamples) == 1 and any(key in nsamples for key in ["md", "tvd"]):
485
+ use_numeric = False
486
+ else:
487
+ raise ValueError(f"Keyword nsamples is incorrect in some way: {nsamples}")
488
+ else:
489
+ raise ValueError("Keyword nsamples is not an int or a dictionary")
490
+
491
+ # return a list of input logs to be used (useinputs), a list of target logs to
492
+ # be used (usetargets) and a use_numeric bool (True if nsamples is an int)
493
+ return useinputs, usetargets, use_numeric
494
+
495
+
496
+ def _get_bseries(inseries, nsamples):
497
+ """Return a bool filter based on number of samples."""
498
+ if not isinstance(inseries, pd.Series):
499
+ raise RuntimeError("Bug, input must be a pandas Series() instance.")
500
+
501
+ if len(inseries) == 0:
502
+ return pd.Series([], dtype=bool)
503
+
504
+ # nsmaples < 1 or input series with <= 1 element will not be prosessed
505
+ if nsamples < 1 or len(inseries) <= 1:
506
+ return pd.Series(inseries, dtype=bool).replace(True, False)
507
+
508
+ def _growfilter(bseries, nleft):
509
+ if not nleft:
510
+ return bseries
511
+
512
+ return _growfilter(bseries | bseries.shift(-1) | bseries.shift(1), nleft - 1)
513
+
514
+ # make a tmp mask log (series) based input logs and use that for mask filterings
515
+ transitions = inseries.diff().abs() > 0
516
+ bseries = transitions | transitions.shift(-1)
517
+
518
+ return _growfilter(bseries, nsamples - 1)
519
+
520
+
521
+ def _get_bseries_by_distance(depth, inseries, distance):
522
+ """Return a bool filter defined by distance to log breaks."""
523
+ if not isinstance(inseries, pd.Series):
524
+ raise RuntimeError("BUG: input must be a pandas Series() instance.")
525
+
526
+ if len(inseries) == 0:
527
+ return pd.Series([], dtype=bool)
528
+
529
+ # Input series with <= 1 element will not be prosessed
530
+ if len(inseries) <= 1:
531
+ return pd.Series(inseries, dtype=bool).replace(True, False)
532
+
533
+ bseries = pd.Series(np.zeros(inseries.values.size), dtype="int32").values
534
+ try:
535
+ inseries = np.nan_to_num(inseries.values, nan=UNDEF_INT).astype("int32")
536
+ except TypeError:
537
+ # for older numpy version
538
+ inseries = inseries.values
539
+ inseries[np.isnan(inseries)] = UNDEF_INT
540
+ inseries = inseries.astype("int32")
541
+
542
+ res = _cxtgeo.well_mask_shoulder(
543
+ depth.values.astype("float64"), inseries, bseries, distance
544
+ )
545
+
546
+ if res != 0:
547
+ raise RuntimeError("BUG: return from _cxtgeo.well_mask_shoulder not zero")
548
+
549
+ return np.array(bseries, dtype=bool)
550
+
551
+
552
+ def create_surf_distance_log(self, surf, name):
553
+ """Create a log which is vertical distance from a RegularSurface."""
554
+ from xtgeo.surface.regular_surface import RegularSurface
555
+
556
+ logger.debug("Create a log which is distance to surface")
557
+
558
+ if not isinstance(surf, RegularSurface):
559
+ raise ValueError("Input surface is not a RegularSurface instance.")
560
+
561
+ # make a Points instance since points has the snap
562
+ zvalues = self.get_dataframe()[self.zname]
563
+ points = Points()
564
+ dframe = self.get_dataframe().iloc[:, 0:3]
565
+ points.set_dataframe(dframe)
566
+ points.snap_surface(surf)
567
+ snapped = points.get_dataframe(copy=False)[self.zname]
568
+ diff = snapped - zvalues
569
+
570
+ # create log (default is force overwrite if it exists)
571
+ self.create_log(name)
572
+ dframe = self.get_dataframe()
573
+ dframe[name] = diff
574
+ self.set_dataframe(dframe)