resfo-utilities 0.3.0b0__py3-none-any.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 resfo-utilities might be problematic. Click here for more details.
- resfo_utilities/__init__.py +29 -0
- resfo_utilities/_cornerpoint_grid.py +569 -0
- resfo_utilities/_summary_keys.py +404 -0
- resfo_utilities/_summary_reader.py +594 -0
- resfo_utilities/testing/__init__.py +88 -0
- resfo_utilities/testing/_egrid_generator.py +422 -0
- resfo_utilities/testing/_summary_generator.py +568 -0
- resfo_utilities-0.3.0b0.dist-info/METADATA +74 -0
- resfo_utilities-0.3.0b0.dist-info/RECORD +12 -0
- resfo_utilities-0.3.0b0.dist-info/WHEEL +5 -0
- resfo_utilities-0.3.0b0.dist-info/licenses/LICENSE.md +166 -0
- resfo_utilities-0.3.0b0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Some applications use a colon separated list, called a summarykey,
|
|
3
|
+
of the required properties needed to uniquely specify a summary vector.
|
|
4
|
+
|
|
5
|
+
What properties are required is specified in `OPM Flow manual`_ section
|
|
6
|
+
F.9.2. Summary variables are described in the `OPM Flow manual`_
|
|
7
|
+
section 11.1.
|
|
8
|
+
|
|
9
|
+
A summary vector is uniquely specified by giving a summary variable, and
|
|
10
|
+
potentially one or more of the following properties: well name, region name, lgr
|
|
11
|
+
name, block index, completion index, network name.
|
|
12
|
+
|
|
13
|
+
For example for field variables, no additional information is required so the
|
|
14
|
+
summary key is just the variable name: `FOPR`, `FWPR`, etc. For well variables, the
|
|
15
|
+
well name need to be specified: `WOPR:WELL_NAME`, `WAQR:MY_WELL` etc. For
|
|
16
|
+
block variables, the index has to be given: `BOPR:10,9,50`. For a local completion,
|
|
17
|
+
both the lgr name, well name, and index has to be given: `LWWITH:LGR1:WELL2:3,5,5`.
|
|
18
|
+
|
|
19
|
+
.. _OPM Flow manual: https://opm-project.org/wp-content/uploads/2023/06/OPM_Flow_Reference_Manual_2023-04_Rev-0_Reduced.pdf
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
from enum import Enum, auto
|
|
26
|
+
from typing import TypeVar, assert_never
|
|
27
|
+
|
|
28
|
+
SPECIAL_KEYWORDS = [
|
|
29
|
+
"NAIMFRAC",
|
|
30
|
+
"NBAKFL",
|
|
31
|
+
"NBYTOT",
|
|
32
|
+
"NCPRLINS",
|
|
33
|
+
"NEWTFL",
|
|
34
|
+
"NEWTON",
|
|
35
|
+
"NLINEARP",
|
|
36
|
+
"NLINEARS",
|
|
37
|
+
"NLINSMAX",
|
|
38
|
+
"NLINSMIN",
|
|
39
|
+
"NLRESMAX",
|
|
40
|
+
"NLRESSUM",
|
|
41
|
+
"NMESSAGE",
|
|
42
|
+
"NNUMFL",
|
|
43
|
+
"NNUMST",
|
|
44
|
+
"NTS",
|
|
45
|
+
"NTSECL",
|
|
46
|
+
"NTSMCL",
|
|
47
|
+
"NTSPCL",
|
|
48
|
+
"ELAPSED",
|
|
49
|
+
"MAXDPR",
|
|
50
|
+
"MAXDSO",
|
|
51
|
+
"MAXDSG",
|
|
52
|
+
"MAXDSW",
|
|
53
|
+
"STEPTYPE",
|
|
54
|
+
"WNEWTON",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SummaryKeyType(Enum):
|
|
59
|
+
"""Summary keys are divided into types based on summary variable name."""
|
|
60
|
+
|
|
61
|
+
AQUIFER = auto()
|
|
62
|
+
BLOCK = auto()
|
|
63
|
+
COMPLETION = auto()
|
|
64
|
+
FIELD = auto()
|
|
65
|
+
GROUP = auto()
|
|
66
|
+
LOCAL_BLOCK = auto()
|
|
67
|
+
LOCAL_COMPLETION = auto()
|
|
68
|
+
LOCAL_WELL = auto()
|
|
69
|
+
NETWORK = auto()
|
|
70
|
+
SEGMENT = auto()
|
|
71
|
+
WELL = auto()
|
|
72
|
+
REGION = auto()
|
|
73
|
+
INTER_REGION = auto()
|
|
74
|
+
OTHER = auto()
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def from_variable(cls, summary_variable: str) -> SummaryKeyType:
|
|
78
|
+
"""Returns the type corresponding to the given summary variable
|
|
79
|
+
|
|
80
|
+
>>> SummaryKeyType.from_variable("FOPR").name
|
|
81
|
+
'FIELD'
|
|
82
|
+
>>> SummaryKeyType.from_variable("LWWIT").name
|
|
83
|
+
'LOCAL_WELL'
|
|
84
|
+
"""
|
|
85
|
+
KEYWORD_TYPE_MAPPING = {
|
|
86
|
+
"A": cls.AQUIFER,
|
|
87
|
+
"B": cls.BLOCK,
|
|
88
|
+
"C": cls.COMPLETION,
|
|
89
|
+
"F": cls.FIELD,
|
|
90
|
+
"G": cls.GROUP,
|
|
91
|
+
"LB": cls.LOCAL_BLOCK,
|
|
92
|
+
"LC": cls.LOCAL_COMPLETION,
|
|
93
|
+
"LW": cls.LOCAL_WELL,
|
|
94
|
+
"N": cls.NETWORK,
|
|
95
|
+
"S": cls.SEGMENT,
|
|
96
|
+
"W": cls.WELL,
|
|
97
|
+
}
|
|
98
|
+
if not summary_variable:
|
|
99
|
+
raise InvalidSummaryKeyError("Got empty summary keyword")
|
|
100
|
+
if any(special in summary_variable for special in SPECIAL_KEYWORDS):
|
|
101
|
+
return cls.OTHER
|
|
102
|
+
if summary_variable[0] in KEYWORD_TYPE_MAPPING:
|
|
103
|
+
return KEYWORD_TYPE_MAPPING[summary_variable[0]]
|
|
104
|
+
if summary_variable[0:2] in KEYWORD_TYPE_MAPPING:
|
|
105
|
+
return KEYWORD_TYPE_MAPPING[summary_variable[0:2]]
|
|
106
|
+
if summary_variable == "RORFR":
|
|
107
|
+
return cls.REGION
|
|
108
|
+
|
|
109
|
+
if any(
|
|
110
|
+
re.fullmatch(pattern, summary_variable)
|
|
111
|
+
for pattern in [r"R.FT.*", r"R..FT.*", r"R.FR.*", r"R..FR.*", r"R.F"]
|
|
112
|
+
):
|
|
113
|
+
return cls.INTER_REGION
|
|
114
|
+
if summary_variable[0] == "R":
|
|
115
|
+
return cls.REGION
|
|
116
|
+
|
|
117
|
+
return cls.OTHER
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def is_rate(summary_variable: str) -> bool:
|
|
121
|
+
"""Whether the given summary variable is a rate.
|
|
122
|
+
|
|
123
|
+
See `opm flow reference manual
|
|
124
|
+
<https://opm-project.org/wp-content/uploads/2023/06/OPM_Flow_Reference_Manual_2023-04_Rev-0_Reduced.pdf>`
|
|
125
|
+
table 11.4 for details.
|
|
126
|
+
"""
|
|
127
|
+
match SummaryKeyType.from_variable(summary_variable):
|
|
128
|
+
case (
|
|
129
|
+
SummaryKeyType.WELL
|
|
130
|
+
| SummaryKeyType.GROUP
|
|
131
|
+
| SummaryKeyType.FIELD
|
|
132
|
+
| SummaryKeyType.REGION
|
|
133
|
+
| SummaryKeyType.COMPLETION
|
|
134
|
+
):
|
|
135
|
+
return _match_rate_root(1, _rate_roots, summary_variable)
|
|
136
|
+
case (
|
|
137
|
+
SummaryKeyType.LOCAL_WELL
|
|
138
|
+
| SummaryKeyType.LOCAL_COMPLETION
|
|
139
|
+
| SummaryKeyType.NETWORK
|
|
140
|
+
):
|
|
141
|
+
return _match_rate_root(2, _rate_roots, summary_variable)
|
|
142
|
+
case SummaryKeyType.SEGMENT:
|
|
143
|
+
return _match_rate_root(1, _segment_rate_roots, summary_variable)
|
|
144
|
+
case SummaryKeyType.INTER_REGION:
|
|
145
|
+
# Region to region rates are identified by R*FR or R**FR
|
|
146
|
+
return _match_rate_root(2, ["FR"], summary_variable) or _match_rate_root(
|
|
147
|
+
3, ["FR"], summary_variable
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def history_key(key: str) -> str:
|
|
154
|
+
"""The history summary key responding to given summary key
|
|
155
|
+
|
|
156
|
+
>>> history_key("FOPR")
|
|
157
|
+
'FOPRH'
|
|
158
|
+
>>> history_key("BPR:1,3,8")
|
|
159
|
+
'BPRH:1,3,8'
|
|
160
|
+
>>> history_key("LWWIT:WNAME:LGRNAME")
|
|
161
|
+
'LWWITH:WNAME:LGRNAME'
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# Note that this function is not idempotent and only ad-hoc.
|
|
165
|
+
# It is possible to create to make a better version by looking
|
|
166
|
+
# at opm-flow-manual 2023-04 table 11.8, 11.9, 11.14 11.9
|
|
167
|
+
keyword, *rest = key.split(":")
|
|
168
|
+
return ":".join([keyword + "H", *rest])
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class InvalidSummaryKeyError(ValueError):
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def make_summary_key(
|
|
176
|
+
keyword: str,
|
|
177
|
+
number: int | None = None,
|
|
178
|
+
name: str | None = None,
|
|
179
|
+
nx: int | None = None,
|
|
180
|
+
ny: int | None = None,
|
|
181
|
+
lgr_name: str | None = None,
|
|
182
|
+
li: int | None = None,
|
|
183
|
+
lj: int | None = None,
|
|
184
|
+
lk: int | None = None,
|
|
185
|
+
) -> str:
|
|
186
|
+
"""Converts values found in the um to the summary_key format.
|
|
187
|
+
|
|
188
|
+
>>> make_summary_key(keyword="WOPR", name="WELL1")
|
|
189
|
+
'WOPR:WELL1'
|
|
190
|
+
>>> make_summary_key(keyword="BOPR", number=4, nx=2, ny=2)
|
|
191
|
+
'BOPR:2,2,1'
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
keyword: Summary variable name (e.g., ``"WOPR"``, ``"BPR"``).
|
|
196
|
+
number: Numeric qualifier from ``NUMS`` (cell index, region id, etc.).
|
|
197
|
+
name: Text qualifier from ``WGNAMES`` (well/group name).
|
|
198
|
+
nx: Grid dimension in x for block/completion keys.
|
|
199
|
+
ny: Grid dimension in y for block/completion keys.
|
|
200
|
+
lgr_name: Local grid name for local keys.
|
|
201
|
+
li: Local i-index for local block/completion.
|
|
202
|
+
lj: Local j-index for local block/completion.
|
|
203
|
+
lk: Local k-index for local block/completion.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
InvalidSummaryKeyError: If the key is invalid
|
|
207
|
+
"""
|
|
208
|
+
match SummaryKeyType.from_variable(keyword):
|
|
209
|
+
case SummaryKeyType.FIELD | SummaryKeyType.OTHER:
|
|
210
|
+
return keyword
|
|
211
|
+
case SummaryKeyType.REGION:
|
|
212
|
+
(number,) = _check_if_missing("region", "nums", number)
|
|
213
|
+
_check_is_positive_number("region", number)
|
|
214
|
+
return f"{keyword}:{number}"
|
|
215
|
+
case SummaryKeyType.AQUIFER:
|
|
216
|
+
(number,) = _check_if_missing("aquifer", "nums", number)
|
|
217
|
+
_check_is_positive_number("aquifer", number)
|
|
218
|
+
return f"{keyword}:{number}"
|
|
219
|
+
case SummaryKeyType.BLOCK:
|
|
220
|
+
nx, ny = _check_if_missing("block", "dimens", nx, ny)
|
|
221
|
+
(number,) = _check_if_missing("block", "nums", number)
|
|
222
|
+
_check_is_positive_number("block", number)
|
|
223
|
+
i, j, k = _cell_index(number - 1, nx, ny)
|
|
224
|
+
return f"{keyword}:{i},{j},{k}"
|
|
225
|
+
case SummaryKeyType.WELL:
|
|
226
|
+
(name,) = _check_if_missing("well", "name", name)
|
|
227
|
+
_check_if_valid_name("well", name)
|
|
228
|
+
return f"{keyword}:{name}"
|
|
229
|
+
case SummaryKeyType.GROUP:
|
|
230
|
+
(name,) = _check_if_missing("group", "name", name)
|
|
231
|
+
_check_if_valid_name("group", name)
|
|
232
|
+
return f"{keyword}:{name}"
|
|
233
|
+
case SummaryKeyType.SEGMENT:
|
|
234
|
+
(name,) = _check_if_missing("segment", "name", name)
|
|
235
|
+
_check_if_valid_name("segment", name)
|
|
236
|
+
(number,) = _check_if_missing("segment", "nums", number)
|
|
237
|
+
_check_is_positive_number("segment", number)
|
|
238
|
+
return f"{keyword}:{name}:{number}"
|
|
239
|
+
case SummaryKeyType.COMPLETION:
|
|
240
|
+
nx, ny = _check_if_missing("completion", "dimens", nx, ny)
|
|
241
|
+
(number,) = _check_if_missing("completion", "nums", number)
|
|
242
|
+
_check_is_positive_number("completion", number)
|
|
243
|
+
(name,) = _check_if_missing("completion", "name", name)
|
|
244
|
+
_check_if_valid_name("completion", name)
|
|
245
|
+
i, j, k = _cell_index(number - 1, nx, ny)
|
|
246
|
+
return f"{keyword}:{name}:{i},{j},{k}"
|
|
247
|
+
case SummaryKeyType.INTER_REGION:
|
|
248
|
+
(number,) = _check_if_missing("inter region", "nums", number)
|
|
249
|
+
_check_is_positive_number("inter region", number)
|
|
250
|
+
r1 = number % 32768
|
|
251
|
+
r2 = ((number - r1) // 32768) - 10
|
|
252
|
+
return f"{keyword}:{r1}-{r2}"
|
|
253
|
+
case SummaryKeyType.LOCAL_WELL:
|
|
254
|
+
(name,) = _check_if_missing("local well", "WGNAMES", name)
|
|
255
|
+
_check_if_valid_name("local well", name)
|
|
256
|
+
(lgr_name,) = _check_if_missing("local well", "LGRS", lgr_name)
|
|
257
|
+
return f"{keyword}:{lgr_name}:{name}"
|
|
258
|
+
case SummaryKeyType.LOCAL_BLOCK:
|
|
259
|
+
li, lj, lk = _check_if_missing("local block", "NUMLX", li, lj, lk)
|
|
260
|
+
(lgr_name,) = _check_if_missing("local block", "LGRS", lgr_name)
|
|
261
|
+
return f"{keyword}:{lgr_name}:{li},{lj},{lk}"
|
|
262
|
+
case SummaryKeyType.LOCAL_COMPLETION:
|
|
263
|
+
(name,) = _check_if_missing("local completion", "WGNAMES", name)
|
|
264
|
+
_check_if_valid_name("local completion", name)
|
|
265
|
+
li, lj, lk = _check_if_missing("local completion", "NUMLX", li, lj, lk)
|
|
266
|
+
(lgr_name,) = _check_if_missing("local completion", "LGRS", lgr_name)
|
|
267
|
+
return f"{keyword}:{lgr_name}:{name}:{li},{lj},{lk}"
|
|
268
|
+
case SummaryKeyType.NETWORK:
|
|
269
|
+
(name,) = _check_if_missing("network", "WGNAMES", name)
|
|
270
|
+
return f"{keyword}:{name}"
|
|
271
|
+
case default:
|
|
272
|
+
assert_never(default)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
T = TypeVar("T")
|
|
276
|
+
|
|
277
|
+
__all__ = [
|
|
278
|
+
"SummaryKeyType",
|
|
279
|
+
"history_key",
|
|
280
|
+
"is_rate",
|
|
281
|
+
"make_summary_key",
|
|
282
|
+
"InvalidSummaryKeyError",
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _check_if_missing(
|
|
287
|
+
keyword_name: str, missing_key: str, *test_vars: T | None
|
|
288
|
+
) -> list[T]:
|
|
289
|
+
if any(v is None for v in test_vars):
|
|
290
|
+
raise InvalidSummaryKeyError(f"{keyword_name} keyword without {missing_key}")
|
|
291
|
+
return test_vars # type: ignore
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
_DUMMY_NAME = ":+:+:+:+"
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _check_if_valid_name(keyword_name: str, name: str) -> None:
|
|
298
|
+
if not name or name == _DUMMY_NAME:
|
|
299
|
+
raise InvalidSummaryKeyError(
|
|
300
|
+
f"{keyword_name} keyword given invalid name '{name}'"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _check_is_positive_number(keyword_name: str, number: int) -> None:
|
|
305
|
+
if number < 0:
|
|
306
|
+
raise InvalidSummaryKeyError(
|
|
307
|
+
f"{keyword_name} keyword given negative number {number}"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _cell_index(array_index: int, nx: int, ny: int) -> tuple[int, int, int]:
|
|
312
|
+
"""Convert a flat (0-based) index to 1-based (i, j, k) grid indices.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
array_index: Zero-based flat index into a grid laid
|
|
316
|
+
out as ``k`` layers of ``ny*nx``.
|
|
317
|
+
nx: Number of cells in the x-direction (strictly positive).
|
|
318
|
+
ny: Number of cells in the y-direction (strictly positive).
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
A tuple ``(i, j, k)`` where each component is **1-based**.
|
|
322
|
+
"""
|
|
323
|
+
k = array_index // (nx * ny)
|
|
324
|
+
array_index -= k * (nx * ny)
|
|
325
|
+
j = array_index // nx
|
|
326
|
+
array_index -= j * nx
|
|
327
|
+
return array_index + 1, j + 1, k + 1
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
_rate_roots = [ # see opm-flow-manual 2023-04 table 11.8, 11.9 & 11.14
|
|
331
|
+
"OPR",
|
|
332
|
+
"OIR",
|
|
333
|
+
"OVPR",
|
|
334
|
+
"OVIR",
|
|
335
|
+
"OFR",
|
|
336
|
+
"OPP",
|
|
337
|
+
"OPI",
|
|
338
|
+
"OMR",
|
|
339
|
+
"GPR",
|
|
340
|
+
"GIR",
|
|
341
|
+
"GVPR",
|
|
342
|
+
"GVIR",
|
|
343
|
+
"GFR",
|
|
344
|
+
"GPP",
|
|
345
|
+
"GPI",
|
|
346
|
+
"GMR",
|
|
347
|
+
"WGPR",
|
|
348
|
+
"WGIR",
|
|
349
|
+
"WPR",
|
|
350
|
+
"WIR",
|
|
351
|
+
"WVPR",
|
|
352
|
+
"WVIR",
|
|
353
|
+
"WFR",
|
|
354
|
+
"WPP",
|
|
355
|
+
"WPI",
|
|
356
|
+
"WMR",
|
|
357
|
+
"LPR",
|
|
358
|
+
"LFR",
|
|
359
|
+
"VPR",
|
|
360
|
+
"VIR",
|
|
361
|
+
"VFR",
|
|
362
|
+
"GLIR",
|
|
363
|
+
"RGR",
|
|
364
|
+
"EGR",
|
|
365
|
+
"EXGR",
|
|
366
|
+
"SGR",
|
|
367
|
+
"GSR",
|
|
368
|
+
"FGR",
|
|
369
|
+
"GIMR",
|
|
370
|
+
"GCR",
|
|
371
|
+
"NPR",
|
|
372
|
+
"NIR",
|
|
373
|
+
"CPR",
|
|
374
|
+
"CIR",
|
|
375
|
+
"SIR",
|
|
376
|
+
"SPR",
|
|
377
|
+
"TIR",
|
|
378
|
+
"TPR",
|
|
379
|
+
"GOR", # dimensionless but considered a rate, as the ratio of two rates
|
|
380
|
+
"WCT", # dimensionless but considered a rate, as the ratio of two rates
|
|
381
|
+
"OGR", # dimensionless but considered a rate, as the ratio of two rates
|
|
382
|
+
"WGR", # dimensionless but considered a rate, as the ratio of two rates
|
|
383
|
+
"GLR", # dimensionless but considered a rate, as the ratio of two rates
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
_segment_rate_roots = [ # see opm-flow-manual 2023-04 table 11.19
|
|
387
|
+
"OFR",
|
|
388
|
+
"GFR",
|
|
389
|
+
"WFR",
|
|
390
|
+
"CFR",
|
|
391
|
+
"SFR",
|
|
392
|
+
"TFR",
|
|
393
|
+
"CVPR",
|
|
394
|
+
"WCT", # dimensionless but considered a rate, as the ratio of two rates
|
|
395
|
+
"GOR", # dimensionless but considered a rate, as the ratio of two rates
|
|
396
|
+
"OGR", # dimensionless but considered a rate, as the ratio of two rates
|
|
397
|
+
"WGR", # dimensionless but considered a rate, as the ratio of two rates
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _match_rate_root(start: int, rate_roots: list[str], keyword: str) -> bool:
|
|
402
|
+
if len(keyword) < start:
|
|
403
|
+
return False
|
|
404
|
+
return any(keyword[start:].startswith(key) for key in rate_roots)
|