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.

@@ -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)