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.

Files changed (117) hide show
  1. cxtgeo.py +582 -0
  2. cxtgeoPYTHON_wrap.c +20938 -0
  3. xtgeo/__init__.py +246 -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 +21 -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 +340 -0
  23. xtgeo/cube/cube1.py +1023 -0
  24. xtgeo/grid3d/__init__.py +15 -0
  25. xtgeo/grid3d/_ecl_grid.py +774 -0
  26. xtgeo/grid3d/_ecl_inte_head.py +148 -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 +266 -0
  32. xtgeo/grid3d/_grdecl_grid.py +388 -0
  33. xtgeo/grid3d/_grid3d.py +29 -0
  34. xtgeo/grid3d/_grid3d_fence.py +181 -0
  35. xtgeo/grid3d/_grid3d_utils.py +228 -0
  36. xtgeo/grid3d/_grid_boundary.py +76 -0
  37. xtgeo/grid3d/_grid_etc1.py +1566 -0
  38. xtgeo/grid3d/_grid_export.py +221 -0
  39. xtgeo/grid3d/_grid_hybrid.py +66 -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 +125 -0
  45. xtgeo/grid3d/_grid_roxapi.py +292 -0
  46. xtgeo/grid3d/_grid_wellzone.py +165 -0
  47. xtgeo/grid3d/_gridprop_export.py +178 -0
  48. xtgeo/grid3d/_gridprop_import_eclrun.py +164 -0
  49. xtgeo/grid3d/_gridprop_import_grdecl.py +130 -0
  50. xtgeo/grid3d/_gridprop_import_roff.py +52 -0
  51. xtgeo/grid3d/_gridprop_import_xtgcpprop.py +168 -0
  52. xtgeo/grid3d/_gridprop_lowlevel.py +171 -0
  53. xtgeo/grid3d/_gridprop_op1.py +174 -0
  54. xtgeo/grid3d/_gridprop_roxapi.py +239 -0
  55. xtgeo/grid3d/_gridprop_value_init.py +140 -0
  56. xtgeo/grid3d/_gridprops_import_eclrun.py +344 -0
  57. xtgeo/grid3d/_gridprops_import_roff.py +83 -0
  58. xtgeo/grid3d/_roff_grid.py +469 -0
  59. xtgeo/grid3d/_roff_parameter.py +303 -0
  60. xtgeo/grid3d/grid.py +2537 -0
  61. xtgeo/grid3d/grid_properties.py +699 -0
  62. xtgeo/grid3d/grid_property.py +1341 -0
  63. xtgeo/grid3d/types.py +15 -0
  64. xtgeo/io/__init__.py +1 -0
  65. xtgeo/io/_file.py +592 -0
  66. xtgeo/metadata/__init__.py +17 -0
  67. xtgeo/metadata/metadata.py +431 -0
  68. xtgeo/roxutils/__init__.py +7 -0
  69. xtgeo/roxutils/_roxar_loader.py +54 -0
  70. xtgeo/roxutils/_roxutils_etc.py +122 -0
  71. xtgeo/roxutils/roxutils.py +207 -0
  72. xtgeo/surface/__init__.py +18 -0
  73. xtgeo/surface/_regsurf_boundary.py +26 -0
  74. xtgeo/surface/_regsurf_cube.py +210 -0
  75. xtgeo/surface/_regsurf_cube_window.py +391 -0
  76. xtgeo/surface/_regsurf_cube_window_v2.py +297 -0
  77. xtgeo/surface/_regsurf_cube_window_v3.py +360 -0
  78. xtgeo/surface/_regsurf_export.py +388 -0
  79. xtgeo/surface/_regsurf_grid3d.py +271 -0
  80. xtgeo/surface/_regsurf_gridding.py +347 -0
  81. xtgeo/surface/_regsurf_ijxyz_parser.py +278 -0
  82. xtgeo/surface/_regsurf_import.py +347 -0
  83. xtgeo/surface/_regsurf_lowlevel.py +122 -0
  84. xtgeo/surface/_regsurf_oper.py +631 -0
  85. xtgeo/surface/_regsurf_roxapi.py +241 -0
  86. xtgeo/surface/_regsurf_utils.py +81 -0
  87. xtgeo/surface/_surfs_import.py +43 -0
  88. xtgeo/surface/_zmap_parser.py +138 -0
  89. xtgeo/surface/regular_surface.py +2967 -0
  90. xtgeo/surface/surfaces.py +276 -0
  91. xtgeo/well/__init__.py +24 -0
  92. xtgeo/well/_blockedwell_roxapi.py +221 -0
  93. xtgeo/well/_blockedwells_roxapi.py +68 -0
  94. xtgeo/well/_well_aux.py +30 -0
  95. xtgeo/well/_well_io.py +327 -0
  96. xtgeo/well/_well_oper.py +574 -0
  97. xtgeo/well/_well_roxapi.py +304 -0
  98. xtgeo/well/_wellmarkers.py +486 -0
  99. xtgeo/well/_wells_utils.py +158 -0
  100. xtgeo/well/blocked_well.py +216 -0
  101. xtgeo/well/blocked_wells.py +122 -0
  102. xtgeo/well/well1.py +1514 -0
  103. xtgeo/well/wells.py +211 -0
  104. xtgeo/xyz/__init__.py +6 -0
  105. xtgeo/xyz/_polygons_oper.py +272 -0
  106. xtgeo/xyz/_xyz.py +741 -0
  107. xtgeo/xyz/_xyz_data.py +646 -0
  108. xtgeo/xyz/_xyz_io.py +490 -0
  109. xtgeo/xyz/_xyz_lowlevel.py +42 -0
  110. xtgeo/xyz/_xyz_oper.py +613 -0
  111. xtgeo/xyz/_xyz_roxapi.py +766 -0
  112. xtgeo/xyz/points.py +681 -0
  113. xtgeo/xyz/polygons.py +811 -0
  114. xtgeo-4.8.0.dist-info/METADATA +145 -0
  115. xtgeo-4.8.0.dist-info/RECORD +117 -0
  116. xtgeo-4.8.0.dist-info/WHEEL +5 -0
  117. xtgeo-4.8.0.dist-info/licenses/LICENSE.md +165 -0
@@ -0,0 +1,266 @@
1
+ from __future__ import annotations
2
+
3
+ import warnings
4
+ from contextlib import contextmanager
5
+ from typing import TYPE_CHECKING
6
+
7
+ from xtgeo.common import null_logger
8
+
9
+ if TYPE_CHECKING:
10
+ from collections.abc import Generator
11
+ from io import TextIOWrapper
12
+
13
+ from xtgeo.common.types import FileLike
14
+
15
+ logger = null_logger(__name__)
16
+
17
+
18
+ def split_line(line: str) -> Generator[str, None, None]:
19
+ """
20
+ split a keyword line inside a grdecl file. This splits the values of a
21
+ 'simple' keyword into tokens. ie.
22
+
23
+ >>> list(split_line("3 1.0 3*4 PORO 3*INC 'HELLO WORLD ' 3*'NAME'"))
24
+ ['3', '1.0', '3*4', 'PORO', '3*INC', "'HELLO WORLD '", "3*'NAME'"]
25
+
26
+ note that we do not require string literals to have delimiting space at the
27
+ end, but at the start. This is to be permissive at the end (as there is no
28
+ formal requirement for spaces at end of string literals), but no space at
29
+ the start of a string literal might indicate a repeating count.
30
+
31
+ >>> list(split_line("3'hello world'4"))
32
+ ["3'hello world'", '4']
33
+
34
+ """
35
+ value = ""
36
+ inside_str = False
37
+ for char in line:
38
+ if char == "'":
39
+ # Either the start or
40
+ # the end of a string literal
41
+ if inside_str:
42
+ yield value + char
43
+ value = ""
44
+ inside_str = False
45
+ else:
46
+ inside_str = True
47
+ value += char
48
+ elif inside_str:
49
+ # inside a string literal
50
+ value += char
51
+ elif value and value[-1] == "-" and char == "-":
52
+ # a comment
53
+ value = value[0:-1]
54
+ break
55
+ elif char.isspace():
56
+ # delimiting space
57
+ if value:
58
+ yield value
59
+ value = ""
60
+ else:
61
+ value += char
62
+ if value:
63
+ yield value
64
+
65
+
66
+ def split_line_no_string(line: str) -> Generator[str, None, None]:
67
+ """
68
+ Same as split_line, but does not handle string literals, instead
69
+ its quite a bit faster.
70
+ """
71
+ for w in line.split():
72
+ if w.startswith("--"):
73
+ return
74
+ yield w
75
+
76
+
77
+ def match_keyword(kw1: str, kw2: str) -> bool:
78
+ """
79
+ Perhaps surprisingly, the eclipse input format considers keywords
80
+ as 8 character strings with space denoting end. So PORO, 'PORO ', and
81
+ 'PORO ' are all considered the same keyword.
82
+
83
+ Note that spaces may also occur inside e.g. tracer keywords, hence 'G1 F' vs 'G1 S'
84
+ are different keywords.
85
+
86
+ >>> match_keyword("PORO", "PORO ")
87
+ True
88
+ >>> match_keyword("PORO", "PERM")
89
+ False
90
+ >>> match_keyword("MORETHAN8LETTERS1)", "MORETHAN8LETTER2")
91
+ True
92
+ >>> match_keyword("G1 F", "G1 S")
93
+ False
94
+
95
+ """
96
+ return kw1[0:8].rstrip() == kw2[0:8].rstrip()
97
+
98
+
99
+ def interpret_token(val: str) -> list[str]:
100
+ """
101
+ Interpret a eclipse token, tries to interpret the
102
+ value in the following order:
103
+ * string literal
104
+ * keyword
105
+ * repreated keyword
106
+ * number
107
+
108
+ If the token cannot be matched, we default to returning
109
+ the uninterpreted token.
110
+
111
+ >>> interpret_token("3")
112
+ ['3']
113
+ >>> interpret_token("1.0")
114
+ ['1.0']
115
+ >>> interpret_token("'hello'")
116
+ ['hello']
117
+ >>> interpret_token("PORO")
118
+ ['PORO']
119
+ >>> interpret_token("3PORO")
120
+ ['3PORO']
121
+ >>> interpret_token("3*PORO")
122
+ ['PORO', 'PORO', 'PORO']
123
+ >>> interpret_token("3*'PORO '")
124
+ ['PORO ', 'PORO ', 'PORO ']
125
+ >>> interpret_token("3'PORO '")
126
+ ["3'PORO '"]
127
+
128
+ """
129
+ if val[0] == "'" and val[-1] == "'":
130
+ # A string literal
131
+ return [val[1:-1]]
132
+ if val[0].isalpha():
133
+ # A keyword
134
+ return [val]
135
+ if "*" in val:
136
+ multiplicand, value = val.split("*")
137
+ return interpret_token(value) * int(multiplicand)
138
+ return [val]
139
+
140
+
141
+ IGNORE_ALL = None
142
+
143
+
144
+ @contextmanager
145
+ def open_grdecl(
146
+ grdecl_file: FileLike,
147
+ keywords: list[str],
148
+ simple_keywords: list[str] | None = None,
149
+ max_len: int | None = None,
150
+ ignore: list[str] | None = IGNORE_ALL,
151
+ strict: bool = True,
152
+ ) -> Generator[Generator[tuple[str, list[str]], None, None], None, None]:
153
+ """Generates tuples of keyword and values in records of a grdecl file.
154
+
155
+ The format of the file must be that of the GRID section of a eclipse input
156
+ DATA file.
157
+
158
+ The records looked for must be "simple" ie. start with the keyword, be
159
+ followed by single word values and ended by a slash ('/').
160
+
161
+ .. code-block:: none
162
+
163
+ KEYWORD
164
+ value value value /
165
+
166
+ reading the above file with :code:`open_grdecl("filename.grdecl",
167
+ keywords="KEYWORD")` will generate :code:`[("KEYWORD", ["value", "value",
168
+ "value"])]`
169
+
170
+ open_grdecl does not follow includes, obey skips, parse MESSAGE commands or
171
+ make exception for groups and subrecords.
172
+
173
+ Raises:
174
+ ValueError: when end of file is reached without terminating a keyword,
175
+ or the file contains an unrecognized (or ignored) keyword.
176
+
177
+ Args:
178
+ keywords (List[str]): Which keywords to look for, these are expected to
179
+ be at the start of a line in the file and the respective values
180
+ following on subsequent lines separated by whitespace. Reading of a
181
+ keyword is completed by a final '\'. See example above.
182
+
183
+ simple_keywords (List[str]): Similar to keywords, but faster and
184
+ cannot contain any string literals, such as the GRIDUNIT keyword
185
+ which can be followed by the string literal 'METRES '.
186
+
187
+ max_len (int): The maximum significant length of a keyword (Eclipse
188
+ uses 8) ignore (List[str]): Keywords that have no associated data, and
189
+ should be ignored, e.g. ECHO. Defaults to ignore all keywords that are
190
+ not part of the results.
191
+
192
+ ignore (List[str]): list of unmatched keywords to ignore, defaults to
193
+ ignoring all unmatched keywords. Any keyword not ignored and not in
194
+ the list of keywords looked for will give an error unless strict=False.
195
+ Although a keyword is ignored, if it has trailing values on new lines
196
+ those are interpreted as keywords, in order to ignore keywords with
197
+ trailing values, use strict=False and filter warnings. Alternatively,
198
+ add it to the list of expected keywords.
199
+
200
+ strict (boolean): Whether unmatched keywords should raise an error or
201
+ a warning.
202
+ """
203
+
204
+ if simple_keywords is None:
205
+ simple_keywords = []
206
+
207
+ def read_grdecl(
208
+ grdecl_stream: TextIOWrapper,
209
+ ) -> Generator[tuple[str, list[str]], None, None]:
210
+ words: list[str] = []
211
+ keyword = None
212
+ line_splitter = split_line
213
+
214
+ line_no = 1
215
+ line = grdecl_stream.readline()
216
+
217
+ while line:
218
+ if line is None:
219
+ break
220
+
221
+ if keyword is None:
222
+ snubbed = line[0 : min(max_len, len(line))] if max_len else line
223
+ simple_matched_keywords = [
224
+ kw for kw in simple_keywords if match_keyword(kw, snubbed)
225
+ ]
226
+ matched_keywords = [kw for kw in keywords if match_keyword(kw, snubbed)]
227
+ if matched_keywords or simple_matched_keywords:
228
+ if matched_keywords:
229
+ keyword = matched_keywords[0]
230
+ line_splitter = split_line
231
+ else:
232
+ keyword = simple_matched_keywords[0]
233
+ line_splitter = split_line_no_string
234
+ logger.debug("Keyword %s found on line %d", keyword, line_no)
235
+ elif (
236
+ list(split_line(line)) # Not an empty line
237
+ and ignore is not IGNORE_ALL # Not ignoring all
238
+ and not any(
239
+ match_keyword(snubbed, i) for i in ignore
240
+ ) # Not ignoring this
241
+ ):
242
+ if strict:
243
+ raise ValueError(
244
+ f"Unrecognized keyword {repr(line)} on line {line_no}"
245
+ )
246
+ else:
247
+ warnings.warn(
248
+ f"Unrecognized keyword {repr(line)} on line {line_no}"
249
+ )
250
+
251
+ else:
252
+ for word in line_splitter(line):
253
+ if word == "/":
254
+ yield (keyword, words)
255
+ keyword = None
256
+ words = []
257
+ break
258
+ words += interpret_token(word)
259
+ line = grdecl_stream.readline()
260
+ line_no += 1
261
+
262
+ if keyword is not None:
263
+ raise ValueError(f"Reached end of stream while reading {keyword}")
264
+
265
+ with open(grdecl_file, "r") as stream:
266
+ yield read_grdecl(stream)
@@ -0,0 +1,388 @@
1
+ """
2
+ Datastructure for the contents of grdecl files.
3
+
4
+ The grdecl file format is not specified in a strict manner
5
+ but in the most general sense it is a file that can be included
6
+ into the GRID section of a eclipse input file.
7
+
8
+ However, it is nearly impossible to support such a file format completely,
9
+ instead we narrow it down to the following subset of keywords:
10
+
11
+ * COORD
12
+ * ZCORN
13
+ * ACTNUM
14
+ * MAPAXES
15
+ * GRIDUNIT
16
+ * SPECGRID
17
+ * GDORIENT
18
+
19
+ And ignore ECHO and NOECHO keywords. see _grid_format for the details
20
+ of how these keywords are layed out in a text file, and see GrdeclGrid
21
+ for how the grid geometry is interpreted from these keywords.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from dataclasses import dataclass
27
+
28
+ import numpy as np
29
+ import resfo
30
+
31
+ from xtgeo.common.types import Dimensions
32
+ from xtgeo.io._file import FileFormat
33
+
34
+ from ._ecl_grid import (
35
+ CoordinateType,
36
+ EclGrid,
37
+ GdOrient,
38
+ GrdeclKeyword,
39
+ GridRelative,
40
+ GridUnit,
41
+ MapAxes,
42
+ Units,
43
+ )
44
+ from ._grdecl_format import IGNORE_ALL, open_grdecl
45
+
46
+
47
+ @dataclass
48
+ class SpecGrid(GrdeclKeyword):
49
+ """The SPECGRID keyword gives the size of the grid.
50
+
51
+ The 3 first values is the number of cells in each dimension
52
+ of the grid. The next is the number of reservoirs in the file and
53
+ the last is the type of coordinates, see CoordinateType.
54
+
55
+ example:
56
+ "SPECGRID 10 10 10 1 T /" meaning 10x10x10 grid with 1 reservoir
57
+ and cylindrical coordinates.
58
+
59
+ """
60
+
61
+ ndivix: int = 1
62
+ ndiviy: int = 1
63
+ ndiviz: int = 1
64
+ numres: int = 1
65
+ coordinate_type: CoordinateType = CoordinateType.CARTESIAN
66
+
67
+ def to_grdecl(self):
68
+ return [
69
+ self.ndivix,
70
+ self.ndiviy,
71
+ self.ndiviz,
72
+ self.numres,
73
+ self.coordinate_type.to_grdecl(),
74
+ ]
75
+
76
+ def to_bgrdecl(self):
77
+ return np.array(
78
+ [
79
+ self.ndivix,
80
+ self.ndiviy,
81
+ self.ndiviz,
82
+ self.numres,
83
+ self.coordinate_type.to_bgrdecl(),
84
+ ],
85
+ dtype=np.int32,
86
+ )
87
+
88
+ @classmethod
89
+ def from_bgrdecl(cls, values):
90
+ ivalues = [int(v) for v in values[:4]]
91
+ if len(values) < 5:
92
+ return cls(*ivalues)
93
+ if len(values) == 5:
94
+ return cls(*ivalues, CoordinateType.from_bgrdecl(values[-1]))
95
+ raise ValueError("SPECGRID should have at most 5 values")
96
+
97
+ @classmethod
98
+ def from_grdecl(cls, values):
99
+ ivalues = [int(v) for v in values[:4]]
100
+ if len(values) < 5:
101
+ return cls(*ivalues)
102
+ if len(values) == 5:
103
+ return cls(*ivalues, CoordinateType.from_grdecl(values[-1]))
104
+ raise ValueError("SPECGRID should have at most 5 values")
105
+
106
+
107
+ class GrdeclGrid(EclGrid):
108
+ """
109
+ The main keywords that describe a grdecl grid is COORD, ZCORN and ACTNUM
110
+ and are described in xtgeo.grid3d._ecl_grid.
111
+ The remaining fields (SPECGRID, MAPAXES, MAPUNITS, GRIDUNIT, GDORIENT)
112
+ describe units, orientation and dimensions, see corresponding dataclasses.
113
+ The number of cells in each direction is described in the SPECGRID keyword.
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ coord: np.ndarray,
119
+ zcorn: np.ndarray,
120
+ specgrid: SpecGrid,
121
+ actnum: np.ndarray | None = None,
122
+ mapaxes: MapAxes | None = None,
123
+ mapunits: Units | None = None,
124
+ gridunit: GridUnit | None = None,
125
+ gdorient: GdOrient | None = None,
126
+ ):
127
+ self._coord = coord
128
+ self._zcorn = zcorn
129
+ self.specgrid = specgrid
130
+ self._actnum = actnum
131
+ self._mapaxes = mapaxes
132
+ self.mapunits = mapunits
133
+ self.gridunit = gridunit
134
+ self.gdorient = gdorient
135
+
136
+ def __str__(self):
137
+ return (
138
+ "GrdeclGrid("
139
+ f"coord={self._coord}, "
140
+ f"zcorn={self._zcorn}, "
141
+ f"specgrid={self.specgrid}, "
142
+ f"actnum={self._actnum}, "
143
+ f"mapaxes={self._mapaxes}, "
144
+ f"mapunits={self.mapunits}, "
145
+ f"gridunit={self.gridunit}, "
146
+ f"gdorient={self.gdorient})"
147
+ )
148
+
149
+ def __repr__(self):
150
+ return str(self)
151
+
152
+ @property
153
+ def mapaxes(self) -> MapAxes | None:
154
+ return self._mapaxes
155
+
156
+ @mapaxes.setter
157
+ def mapaxes(self, value):
158
+ self._mapaxes = value
159
+
160
+ @property
161
+ def coord(self) -> np.ndarray:
162
+ return self._coord
163
+
164
+ @coord.setter
165
+ def coord(self, value):
166
+ self._coord = value
167
+
168
+ @property
169
+ def zcorn(self) -> np.ndarray:
170
+ return self._zcorn
171
+
172
+ @zcorn.setter
173
+ def zcorn(self, value):
174
+ self._zcorn = value
175
+
176
+ @property
177
+ def actnum(self) -> np.ndarray | None:
178
+ return self._actnum
179
+
180
+ @classmethod
181
+ def default_settings_grid(
182
+ cls,
183
+ coord: np.ndarray,
184
+ zcorn: np.ndarray,
185
+ actnum: np.ndarray | None,
186
+ size: tuple[int, int, int],
187
+ ):
188
+ return cls(coord, zcorn, SpecGrid(*size), actnum)
189
+
190
+ def __eq__(self, other):
191
+ if not isinstance(other, GrdeclGrid):
192
+ return False
193
+ return (
194
+ self.specgrid == other.specgrid
195
+ and self.mapaxes == other.mapaxes
196
+ and self.mapunits == other.mapunits
197
+ and self.gridunit == other.gridunit
198
+ and self.gdorient == other.gdorient
199
+ and np.array_equal(self.actnum, other.actnum)
200
+ and np.array_equal(self.coord, other.coord)
201
+ and np.array_equal(self.zcorn, other.zcorn)
202
+ )
203
+
204
+ @property
205
+ def dimensions(self) -> Dimensions:
206
+ """Dimensions NamedTuple: The grid dimensions (read only)."""
207
+ return Dimensions(
208
+ self.specgrid.ndivix, self.specgrid.ndiviy, self.specgrid.ndiviz
209
+ )
210
+
211
+ @property
212
+ def is_map_relative(self) -> bool:
213
+ return (
214
+ self.gridunit is not None
215
+ and self.gridunit.grid_relative == GridRelative.MAP
216
+ )
217
+
218
+ @property
219
+ def map_axis_units(self):
220
+ if self.mapunits is None:
221
+ return Units.METRES
222
+ return self.mapunits
223
+
224
+ @map_axis_units.setter
225
+ def map_axis_units(self, value):
226
+ self.mapunits = value
227
+
228
+ @property
229
+ def grid_units(self):
230
+ if self.gridunit is None:
231
+ return Units.METRES
232
+ return self.gridunit.unit
233
+
234
+ @grid_units.setter
235
+ def grid_units(self, value):
236
+ if self.gridunit is None and value != Units.METRES:
237
+ self.gridunit = GridUnit(unit=value)
238
+ elif self.gridunit is not None:
239
+ self.gridunit.unit = value
240
+
241
+ @classmethod
242
+ def from_file(cls, filename, fileformat: FileFormat = FileFormat.GRDECL):
243
+ """
244
+ write the grdeclgrid to a file.
245
+ :param filename: path to file to write.
246
+ :param fileformat: Either "grdecl" or "bgrdecl" to
247
+ indicate binary or ascii format.
248
+ """
249
+ if fileformat == FileFormat.GRDECL:
250
+ return cls._from_grdecl_file(filename)
251
+ if fileformat == FileFormat.BGRDECL:
252
+ return cls._from_bgrdecl_file(filename)
253
+ raise ValueError(b"Unknown grdecl file format {fileformat}")
254
+
255
+ @classmethod
256
+ def _from_bgrdecl_file(cls, filename, fileformat=None):
257
+ keyword_factories = {
258
+ "COORD": lambda x: np.array(x, dtype=np.float32),
259
+ "ZCORN": lambda x: np.array(x, dtype=np.float32),
260
+ "ACTNUM": lambda x: np.array(x, dtype=np.int32),
261
+ "MAPAXES": MapAxes.from_bgrdecl,
262
+ "MAPUNITS": lambda x: Units.from_bgrdecl(x[0]),
263
+ "GRIDUNIT": GridUnit.from_bgrdecl,
264
+ "SPECGRID": SpecGrid.from_bgrdecl,
265
+ "GDORIENT": GdOrient.from_bgrdecl,
266
+ }
267
+ results = {}
268
+ for entry in resfo.lazy_read(filename, fileformat=fileformat):
269
+ if len(results) == len(keyword_factories):
270
+ break
271
+ kw = entry.read_keyword().rstrip()
272
+ if kw in results:
273
+ raise ValueError(f"Duplicate keyword {kw} in {filename}")
274
+ try:
275
+ factory = keyword_factories[kw]
276
+ except KeyError as e:
277
+ raise ValueError(f"Unknown grdecl keyword {kw}") from e
278
+ results[kw.lower()] = factory(entry.read_array())
279
+ return cls(**results)
280
+
281
+ @classmethod
282
+ def _from_grdecl_file(cls, filename):
283
+ keyword_factories = {
284
+ "COORD": lambda x: np.array(x, dtype=np.float32),
285
+ "ZCORN": lambda x: np.array(x, dtype=np.float32),
286
+ "ACTNUM": lambda x: np.array(x, dtype=np.int32),
287
+ "MAPAXES": MapAxes.from_grdecl,
288
+ "MAPUNITS": lambda x: Units.from_grdecl(x[0]),
289
+ "GRIDUNIT": GridUnit.from_grdecl,
290
+ "SPECGRID": SpecGrid.from_grdecl,
291
+ "GDORIENT": GdOrient.from_grdecl,
292
+ }
293
+ results = {}
294
+ with open_grdecl(
295
+ filename,
296
+ keywords=["MAPAXES", "MAPUNITS", "GRIDUNIT", "SPECGRID", "GDORIENT"],
297
+ simple_keywords=["COORD", "ZCORN", "ACTNUM"],
298
+ max_len=None,
299
+ ignore=IGNORE_ALL,
300
+ strict=False,
301
+ ) as keyword_generator:
302
+ for kw, values in keyword_generator:
303
+ if len(results) == len(keyword_factories):
304
+ break
305
+ if kw in results:
306
+ raise ValueError(f"Duplicate keyword {kw} in {filename}")
307
+ try:
308
+ factory = keyword_factories[kw]
309
+ except KeyError as e:
310
+ raise ValueError(f"Unknown grdecl keyword {kw}") from e
311
+ results[kw.lower()] = factory(values)
312
+ return cls(**results)
313
+
314
+ def to_file(self, filename, fileformat="grdecl"):
315
+ """
316
+ write the grdeclgrid to a file.
317
+ :param filename: path to file to write.
318
+ :param fileformat: Either "grdecl" or "bgrdecl" to
319
+ indicate binary or ascii format.
320
+ """
321
+ if fileformat == "grdecl":
322
+ return self._to_grdecl_file(filename)
323
+ if fileformat == "bgrdecl":
324
+ return self._to_bgrdecl_file(filename)
325
+ raise ValueError(b"Unknown grdecl file format {fileformat}")
326
+
327
+ def _to_grdecl_file(self, filename):
328
+ with open(filename, "w") as filestream:
329
+ keywords = [
330
+ ("SPECGRID", self.specgrid.to_grdecl()),
331
+ ("MAPAXES", self.mapaxes.to_grdecl() if self.mapaxes else None),
332
+ ("MAPUNITS", [self.mapunits.to_grdecl()] if self.mapunits else None),
333
+ ("GRIDUNIT", self.gridunit.to_grdecl() if self.gridunit else None),
334
+ ("GDORIENT", self.gdorient.to_grdecl() if self.gdorient else None),
335
+ ("COORD", self.coord),
336
+ ("ZCORN", self.zcorn),
337
+ ("ACTNUM", self.actnum),
338
+ ]
339
+ for kw, values in keywords:
340
+ if values is None:
341
+ continue
342
+ filestream.write(f"{kw}\n")
343
+ numcolumns = 0
344
+ for value in values:
345
+ numcolumns += 1
346
+ filestream.write(f" {value}")
347
+
348
+ if numcolumns >= 6: # 6 should ensure < 128 character width total
349
+ filestream.write("\n")
350
+ numcolumns = 0
351
+
352
+ filestream.write("\n /\n")
353
+
354
+ def _to_bgrdecl_file(self, filename, fileformat=resfo.Format.UNFORMATTED):
355
+ contents = filter(
356
+ lambda x: x[1] is not None,
357
+ [
358
+ ("SPECGRID", self.specgrid.to_bgrdecl()),
359
+ ("MAPAXES ", self.mapaxes.to_bgrdecl() if self.mapaxes else None),
360
+ ("MAPUNITS", [self.mapunits.to_bgrdecl()] if self.mapunits else None),
361
+ ("GRIDUNIT", self.gridunit.to_bgrdecl() if self.gridunit else None),
362
+ ("GDORIENT", self.gdorient.to_bgrdecl() if self.gdorient else None),
363
+ ("COORD ", self.coord.astype(np.float32)),
364
+ ("ZCORN ", self.zcorn.astype(np.float32)),
365
+ (
366
+ "ACTNUM ",
367
+ self.actnum.astype(np.int32) if self.actnum is not None else None,
368
+ ),
369
+ ],
370
+ )
371
+ resfo.write(
372
+ filename,
373
+ contents,
374
+ )
375
+
376
+ def _check_xtgeo_compatible(self):
377
+ if self.specgrid.coordinate_type == CoordinateType.CYLINDRICAL:
378
+ raise NotImplementedError(
379
+ "Xtgeo does not currently support cylindrical coordinate systems"
380
+ )
381
+ if self.specgrid.numres != 1:
382
+ raise NotImplementedError(
383
+ "Xtgeo does not currently support multiple reservoirs"
384
+ )
385
+ if self.gridunit and self.gridunit.grid_relative == GridRelative.MAP:
386
+ raise NotImplementedError(
387
+ "Xtgeo does not currently support conversion of map relative grid units"
388
+ )
@@ -0,0 +1,29 @@
1
+ """Private baseclass for Grid and GridProperties, not to be used by itself."""
2
+
3
+ from xtgeo.common import XTGeoDialog
4
+
5
+ xtg = XTGeoDialog()
6
+
7
+
8
+ class _Grid3D:
9
+ """Abstract base class for Grid3D."""
10
+
11
+ def __init__(self, ncol: int = 4, nrow: int = 3, nlay: int = 5):
12
+ self._ncol = ncol
13
+ self._nrow = nrow
14
+ self._nlay = nlay
15
+
16
+ @property
17
+ def ncol(self) -> int:
18
+ """Returns the NCOL (NX or Ncolumns) number of cells."""
19
+ return self._ncol
20
+
21
+ @property
22
+ def nrow(self) -> int:
23
+ """Returns the NROW (NY or Nrows) number of cells."""
24
+ return self._nrow
25
+
26
+ @property
27
+ def nlay(self) -> int:
28
+ """Returns the NLAY (NZ or Nlayers) number of cells."""
29
+ return self._nlay