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.
Files changed (122) hide show
  1. cxtgeo.py +558 -0
  2. cxtgeoPYTHON_wrap.c +19537 -0
  3. xtgeo/__init__.py +248 -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 +34 -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 +273 -0
  23. xtgeo/cube/cube1.py +1023 -0
  24. xtgeo/grid3d/__init__.py +15 -0
  25. xtgeo/grid3d/_ecl_grid.py +778 -0
  26. xtgeo/grid3d/_ecl_inte_head.py +152 -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 +309 -0
  32. xtgeo/grid3d/_grdecl_grid.py +400 -0
  33. xtgeo/grid3d/_grid3d.py +29 -0
  34. xtgeo/grid3d/_grid3d_fence.py +284 -0
  35. xtgeo/grid3d/_grid3d_utils.py +228 -0
  36. xtgeo/grid3d/_grid_boundary.py +76 -0
  37. xtgeo/grid3d/_grid_etc1.py +1683 -0
  38. xtgeo/grid3d/_grid_export.py +222 -0
  39. xtgeo/grid3d/_grid_hybrid.py +50 -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 +258 -0
  45. xtgeo/grid3d/_grid_roxapi.py +292 -0
  46. xtgeo/grid3d/_grid_translate_coords.py +154 -0
  47. xtgeo/grid3d/_grid_wellzone.py +165 -0
  48. xtgeo/grid3d/_gridprop_export.py +202 -0
  49. xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
  50. xtgeo/grid3d/_gridprop_import_grdecl.py +132 -0
  51. xtgeo/grid3d/_gridprop_import_roff.py +52 -0
  52. xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
  53. xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
  54. xtgeo/grid3d/_gridprop_op1.py +272 -0
  55. xtgeo/grid3d/_gridprop_roxapi.py +301 -0
  56. xtgeo/grid3d/_gridprop_value_init.py +140 -0
  57. xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
  58. xtgeo/grid3d/_gridprops_import_roff.py +83 -0
  59. xtgeo/grid3d/_roff_grid.py +470 -0
  60. xtgeo/grid3d/_roff_parameter.py +303 -0
  61. xtgeo/grid3d/grid.py +3010 -0
  62. xtgeo/grid3d/grid_properties.py +699 -0
  63. xtgeo/grid3d/grid_property.py +1313 -0
  64. xtgeo/grid3d/types.py +15 -0
  65. xtgeo/interfaces/rms/__init__.py +18 -0
  66. xtgeo/interfaces/rms/_regular_surface.py +460 -0
  67. xtgeo/interfaces/rms/_rms_base.py +100 -0
  68. xtgeo/interfaces/rms/_rmsapi_package.py +69 -0
  69. xtgeo/interfaces/rms/rmsapi_utils.py +438 -0
  70. xtgeo/io/__init__.py +1 -0
  71. xtgeo/io/_file.py +603 -0
  72. xtgeo/metadata/__init__.py +17 -0
  73. xtgeo/metadata/metadata.py +435 -0
  74. xtgeo/roxutils/__init__.py +7 -0
  75. xtgeo/roxutils/_roxar_loader.py +54 -0
  76. xtgeo/roxutils/_roxutils_etc.py +122 -0
  77. xtgeo/roxutils/roxutils.py +207 -0
  78. xtgeo/surface/__init__.py +20 -0
  79. xtgeo/surface/_regsurf_boundary.py +26 -0
  80. xtgeo/surface/_regsurf_cube.py +210 -0
  81. xtgeo/surface/_regsurf_cube_window.py +391 -0
  82. xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
  83. xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
  84. xtgeo/surface/_regsurf_export.py +388 -0
  85. xtgeo/surface/_regsurf_grid3d.py +275 -0
  86. xtgeo/surface/_regsurf_gridding.py +347 -0
  87. xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
  88. xtgeo/surface/_regsurf_import.py +347 -0
  89. xtgeo/surface/_regsurf_lowlevel.py +122 -0
  90. xtgeo/surface/_regsurf_oper.py +538 -0
  91. xtgeo/surface/_regsurf_utils.py +81 -0
  92. xtgeo/surface/_surfs_import.py +43 -0
  93. xtgeo/surface/_zmap_parser.py +138 -0
  94. xtgeo/surface/regular_surface.py +3043 -0
  95. xtgeo/surface/surfaces.py +276 -0
  96. xtgeo/well/__init__.py +24 -0
  97. xtgeo/well/_blockedwell_roxapi.py +241 -0
  98. xtgeo/well/_blockedwells_roxapi.py +68 -0
  99. xtgeo/well/_well_aux.py +30 -0
  100. xtgeo/well/_well_io.py +327 -0
  101. xtgeo/well/_well_oper.py +483 -0
  102. xtgeo/well/_well_roxapi.py +304 -0
  103. xtgeo/well/_wellmarkers.py +486 -0
  104. xtgeo/well/_wells_utils.py +158 -0
  105. xtgeo/well/blocked_well.py +220 -0
  106. xtgeo/well/blocked_wells.py +134 -0
  107. xtgeo/well/well1.py +1516 -0
  108. xtgeo/well/wells.py +211 -0
  109. xtgeo/xyz/__init__.py +6 -0
  110. xtgeo/xyz/_polygons_oper.py +272 -0
  111. xtgeo/xyz/_xyz.py +758 -0
  112. xtgeo/xyz/_xyz_data.py +646 -0
  113. xtgeo/xyz/_xyz_io.py +737 -0
  114. xtgeo/xyz/_xyz_lowlevel.py +42 -0
  115. xtgeo/xyz/_xyz_oper.py +613 -0
  116. xtgeo/xyz/_xyz_roxapi.py +766 -0
  117. xtgeo/xyz/points.py +698 -0
  118. xtgeo/xyz/polygons.py +827 -0
  119. xtgeo-4.14.1.dist-info/METADATA +146 -0
  120. xtgeo-4.14.1.dist-info/RECORD +122 -0
  121. xtgeo-4.14.1.dist-info/WHEEL +5 -0
  122. xtgeo-4.14.1.dist-info/licenses/LICENSE.md +165 -0
@@ -0,0 +1,276 @@
1
+ """The surfaces module, which has the Surfaces class (collection of surface objects)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from typing import Literal
7
+
8
+ import numpy as np
9
+
10
+ from xtgeo.common.log import null_logger
11
+ from xtgeo.common.xtgeo_dialog import XTGDescription, XTGeoDialog
12
+
13
+ from . import _surfs_import
14
+ from .regular_surface import RegularSurface, surface_from_file
15
+
16
+ xtg = XTGeoDialog()
17
+ logger = null_logger(__name__)
18
+
19
+
20
+ def surfaces_from_grid(grid, subgrids=True, rfactor=1):
21
+ surf, subtype, order = _surfs_import.from_grid3d(grid, subgrids, rfactor)
22
+ return Surfaces(surfaces=surf, subtype=subtype, order=order)
23
+
24
+
25
+ class Surfaces:
26
+ """Class for a collection of Surface objects, for operations that involves
27
+ a number of surfaces, such as statistical numbers.
28
+
29
+ A collection of surfaces can be different things:
30
+
31
+ * A list if surfaces in stratigraphic order
32
+ * A collection of different realisations of the same surface
33
+ * A collection of isochores
34
+
35
+ Args:
36
+ input (list, optional): A list of XTGeo objects and/or file names)
37
+ subtype (str): "tops", "isochores", or None (default)
38
+ order (str): Assummed order: "same", "stratigraphic", None(default)
39
+
40
+ .. seealso::
41
+ Class :class:`~xtgeo.surface.regular_surface.RegularSurface` class.
42
+
43
+ .. versionadded:: 2.1
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ surfaces: list[RegularSurface] | None = None,
49
+ subtype: Literal["tops", "isochores"] | None = None,
50
+ order: Literal["same", "stratigraphic"] | None = None,
51
+ ):
52
+ self._surfaces = []
53
+ if surfaces is not None:
54
+ self.append(surfaces)
55
+ self._subtype = subtype
56
+ self._order = order
57
+
58
+ @property
59
+ def surfaces(self):
60
+ """Get or set a list of individual surfaces"""
61
+ return self._surfaces
62
+
63
+ @surfaces.setter
64
+ def surfaces(self, slist):
65
+ if not isinstance(slist, list):
66
+ raise ValueError("Input not a list")
67
+
68
+ for elem in slist:
69
+ if not isinstance(elem, RegularSurface):
70
+ raise ValueError("Element in list not a valid type of Surface")
71
+
72
+ self._surfaces = slist
73
+
74
+ def append(self, input: list[RegularSurface] | list[str] | RegularSurface) -> None:
75
+ """Append surface(s) from a RegularSurface or a list of objects or files.
76
+
77
+ Args:
78
+ input: A single RegularSurface, or list of RegularSurface objects and/or
79
+ file names.
80
+ """
81
+ if isinstance(input, RegularSurface):
82
+ self.surfaces.append(input)
83
+ return
84
+
85
+ if not isinstance(input, list):
86
+ raise ValueError("Input not a list or a RegularSurface object.")
87
+
88
+ for item in input:
89
+ if isinstance(item, RegularSurface):
90
+ self.surfaces.append(item)
91
+ else:
92
+ try:
93
+ sobj = surface_from_file(item, fformat="guess")
94
+ self.surfaces.append(sobj)
95
+ except OSError:
96
+ xtg.warnuser(f"Cannot read as file, skip: {item}")
97
+
98
+ def describe(self, flush=True):
99
+ """Describe an instance by printing to stdout"""
100
+
101
+ dsc = XTGDescription()
102
+ dsc.title(f"Description of {self.__class__.__name__} instance")
103
+ dsc.txt("Object ID", id(self))
104
+
105
+ for inum, surf in enumerate(self.surfaces):
106
+ dsc.txt("Surface:", inum, surf.name)
107
+
108
+ if flush:
109
+ dsc.flush()
110
+ return None
111
+
112
+ return dsc.astext()
113
+
114
+ def copy(self):
115
+ """Copy a Surfaces instance to a new unique instance (a deep copy)."""
116
+
117
+ new = Surfaces()
118
+
119
+ for surf in self._surfaces:
120
+ newsurf = surf.copy()
121
+ new._surfaces.append(newsurf)
122
+
123
+ new._order = self._order
124
+ new._subtype = self._subtype
125
+
126
+ return new
127
+
128
+ def get_surface(self, name):
129
+ """Get a RegularSurface() instance by name, or return None if name not found"""
130
+
131
+ logger.info("Asking for a surface with name %s", name)
132
+ for surf in self._surfaces:
133
+ if surf.name == name:
134
+ return surf
135
+ return None
136
+
137
+ def apply(self, func, *args, **kwargs):
138
+ """Apply a function to the Surfaces array.
139
+
140
+ The return value of the function (numpy nan comptatible) will be a
141
+ numpy array of the same shape as the first surface.
142
+
143
+ E.g. surfs.apply(np.nanmean, axis=0) will return the mean surface.
144
+
145
+ Args:
146
+ func: Function to apply, e.g. np.nanmean
147
+ args: The function arguments
148
+ kwargs: The function keyword arguments
149
+
150
+ Raises:
151
+ ValueError: If surfaces differ in topology.
152
+
153
+ """
154
+ template = self.surfaces[0].copy()
155
+ slist = []
156
+ for surf in self.surfaces:
157
+ status = template.compare_topology(surf, strict=False)
158
+ if not status:
159
+ raise ValueError("Cannot do statistics, surfaces differ in topology")
160
+ slist.append(np.ma.filled(surf.values, fill_value=np.nan))
161
+
162
+ xlist = np.array(slist)
163
+
164
+ with warnings.catch_warnings():
165
+ warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered")
166
+ template.values = func(xlist, *args, **kwargs)
167
+
168
+ return template
169
+
170
+ def statistics(self, percentiles=None):
171
+ """Return statistical measures from the surfaces.
172
+
173
+ The statistics returned is:
174
+ * mean: the arithmetic mean surface
175
+ * std: the standard deviation surface (where ddof = 1)
176
+ * percentiles: on demand (such operations may be slow)
177
+
178
+ Currently this function expects that the surfaces all have the same
179
+ shape/topology.
180
+
181
+ Args:
182
+ percentiles (list of float): If defined, a list of perecentiles to evaluate
183
+ e.g. [10, 50, 90] for p10, p50, p90
184
+
185
+ Returns:
186
+ dict: A dictionary of statistical measures, see list above
187
+
188
+ Raises:
189
+ ValueError: If surfaces differ in topology.
190
+
191
+ Example::
192
+
193
+ surfs = Surfaces(mylist) # mylist is a collection of files
194
+ stats = surfs.statistics()
195
+ # export the mean surface
196
+ stats["mean"].to_file("mymean.gri")
197
+
198
+ .. versionchanged:: 2.13 Added `percentile`
199
+ """
200
+ result = {}
201
+
202
+ template = self.surfaces[0].copy()
203
+
204
+ slist = []
205
+ for surf in self.surfaces:
206
+ status = template.compare_topology(surf, strict=False)
207
+ if not status:
208
+ raise ValueError("Cannot do statistics, surfaces differ in topology")
209
+ slist.append(np.ma.filled(surf.values, fill_value=np.nan).ravel())
210
+
211
+ xlist = np.array(slist)
212
+
213
+ template.values = np.ma.masked_invalid(xlist).mean(axis=0)
214
+ result["mean"] = template.copy()
215
+ template.values = np.ma.masked_invalid(xlist).std(axis=0, ddof=1)
216
+ result["std"] = template.copy()
217
+
218
+ if percentiles is not None:
219
+ # nan on a axis tends to give warnings that are not a worry; suppress:
220
+ with warnings.catch_warnings():
221
+ warnings.filterwarnings("ignore", r"All-NaN (slice|axis) encountered")
222
+ res = np.nanpercentile(xlist, percentiles, axis=0)
223
+
224
+ for slice, prc in enumerate(percentiles):
225
+ template.values = res[slice, :]
226
+ result["p" + str(prc)] = template.copy()
227
+ if prc == 50:
228
+ result["median"] = result["p50"]
229
+
230
+ return result
231
+
232
+ def is_depth_consistent(self) -> bool:
233
+ """Check that surfaces are depth consistent, i.e. not crossing each other."""
234
+ previous = self.surfaces[0]
235
+ for surf in self.surfaces[1:]:
236
+ ok_topology = previous.compare_topology(surf, strict=True)
237
+ if not ok_topology:
238
+ raise ValueError(
239
+ "Cannot check if surfaces are depth consistent, surfaces differ "
240
+ "in topology (definitions of origin, shape, etc.)"
241
+ )
242
+
243
+ diff = surf - previous
244
+ if np.any(diff.values < 0):
245
+ return False
246
+ previous = surf
247
+ return True
248
+
249
+ def make_depth_consistent(self, inplace: bool = True) -> Surfaces | None:
250
+ """Make surfaces depth consistent, i.e. not crossing each other.
251
+
252
+ The algorithm is starting with top surface and iteratively adjust
253
+ the surface below to be consistent with the previous surface.
254
+
255
+ Args:
256
+ inplace: If True (default), the object is changed in-place, if False,
257
+ a new object is returned.
258
+ """
259
+ logger.debug("Make surfaces depth consistent (in-place=%s)", inplace)
260
+
261
+ surfs = self.copy() if not inplace else self
262
+
263
+ previous = surfs.surfaces[0]
264
+ for surf in surfs.surfaces[1:]:
265
+ ok_topology = previous.compare_topology(surf, strict=True)
266
+ if not ok_topology:
267
+ raise ValueError(
268
+ "Cannot make surfaces depth consistent, surfaces differ in "
269
+ "topology (definitions of origin, shape, etc.)"
270
+ )
271
+
272
+ surf.values = np.where(
273
+ surf.values < previous.values, previous.values, surf.values
274
+ )
275
+ previous = surf
276
+ return surfs if not inplace else None
xtgeo/well/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ """The XTGeo well package"""
2
+
3
+ from .blocked_well import BlockedWell, blockedwell_from_file, blockedwell_from_roxar
4
+ from .blocked_wells import (
5
+ BlockedWells,
6
+ blockedwells_from_files,
7
+ blockedwells_from_roxar,
8
+ )
9
+ from .well1 import Well, well_from_file, well_from_roxar
10
+ from .wells import Wells, wells_from_files
11
+
12
+ __all__ = [
13
+ "BlockedWell",
14
+ "blockedwell_from_file",
15
+ "blockedwell_from_roxar",
16
+ "BlockedWells",
17
+ "blockedwells_from_files",
18
+ "blockedwells_from_roxar",
19
+ "Well",
20
+ "well_from_file",
21
+ "well_from_roxar",
22
+ "Wells",
23
+ "wells_from_files",
24
+ ]
@@ -0,0 +1,241 @@
1
+ """Blocked Well input and output, private module for ROXAPI"""
2
+
3
+ import warnings
4
+
5
+ import numpy as np
6
+ import numpy.ma as npma
7
+ import pandas as pd
8
+
9
+ from xtgeo.common._xyz_enum import _AttrName, _AttrType
10
+ from xtgeo.common.constants import INT_MIN
11
+ from xtgeo.common.exceptions import WellNotFoundError
12
+ from xtgeo.common.log import null_logger
13
+ from xtgeo.roxutils import RoxUtils
14
+ from xtgeo.roxutils._roxar_loader import roxar
15
+
16
+ logger = null_logger(__name__)
17
+
18
+
19
+ # Import / export via ROX/RMS api
20
+
21
+
22
+ def import_bwell_roxapi(
23
+ self, project, gname, bwname, wname, lognames=None, ijk=True, realisation=0
24
+ ):
25
+ """Private function for loading project and ROXAPI blockwell import"""
26
+
27
+ logger.debug("Opening RMS project ...")
28
+ rox = RoxUtils(project, readonly=True)
29
+
30
+ _roxapi_import_bwell(self, rox, gname, bwname, wname, lognames, ijk, realisation)
31
+
32
+ rox.safe_close()
33
+
34
+
35
+ def export_bwell_roxapi(
36
+ self, project, gname, bwname, wname, lognames="all", ijk=False, realisation=0
37
+ ):
38
+ """Private function for blockwell export (store in RMS) from XTGeo to RoxarAPI"""
39
+
40
+ logger.debug("Opening RMS project ...")
41
+ rox = RoxUtils(project, readonly=False)
42
+
43
+ _roxapi_export_bwell(self, rox, gname, bwname, wname, lognames, ijk, realisation)
44
+
45
+ if rox._roxexternal:
46
+ rox.project.save()
47
+
48
+ rox.safe_close()
49
+
50
+
51
+ def _roxapi_import_bwell(
52
+ self,
53
+ rox,
54
+ gname,
55
+ bwname,
56
+ wname,
57
+ lognames,
58
+ ijk,
59
+ realisation,
60
+ ):
61
+ """Private function for ROXAPI well import (get well from Roxar)"""
62
+
63
+ if gname in rox.project.grid_models:
64
+ gmodel = rox.project.grid_models[gname]
65
+ logger.debug("RMS grid model <%s> OK", gname)
66
+ else:
67
+ raise ValueError(f"No such grid name present: {gname}")
68
+
69
+ if bwname in gmodel.blocked_wells_set:
70
+ bwset = gmodel.blocked_wells_set[bwname]
71
+ logger.debug("Blocked well set <%s> OK", bwname)
72
+ else:
73
+ raise ValueError(f"No such blocked well set: {bwname}")
74
+
75
+ if wname in bwset.get_well_names(realisation=realisation):
76
+ self._wname = wname
77
+ else:
78
+ raise WellNotFoundError(f"No such well in blocked well set: {wname}")
79
+
80
+ bwprops = list(bwset.properties)
81
+ bwnames = [item.name for item in bwset.properties]
82
+
83
+ bw_cellindices = bwset.get_cell_numbers(realisation=realisation)
84
+ dind = bwset.get_data_indices([wname], realisation=realisation)
85
+
86
+ cind = bw_cellindices[dind]
87
+ xyz = np.transpose(gmodel.get_grid(realisation=realisation).get_cell_centers(cind))
88
+
89
+ logs = {}
90
+ logs[_AttrName.XNAME.value] = xyz[0].astype(np.float64)
91
+ logs[_AttrName.YNAME.value] = xyz[1].astype(np.float64)
92
+ logs[_AttrName.ZNAME.value] = xyz[2].astype(np.float64)
93
+
94
+ if ijk:
95
+ ijk = np.transpose(
96
+ gmodel.get_grid(realisation=realisation).grid_indexer.get_indices(cind)
97
+ )
98
+ logs[_AttrName.I_INDEX.value] = ijk[0].astype(np.float64)
99
+ logs[_AttrName.J_INDEX.value] = ijk[1].astype(np.float64)
100
+ logs[_AttrName.K_INDEX.value] = ijk[2].astype(np.float64)
101
+
102
+ usenames = []
103
+ if lognames and lognames == "all":
104
+ usenames = bwnames
105
+ elif lognames:
106
+ usenames = lognames
107
+
108
+ for bwprop in bwprops:
109
+ lname = bwprop.name
110
+ if lname not in usenames:
111
+ continue
112
+ propvalues = bwprop.get_values(realisation=realisation)
113
+ tmplog = propvalues[dind].astype(np.float64)
114
+ tmplog = npma.filled(tmplog, fill_value=np.nan)
115
+ tmplog[tmplog == -999] = np.nan
116
+ if "discrete" in str(bwprop.type):
117
+ self.create_log(
118
+ lname, logtype=_AttrType.DISC.value, logrecord=bwprop.code_names
119
+ )
120
+ else:
121
+ self.create_log(lname, logtype=_AttrType.CONT.value)
122
+
123
+ logs[lname] = tmplog
124
+
125
+ self.set_dataframe(pd.DataFrame.from_dict(logs))
126
+ self._gridname = gname
127
+ self._filesrc = None
128
+
129
+ # finally get some other metadata like RKB and topside X Y; as they
130
+ # seem to miss for the BW in RMS, try and get them from the
131
+ # well itself...
132
+
133
+ if wname in rox.project.wells:
134
+ self._rkb = rox.project.wells[wname].rkb
135
+ self._xpos, self._ypos = rox.project.wells[wname].wellhead
136
+ else:
137
+ self._rkb = None
138
+ self._xpos, self._ypos = (
139
+ self._df[_AttrName.XNAME.value][0],
140
+ self._df[_AttrName.YNAME.value][0],
141
+ )
142
+
143
+
144
+ def _roxapi_export_bwell(self, rox, gname, bwname, wname, lognames, ijk, realisation):
145
+ """Private function for ROXAPI well export (set well with updated logs to Roxar)"""
146
+
147
+ if gname in rox.project.grid_models:
148
+ gmodel = rox.project.grid_models[gname]
149
+ logger.debug("RMS grid model <%s> OK", gname)
150
+ else:
151
+ raise ValueError(f"No such grid name present: {gname}")
152
+
153
+ if bwname in gmodel.blocked_wells_set:
154
+ bwset = gmodel.blocked_wells_set[bwname]
155
+ logger.debug("Blocked well set <%s> OK", bwname)
156
+ bwprops = bwset.properties
157
+ else:
158
+ raise ValueError(f"No such blocked well set: {bwname}")
159
+
160
+ if wname in bwset.get_well_names(realisation=realisation):
161
+ self._wname = wname
162
+ else:
163
+ raise WellNotFoundError(f"No such well in blocked well set: {wname}")
164
+
165
+ bwnames = [item.name for item in bwset.properties] # updated list
166
+
167
+ # get the current indices for the well
168
+ dind = bwset.get_data_indices([self._wname], realisation=realisation)
169
+
170
+ for lname in self.lognames:
171
+ if not ijk and lname in [
172
+ _AttrName.I_INDEX.value,
173
+ _AttrName.J_INDEX.value,
174
+ _AttrName.K_INDEX.value,
175
+ ]:
176
+ continue
177
+
178
+ if lognames != "all" and lname not in lognames:
179
+ continue
180
+
181
+ if lname not in bwnames:
182
+ if self.wlogtypes[lname] == _AttrType.CONT.value:
183
+ bwlog = bwset.properties.create(
184
+ lname, roxar.GridPropertyType.continuous, np.float32
185
+ )
186
+ bwprop = bwset.generate_values(discrete=False, fill_value=0.0)
187
+ else:
188
+ bwlog = bwset.properties.create(
189
+ lname, roxar.GridPropertyType.discrete, np.int32
190
+ )
191
+ bwprop = bwset.generate_values(discrete=True, fill_value=0)
192
+
193
+ else:
194
+ bwlog = bwprops[lname]
195
+ bwprop = bwlog.get_values(realisation=realisation)
196
+ usedtype = bwprop.dtype
197
+ dind = bwset.get_data_indices([self._wname], realisation=realisation)
198
+
199
+ if self.get_dataframe(copy=False)[lname].values.size != dind.size:
200
+ raise ValueError(
201
+ "Dataframe is of wrong size, changing numbers of rows is not possible"
202
+ )
203
+
204
+ # Get the values
205
+ values = self.get_dataframe(copy=False)[lname].values
206
+
207
+ # Create masked array first, then handle casting
208
+ masked_values = np.ma.masked_invalid(values)
209
+
210
+ # Handle casting based on target type
211
+ if np.issubdtype(usedtype, np.integer):
212
+ # For integer types, suppress the warning and handle invalid values
213
+ with warnings.catch_warnings():
214
+ warnings.filterwarnings(
215
+ "ignore", "invalid value encountered in cast", RuntimeWarning
216
+ )
217
+ # Fill masked values with a valid integer before casting
218
+ filled_values = np.ma.filled(masked_values, fill_value=0)
219
+ cast_values = filled_values.astype(usedtype)
220
+ # Recreate the masked array with the original mask
221
+ maskedvalues = np.ma.masked_array(cast_values, mask=masked_values.mask)
222
+ else:
223
+ # For float types, direct cast is fine
224
+ maskedvalues = masked_values.astype(usedtype)
225
+
226
+ # there are cases where the RMS API complains on the actual value of the masked
227
+ # array being outside range; hence remedy is to set 'data' value to a usedtype
228
+ # dependent 'undef'. Hpwever still some flaky stuff here...
229
+ undef = INT_MIN if "int" in str(usedtype) else np.nan
230
+ maskedvalues.data[maskedvalues.mask] = undef
231
+
232
+ bwprop[dind] = maskedvalues
233
+ try:
234
+ bwlog.set_values(bwprop, realisation=realisation)
235
+ except ValueError as err:
236
+ msg = (
237
+ f"\nFailed setting blocked log for {lname} in RMS.\n"
238
+ f"Details:\n{maskedvalues.data}\n{maskedvalues.mask}\n{usedtype}"
239
+ )
240
+ logger.critical(msg)
241
+ raise err
@@ -0,0 +1,68 @@
1
+ """Well input and output, private module for ROXAPI"""
2
+
3
+ from xtgeo.common.log import null_logger
4
+ from xtgeo.common.xtgeo_dialog import XTGeoDialog
5
+ from xtgeo.roxutils import RoxUtils
6
+
7
+ from .blocked_well import blockedwell_from_roxar
8
+
9
+ xtg = XTGeoDialog()
10
+ logger = null_logger(__name__)
11
+
12
+
13
+ # Import from ROX api
14
+ # --------------------------------------------------------------------------------------
15
+ def import_bwells_roxapi(
16
+ self, project, gname, bwname, lognames=None, ijk=True, realisation=0
17
+ ): # pragma: no cover
18
+ """Private function for loading project and ROXAPI blockwell import"""
19
+
20
+ logger.debug("Opening RMS project ...")
21
+ rox = RoxUtils(project, readonly=True)
22
+
23
+ _roxapi_import_bwells(self, rox, gname, bwname, lognames, ijk, realisation)
24
+
25
+ rox.safe_close()
26
+
27
+
28
+ def _roxapi_import_bwells(
29
+ self, rox, gname, bwname, lnames, ijk, realisation
30
+ ): # pragma: no cover
31
+ """Private function for ROXAPI blocked wells import"""
32
+
33
+ if gname in rox.project.grid_models:
34
+ gmodel = rox.project.grid_models[gname]
35
+ logger.debug("RMS grid model <%s> OK", gname)
36
+ else:
37
+ raise ValueError(f"No such grid name present: {gname}")
38
+
39
+ if bwname in gmodel.blocked_wells_set:
40
+ bwset = gmodel.blocked_wells_set[bwname]
41
+ logger.debug("Blocked well set <%s> OK", bwname)
42
+ else:
43
+ raise ValueError(f"No such blocked well set: {bwname}")
44
+
45
+ wnames = bwset.get_well_names()
46
+
47
+ logger.debug("Lognames are %s", lnames)
48
+
49
+ bwlist = []
50
+ logger.debug("Loading wells ...")
51
+ for wname in wnames:
52
+ logger.debug("Loading well %s", wname)
53
+ bwtmp = blockedwell_from_roxar(
54
+ rox.project,
55
+ gname,
56
+ bwname,
57
+ wname,
58
+ lognames=lnames,
59
+ ijk=ijk,
60
+ realisation=realisation,
61
+ )
62
+ bwlist.append(bwtmp)
63
+
64
+ self._wells = bwlist
65
+
66
+ if not self._wells:
67
+ xtg.warn("No wells imported to BlockedWells")
68
+ self._wells = None
@@ -0,0 +1,30 @@
1
+ """Auxillary functions for the well class
2
+
3
+ 'self' is a Well() instance
4
+
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from xtgeo.common.exceptions import InvalidFileFormatError
10
+ from xtgeo.common.log import null_logger
11
+ from xtgeo.io._file import FileFormat
12
+
13
+ from . import _well_io
14
+
15
+ logger = null_logger(__name__)
16
+
17
+
18
+ def _data_reader_factory(file_format: FileFormat):
19
+ if file_format in (FileFormat.RMSWELL, FileFormat.IRAP_ASCII):
20
+ return _well_io.import_rms_ascii
21
+ if file_format == FileFormat.HDF:
22
+ return _well_io.import_hdf5_well
23
+
24
+ extensions = FileFormat.extensions_string(
25
+ [FileFormat.RMSWELL, FileFormat.IRAP_ASCII, FileFormat.HDF]
26
+ )
27
+ raise InvalidFileFormatError(
28
+ f"File format {file_format} is invalid for Well types. "
29
+ f"Supported formats are {extensions}."
30
+ )