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
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import IO, TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import roffio
|
|
9
|
+
|
|
10
|
+
from xtgeo import _cxtgeo
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import pathlib
|
|
14
|
+
from collections.abc import MutableMapping, Sequence
|
|
15
|
+
|
|
16
|
+
from xtgeo.grid3d import Grid
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class RoffGrid:
|
|
21
|
+
"""
|
|
22
|
+
A RoffGrid contains a grid as represented in a roff file. The
|
|
23
|
+
grid layout is corner point geometry with increasing x,y, and z
|
|
24
|
+
values. The coordinate values are stored in an local coordinate
|
|
25
|
+
system, defined by the x,y,z offset and scale values which
|
|
26
|
+
converts to utm/tvd values by the usual formula.
|
|
27
|
+
|
|
28
|
+
.. code-block:: python
|
|
29
|
+
|
|
30
|
+
x_utm = (x_local + xoffset) * xscale
|
|
31
|
+
|
|
32
|
+
The corner lines are stored in the corner_lines array and define
|
|
33
|
+
the edges of the cell. They go from the bottom layer to the
|
|
34
|
+
top layer in a straight line such that the north-west to south-west
|
|
35
|
+
line of cell at index i,j,k is stored at
|
|
36
|
+
|
|
37
|
+
.. code-block:: python
|
|
38
|
+
|
|
39
|
+
node_index = i * (ny + 1) + j
|
|
40
|
+
bottom_x = corner_lines[node_index]
|
|
41
|
+
bottom_y = corner_lines[node_index+1]
|
|
42
|
+
bottom_z = corner_lines[node_index+2]
|
|
43
|
+
top_x = corner_lines[node_index+3]
|
|
44
|
+
top_y = corner_lines[node_index+4]
|
|
45
|
+
top_z = corner_lines[node_index+5]
|
|
46
|
+
|
|
47
|
+
The z values of the corner of a cell is stored in zvals. For any
|
|
48
|
+
given corner there are 8 adjacent cells. These are usually given
|
|
49
|
+
the directions
|
|
50
|
+
|
|
51
|
+
below_nw, below_ne, below_sw, below_se,
|
|
52
|
+
above_nw, above_ne, above_sw and above_se.
|
|
53
|
+
|
|
54
|
+
where below means lower k value, above means higher k value,
|
|
55
|
+
south means lower j value, north means high j value, and
|
|
56
|
+
west means lower i value, east means higher i value.
|
|
57
|
+
|
|
58
|
+
All of these cells might have different z values along the line, meaning
|
|
59
|
+
there is a 'split' in the layers. How many different values there are is
|
|
60
|
+
dependent on the split_enz values which for any given corner can be 1,2,4
|
|
61
|
+
or 8. 1 means all cells have the same z value, 2 means the below cells
|
|
62
|
+
have different z value than the above cells in that corner. 4 means split
|
|
63
|
+
in north/south and east/west directions. 8 means split in all directions.
|
|
64
|
+
|
|
65
|
+
.. code-block:: python
|
|
66
|
+
|
|
67
|
+
node_index = i * (ny + 1) * (nz + 1) + j * (nz + 1) + k
|
|
68
|
+
split_number = roff_grid.split_enz[node_index]
|
|
69
|
+
|
|
70
|
+
The z-values are listed in c-order of i,j,k coordines, with the number
|
|
71
|
+
of z-values depends on the split number. for instance, if split_enz[0]=2
|
|
72
|
+
Then zvals[0] is the below z value for the corner at i=0,j=0,k=0 and
|
|
73
|
+
zvals[1] is the above z value for the same corner. zvals[2] is then
|
|
74
|
+
a z value for the i=0,j=0,k=1 corner. The z values are listed in
|
|
75
|
+
the order of below before above, south before north, and west before east.
|
|
76
|
+
|
|
77
|
+
The grid can optionally be divided into subgrids by layers. This is
|
|
78
|
+
defined by the subgrids array which for each subgrid contains the
|
|
79
|
+
number of layers to each subgrid in decreasing k-value.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
nx (int): The number of cells in x direction.
|
|
83
|
+
ny (int): The number of cells in y direction.
|
|
84
|
+
nz (int): The number of cells in z direction.
|
|
85
|
+
subgrids (numpy.array of numpy.int32): The number of layers to each
|
|
86
|
+
subgrid by decreasing k value.
|
|
87
|
+
corner_lines (numpy.array of numpy.float32): The coordinates of the top
|
|
88
|
+
and bottom node for each corner line.
|
|
89
|
+
split_enz (bytes): The split number for any given node.
|
|
90
|
+
zvals (numpy.array of numpy.float32): Z values for each cell.
|
|
91
|
+
active (numpy.array of bool): Whether a given cell is active.
|
|
92
|
+
xoffset (float): translation value for utm coordinates.
|
|
93
|
+
yoffset (float): translation value for utm coordinates.
|
|
94
|
+
zoffset (float): translation value for tvd coordinates.
|
|
95
|
+
xscale (float): scaling value for utm coordinates.
|
|
96
|
+
yscale (float): scaling value for utm coordinates.
|
|
97
|
+
zscale (float): scaling value for tvd coordinates.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
nx: int
|
|
102
|
+
ny: int
|
|
103
|
+
nz: int
|
|
104
|
+
corner_lines: np.ndarray
|
|
105
|
+
zvals: np.ndarray
|
|
106
|
+
|
|
107
|
+
split_enz: bytes | None = None
|
|
108
|
+
active: np.ndarray | None = None
|
|
109
|
+
subgrids: np.ndarray | None = None
|
|
110
|
+
xoffset: float = 0.0
|
|
111
|
+
yoffset: float = 0.0
|
|
112
|
+
zoffset: float = 0.0
|
|
113
|
+
xscale: float = 1.0
|
|
114
|
+
yscale: float = 1.0
|
|
115
|
+
zscale: float = -1.0
|
|
116
|
+
|
|
117
|
+
def __post_init__(self) -> None:
|
|
118
|
+
if self.active is None:
|
|
119
|
+
self.active = np.ones(self.nx * self.ny * self.nz, dtype=np.bool_)
|
|
120
|
+
if self.split_enz is None:
|
|
121
|
+
self.split_enz = np.ones(
|
|
122
|
+
self.nx * self.ny * self.nz, dtype=np.uint8
|
|
123
|
+
).tobytes()
|
|
124
|
+
|
|
125
|
+
def __eq__(self, other: Any) -> bool:
|
|
126
|
+
if not isinstance(other, RoffGrid):
|
|
127
|
+
return False
|
|
128
|
+
return (
|
|
129
|
+
self.nx == other.nx
|
|
130
|
+
and self.ny == other.ny
|
|
131
|
+
and self.nz == other.nz
|
|
132
|
+
and self.xoffset == other.xoffset
|
|
133
|
+
and self.yoffset == other.yoffset
|
|
134
|
+
and self.zoffset == other.zoffset
|
|
135
|
+
and self.xscale == other.xscale
|
|
136
|
+
and self.yscale == other.yscale
|
|
137
|
+
and self.zscale == other.zscale
|
|
138
|
+
and np.array_equal(self.subgrids, other.subgrids)
|
|
139
|
+
and np.array_equal(self.split_enz, other.split_enz)
|
|
140
|
+
and np.array_equal(self.zvals, other.zvals)
|
|
141
|
+
and np.array_equal(self.corner_lines, other.corner_lines)
|
|
142
|
+
and np.array_equal(self.active, other.active)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def num_nodes(self) -> int:
|
|
147
|
+
"""
|
|
148
|
+
The number of nodes in the grid, ie. the size of split_enz.
|
|
149
|
+
"""
|
|
150
|
+
return (self.nx + 1) * (self.ny + 1) * (self.nz + 1)
|
|
151
|
+
|
|
152
|
+
def _create_lookup(self) -> None:
|
|
153
|
+
if not hasattr(self, "_lookup"):
|
|
154
|
+
n = self.num_nodes
|
|
155
|
+
self._lookup = np.zeros(n + 1, dtype=np.int32)
|
|
156
|
+
for i in range(n):
|
|
157
|
+
if self.split_enz is not None:
|
|
158
|
+
self._lookup[i + 1] = self.split_enz[i] + self._lookup[i]
|
|
159
|
+
else:
|
|
160
|
+
self._lookup[i + 1] = 1 + self._lookup[i]
|
|
161
|
+
|
|
162
|
+
def z_value(self, node: tuple[int, int, int]) -> np.ndarray:
|
|
163
|
+
"""
|
|
164
|
+
Gives the 8 z values for any given node for
|
|
165
|
+
adjacent cells in the order:
|
|
166
|
+
|
|
167
|
+
* below_sw
|
|
168
|
+
* below_se
|
|
169
|
+
* below_nw
|
|
170
|
+
* below_ne
|
|
171
|
+
* above_sw
|
|
172
|
+
* above_se
|
|
173
|
+
* above_nw
|
|
174
|
+
* above_ne
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
node (tuple of i,j,k index): The index of the node.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValueError if the split array contains unsupported
|
|
181
|
+
split types. (must be 1,2,4 or 8)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
numpy array of float32 with z values for adjacent
|
|
185
|
+
corners in the order given above.
|
|
186
|
+
"""
|
|
187
|
+
i, j, k = node
|
|
188
|
+
self._create_lookup()
|
|
189
|
+
|
|
190
|
+
node_number = i * (self.ny + 1) * (self.nz + 1) + j * (self.nz + 1) + k
|
|
191
|
+
pos = self._lookup[node_number]
|
|
192
|
+
split = self._lookup[node_number + 1] - self._lookup[node_number]
|
|
193
|
+
|
|
194
|
+
if split == 1:
|
|
195
|
+
return np.array([self.zvals[pos]] * 8)
|
|
196
|
+
|
|
197
|
+
if split == 2:
|
|
198
|
+
return np.array([self.zvals[pos]] * 4 + [self.zvals[pos + 1]] * 4)
|
|
199
|
+
|
|
200
|
+
if split == 4:
|
|
201
|
+
return np.array(
|
|
202
|
+
[
|
|
203
|
+
self.zvals[pos],
|
|
204
|
+
self.zvals[pos + 1],
|
|
205
|
+
self.zvals[pos + 2],
|
|
206
|
+
self.zvals[pos + 3],
|
|
207
|
+
]
|
|
208
|
+
* 2
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if split == 8:
|
|
212
|
+
return np.array(
|
|
213
|
+
[
|
|
214
|
+
self.zvals[pos],
|
|
215
|
+
self.zvals[pos + 1],
|
|
216
|
+
self.zvals[pos + 2],
|
|
217
|
+
self.zvals[pos + 3],
|
|
218
|
+
self.zvals[pos + 4],
|
|
219
|
+
self.zvals[pos + 5],
|
|
220
|
+
self.zvals[pos + 6],
|
|
221
|
+
self.zvals[pos + 7],
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
raise ValueError("Only split types 1, 2, 4 and 8 are supported!")
|
|
226
|
+
|
|
227
|
+
def xtgeo_coord(self) -> np.ndarray:
|
|
228
|
+
"""
|
|
229
|
+
Returns:
|
|
230
|
+
The coordinates of nodes in the format of xtgeo.Grid.coordsv
|
|
231
|
+
"""
|
|
232
|
+
offset = (self.xoffset, self.yoffset, self.zoffset)
|
|
233
|
+
scale = (self.xscale, self.yscale, self.zscale)
|
|
234
|
+
coordsv = self.corner_lines.reshape((self.nx + 1, self.ny + 1, 2, 3))
|
|
235
|
+
coordsv = coordsv.astype(np.float64) # ensure 64 bit precision for large coords
|
|
236
|
+
coordsv = np.flip(coordsv, -2)
|
|
237
|
+
coordsv = coordsv + offset
|
|
238
|
+
coordsv *= scale
|
|
239
|
+
return coordsv.reshape((self.nx + 1, self.ny + 1, 6)).astype(np.float64)
|
|
240
|
+
|
|
241
|
+
def xtgeo_actnum(self) -> np.ndarray:
|
|
242
|
+
"""
|
|
243
|
+
Returns:
|
|
244
|
+
The active field in the format of xtgeo.Grid.actnumsv
|
|
245
|
+
"""
|
|
246
|
+
assert self.active is not None
|
|
247
|
+
actnum = self.active.reshape((self.nx, self.ny, self.nz))
|
|
248
|
+
actnum = np.flip(actnum, -1)
|
|
249
|
+
return actnum.astype(np.int32)
|
|
250
|
+
|
|
251
|
+
def xtgeo_zcorn(self) -> np.ndarray:
|
|
252
|
+
"""
|
|
253
|
+
Returns:
|
|
254
|
+
The z values for nodes in the format of xtgeo.Grid.zcornsv
|
|
255
|
+
"""
|
|
256
|
+
zcornsv = np.zeros(
|
|
257
|
+
(self.nx + 1) * (self.ny + 1) * (self.nz + 1) * 4, dtype=np.float32
|
|
258
|
+
)
|
|
259
|
+
assert self.split_enz is not None
|
|
260
|
+
retval = _cxtgeo.grd3d_roff2xtgeo_splitenz(
|
|
261
|
+
int(self.nz + 1),
|
|
262
|
+
float(self.zoffset),
|
|
263
|
+
float(self.zscale),
|
|
264
|
+
self.split_enz,
|
|
265
|
+
self.zvals,
|
|
266
|
+
zcornsv,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if retval == 0:
|
|
270
|
+
return zcornsv.reshape((self.nx + 1, self.ny + 1, self.nz + 1, 4))
|
|
271
|
+
|
|
272
|
+
if retval == -1:
|
|
273
|
+
raise ValueError("Unsupported split type in split_enz")
|
|
274
|
+
|
|
275
|
+
if retval == -2:
|
|
276
|
+
expected_size = (self.nx + 1) * (self.ny + 1) * (self.nz + 1)
|
|
277
|
+
raise ValueError(
|
|
278
|
+
"Incorrect size of splitenz,"
|
|
279
|
+
f" expected {expected_size} got {len(self.split_enz)}"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if retval == -3:
|
|
283
|
+
expected_size = sum(self.split_enz)
|
|
284
|
+
raise ValueError(
|
|
285
|
+
"Incorrect size of zdata,"
|
|
286
|
+
f" expected {expected_size} got {len(self.zvals)}"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
if retval == -4:
|
|
290
|
+
raise ValueError(
|
|
291
|
+
"Incorrect size of zcorn,"
|
|
292
|
+
f" found {zcornsv.shape} should be multiple of {4 * self.nz}"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
raise ValueError(f"Unknown error {retval} occurred")
|
|
296
|
+
|
|
297
|
+
def xtgeo_subgrids(self) -> dict[str, range] | None:
|
|
298
|
+
"""
|
|
299
|
+
Returns:
|
|
300
|
+
The z values for nodes in the format of xtgeo.Grid.zcornsv
|
|
301
|
+
"""
|
|
302
|
+
if self.subgrids is None:
|
|
303
|
+
return None
|
|
304
|
+
result = {}
|
|
305
|
+
next_ind = 1
|
|
306
|
+
for i, current in enumerate(self.subgrids):
|
|
307
|
+
result[f"subgrid_{i}"] = range(next_ind, current + next_ind)
|
|
308
|
+
next_ind += current
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def _from_xtgeo_subgrids(
|
|
313
|
+
xtgeo_subgrids: MutableMapping[str, range | Sequence],
|
|
314
|
+
) -> np.ndarray:
|
|
315
|
+
"""
|
|
316
|
+
Args:
|
|
317
|
+
A xtgeo.Grid._subgrids dictionary
|
|
318
|
+
Returns:
|
|
319
|
+
The corresponding RoffGrid.subgrids
|
|
320
|
+
"""
|
|
321
|
+
if xtgeo_subgrids is None:
|
|
322
|
+
return None
|
|
323
|
+
subgrids = []
|
|
324
|
+
for _, value in xtgeo_subgrids.items():
|
|
325
|
+
if isinstance(value, range):
|
|
326
|
+
subgrids.append(value.stop - value.start)
|
|
327
|
+
elif value != list(range(value[0], value[-1] + 1)):
|
|
328
|
+
raise ValueError(
|
|
329
|
+
"Cannot convert non-consecutive subgrids to roff format."
|
|
330
|
+
)
|
|
331
|
+
else:
|
|
332
|
+
subgrids.append(value[-1] + 1 - value[0])
|
|
333
|
+
return np.array(subgrids, dtype=np.int32)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def from_xtgeo_grid(xtgeo_grid: Grid) -> RoffGrid:
|
|
337
|
+
"""
|
|
338
|
+
Args:
|
|
339
|
+
An xtgeo.Grid
|
|
340
|
+
Returns:
|
|
341
|
+
That grid geometry converted to a RoffGrid.
|
|
342
|
+
"""
|
|
343
|
+
xtgeo_grid._set_xtgformat2()
|
|
344
|
+
nx, ny, nz = xtgeo_grid.dimensions
|
|
345
|
+
active = xtgeo_grid._actnumsv.reshape((nx, ny, nz))
|
|
346
|
+
active = np.flip(active, -1).ravel().astype(np.bool_)
|
|
347
|
+
corner_lines = xtgeo_grid._coordsv.reshape((nx + 1, ny + 1, 2, 3)) * np.array(
|
|
348
|
+
[1, 1, -1]
|
|
349
|
+
)
|
|
350
|
+
corner_lines = np.flip(corner_lines, -2).ravel().astype(np.float32)
|
|
351
|
+
zvals = xtgeo_grid._zcornsv.reshape((nx + 1, ny + 1, nz + 1, 4))
|
|
352
|
+
zvals = np.flip(zvals, 2).ravel().view(np.float32) * -1
|
|
353
|
+
split_enz = np.repeat(b"\x04", (nx + 1) * (ny + 1) * (nz + 1)).tobytes()
|
|
354
|
+
subgrids = RoffGrid._from_xtgeo_subgrids(xtgeo_grid._subgrids)
|
|
355
|
+
|
|
356
|
+
return RoffGrid(nx, ny, nz, corner_lines, zvals, split_enz, active, subgrids)
|
|
357
|
+
|
|
358
|
+
def to_file(
|
|
359
|
+
self,
|
|
360
|
+
filelike: str | pathlib.Path | IO,
|
|
361
|
+
roff_format: roffio.Format = roffio.Format.BINARY,
|
|
362
|
+
) -> None:
|
|
363
|
+
"""
|
|
364
|
+
Writes the RoffGrid to a roff file
|
|
365
|
+
Args:
|
|
366
|
+
filelike (str or byte stream): The file to write to.
|
|
367
|
+
"""
|
|
368
|
+
data: dict[str, dict] = {
|
|
369
|
+
"filedata": {"filetype": "grid"},
|
|
370
|
+
"dimensions": {"nX": self.nx, "nY": self.ny, "nZ": self.nz},
|
|
371
|
+
"translate": {
|
|
372
|
+
"xoffset": np.float32(self.xoffset),
|
|
373
|
+
"yoffset": np.float32(self.yoffset),
|
|
374
|
+
"zoffset": np.float32(self.zoffset),
|
|
375
|
+
},
|
|
376
|
+
"scale": {
|
|
377
|
+
"xscale": np.float32(self.xscale),
|
|
378
|
+
"yscale": np.float32(self.yscale),
|
|
379
|
+
"zscale": np.float32(self.zscale),
|
|
380
|
+
},
|
|
381
|
+
"cornerLines": {"data": self.corner_lines},
|
|
382
|
+
"zvalues": {"data": self.zvals},
|
|
383
|
+
"active": {"data": self.active},
|
|
384
|
+
}
|
|
385
|
+
if self.subgrids is not None:
|
|
386
|
+
data["subgrids"] = {"nLayers": self.subgrids}
|
|
387
|
+
if self.split_enz is not None:
|
|
388
|
+
data["zvalues"]["splitEnz"] = self.split_enz
|
|
389
|
+
roffio.write(filelike, data, roff_format=roff_format)
|
|
390
|
+
|
|
391
|
+
@staticmethod
|
|
392
|
+
def from_file(filelike: str | pathlib.Path | IO) -> RoffGrid:
|
|
393
|
+
"""
|
|
394
|
+
Read a RoffGrid from a roff file
|
|
395
|
+
Args:
|
|
396
|
+
filelike (str or byte stream): The file to read from.
|
|
397
|
+
Returns:
|
|
398
|
+
The RoffGrid in the roff file.
|
|
399
|
+
"""
|
|
400
|
+
translate_kws = {
|
|
401
|
+
"dimensions": {"nX": "nx", "nY": "ny", "nZ": "nz"},
|
|
402
|
+
"translate": {
|
|
403
|
+
"xoffset": "xoffset",
|
|
404
|
+
"yoffset": "yoffset",
|
|
405
|
+
"zoffset": "zoffset",
|
|
406
|
+
},
|
|
407
|
+
"scale": {
|
|
408
|
+
"xscale": "xscale",
|
|
409
|
+
"yscale": "yscale",
|
|
410
|
+
"zscale": "zscale",
|
|
411
|
+
},
|
|
412
|
+
"cornerLines": {"data": "corner_lines"},
|
|
413
|
+
"zvalues": {"splitEnz": "split_enz", "data": "zvals"},
|
|
414
|
+
"active": {"data": "active"},
|
|
415
|
+
"subgrids": {"nLayers": "subgrids"},
|
|
416
|
+
}
|
|
417
|
+
optional_keywords = defaultdict(
|
|
418
|
+
list,
|
|
419
|
+
{
|
|
420
|
+
"translate": ["xoffset", "yoffset", "zoffset"],
|
|
421
|
+
"scale": ["xscale", "yscale", "zscale"],
|
|
422
|
+
"subgrids": ["nLayers"],
|
|
423
|
+
"active": ["data"],
|
|
424
|
+
},
|
|
425
|
+
)
|
|
426
|
+
# The found dictionary contains all tags/tagkeys which we are
|
|
427
|
+
# interested in with None as the initial value. We go through the
|
|
428
|
+
# tag/tagkeys in the file and replace as they are found.
|
|
429
|
+
found = {
|
|
430
|
+
tag_name: dict.fromkeys(tag_keys)
|
|
431
|
+
for tag_name, tag_keys in translate_kws.items()
|
|
432
|
+
}
|
|
433
|
+
found["filedata"] = {"filetype": None}
|
|
434
|
+
with roffio.lazy_read(filelike) as tag_generator:
|
|
435
|
+
for tag, keys in tag_generator:
|
|
436
|
+
if tag in found:
|
|
437
|
+
# We do not destruct keys yet as this fetches the value too early.
|
|
438
|
+
# key is not a tuple but an object that fetches the value when
|
|
439
|
+
# __getitem__ is called.
|
|
440
|
+
for key in keys:
|
|
441
|
+
if key[0] in found[tag]:
|
|
442
|
+
if found[tag][key[0]] is not None:
|
|
443
|
+
raise ValueError(
|
|
444
|
+
f"Multiple tag, tagkey pair {tag}, {key[0]}"
|
|
445
|
+
" in {filelike}"
|
|
446
|
+
)
|
|
447
|
+
found[tag][key[0]] = key[1]
|
|
448
|
+
|
|
449
|
+
for tag_name, keys in found.items():
|
|
450
|
+
for key_name, value in keys.items():
|
|
451
|
+
if value is None and key_name not in optional_keywords[tag_name]:
|
|
452
|
+
raise ValueError(
|
|
453
|
+
f"Missing non-optional keyword {tag_name}:{key_name}"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
filetype = found["filedata"]["filetype"]
|
|
457
|
+
if filetype != "grid":
|
|
458
|
+
raise ValueError(
|
|
459
|
+
f"File {filelike} did not have filetype set to grid, found {filetype}"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# TODO(JB): This needs more refactoring, tricky to track key-values when
|
|
463
|
+
# added to a dict like this. One option is to use a TypedDict.
|
|
464
|
+
kwarg = {
|
|
465
|
+
translated: found[tag][key]
|
|
466
|
+
for tag, tag_keys in translate_kws.items()
|
|
467
|
+
for key, translated in tag_keys.items()
|
|
468
|
+
if found[tag][key] is not None
|
|
469
|
+
}
|
|
470
|
+
return RoffGrid(**kwarg) # type: ignore
|