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