xtgeo 4.8.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xtgeo might be problematic. Click here for more details.
- cxtgeo.py +582 -0
- cxtgeoPYTHON_wrap.c +20938 -0
- xtgeo/__init__.py +246 -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 +21 -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 +340 -0
- xtgeo/cube/cube1.py +1023 -0
- xtgeo/grid3d/__init__.py +15 -0
- xtgeo/grid3d/_ecl_grid.py +774 -0
- xtgeo/grid3d/_ecl_inte_head.py +148 -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 +266 -0
- xtgeo/grid3d/_grdecl_grid.py +388 -0
- xtgeo/grid3d/_grid3d.py +29 -0
- xtgeo/grid3d/_grid3d_fence.py +181 -0
- xtgeo/grid3d/_grid3d_utils.py +228 -0
- xtgeo/grid3d/_grid_boundary.py +76 -0
- xtgeo/grid3d/_grid_etc1.py +1566 -0
- xtgeo/grid3d/_grid_export.py +221 -0
- xtgeo/grid3d/_grid_hybrid.py +66 -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 +125 -0
- xtgeo/grid3d/_grid_roxapi.py +292 -0
- xtgeo/grid3d/_grid_wellzone.py +165 -0
- xtgeo/grid3d/_gridprop_export.py +178 -0
- xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
- xtgeo/grid3d/_gridprop_import_grdecl.py +130 -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 +174 -0
- xtgeo/grid3d/_gridprop_roxapi.py +239 -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 +469 -0
- xtgeo/grid3d/_roff_parameter.py +303 -0
- xtgeo/grid3d/grid.py +2537 -0
- xtgeo/grid3d/grid_properties.py +699 -0
- xtgeo/grid3d/grid_property.py +1341 -0
- xtgeo/grid3d/types.py +15 -0
- xtgeo/io/__init__.py +1 -0
- xtgeo/io/_file.py +592 -0
- xtgeo/metadata/__init__.py +17 -0
- xtgeo/metadata/metadata.py +431 -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 +18 -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 +271 -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 +631 -0
- xtgeo/surface/_regsurf_roxapi.py +241 -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 +2967 -0
- xtgeo/surface/surfaces.py +276 -0
- xtgeo/well/__init__.py +24 -0
- xtgeo/well/_blockedwell_roxapi.py +221 -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 +574 -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 +216 -0
- xtgeo/well/blocked_wells.py +122 -0
- xtgeo/well/well1.py +1514 -0
- xtgeo/well/wells.py +211 -0
- xtgeo/xyz/__init__.py +6 -0
- xtgeo/xyz/_polygons_oper.py +272 -0
- xtgeo/xyz/_xyz.py +741 -0
- xtgeo/xyz/_xyz_data.py +646 -0
- xtgeo/xyz/_xyz_io.py +490 -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 +681 -0
- xtgeo/xyz/polygons.py +811 -0
- xtgeo-4.8.0.dist-info/METADATA +145 -0
- xtgeo-4.8.0.dist-info/RECORD +117 -0
- xtgeo-4.8.0.dist-info/WHEEL +5 -0
- xtgeo-4.8.0.dist-info/licenses/LICENSE.md +165 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Various grid property operations"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, List, Literal, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
import xtgeo
|
|
10
|
+
from xtgeo import _cxtgeo
|
|
11
|
+
from xtgeo.common import XTGeoDialog, null_logger
|
|
12
|
+
from xtgeo.grid3d import _gridprop_lowlevel as gl
|
|
13
|
+
|
|
14
|
+
xtg = XTGeoDialog()
|
|
15
|
+
logger = null_logger(__name__)
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from xtgeo.grid3d import Grid, GridProperty
|
|
19
|
+
from xtgeo.xyz import Polygons
|
|
20
|
+
|
|
21
|
+
_CoordOrValue = Union[float, int]
|
|
22
|
+
_XYCoordinate = Tuple[_CoordOrValue, _CoordOrValue]
|
|
23
|
+
_XYCoordList = List[List[List[_XYCoordinate]]]
|
|
24
|
+
_ValueList = List[List[_CoordOrValue]]
|
|
25
|
+
XYValueLists = Tuple[_XYCoordList, _ValueList]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_xy_value_lists(
|
|
29
|
+
self: GridProperty,
|
|
30
|
+
grid: Optional[Grid | GridProperty] = None,
|
|
31
|
+
mask: Optional[bool] = None,
|
|
32
|
+
) -> XYValueLists:
|
|
33
|
+
"""Get values for webportal format
|
|
34
|
+
|
|
35
|
+
Two cells:
|
|
36
|
+
[[[(x1,y1), (x2,y2), (x3,y3), (x4,y4)],
|
|
37
|
+
[(x5,y5), (x6,y6), (x7,y7), (x8,y8)]]]
|
|
38
|
+
|
|
39
|
+
If mask is True then inactive cells are ommited from the lists,
|
|
40
|
+
else the active cells corners will be present while the property
|
|
41
|
+
will have a -999 value.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
if grid is None:
|
|
45
|
+
raise RuntimeError("Missing grid object")
|
|
46
|
+
|
|
47
|
+
if not isinstance(grid, xtgeo.grid3d.Grid):
|
|
48
|
+
raise RuntimeError("The input grid is not a XTGeo Grid instance")
|
|
49
|
+
|
|
50
|
+
if not isinstance(self, xtgeo.grid3d.GridProperty):
|
|
51
|
+
raise RuntimeError("The property is not a XTGeo GridProperty instance")
|
|
52
|
+
|
|
53
|
+
clist = grid.get_xyz_corners()
|
|
54
|
+
actnum = grid.get_actnum()
|
|
55
|
+
|
|
56
|
+
# set value 0 if actnum is 0 to facilitate later operations
|
|
57
|
+
if mask:
|
|
58
|
+
for cli in clist:
|
|
59
|
+
cli.values[actnum.values == 0] = 0
|
|
60
|
+
|
|
61
|
+
# now some numpy operations (coffee?, any?)
|
|
62
|
+
xy0 = np.column_stack((clist[0].values1d, clist[1].values1d))
|
|
63
|
+
xy1 = np.column_stack((clist[3].values1d, clist[4].values1d))
|
|
64
|
+
xy2 = np.column_stack((clist[6].values1d, clist[7].values1d))
|
|
65
|
+
xy3 = np.column_stack((clist[9].values1d, clist[10].values1d))
|
|
66
|
+
|
|
67
|
+
xyc = np.column_stack((xy0, xy1, xy2, xy3))
|
|
68
|
+
xyc = xyc.reshape(grid.nlay, grid.ncol * grid.nrow, 4, 2)
|
|
69
|
+
|
|
70
|
+
coordlist = xyc.tolist()
|
|
71
|
+
|
|
72
|
+
# remove cells that are undefined ("marked" as coordinate [0, 0] if mask)
|
|
73
|
+
coordlist = [
|
|
74
|
+
[[tuple(xy) for xy in cell if xy[0] > 0] for cell in lay] for lay in coordlist
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
coordlist = [[cell for cell in lay if len(cell) > 1] for lay in coordlist]
|
|
78
|
+
|
|
79
|
+
pval = self.values1d.reshape((grid.nlay, grid.ncol * grid.nrow))
|
|
80
|
+
valuelist = pval.tolist(fill_value=-999.0)
|
|
81
|
+
if mask:
|
|
82
|
+
valuelist = [[val for val in lay if val != -999.0] for lay in valuelist]
|
|
83
|
+
|
|
84
|
+
return coordlist, valuelist
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def operation_polygons(
|
|
88
|
+
self: GridProperty,
|
|
89
|
+
poly: Polygons,
|
|
90
|
+
value: float | int,
|
|
91
|
+
opname: Literal["add", "sub", "mul", "div", "set"] = "add",
|
|
92
|
+
inside: bool = True,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""A generic function for doing operations restricted to inside
|
|
95
|
+
or outside polygon(s).
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
grid = self.geometry
|
|
99
|
+
grid._xtgformat1()
|
|
100
|
+
|
|
101
|
+
if not isinstance(poly, xtgeo.xyz.Polygons):
|
|
102
|
+
raise ValueError("The poly input is not a Polygons instance")
|
|
103
|
+
|
|
104
|
+
# make a copy of the array which is used a "filter" or "proxy"
|
|
105
|
+
# value will be 1 inside polygons, 0 outside. Undef cells are kept as is
|
|
106
|
+
dtype = self.dtype
|
|
107
|
+
|
|
108
|
+
proxy = self.copy()
|
|
109
|
+
proxy.discrete_to_continuous()
|
|
110
|
+
|
|
111
|
+
proxy.values *= 0.0
|
|
112
|
+
cvals = gl.update_carray(proxy)
|
|
113
|
+
|
|
114
|
+
idgroups = poly.get_dataframe(copy=False).groupby(poly.pname)
|
|
115
|
+
|
|
116
|
+
for id_, grp in idgroups:
|
|
117
|
+
xcor = grp[poly.xname].values
|
|
118
|
+
ycor = grp[poly.yname].values
|
|
119
|
+
|
|
120
|
+
ier = _cxtgeo.grd3d_setval_poly(
|
|
121
|
+
xcor,
|
|
122
|
+
ycor,
|
|
123
|
+
self.ncol,
|
|
124
|
+
self.nrow,
|
|
125
|
+
self.nlay,
|
|
126
|
+
grid._coordsv,
|
|
127
|
+
grid._zcornsv,
|
|
128
|
+
grid._actnumsv,
|
|
129
|
+
cvals,
|
|
130
|
+
1,
|
|
131
|
+
)
|
|
132
|
+
if ier == -9:
|
|
133
|
+
logger.warning(f"Polygon no {id_ + 1} is not closed")
|
|
134
|
+
|
|
135
|
+
gl.update_values_from_carray(proxy, cvals, np.float64, delete=True)
|
|
136
|
+
|
|
137
|
+
proxyv = proxy.values.astype(np.int8)
|
|
138
|
+
|
|
139
|
+
proxytarget = 1
|
|
140
|
+
if not inside:
|
|
141
|
+
proxytarget = 0
|
|
142
|
+
|
|
143
|
+
if opname == "add":
|
|
144
|
+
tmp = self.values.copy() + value
|
|
145
|
+
elif opname == "sub":
|
|
146
|
+
tmp = self.values.copy() - value
|
|
147
|
+
elif opname == "mul":
|
|
148
|
+
tmp = self.values.copy() * value
|
|
149
|
+
elif opname == "div":
|
|
150
|
+
# Dividing a map of zero is always a hazzle; try to obtain 0.0
|
|
151
|
+
# as result in these cases
|
|
152
|
+
if np.isclose(value, 0.0):
|
|
153
|
+
xtg.warn(
|
|
154
|
+
"Dividing a surface with value or surface with zero "
|
|
155
|
+
"elements; may get unexpected results, try to "
|
|
156
|
+
"achieve zero values as result!"
|
|
157
|
+
)
|
|
158
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
159
|
+
this = np.ma.filled(self.values, fill_value=1.0)
|
|
160
|
+
that = np.ma.filled(value, fill_value=1.0)
|
|
161
|
+
mask = np.ma.getmaskarray(self.values)
|
|
162
|
+
tmp = np.true_divide(this, that)
|
|
163
|
+
tmp = np.where(np.isinf(tmp), 0, tmp)
|
|
164
|
+
tmp = np.nan_to_num(tmp)
|
|
165
|
+
tmp = np.ma.array(tmp, mask=mask)
|
|
166
|
+
|
|
167
|
+
elif opname == "set":
|
|
168
|
+
tmp = self.values.copy() * 0 + value
|
|
169
|
+
|
|
170
|
+
# convert tmp back to correct dtype
|
|
171
|
+
tmp = tmp.astype(dtype)
|
|
172
|
+
|
|
173
|
+
self.values[proxyv == proxytarget] = tmp[proxyv == proxytarget]
|
|
174
|
+
del tmp
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""Roxar API functions for XTGeo Grid Property."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from numpy import ma
|
|
10
|
+
|
|
11
|
+
from xtgeo.common import null_logger
|
|
12
|
+
from xtgeo.common.constants import UNDEF, UNDEF_INT, UNDEF_INT_LIMIT, UNDEF_LIMIT
|
|
13
|
+
from xtgeo.roxutils import RoxUtils
|
|
14
|
+
from xtgeo.roxutils._roxar_loader import roxar
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from xtgeo.grid3d.grid_property import GridProperty
|
|
18
|
+
|
|
19
|
+
if roxar is not None:
|
|
20
|
+
from roxar import Project as RoxarProjectType
|
|
21
|
+
from roxar.grids import Grid3D as RoxarGrid3DType
|
|
22
|
+
|
|
23
|
+
logger = null_logger(__name__)
|
|
24
|
+
|
|
25
|
+
VALID_ROXAR_DTYPES = [np.uint8, np.uint16, np.float32]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def import_prop_roxapi(
|
|
29
|
+
project: RoxarProjectType,
|
|
30
|
+
gname: str,
|
|
31
|
+
pname: str,
|
|
32
|
+
realisation: int,
|
|
33
|
+
faciescodes: bool,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
"""Import a Property via ROXAR API spec."""
|
|
36
|
+
logger.info("Opening RMS project ...")
|
|
37
|
+
|
|
38
|
+
rox = RoxUtils(project, readonly=True)
|
|
39
|
+
|
|
40
|
+
result = _get_gridprop_data(rox, gname, pname, realisation, faciescodes)
|
|
41
|
+
|
|
42
|
+
rox.safe_close()
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_gridprop_data(
|
|
47
|
+
rox: RoxUtils, gname: str, pname: str, realisation: int, faciescodes: bool
|
|
48
|
+
) -> dict[str, Any]:
|
|
49
|
+
# inside a RMS project
|
|
50
|
+
|
|
51
|
+
if gname not in rox.project.grid_models:
|
|
52
|
+
raise ValueError(f"No gridmodel with name {gname}")
|
|
53
|
+
if pname not in rox.project.grid_models[gname].properties:
|
|
54
|
+
raise ValueError(f"No property in {gname} with name {pname}")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
return _convert_to_xtgeo_prop(rox, gname, pname, realisation, faciescodes)
|
|
58
|
+
except KeyError as keyerror:
|
|
59
|
+
raise RuntimeError(keyerror) from keyerror
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _convert_to_xtgeo_prop(
|
|
63
|
+
rox: RoxUtils, gname: str, pname: str, realisation: int, faciescodes: bool
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
result: dict[str, Any] = {}
|
|
66
|
+
roxgrid = rox.project.grid_models[gname]
|
|
67
|
+
roxprop = roxgrid.properties[pname]
|
|
68
|
+
|
|
69
|
+
discrete = str(roxprop.type) in ("discrete", "body_facies")
|
|
70
|
+
result["discrete"] = discrete
|
|
71
|
+
|
|
72
|
+
result["roxorigin"] = True
|
|
73
|
+
indexer = roxgrid.get_grid(realisation=realisation).grid_indexer
|
|
74
|
+
result["ncol"], result["nrow"], result["nlay"] = indexer.dimensions
|
|
75
|
+
|
|
76
|
+
logger.info(indexer.ijk_handedness)
|
|
77
|
+
|
|
78
|
+
pvalues = roxprop.get_values(realisation=realisation)
|
|
79
|
+
|
|
80
|
+
if str(roxprop.type) == "body_facies" and faciescodes:
|
|
81
|
+
fmap = roxprop.get_facies_map(realisation=realisation)
|
|
82
|
+
pvalues = fmap[pvalues] # numpy magics
|
|
83
|
+
|
|
84
|
+
result["roxar_dtype"] = pvalues.dtype
|
|
85
|
+
|
|
86
|
+
mybuffer: np.ndarray = np.ndarray(
|
|
87
|
+
indexer.dimensions, dtype=np.int32 if discrete else np.float64
|
|
88
|
+
)
|
|
89
|
+
mybuffer.fill(UNDEF_INT if discrete else UNDEF)
|
|
90
|
+
|
|
91
|
+
cellno = indexer.get_cell_numbers_in_range((0, 0, 0), indexer.dimensions)
|
|
92
|
+
|
|
93
|
+
ijk = indexer.get_indices(cellno)
|
|
94
|
+
|
|
95
|
+
iind = ijk[:, 0]
|
|
96
|
+
jind = ijk[:, 1]
|
|
97
|
+
kind = ijk[:, 2]
|
|
98
|
+
|
|
99
|
+
mybuffer[iind, jind, kind] = pvalues[cellno]
|
|
100
|
+
|
|
101
|
+
mybuffer = ma.masked_greater(mybuffer, UNDEF_INT_LIMIT if discrete else UNDEF_LIMIT)
|
|
102
|
+
|
|
103
|
+
result["values"] = mybuffer
|
|
104
|
+
result["name"] = pname
|
|
105
|
+
|
|
106
|
+
if discrete:
|
|
107
|
+
result["codes"] = _fix_codes(
|
|
108
|
+
result["values"].reshape(-1).compressed(), roxprop.code_names
|
|
109
|
+
)
|
|
110
|
+
logger.info("Fixed codes: %s", result["codes"])
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def export_prop_roxapi(
|
|
115
|
+
self: GridProperty,
|
|
116
|
+
project: RoxarProjectType,
|
|
117
|
+
gname: str,
|
|
118
|
+
pname: str,
|
|
119
|
+
realisation: int = 0,
|
|
120
|
+
casting: Literal["no", "equiv", "safe", "same_kind", "unsafe"] | None = "unsafe",
|
|
121
|
+
) -> None:
|
|
122
|
+
"""Export (i.e. store or save) to a Property icon in RMS via ROXAR API spec."""
|
|
123
|
+
rox = RoxUtils(project, readonly=False)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
roxgrid = rox.project.grid_models[gname]
|
|
127
|
+
_store_in_roxar(self, pname, roxgrid, realisation, casting)
|
|
128
|
+
|
|
129
|
+
except KeyError as keyerror:
|
|
130
|
+
raise RuntimeError(keyerror)
|
|
131
|
+
|
|
132
|
+
if rox._roxexternal:
|
|
133
|
+
rox.project.save()
|
|
134
|
+
|
|
135
|
+
rox.safe_close()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _store_in_roxar(
|
|
139
|
+
self: GridProperty,
|
|
140
|
+
pname: str,
|
|
141
|
+
roxgrid: RoxarGrid3DType,
|
|
142
|
+
realisation: int,
|
|
143
|
+
casting: Literal["no", "equiv", "safe", "same_kind", "unsafe"] | None,
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Store property in RMS."""
|
|
146
|
+
indexer = roxgrid.get_grid(realisation=realisation).grid_indexer
|
|
147
|
+
|
|
148
|
+
logger.info("Store in RMS...")
|
|
149
|
+
|
|
150
|
+
val3d = self.values.copy()
|
|
151
|
+
|
|
152
|
+
cellno = indexer.get_cell_numbers_in_range((0, 0, 0), indexer.dimensions)
|
|
153
|
+
|
|
154
|
+
ijk = indexer.get_indices(cellno)
|
|
155
|
+
|
|
156
|
+
iind = ijk[:, 0]
|
|
157
|
+
jind = ijk[:, 1]
|
|
158
|
+
kind = ijk[:, 2]
|
|
159
|
+
|
|
160
|
+
dtype = self._roxar_dtype
|
|
161
|
+
logger.info("DTYPE is %s for %s", dtype, pname)
|
|
162
|
+
|
|
163
|
+
# casting will secure correct types
|
|
164
|
+
if dtype not in VALID_ROXAR_DTYPES:
|
|
165
|
+
raise TypeError(
|
|
166
|
+
f"Roxar dtype is not valid: {dtype} must be in {VALID_ROXAR_DTYPES}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
pvalues = roxgrid.get_grid(realisation=realisation).generate_values(data_type=dtype)
|
|
170
|
+
|
|
171
|
+
roxtype: Any = roxar # needed for mypy
|
|
172
|
+
roxar_property_type = (
|
|
173
|
+
roxtype.GridPropertyType.discrete
|
|
174
|
+
if self.isdiscrete
|
|
175
|
+
else roxtype.GridPropertyType.continuous
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
pvalues[cellno] = val3d[iind, jind, kind]
|
|
179
|
+
|
|
180
|
+
properties = roxgrid.properties
|
|
181
|
+
|
|
182
|
+
if pname not in properties:
|
|
183
|
+
rprop = properties.create(
|
|
184
|
+
pname, property_type=roxar_property_type, data_type=dtype
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
rprop = properties[pname]
|
|
188
|
+
dtype = rprop.data_type
|
|
189
|
+
|
|
190
|
+
rprop.set_values(pvalues.astype(dtype, casting=casting), realisation=realisation)
|
|
191
|
+
|
|
192
|
+
if self.isdiscrete:
|
|
193
|
+
rprop.code_names = _rox_compatible_codes(self.codes)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _fix_codes(
|
|
197
|
+
active_values: np.ndarray, codes: dict[str | int, str]
|
|
198
|
+
) -> dict[int, str]:
|
|
199
|
+
"""Roxar may provide a code list with empty strings values, fix this issue here.
|
|
200
|
+
|
|
201
|
+
Roxar may also interpolate code values which are actually not present in the
|
|
202
|
+
property. Here, the presence of actual codes is also checked.
|
|
203
|
+
"""
|
|
204
|
+
newcodes = {}
|
|
205
|
+
codes_data = {val: str(val) for val in np.unique(active_values)}
|
|
206
|
+
|
|
207
|
+
for code, name in codes.items():
|
|
208
|
+
if not isinstance(code, int):
|
|
209
|
+
code = int(code)
|
|
210
|
+
|
|
211
|
+
if not name:
|
|
212
|
+
name = str(code)
|
|
213
|
+
|
|
214
|
+
if code not in codes_data:
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
newcodes[code] = name
|
|
218
|
+
|
|
219
|
+
return newcodes
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _rox_compatible_codes(codes: dict) -> dict:
|
|
223
|
+
"""Ensure that keys in codes are int's prior to storage in RMS."""
|
|
224
|
+
|
|
225
|
+
newcodes = {}
|
|
226
|
+
for code, name in codes.items():
|
|
227
|
+
if code is None:
|
|
228
|
+
continue # skip codes of type None; assumed to be spurious
|
|
229
|
+
if not isinstance(code, int):
|
|
230
|
+
try:
|
|
231
|
+
code = int(code)
|
|
232
|
+
except ValueError:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
"The keys in codes must be an integer prior to RMS "
|
|
235
|
+
f"storage. Actual key found here is '{code}' of type {type(code)}"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
newcodes[code] = name
|
|
239
|
+
return newcodes
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""GridProperty (not GridProperies) some etc functions"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numbers
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
import xtgeo
|
|
11
|
+
from xtgeo.common import null_logger
|
|
12
|
+
|
|
13
|
+
logger = null_logger(__name__)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from xtgeo.grid3d import Grid, GridProperty
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def gridproperty_non_dummy_values(
|
|
20
|
+
gridlike: Grid | GridProperty | None,
|
|
21
|
+
dimensions: tuple[int, int, int],
|
|
22
|
+
values: np.ndarray | float | int | None,
|
|
23
|
+
isdiscrete: bool,
|
|
24
|
+
) -> np.ma.MaskedArray:
|
|
25
|
+
"""
|
|
26
|
+
Gives the initial values array of an gridprop.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
gridlike: Either Grid or GridProperty, giving the mask to replicate.
|
|
30
|
+
dimensions: The (ncol, nrow, nlay) dimensions of the grid property.
|
|
31
|
+
values: The values parameter given to init.
|
|
32
|
+
isdiscrete: The discrete parameter given to init.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The array to be set to GridProperty._values.
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
if values is None:
|
|
39
|
+
_values = initial_gridprop_values_zero(dimensions, isdiscrete)
|
|
40
|
+
elif isinstance(values, numbers.Number):
|
|
41
|
+
_values = initial_gridprop_values_from_scalar(dimensions, values, isdiscrete)
|
|
42
|
+
elif isinstance(values, np.ndarray):
|
|
43
|
+
_values = initial_gridprop_values_from_array(dimensions, values, isdiscrete)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"Cannot create GridProperty with values type '{type(values).__name__}.' "
|
|
47
|
+
"Expected an nd.array, float, int, or None"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if gridlike:
|
|
51
|
+
if isinstance(gridlike, xtgeo.grid3d.Grid):
|
|
52
|
+
act = gridlike.get_actnum(asmasked=True)
|
|
53
|
+
_values = np.ma.array(_values, mask=np.ma.getmaskarray(act.values))
|
|
54
|
+
else:
|
|
55
|
+
assert isinstance(gridlike, xtgeo.grid3d.GridProperty)
|
|
56
|
+
_values = np.ma.array(_values, mask=np.ma.getmaskarray(gridlike.values))
|
|
57
|
+
|
|
58
|
+
return _values
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def gridproperty_dummy_values(isdiscrete: bool) -> np.ma.MaskedArray:
|
|
62
|
+
"""
|
|
63
|
+
Given no parameters to init, these dummy values should be set for backwards
|
|
64
|
+
compatability.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
isdiscrete: If the grid property values are discrete.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The array to be set to GridProperty._values.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
values: np.ma.MaskedArray = np.ma.MaskedArray(
|
|
74
|
+
np.full((4, 3, 5), 99.0), dtype=np.int32 if isdiscrete else np.float64
|
|
75
|
+
)
|
|
76
|
+
values[0:4, 0, 0:2] = np.ma.masked
|
|
77
|
+
return values
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def initial_gridprop_values_zero(
|
|
81
|
+
dimensions: tuple[int, int, int], isdiscrete: bool
|
|
82
|
+
) -> np.ma.MaskedArray:
|
|
83
|
+
"""
|
|
84
|
+
Initial values for an GridProperty with zeros.
|
|
85
|
+
|
|
86
|
+
Given that the user supplies at least some parameters, but not a values array,
|
|
87
|
+
values should be initialized to zero.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
dimensions: The (ncol, nrow, nlay) dimensions of the grid property.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Zero initialized values array.
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
return np.ma.zeros(dimensions, dtype=np.int32 if isdiscrete else np.float64)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def initial_gridprop_values_from_scalar(
|
|
100
|
+
dimensions: tuple[int, int, int], value: float | int, isdiscrete: bool
|
|
101
|
+
) -> np.ma.MaskedArray:
|
|
102
|
+
"""
|
|
103
|
+
Initial grid property values from scalar.
|
|
104
|
+
|
|
105
|
+
Given scalar values, the gridproperties value array should be
|
|
106
|
+
filled with that value, with possible conversion depending
|
|
107
|
+
on the isdiscrete parameter.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
dimensions: The (ncol, nrow, nlay) dimensions of the grid property.
|
|
111
|
+
value: The scalar value to initialize with.
|
|
112
|
+
isdiscrete: If the values are discrete.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Filled array with given scalar value.
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(value, numbers.Number):
|
|
119
|
+
raise ValueError("Scalar input values of invalid type")
|
|
120
|
+
return np.ma.zeros(dimensions, dtype=np.int32 if isdiscrete else np.float64) + value
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def initial_gridprop_values_from_array(
|
|
124
|
+
dimensions: tuple[int, int, int], values: np.ndarray, isdiscrete: bool
|
|
125
|
+
) -> np.ma.MaskedArray:
|
|
126
|
+
"""
|
|
127
|
+
Initial GridProperty values from numpy array.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
dimensions: The (ncol, nrow, nlay) dimensions of the grid property.
|
|
131
|
+
value: The numpy array to initialize with.
|
|
132
|
+
isdiscrete: If the values are discrete.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
GridProperty with values initialized from a numpy array.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
return np.ma.MaskedArray(
|
|
139
|
+
values.reshape(dimensions), dtype=np.int32 if isdiscrete else np.float64
|
|
140
|
+
)
|