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,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
4
|
+
|
|
5
|
+
import resfo
|
|
6
|
+
|
|
7
|
+
from xtgeo.io._file import FileFormat
|
|
8
|
+
|
|
9
|
+
from ._find_gridprop_in_eclrun import (
|
|
10
|
+
find_gridprop_from_init_file,
|
|
11
|
+
find_gridprops_from_restart_file,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from xtgeo.io._file import FileWrapper
|
|
16
|
+
|
|
17
|
+
from .grid import Grid
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def decorate_name(
|
|
21
|
+
name: str, dual_porosity: bool, fracture: bool, date: int | None = None
|
|
22
|
+
) -> str:
|
|
23
|
+
"""Decorate a property name with date and matrix/fracture.
|
|
24
|
+
|
|
25
|
+
>>> decorate_name('PORO', True, False, 19991231)
|
|
26
|
+
'POROM_19991231'
|
|
27
|
+
"""
|
|
28
|
+
decorated_name = name
|
|
29
|
+
if dual_porosity:
|
|
30
|
+
if fracture:
|
|
31
|
+
decorated_name += "F"
|
|
32
|
+
else:
|
|
33
|
+
decorated_name += "M"
|
|
34
|
+
|
|
35
|
+
if date is not None:
|
|
36
|
+
decorated_name += "_" + str(date)
|
|
37
|
+
return decorated_name
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def import_gridprop_from_init(
|
|
41
|
+
pfile: FileWrapper, name: str, grid: Grid, fracture: bool = False
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
"""Import one parameter with the given name from an init file.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
pfile: The init file.
|
|
47
|
+
name: The name of the parmaeter
|
|
48
|
+
grid: The grid used by the simulator to produce the init file.
|
|
49
|
+
fracture: If a dual porosity module, indicates that the fracture
|
|
50
|
+
(as apposed to the matrix) grid property should be imported.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: If the parameter does not exist in the file.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
GridProperty parameter dictionary.
|
|
57
|
+
"""
|
|
58
|
+
init_props = find_gridprop_from_init_file(pfile.file, [name], grid, fracture)
|
|
59
|
+
if len(init_props) != 1:
|
|
60
|
+
raise ValueError(f"Could not find property {name} in {pfile}")
|
|
61
|
+
init_props[0]["name"] = decorate_name(
|
|
62
|
+
init_props[0]["name"], grid.dualporo, fracture
|
|
63
|
+
)
|
|
64
|
+
return init_props[0]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def sanitize_date(
|
|
68
|
+
date: int | str | Literal["first", "last"],
|
|
69
|
+
) -> list[int] | Literal["first", "last"]:
|
|
70
|
+
"""
|
|
71
|
+
Converts dateformats of the form 'YYYY-MM-DD', 'YYYYMMDD' or YYYYMMDD to
|
|
72
|
+
list of integers of the form [YYYYMMDD] (ie. suitible for find_gridprops
|
|
73
|
+
functions), but lets the special literals 'first' and 'last' remain
|
|
74
|
+
unchanged.
|
|
75
|
+
|
|
76
|
+
>>> sanitize_date('first')
|
|
77
|
+
'first'
|
|
78
|
+
>>> sanitize_date('last')
|
|
79
|
+
'last'
|
|
80
|
+
>>> sanitize_date('2020-01-01')
|
|
81
|
+
[20200101]
|
|
82
|
+
>>> sanitize_date('20200101')
|
|
83
|
+
[20200101]
|
|
84
|
+
>>> sanitize_date(20200101)
|
|
85
|
+
[20200101]
|
|
86
|
+
"""
|
|
87
|
+
if isinstance(date, int):
|
|
88
|
+
return [date]
|
|
89
|
+
if date not in ("first", "last"):
|
|
90
|
+
try:
|
|
91
|
+
if (
|
|
92
|
+
isinstance(date, str)
|
|
93
|
+
and len(date) == 10
|
|
94
|
+
and date[4] == "-"
|
|
95
|
+
and date[7] == "-"
|
|
96
|
+
):
|
|
97
|
+
date = date.replace("-", "")
|
|
98
|
+
return [int(date)]
|
|
99
|
+
except ValueError as err:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"valid dates are either of the "
|
|
102
|
+
"form 'YYYY-MM-DD', 'YYYYMMDD' or 'first'/'last' "
|
|
103
|
+
f"got {date}"
|
|
104
|
+
) from err
|
|
105
|
+
# Satisfy mypy that we're not returning a str
|
|
106
|
+
return "first" if date == "first" else "last"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def sanitize_fformat(fformat: FileFormat) -> resfo.Format:
|
|
110
|
+
"""Converts 'unrst' and 'funrst' to the corresponding resfo.Format.
|
|
111
|
+
|
|
112
|
+
>>> sanitize_fformat('unrst')
|
|
113
|
+
<Format.UNFORMATTED: 2>
|
|
114
|
+
>>> sanitize_fformat('funrst')
|
|
115
|
+
<Format.FORMATTED: 1>
|
|
116
|
+
"""
|
|
117
|
+
if fformat == FileFormat.UNRST:
|
|
118
|
+
return resfo.Format.UNFORMATTED
|
|
119
|
+
if fformat == FileFormat.FUNRST:
|
|
120
|
+
return resfo.Format.FORMATTED
|
|
121
|
+
raise ValueError(f"fformat must be either 'unrst' or 'funrst' got {fformat}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def import_gridprop_from_restart(
|
|
125
|
+
pfile: FileWrapper,
|
|
126
|
+
name: str,
|
|
127
|
+
grid: Grid,
|
|
128
|
+
date: int | str | Literal["first", "last"],
|
|
129
|
+
fracture: bool = False,
|
|
130
|
+
fformat: FileFormat = FileFormat.UNRST,
|
|
131
|
+
) -> dict[str, Any]:
|
|
132
|
+
"""Import one parameter for the given name and date in a restart file.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
pfile: The restart file.
|
|
136
|
+
name: The name of the parmaeter
|
|
137
|
+
date: xtgeo style date (e.g. int(19990101) or "YYYYMMDD"), also
|
|
138
|
+
accepts "YYYY-MM-DD". "last" and "first" can be given for
|
|
139
|
+
last or first date in the file
|
|
140
|
+
grid: The grid used by the simulator to produce the restart file.
|
|
141
|
+
fracture: If a dual porosity module, indicates that the fracture
|
|
142
|
+
(as apposed to the matrix) grid property should be imported.
|
|
143
|
+
Raises:
|
|
144
|
+
ValueError: If the parameter does not exist in the file.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
GridProperty parameter dictionary.
|
|
148
|
+
"""
|
|
149
|
+
restart_props = find_gridprops_from_restart_file(
|
|
150
|
+
pfile.file,
|
|
151
|
+
[name],
|
|
152
|
+
sanitize_date(date),
|
|
153
|
+
grid,
|
|
154
|
+
fracture,
|
|
155
|
+
sanitize_fformat(fformat),
|
|
156
|
+
)
|
|
157
|
+
if len(restart_props) == 0:
|
|
158
|
+
raise ValueError(f"Could not find property {name} for {date} in {pfile.file}")
|
|
159
|
+
if len(restart_props) > 1:
|
|
160
|
+
raise ValueError(f"Ambiguous property {name} for {date} in {pfile.file}")
|
|
161
|
+
restart_props[0]["name"] = decorate_name(
|
|
162
|
+
restart_props[0]["name"], grid.dualporo, fracture, restart_props[0]["date"]
|
|
163
|
+
)
|
|
164
|
+
return restart_props[0]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Importing grid props from GRDECL, ascii or binary"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.ma as ma
|
|
9
|
+
import resfo
|
|
10
|
+
|
|
11
|
+
from xtgeo.common import null_logger
|
|
12
|
+
from xtgeo.common.exceptions import KeywordNotFoundError
|
|
13
|
+
|
|
14
|
+
from ._grdecl_format import match_keyword, open_grdecl
|
|
15
|
+
|
|
16
|
+
logger = null_logger(__name__)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from xtgeo.common.types import FileLike
|
|
20
|
+
from xtgeo.io._file import FileWrapper
|
|
21
|
+
|
|
22
|
+
from .grid import Grid
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def import_bgrdecl_prop(pfile: FileWrapper, name: str, grid: Grid) -> dict[str, Any]:
|
|
26
|
+
"""Import prop for binary files with GRDECL layout.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
pfile (_XTgeoCFile): xtgeo file instance
|
|
30
|
+
name (str): Name of parameter.
|
|
31
|
+
grid (Grid()): XTGeo Grid instance.
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
KeywordNotFoundError: Cannot find property...
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
GridProperty parameter dictionary.
|
|
38
|
+
"""
|
|
39
|
+
result: dict[str, Any] = {}
|
|
40
|
+
result["ncol"] = grid.ncol
|
|
41
|
+
result["nrow"] = grid.nrow
|
|
42
|
+
result["nlay"] = grid.nlay
|
|
43
|
+
result["name"] = name
|
|
44
|
+
result["filesrc"] = pfile
|
|
45
|
+
|
|
46
|
+
for entry in resfo.lazy_read(pfile.file):
|
|
47
|
+
if match_keyword(entry.read_keyword(), name):
|
|
48
|
+
values = entry.read_array()
|
|
49
|
+
result["discrete"] = np.issubdtype(values.dtype, np.integer)
|
|
50
|
+
if result["discrete"]:
|
|
51
|
+
uniq = np.unique(values).tolist()
|
|
52
|
+
codes = dict(zip(uniq, uniq))
|
|
53
|
+
codes = {key: str(val) for key, val in codes.items()} # val: strings
|
|
54
|
+
result["codes"] = codes
|
|
55
|
+
values = values.astype(np.int32)
|
|
56
|
+
result["roxar_dtype"] = np.uint16
|
|
57
|
+
else:
|
|
58
|
+
values = values.astype(np.float64)
|
|
59
|
+
result["codes"] = {}
|
|
60
|
+
result["roxar_dtype"] = np.float32
|
|
61
|
+
result["values"] = ma.masked_where(
|
|
62
|
+
grid.get_actnum().values < 1, values.reshape(grid.dimensions, order="F")
|
|
63
|
+
)
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
raise KeywordNotFoundError(f"Cannot find property name {name} in file {pfile.name}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def read_grdecl_3d_property(
|
|
70
|
+
filename: FileLike,
|
|
71
|
+
keyword: str,
|
|
72
|
+
dimensions: tuple[int, int, int],
|
|
73
|
+
dtype: type[float] | type[int] = float,
|
|
74
|
+
) -> np.ndarray:
|
|
75
|
+
"""
|
|
76
|
+
Read a 3d grid property from a grdecl file, see open_grdecl for description
|
|
77
|
+
of format.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
filename (pathlib.Path or str): File in grdecl format.
|
|
81
|
+
keyword (str): The keyword of the property in the file
|
|
82
|
+
dimensions ((int,int,int)): Triple of the size of grid.
|
|
83
|
+
dtype (function): The datatype to be read, ie., float.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
numpy array with given dimensions and data type read
|
|
87
|
+
from the grdecl file.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
KeywordNotFoundError: If keyword is not found in the file.
|
|
91
|
+
"""
|
|
92
|
+
with open_grdecl(filename, keywords=[], simple_keywords=[keyword]) as kw_generator:
|
|
93
|
+
try:
|
|
94
|
+
_, result = next(kw_generator)
|
|
95
|
+
except StopIteration as si:
|
|
96
|
+
raise KeywordNotFoundError(
|
|
97
|
+
f"Cannot import {keyword}, not present in file {filename}?"
|
|
98
|
+
) from si
|
|
99
|
+
|
|
100
|
+
# The values are stored in F order in the grdecl file
|
|
101
|
+
f_order_values = np.array([dtype(v) for v in result])
|
|
102
|
+
return np.ascontiguousarray(f_order_values.reshape(dimensions, order="F"))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def import_grdecl_prop(pfile: FileWrapper, name: str, grid: Grid) -> dict[str, Any]:
|
|
106
|
+
"""Import prop for ascii files with GRDECL layout.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
pfile (_XTgeoCFile): xtgeo file instance
|
|
110
|
+
name (str): Name of parameter.
|
|
111
|
+
grid (Grid()): XTGeo Grid instance.
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
KeywordNotFoundError: Cannot find property...
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
GridProperty parameter dictionary.
|
|
118
|
+
"""
|
|
119
|
+
result: dict[str, Any] = {}
|
|
120
|
+
result["ncol"] = grid.ncol
|
|
121
|
+
result["nrow"] = grid.nrow
|
|
122
|
+
result["nlay"] = grid.nlay
|
|
123
|
+
result["name"] = name
|
|
124
|
+
result["filesrc"] = pfile
|
|
125
|
+
actnumv = grid.get_actnum().values
|
|
126
|
+
|
|
127
|
+
result["values"] = ma.masked_where(
|
|
128
|
+
actnumv == 0, read_grdecl_3d_property(pfile.file, name, grid.dimensions, float)
|
|
129
|
+
)
|
|
130
|
+
return result
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Importing grid props from ROFF, binary"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from xtgeo.common import null_logger
|
|
10
|
+
|
|
11
|
+
from ._roff_parameter import RoffParameter
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from xtgeo.grid3d import Grid
|
|
15
|
+
from xtgeo.io._file import FileWrapper
|
|
16
|
+
|
|
17
|
+
logger = null_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def import_roff(
|
|
21
|
+
pfile: FileWrapper,
|
|
22
|
+
name: str | None = None,
|
|
23
|
+
grid: Grid | None = None,
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
"""Import ROFF format"""
|
|
26
|
+
result: dict[str, Any] = {}
|
|
27
|
+
roff_param = RoffParameter.from_file(pfile._file, name)
|
|
28
|
+
result["codes"] = roff_param.xtgeo_codes()
|
|
29
|
+
result["name"] = roff_param.name
|
|
30
|
+
result["ncol"] = int(roff_param.nx)
|
|
31
|
+
result["nrow"] = int(roff_param.ny)
|
|
32
|
+
result["nlay"] = int(roff_param.nz)
|
|
33
|
+
result["discrete"] = roff_param.is_discrete
|
|
34
|
+
result["values"] = roff_param.xtgeo_values()
|
|
35
|
+
|
|
36
|
+
if grid is not None and (actnum := grid.get_actnum()):
|
|
37
|
+
result["values"] = np.ma.masked_where(
|
|
38
|
+
actnum.values < 1,
|
|
39
|
+
result["values"],
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
roff_val = roff_param.values
|
|
43
|
+
if isinstance(roff_val, bytes) or np.issubdtype(roff_val.dtype, np.uint8):
|
|
44
|
+
result["roxar_dtype"] = np.uint8
|
|
45
|
+
elif np.issubdtype(roff_val.dtype, np.integer):
|
|
46
|
+
result["roxar_dtype"] = np.uint16
|
|
47
|
+
elif np.issubdtype(roff_val.dtype, np.floating):
|
|
48
|
+
result["roxar_dtype"] = np.float32
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError(f"Could not deduce roxar type of {roff_val.dtype}")
|
|
51
|
+
|
|
52
|
+
return result
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""GridProperty import function of xtgcpprop format."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from io import BytesIO, StringIO
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from struct import unpack
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Generator
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
import xtgeo.common.sys as xsys
|
|
15
|
+
from xtgeo.common import null_logger
|
|
16
|
+
from xtgeo.common.constants import UNDEF, UNDEF_INT
|
|
17
|
+
from xtgeo.metadata.metadata import MetaDataCPProperty
|
|
18
|
+
|
|
19
|
+
logger = null_logger(__name__)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from collections.abc import Sequence
|
|
23
|
+
|
|
24
|
+
from numpy.typing import DTypeLike
|
|
25
|
+
|
|
26
|
+
from xtgeo.io._file import FileWrapper
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@contextmanager
|
|
30
|
+
def _read_from_stream(
|
|
31
|
+
stream: BytesIO | StringIO,
|
|
32
|
+
size: int | None,
|
|
33
|
+
seek: int | None,
|
|
34
|
+
) -> Generator[bytes, None, None]:
|
|
35
|
+
"""Helper function to read from a stream with optional seeking."""
|
|
36
|
+
was_at = stream.tell()
|
|
37
|
+
if seek is not None:
|
|
38
|
+
stream.seek(seek)
|
|
39
|
+
try:
|
|
40
|
+
data = stream.read(size)
|
|
41
|
+
yield data if isinstance(data, bytes) else data.encode()
|
|
42
|
+
finally:
|
|
43
|
+
stream.seek(was_at)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@contextmanager
|
|
47
|
+
def _read_filelike(
|
|
48
|
+
filelike: Path | BytesIO | StringIO,
|
|
49
|
+
size: int | None = None,
|
|
50
|
+
seek: int | None = None,
|
|
51
|
+
) -> Generator[bytes, None, None]:
|
|
52
|
+
"""Context manager for reading a specified number of bytes from a file-like object.
|
|
53
|
+
|
|
54
|
+
Accepts either a Path, BytesIO, or StringIO object as input. The function reads
|
|
55
|
+
up to 'offset' bytes from the file-like object and yields these bytes.
|
|
56
|
+
|
|
57
|
+
For BytesIO and StringIO, the read operation preserves the original
|
|
58
|
+
file cursor position.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if isinstance(filelike, Path):
|
|
62
|
+
with filelike.open("rb") as f:
|
|
63
|
+
if seek is not None:
|
|
64
|
+
f.seek(seek)
|
|
65
|
+
yield f.read(size)
|
|
66
|
+
elif isinstance(filelike, (BytesIO, StringIO)):
|
|
67
|
+
with _read_from_stream(stream=filelike, size=size, seek=seek) as f:
|
|
68
|
+
yield f
|
|
69
|
+
else:
|
|
70
|
+
raise TypeError("Filelike must be one of: Path, BytesIO or StringIO.")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def import_xtgcpprop(
|
|
74
|
+
mfile: FileWrapper,
|
|
75
|
+
ijrange: Sequence[int] | None = None,
|
|
76
|
+
zerobased: bool = False,
|
|
77
|
+
) -> dict[str, Any]:
|
|
78
|
+
"""Using pure python for experimental xtgcpprop import.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
mfile (FileWrapper): Input file reference
|
|
82
|
+
ijrange (list-like): List or tuple with 4 members [i_from, i_to, j_from, j_to]
|
|
83
|
+
where cell indices are zero based (starts with 0)
|
|
84
|
+
zerobased (bool): If ijrange basis is zero or one.
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
offset = 36
|
|
88
|
+
|
|
89
|
+
with _read_filelike(mfile.file, size=offset) as header:
|
|
90
|
+
# unpack header
|
|
91
|
+
swap, magic, nbyte, ncol, nrow, nlay = unpack("= i i i q q q", header)
|
|
92
|
+
|
|
93
|
+
if swap != 1 or magic not in (1351, 1352):
|
|
94
|
+
raise ValueError("Invalid file format (wrong swap id or magic number).")
|
|
95
|
+
|
|
96
|
+
if magic == 1351:
|
|
97
|
+
dtype: DTypeLike = np.float32 if nbyte == 4 else np.float64
|
|
98
|
+
else:
|
|
99
|
+
dtype = f"int{nbyte * 8}"
|
|
100
|
+
|
|
101
|
+
narr = ncol * nrow * nlay
|
|
102
|
+
|
|
103
|
+
ncolnew = nrownew = 0
|
|
104
|
+
|
|
105
|
+
if ijrange:
|
|
106
|
+
vals, ncolnew, nrownew = _import_xtgcpprop_partial(
|
|
107
|
+
mfile, nbyte, dtype, offset, ijrange, zerobased, ncol, nrow, nlay
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
vals = xsys.npfromfile(mfile.file, dtype=dtype, count=narr, offset=offset)
|
|
112
|
+
|
|
113
|
+
# read metadata which will be at position offet + nfloat*narr +13
|
|
114
|
+
with _read_filelike(
|
|
115
|
+
mfile.file,
|
|
116
|
+
seek=offset + nbyte * narr + 13,
|
|
117
|
+
) as _meta:
|
|
118
|
+
meta = json.loads(_meta, object_pairs_hook=dict)
|
|
119
|
+
|
|
120
|
+
req = meta["_required_"]
|
|
121
|
+
|
|
122
|
+
result = {att: req[att] for att in MetaDataCPProperty.REQUIRED}
|
|
123
|
+
|
|
124
|
+
if ijrange:
|
|
125
|
+
result["ncol"] = ncolnew
|
|
126
|
+
result["nrow"] = nrownew
|
|
127
|
+
|
|
128
|
+
result["values"] = np.ma.masked_equal(
|
|
129
|
+
vals.reshape((result["ncol"], result["nrow"], result["nlay"])),
|
|
130
|
+
UNDEF_INT if result["discrete"] else UNDEF,
|
|
131
|
+
)
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _import_xtgcpprop_partial(
|
|
136
|
+
mfile: FileWrapper,
|
|
137
|
+
nbyte: int,
|
|
138
|
+
dtype: DTypeLike,
|
|
139
|
+
offset: int,
|
|
140
|
+
ijrange: Sequence[int],
|
|
141
|
+
zerobased: bool,
|
|
142
|
+
ncol: int,
|
|
143
|
+
nrow: int,
|
|
144
|
+
nlay: int,
|
|
145
|
+
) -> tuple[np.ndarray, int, int]:
|
|
146
|
+
"""Partial import of a property."""
|
|
147
|
+
i1, i2, j1, j2 = ijrange
|
|
148
|
+
if not zerobased:
|
|
149
|
+
i1 -= 1
|
|
150
|
+
i2 -= 1
|
|
151
|
+
j1 -= 1
|
|
152
|
+
j2 -= 1
|
|
153
|
+
|
|
154
|
+
ncolnew = i2 - i1 + 1
|
|
155
|
+
nrownew = j2 - j1 + 1
|
|
156
|
+
|
|
157
|
+
if ncolnew < 1 or ncolnew > ncol or nrownew < 1 or nrownew > nrow:
|
|
158
|
+
raise ValueError("The ijrange spesification is invalid.")
|
|
159
|
+
|
|
160
|
+
vals = np.zeros(ncolnew * nrownew * nlay, dtype=dtype)
|
|
161
|
+
|
|
162
|
+
for newnum, inum in enumerate(range(i1, i2 + 1)):
|
|
163
|
+
newpos = offset + (inum * nrow * nlay + j1 * nlay) * nbyte
|
|
164
|
+
ncount = nrownew * nlay
|
|
165
|
+
xvals = xsys.npfromfile(mfile.file, dtype=dtype, count=ncount, offset=newpos)
|
|
166
|
+
vals[newnum * ncount : newnum * ncount + ncount] = xvals
|
|
167
|
+
|
|
168
|
+
return vals, ncolnew, nrownew
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""GridProperty (not GridProperies) low level functions"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import numpy.ma as ma
|
|
9
|
+
|
|
10
|
+
from xtgeo import _cxtgeo
|
|
11
|
+
from xtgeo.common import null_logger
|
|
12
|
+
from xtgeo.common.constants import UNDEF, UNDEF_INT
|
|
13
|
+
|
|
14
|
+
logger = null_logger(__name__)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from ctypes import Array as cArray
|
|
18
|
+
|
|
19
|
+
from numpy.typing import DTypeLike
|
|
20
|
+
|
|
21
|
+
from xtgeo.grid3d import Grid, GridProperty
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def f2c_order(obj: Grid | GridProperty, values1d: np.ndarray) -> np.ndarray:
|
|
25
|
+
"""Convert values1d from Fortran to C order, obj can be a Grid() or GridProperty()
|
|
26
|
+
instance
|
|
27
|
+
"""
|
|
28
|
+
val = np.reshape(values1d, (obj.ncol, obj.nrow, obj.nlay), order="F")
|
|
29
|
+
val = np.asanyarray(val, order="C")
|
|
30
|
+
return val.ravel()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def c2f_order(obj: Grid | GridProperty, values1d: np.ndarray) -> np.ndarray:
|
|
34
|
+
"""Convert values1d from C to F order, obj can be a Grid() or GridProperty()
|
|
35
|
+
instance
|
|
36
|
+
"""
|
|
37
|
+
val = np.reshape(values1d, (obj.ncol, obj.nrow, obj.nlay), order="C")
|
|
38
|
+
val = np.asanyarray(val, order="F")
|
|
39
|
+
return val.ravel(order="F")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def update_values_from_carray(
|
|
43
|
+
self: GridProperty,
|
|
44
|
+
carray: cArray,
|
|
45
|
+
dtype: DTypeLike,
|
|
46
|
+
delete: bool = False,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Transfer values from SWIG 1D carray to numpy, 3D array"""
|
|
49
|
+
|
|
50
|
+
logger.debug("Update numpy from C array values")
|
|
51
|
+
|
|
52
|
+
nv = self.ntotal
|
|
53
|
+
|
|
54
|
+
self._isdiscrete = False
|
|
55
|
+
|
|
56
|
+
if dtype == np.float64:
|
|
57
|
+
logger.info("Entering conversion to numpy (float64) ...")
|
|
58
|
+
values1d = _cxtgeo.swig_carr_to_numpy_1d(nv, carray)
|
|
59
|
+
else:
|
|
60
|
+
logger.info("Entering conversion to numpy (int32) ...")
|
|
61
|
+
values1d = _cxtgeo.swig_carr_to_numpy_i1d(nv, carray)
|
|
62
|
+
self._isdiscrete = True
|
|
63
|
+
|
|
64
|
+
values = np.reshape(values1d, (self._ncol, self._nrow, self._nlay), order="F")
|
|
65
|
+
|
|
66
|
+
# make into C order as this is standard Python order...
|
|
67
|
+
values = np.asanyarray(values, order="C")
|
|
68
|
+
|
|
69
|
+
# make it float64 or whatever(?) and mask it
|
|
70
|
+
self.values = values # type: ignore
|
|
71
|
+
self.mask_undef()
|
|
72
|
+
|
|
73
|
+
# optionally delete the C array if needed
|
|
74
|
+
if delete:
|
|
75
|
+
delete_carray(self, carray)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def update_carray(
|
|
79
|
+
self: GridProperty,
|
|
80
|
+
undef: int | float | None = None,
|
|
81
|
+
discrete: bool | None = None,
|
|
82
|
+
dtype: DTypeLike = None,
|
|
83
|
+
order: Literal["C", "F", "A", "K"] = "F",
|
|
84
|
+
) -> cArray:
|
|
85
|
+
"""Copy (update) values from numpy to SWIG, 1D array, returns a pointer
|
|
86
|
+
to SWIG C array. If discrete is defined as True or False, force
|
|
87
|
+
the SWIG array to be of that kind.
|
|
88
|
+
|
|
89
|
+
Note that dtype will "override" current datatype if set. The resulting
|
|
90
|
+
carray will be in Fortran order, unless order is specified as 'C'
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
dstatus = self._isdiscrete
|
|
94
|
+
if discrete is not None:
|
|
95
|
+
dstatus = bool(discrete)
|
|
96
|
+
|
|
97
|
+
if undef is None:
|
|
98
|
+
undef = UNDEF
|
|
99
|
+
if dstatus:
|
|
100
|
+
undef = UNDEF_INT
|
|
101
|
+
|
|
102
|
+
logger.debug("Entering conversion from numpy to C array ...")
|
|
103
|
+
|
|
104
|
+
values = self.values.copy()
|
|
105
|
+
|
|
106
|
+
if not dtype:
|
|
107
|
+
values = values.astype(np.int32) if dstatus else values.astype(np.float64)
|
|
108
|
+
else:
|
|
109
|
+
values = values.astype(dtype)
|
|
110
|
+
|
|
111
|
+
values = ma.filled(values, undef)
|
|
112
|
+
values = np.asfortranarray(values)
|
|
113
|
+
|
|
114
|
+
if order == "F":
|
|
115
|
+
values = np.asfortranarray(values)
|
|
116
|
+
|
|
117
|
+
values1d = np.ravel(values, order=order)
|
|
118
|
+
|
|
119
|
+
if values1d.dtype == "float64" and dstatus and not dtype:
|
|
120
|
+
values1d = values1d.astype("int32")
|
|
121
|
+
logger.debug("Casting has been done")
|
|
122
|
+
|
|
123
|
+
if values1d.dtype == "float64":
|
|
124
|
+
logger.debug("Convert to carray (double)")
|
|
125
|
+
carray = _cxtgeo.new_doublearray(self.ntotal)
|
|
126
|
+
_cxtgeo.swig_numpy_to_carr_1d(values1d, carray)
|
|
127
|
+
elif values1d.dtype == "float32":
|
|
128
|
+
logger.debug("Convert to carray (float)")
|
|
129
|
+
carray = _cxtgeo.new_floatarray(self.ntotal)
|
|
130
|
+
_cxtgeo.swig_numpy_to_carr_f1d(values1d, carray)
|
|
131
|
+
elif values1d.dtype == "int32":
|
|
132
|
+
logger.debug("Convert to carray (int32)")
|
|
133
|
+
carray = _cxtgeo.new_intarray(self.ntotal)
|
|
134
|
+
_cxtgeo.swig_numpy_to_carr_i1d(values1d, carray)
|
|
135
|
+
else:
|
|
136
|
+
raise RuntimeError(f"Unsupported dtype, probable bug in {__name__}")
|
|
137
|
+
return carray
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def delete_carray(self: GridProperty, carray: cArray) -> None:
|
|
141
|
+
"""Delete carray SWIG C pointer, return carray as None"""
|
|
142
|
+
|
|
143
|
+
logger.debug("Enter delete carray values method for %d", id(self))
|
|
144
|
+
if carray is None:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if "int" in str(carray):
|
|
148
|
+
_cxtgeo.delete_intarray(carray)
|
|
149
|
+
return
|
|
150
|
+
if "float" in str(carray):
|
|
151
|
+
_cxtgeo.delete_floatarray(carray)
|
|
152
|
+
return
|
|
153
|
+
if "double" in str(carray):
|
|
154
|
+
_cxtgeo.delete_doublearray(carray)
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
raise RuntimeError("BUG?")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def check_shape_ok(self: GridProperty, values: np.ndarray) -> bool:
|
|
161
|
+
"""Check if chape of values is OK"""
|
|
162
|
+
if values.shape == (self._ncol, self._nrow, self._nlay):
|
|
163
|
+
return True
|
|
164
|
+
logger.error(
|
|
165
|
+
"Wrong shape: Dimens of values %s %s %svs %s %s %s",
|
|
166
|
+
*values.shape,
|
|
167
|
+
self._ncol,
|
|
168
|
+
self._nrow,
|
|
169
|
+
self._nlay,
|
|
170
|
+
)
|
|
171
|
+
return False
|