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,388 @@
1
+ """Export RegularSurface data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import json
7
+ import struct
8
+ from typing import TYPE_CHECKING
9
+
10
+ import h5py
11
+ import hdf5plugin
12
+ import numpy as np
13
+
14
+ from xtgeo import _cxtgeo
15
+ from xtgeo.common.constants import UNDEF_MAP_IRAPA, UNDEF_MAP_IRAPB
16
+ from xtgeo.common.log import null_logger
17
+
18
+ if TYPE_CHECKING:
19
+ from xtgeo.io._file import FileWrapper
20
+ from xtgeo.surface.regular_surface import RegularSurface
21
+
22
+ logger = null_logger(__name__)
23
+
24
+
25
+ DEBUG = 0
26
+
27
+ if DEBUG < 0:
28
+ DEBUG = 0
29
+
30
+ PMD_DATAUNITDISTANCE = {
31
+ 15: "meter",
32
+ 16: "km",
33
+ 17: "feet",
34
+ 18: "yard",
35
+ 19: "mile",
36
+ 221: "global",
37
+ }
38
+
39
+ PMD_DATAUNITZ = {
40
+ 10: "10",
41
+ 31: "31",
42
+ 44: "44",
43
+ 300: "300",
44
+ }
45
+
46
+
47
+ def export_irap_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
48
+ """Export to Irap RMS ascii format."""
49
+
50
+ vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPA, order="F")
51
+
52
+ yinc = self.yinc * self.yflip
53
+
54
+ xmax = self.xori + (self.ncol - 1) * self.xinc
55
+ ymax = self.yori + (self.nrow - 1) * yinc
56
+
57
+ header = (
58
+ f"-996 {self.nrow} {self.xinc} {yinc}\n"
59
+ f"{self.xori} {xmax} {self.yori} {ymax}\n"
60
+ f"{self.ncol} {self.rotation} {self.xori} {self.yori}\n"
61
+ "0 0 0 0 0 0 0\n"
62
+ )
63
+
64
+ def _optimal_shape(vals, start=9):
65
+ """Optimal shape for the data.
66
+
67
+ It seems by reverse engineering that RMS accepts only 9 or less items per line
68
+ """
69
+ # Check if divisible by a number
70
+ size = len(vals)
71
+ # Find the nearest factorization
72
+ for i in range(start, 1, -1):
73
+ if size % i == 0:
74
+ return (int(size // i), int(i)) # Ensure integers are returned
75
+
76
+ # If we can't find a perfect divisor, return a valid shape
77
+ return (int(size), 1)
78
+
79
+ buffer = io.StringIO()
80
+
81
+ np.savetxt(buffer, vals.reshape(_optimal_shape(vals)), fmt="%f", delimiter=" ")
82
+ data = buffer.getvalue()
83
+
84
+ # Combine header and data
85
+ buf = (header + data).encode("latin1")
86
+
87
+ if mfile.memstream:
88
+ mfile.file.write(buf)
89
+ else:
90
+ with open(mfile.name, "wb") as fout:
91
+ fout.write(buf)
92
+
93
+ del vals
94
+
95
+
96
+ def export_irap_binary(self: RegularSurface, mfile: FileWrapper) -> None:
97
+ """Export to Irap RMS binary format."""
98
+
99
+ vals = self.get_values1d(fill_value=UNDEF_MAP_IRAPB, order="F")
100
+
101
+ yinc = self.yinc * self.yflip
102
+
103
+ header = struct.pack(
104
+ ">3i6f3i3f10i", # > means big endian storage
105
+ 32,
106
+ -996,
107
+ self.nrow,
108
+ self.xori,
109
+ self.xori + self.xinc * (self.ncol - 1),
110
+ self.yori,
111
+ self.yori + yinc * (self.nrow - 1),
112
+ self.xinc,
113
+ yinc,
114
+ 32,
115
+ 16,
116
+ self.ncol,
117
+ self.rotation,
118
+ self.xori,
119
+ self.yori,
120
+ 16,
121
+ 28,
122
+ 0,
123
+ 0,
124
+ 0,
125
+ 0,
126
+ 0,
127
+ 0,
128
+ 0,
129
+ 28,
130
+ )
131
+ inum = self.nrow * self.ncol
132
+
133
+ # export to Irap binary in ncol chunks (the only chunk size accepted by RMS)
134
+ nchunk = self.ncol
135
+ chunks = [nchunk] * (inum // nchunk)
136
+ if (inum % nchunk) > 0:
137
+ chunks.append(inum % nchunk)
138
+ start = 0
139
+ data = bytearray(header)
140
+ chunk_size = nchunk * 4
141
+
142
+ # Precompute the struct.pack format for chunk size
143
+ chunk_size_pack = struct.pack(">i", chunk_size)
144
+
145
+ for chunk in chunks:
146
+ chunk_data = np.array(vals[start : start + chunk], dtype=">f4").tobytes()
147
+ data.extend(chunk_size_pack)
148
+ data.extend(chunk_data)
149
+ data.extend(chunk_size_pack)
150
+ start += chunk
151
+
152
+ if mfile.memstream:
153
+ mfile.file.write(data)
154
+ else:
155
+ with open(mfile.name, "wb") as fout:
156
+ fout.write(data)
157
+
158
+
159
+ def export_ijxyz_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
160
+ """Export to DSG IJXYZ ascii format."""
161
+
162
+ dfr = self.get_dataframe(ij=True, order="F") # order F since this was in cxtgeo
163
+ ix = dfr.IX - 1 # since dataframe indexing starts at 1
164
+ jy = dfr.JY - 1
165
+ inlines = self.ilines[ix]
166
+ xlines = self.xlines[jy]
167
+ dfr["IL"] = inlines
168
+ dfr["XL"] = xlines
169
+ dfr = dfr[["IL", "XL", "X_UTME", "Y_UTMN", "VALUES"]]
170
+
171
+ fmt = "%f"
172
+
173
+ if mfile.memstream:
174
+ buf = dfr.to_csv(sep="\t", float_format=fmt, index=False, header=False)
175
+ buf = buf.encode("latin1")
176
+ mfile.file.write(buf)
177
+ else:
178
+ dfr.to_csv(mfile.name, sep="\t", float_format=fmt, index=False, header=False)
179
+
180
+
181
+ def export_zmap_ascii(self: RegularSurface, mfile: FileWrapper) -> None:
182
+ """Export to ZMAP ascii format (non-rotated)."""
183
+
184
+ # zmap can only deal with non-rotated formats; hence make a copy
185
+ # of the instance and derotate that prior to export, so that the
186
+ # original instance is unchanged
187
+
188
+ scopy = self.copy()
189
+
190
+ undef = -99999.0
191
+ if abs(scopy.rotation) > 1.0e-20:
192
+ scopy.unrotate()
193
+
194
+ yinc = scopy._yinc * scopy._yflip
195
+
196
+ vals = scopy.get_values1d(order="C", asmasked=False, fill_value=undef)
197
+
198
+ xmax = scopy.xori + (scopy.ncol - 1) * scopy.xinc
199
+ ymax = scopy.yori + (scopy.nrow - 1) * yinc
200
+
201
+ fcode = 8
202
+ if scopy.values.min() > -10 and scopy.values.max() < 10:
203
+ fcode = 4
204
+
205
+ nfrow = scopy.nrow if scopy.nrow < 5 else 5
206
+
207
+ buf = "! Export from XTGeo (python engine)\n"
208
+ buf += f"@ GRIDFILE, GRID, {nfrow}\n"
209
+ buf += f"20, {undef}, , {fcode}, 1\n"
210
+ buf += f"{scopy.nrow}, {scopy.ncol}, {scopy.xori}, {xmax}, {scopy.yori}, {ymax}\n"
211
+
212
+ buf += "0.0, 0.0, 0.0\n"
213
+ buf += "@\n"
214
+
215
+ vals = vals.tolist()
216
+ ncol = 0
217
+ for icol in range(scopy.ncol):
218
+ for jrow in range(scopy.nrow - 1, -1, -1):
219
+ ic = icol * scopy.nrow + jrow
220
+ buf += f" {vals[ic]:19.{fcode}f}"
221
+ ncol += 1
222
+ if ncol == 5 or jrow == 0:
223
+ buf += "\n"
224
+ ncol = 0
225
+
226
+ # convert buffer to ascii
227
+ buf = buf.encode("latin1")
228
+
229
+ if mfile.memstream:
230
+ mfile.file.write(buf)
231
+ else:
232
+ with open(mfile.name, "wb") as fout:
233
+ fout.write(buf)
234
+
235
+ del vals
236
+
237
+
238
+ def export_storm_binary(self: RegularSurface, mfile: FileWrapper) -> None:
239
+ """Export to Storm binary format (non-rotated)."""
240
+
241
+ # storm can only deal with non-rotated formats; hence make a copy
242
+ # of the instance and derotate that prior to export, so that the
243
+ # original instance is unchanged
244
+
245
+ scopy = self.copy()
246
+
247
+ if abs(scopy.rotation) > 1.0e-20:
248
+ scopy.unrotate()
249
+
250
+ zmin = scopy.values.min()
251
+ zmax = scopy.values.max()
252
+
253
+ yinc = scopy._yinc * scopy._yflip
254
+
255
+ ier = _cxtgeo.surf_export_storm_bin(
256
+ mfile.get_cfhandle(),
257
+ scopy._ncol,
258
+ scopy._nrow,
259
+ scopy._xori,
260
+ scopy._yori,
261
+ scopy._xinc,
262
+ yinc,
263
+ scopy.get_values1d(order="F", asmasked=False, fill_value=self.undef).astype(
264
+ np.float64
265
+ ),
266
+ zmin,
267
+ zmax,
268
+ 0,
269
+ )
270
+ if ier != 0:
271
+ raise RuntimeError(f"Export to Storm binary went wrong, code is {ier}")
272
+ del scopy
273
+
274
+ mfile.cfclose()
275
+
276
+
277
+ def export_petromod_binary(
278
+ self: RegularSurface, mfile: FileWrapper, pmd_dataunits: tuple[int, int]
279
+ ):
280
+ """Export to petromod binary format."""
281
+ validunits = False
282
+ unitd = 15
283
+ unitz = 10
284
+ if isinstance(pmd_dataunits, tuple) and len(pmd_dataunits) == 2:
285
+ unitd, unitz = pmd_dataunits
286
+ if isinstance(unitd, int) and isinstance(unitz, int):
287
+ if unitd in PMD_DATAUNITDISTANCE and unitz in PMD_DATAUNITZ:
288
+ validunits = True
289
+
290
+ if unitd <= 0 or unitz <= 0:
291
+ raise ValueError("Values for pmd_dataunits cannot be negative!")
292
+
293
+ if not validunits:
294
+ UserWarning(
295
+ "Format or values for pmd_dataunits out of range: Pair should be in ranges "
296
+ f"{PMD_DATAUNITDISTANCE} and {PMD_DATAUNITZ}"
297
+ )
298
+
299
+ undef = 99999
300
+
301
+ dsc = "Content=Map,"
302
+ dsc += f"DataUnitDistance={unitd},"
303
+ dsc += f"DataUnitZ={unitz},"
304
+ dsc += f"GridNoX={self.ncol},"
305
+ dsc += f"GridNoY={self.nrow},"
306
+ dsc += f"GridStepX={self.xinc},"
307
+ dsc += f"GridStepY={self.yinc},"
308
+ dsc += "MapType=GridMap,"
309
+ dsc += f"OriginX={self.xori},"
310
+ dsc += f"OriginY={self.yori},"
311
+ dsc += f"RotationAngle={self.rotation},"
312
+ dsc += f"RotationOriginX={self.xori},"
313
+ dsc += f"RotationOriginY={self.yori},"
314
+ dsc += f"Undefined={undef},"
315
+ dsc += "Version=1.0"
316
+
317
+ values = np.ma.filled(self.values1d, fill_value=undef)
318
+
319
+ _cxtgeo.surf_export_petromod_bin(
320
+ mfile.get_cfhandle(),
321
+ dsc,
322
+ values.astype(np.float64),
323
+ )
324
+
325
+ mfile.cfclose()
326
+
327
+
328
+ def export_xtgregsurf(self, mfile):
329
+ """Export to experimental xtgregsurf format, python version."""
330
+ logger.info("Export to xtgregsurf format...")
331
+
332
+ self.metadata.required = self
333
+
334
+ vals = self.get_values1d(fill_value=self._undef, order="C").astype(np.float32)
335
+
336
+ prevalues = (1, 1101, 4, self.ncol, self.nrow)
337
+ mystruct = struct.Struct("= i i i q q")
338
+ hdr = mystruct.pack(*prevalues)
339
+
340
+ meta = self.metadata.get_metadata()
341
+
342
+ jmeta = json.dumps(meta).encode()
343
+
344
+ with open(mfile.name, "wb") as fout:
345
+ fout.write(hdr)
346
+
347
+ with open(mfile.name, "ab") as fout:
348
+ vals.tofile(fout)
349
+
350
+ with open(mfile.name, "ab") as fout:
351
+ fout.write("\nXTGMETA.v01\n".encode())
352
+
353
+ with open(mfile.name, "ab") as fout:
354
+ fout.write(jmeta)
355
+
356
+ logger.info("Export to xtgregsurf format... done!")
357
+
358
+
359
+ def export_hdf5_regsurf(self, mfile, compression="lzf", dtype="float32"):
360
+ """Export to experimental hdf5 format."""
361
+ logger.info("Export to hdf5 format...")
362
+
363
+ self.metadata.required = self
364
+
365
+ meta = self.metadata.get_metadata()
366
+ jmeta = json.dumps(meta).encode()
367
+
368
+ if compression and compression == "blosc":
369
+ compression = hdf5plugin.Blosc(
370
+ cname="blosclz", clevel=9, shuffle=hdf5plugin.Blosc.SHUFFLE
371
+ )
372
+
373
+ if dtype not in ("float32", "float64", np.float32, np.float64):
374
+ raise ValueError("Wrong dtype input, must be 'float32' or 'float64'")
375
+
376
+ with h5py.File(mfile.name, "w") as fh5:
377
+ grp = fh5.create_group("RegularSurface")
378
+ grp.create_dataset(
379
+ "values",
380
+ data=np.ma.filled(self.values, fill_value=self.undef).astype(dtype),
381
+ compression=compression,
382
+ chunks=True,
383
+ )
384
+ grp.attrs["metadata"] = jmeta
385
+ grp.attrs["provider"] = "xtgeo"
386
+ grp.attrs["format-idcode"] = 1101
387
+
388
+ logger.info("Export to hdf5 format... done!")
@@ -0,0 +1,275 @@
1
+ """Regular surface vs Grid3D"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Literal
7
+
8
+ import numpy as np
9
+
10
+ from xtgeo import _cxtgeo, _internal
11
+ from xtgeo.common.log import null_logger
12
+ from xtgeo.grid3d import _gridprop_lowlevel
13
+ from xtgeo.grid3d.grid_property import GridProperty
14
+
15
+ if TYPE_CHECKING:
16
+ from xtgeo.grid3d.grid import Grid
17
+ from xtgeo.surface.regular_surface import RegularSurface
18
+
19
+
20
+ # self = RegularSurface instance!
21
+
22
+ logger = null_logger(__name__)
23
+
24
+
25
+ def slice_grid3d(
26
+ self: RegularSurface,
27
+ grid: Grid,
28
+ prop: GridProperty,
29
+ zsurf: RegularSurface | None = None,
30
+ sbuffer: int = 1,
31
+ ):
32
+ """Private function for the Grid3D slicing."""
33
+
34
+ grid._set_xtgformat1()
35
+ if zsurf is not None:
36
+ other = zsurf
37
+ else:
38
+ logger.info('The current surface is copied as "other"')
39
+ other = self.copy()
40
+ if not self.compare_topology(other, strict=False):
41
+ raise RuntimeError("Topology of maps differ. Stop!")
42
+
43
+ zslice = other.copy()
44
+
45
+ nsurf = self.ncol * self.nrow
46
+
47
+ p_prop = _gridprop_lowlevel.update_carray(prop, discrete=False)
48
+
49
+ istat, updatedval = _cxtgeo.surf_slice_grd3d(
50
+ self.ncol,
51
+ self.nrow,
52
+ self.xori,
53
+ self.xinc,
54
+ self.yori,
55
+ self.yinc,
56
+ self.rotation,
57
+ self.yflip,
58
+ zslice.get_values1d(),
59
+ nsurf,
60
+ grid.ncol,
61
+ grid.nrow,
62
+ grid.nlay,
63
+ grid._coordsv,
64
+ grid._zcornsv,
65
+ grid._actnumsv,
66
+ p_prop,
67
+ sbuffer,
68
+ )
69
+
70
+ if istat != 0:
71
+ logger.warning("Problem, ISTAT = %s", istat)
72
+
73
+ self.set_values1d(updatedval)
74
+
75
+ return istat
76
+
77
+
78
+ @dataclass
79
+ class DeriveSurfaceFromGrid3D:
80
+ """Private class; derive a surface from a 3D grid / gridproperty."""
81
+
82
+ grid: Grid
83
+ template: RegularSurface | str | None = None
84
+ where: str | int = "top"
85
+ property: str | GridProperty = "depth"
86
+ rfactor: float = 1.0
87
+ index_position: Literal["center", "top", "base", "bot"] = "center"
88
+
89
+ # private state variables
90
+ _args: dict | None = None
91
+ _tempsurf: RegularSurface | None = None
92
+ _klayer: int | None = None
93
+ _option: Literal["top", "bot"] = "top"
94
+ _result: np.ma.MaskedArray | list[np.ndarray] | None = None
95
+
96
+ def __post_init__(self) -> None:
97
+ logger.debug("DeriveSurfaceFromGrid3D: __post_init__")
98
+
99
+ self._parametrize_regsurf()
100
+ self._generate_working_surface()
101
+ self._eval_where()
102
+
103
+ xtgformat_convert = self.grid._xtgformat == 1
104
+ if xtgformat_convert:
105
+ self.grid._set_xtgformat2()
106
+
107
+ self._sample_regsurf_from_grid3d()
108
+
109
+ # convert back to original xtgformat due to unforseen consequences
110
+ if xtgformat_convert:
111
+ self.grid._set_xtgformat1()
112
+
113
+ def result(self) -> dict:
114
+ args = {}
115
+ args["xori"] = self._tempsurf.xori
116
+ args["yori"] = self._tempsurf.yori
117
+ args["xinc"] = self._tempsurf.xinc
118
+ args["yinc"] = self._tempsurf.yinc
119
+ args["ncol"] = self._tempsurf.ncol
120
+ args["nrow"] = self._tempsurf.nrow
121
+ args["rotation"] = self._tempsurf.rotation
122
+ args["yflip"] = self._tempsurf.yflip
123
+ args["values"] = self._result
124
+
125
+ return args
126
+
127
+ def _parametrize_regsurf(self) -> None:
128
+ """Parametrize setting for the regular surface based on template and grid."""
129
+ args = {}
130
+ if self.template is None:
131
+ # need to estimate map settings from the existing 3D grid
132
+ geom = self.grid.get_geometrics(
133
+ allcells=True, cellcenter=True, return_dict=True, _ver=2
134
+ )
135
+ gminx, gminy, _, gmaxx, gmaxy, _ = self.grid.get_bounding_box()
136
+
137
+ xlen = gmaxx - gminx
138
+ ylen = gmaxy - gminy
139
+ xinc = yinc = (1.0 / self.rfactor) * 0.5 * (geom["avg_dx"] + geom["avg_dy"])
140
+ xori = gminx - xinc
141
+ yori = gminy - xinc
142
+
143
+ xlen += 2 * xinc
144
+ ylen += 2 * yinc
145
+
146
+ # take same xinc and yinc
147
+ ncol = int(xlen / xinc) + 1
148
+ nrow = int(ylen / yinc) + 1
149
+
150
+ args["xori"] = xori
151
+ args["yori"] = yori
152
+ args["xinc"] = xinc
153
+ args["yinc"] = yinc
154
+ args["ncol"] = ncol
155
+ args["nrow"] = nrow
156
+ args["values"] = np.ma.zeros((ncol, nrow), dtype=np.float64)
157
+ elif isinstance(self.template, str) and self.template == "native":
158
+ geom = self.grid.get_geometrics(
159
+ allcells=True, cellcenter=True, return_dict=True
160
+ )
161
+
162
+ args["ncol"] = self.grid.ncol
163
+ args["nrow"] = self.grid.nrow
164
+ args["xori"] = geom["xori"]
165
+ args["yori"] = geom["yori"]
166
+ args["xinc"] = geom["avg_dx"]
167
+ args["yinc"] = geom["avg_dy"]
168
+ args["rotation"] = geom["avg_rotation"]
169
+ args["values"] = np.ma.zeros(
170
+ (self.grid.ncol, self.grid.nrow), dtype=np.float64
171
+ )
172
+ args["yflip"] = -1 if self.grid.ijk_handedness == "right" else 1
173
+
174
+ else:
175
+ args["xori"] = self.template.xori
176
+ args["yori"] = self.template.yori
177
+ args["xinc"] = self.template.xinc
178
+ args["yinc"] = self.template.yinc
179
+ args["ncol"] = self.template.ncol
180
+ args["nrow"] = self.template.nrow
181
+ args["rotation"] = self.template.rotation
182
+ args["yflip"] = self.template.yflip
183
+ args["values"] = self.template.values.copy()
184
+
185
+ self._args = args
186
+
187
+ def _generate_working_surface(self) -> None:
188
+ from xtgeo.surface.regular_surface import RegularSurface
189
+
190
+ self._tempsurf = RegularSurface(**self._args)
191
+ # ensure that this is a subsurface left-handed system
192
+ self._tempsurf.make_lefthanded()
193
+
194
+ def _eval_where(self):
195
+ """Set klayer and option based on ``where`` parameter.
196
+
197
+ Note that klayer is zero-based, while where is one-based.
198
+ """
199
+ if isinstance(self.where, str):
200
+ where = self.where.lower()
201
+ if where == "top":
202
+ self._klayer = 0
203
+ self._option = "top"
204
+ elif where == "base":
205
+ self._klayer = self.grid.nlay - 1
206
+ self._option = "bot"
207
+ else:
208
+ klayer, what = where.split("_")
209
+ self._klayer = int(klayer) - 1
210
+ if self._klayer < 0 or self._klayer >= self.grid.nlay:
211
+ raise ValueError(f"Klayer out of range in where={where}")
212
+ self._option = "top"
213
+ if what == "base":
214
+ self._option = "bot"
215
+ else:
216
+ self._klayer = self.where - 1
217
+ self._option = "top"
218
+
219
+ def _sample_regsurf_from_grid3d(self) -> None:
220
+ """Sample the grid3d to get the values for the regular surface."""
221
+
222
+ regsurf_cpp = _internal.regsurf.RegularSurface(self._tempsurf)
223
+
224
+ iindex, jindex, d_top, d_bot, mask = regsurf_cpp.sample_grid3d_layer(
225
+ self.grid._get_grid_cpp(),
226
+ self._klayer,
227
+ {"top": 0, "bot": 1, "base": 1}.get(self.index_position, 2),
228
+ -1, # number of threads for OpenMP; -1 means let the system decide
229
+ )
230
+
231
+ if self.property == "raw":
232
+ self._result = [iindex, jindex, d_top, d_bot, mask]
233
+ return
234
+
235
+ additional_mask = iindex == -1
236
+ iindex = np.where(iindex == -1, 0, iindex)
237
+ jindex = np.where(jindex == -1, 0, jindex)
238
+
239
+ if isinstance(self.property, str):
240
+ if self.property == "depth":
241
+ if self._option == "top":
242
+ d_top = np.ma.masked_invalid(d_top)
243
+ # to avoid problems when nan is present behind the mask
244
+ d_top.data[d_top.mask] = _internal.numerics.UNDEF_DOUBLE
245
+ else:
246
+ d_bot = np.ma.masked_invalid(d_bot)
247
+ d_bot.data[d_bot.mask] = _internal.numerics.UNDEF_DOUBLE
248
+
249
+ self._result = d_top if self._option == "top" else d_bot
250
+
251
+ elif self.property == "i":
252
+ self._result = np.ma.masked_less(iindex, 0)
253
+ self._result.mask += additional_mask
254
+
255
+ elif self.property == "j":
256
+ self._result = np.ma.masked_less(jindex, 0)
257
+ self._result.mask += additional_mask
258
+ else:
259
+ raise ValueError(f"Unknown option: {self.property}")
260
+
261
+ elif isinstance(self.property, GridProperty):
262
+ propmask = iindex < 0
263
+ iindex[iindex < 0] = 0
264
+ jindex[jindex < 0] = 0
265
+ self._result = np.ma.masked_array(np.zeros_like(d_top), mask=propmask)
266
+ self._result = self.property.values[iindex, jindex, self._klayer]
267
+ self._result.mask += additional_mask
268
+
269
+
270
+ def from_grid3d(grid, template, where, property, rfactor, index_position) -> dict:
271
+ """Private function for deriving a surface from a 3D grid / gridproperty."""
272
+
273
+ return DeriveSurfaceFromGrid3D(
274
+ grid, template, where, property, rfactor, index_position
275
+ ).result()