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,568 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implements a hypothesis strategy for unified summary files
|
|
3
|
+
(.SMSPEC and .UNSMRY)
|
|
4
|
+
See https://opm-project.org/?page_id=955
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import astuple, dataclass
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from enum import Enum, unique
|
|
10
|
+
from typing import Any, Self, IO
|
|
11
|
+
from itertools import zip_longest
|
|
12
|
+
from os import PathLike
|
|
13
|
+
|
|
14
|
+
import hypothesis.strategies as st
|
|
15
|
+
import numpy as np
|
|
16
|
+
import resfo
|
|
17
|
+
from hypothesis import assume
|
|
18
|
+
from hypothesis.extra.numpy import from_dtype
|
|
19
|
+
from pydantic import PositiveInt, conint
|
|
20
|
+
from collections.abc import Iterator
|
|
21
|
+
import numpy.typing as npt
|
|
22
|
+
|
|
23
|
+
from ._egrid_generator import GrdeclKeyword
|
|
24
|
+
from resfo_utilities._summary_keys import SPECIAL_KEYWORDS
|
|
25
|
+
from resfo_utilities import make_summary_key
|
|
26
|
+
|
|
27
|
+
_inter_region_summary_variables = [
|
|
28
|
+
"RGFR",
|
|
29
|
+
"RGFR+",
|
|
30
|
+
"RGFR-",
|
|
31
|
+
"RGFT",
|
|
32
|
+
"RGFT+",
|
|
33
|
+
"RGFT-",
|
|
34
|
+
"RGFTG",
|
|
35
|
+
"RGFTL",
|
|
36
|
+
"ROFR",
|
|
37
|
+
"ROFR+",
|
|
38
|
+
"ROFR-",
|
|
39
|
+
"ROFT",
|
|
40
|
+
"ROFT+",
|
|
41
|
+
"ROFT-",
|
|
42
|
+
"ROFTG",
|
|
43
|
+
"ROFTL",
|
|
44
|
+
"RWFR",
|
|
45
|
+
"RWFR+",
|
|
46
|
+
"RWFR-",
|
|
47
|
+
"RWFT",
|
|
48
|
+
"RWFT+",
|
|
49
|
+
"RWFT-",
|
|
50
|
+
"RCFT",
|
|
51
|
+
"RSFT",
|
|
52
|
+
"RNFT",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@st.composite
|
|
57
|
+
def _root_memnonic(draw: st.DrawFn) -> str:
|
|
58
|
+
first_character = draw(st.sampled_from("ABFGRWCS"))
|
|
59
|
+
if first_character == "A":
|
|
60
|
+
second_character = draw(st.sampled_from("ALN"))
|
|
61
|
+
third_character = draw(st.sampled_from("QL"))
|
|
62
|
+
fourth_character = draw(st.sampled_from("RT"))
|
|
63
|
+
return first_character + second_character + third_character + fourth_character
|
|
64
|
+
else:
|
|
65
|
+
second_character = draw(st.sampled_from("OWGVLPT"))
|
|
66
|
+
third_character = draw(st.sampled_from("PIF"))
|
|
67
|
+
fourth_character = draw(st.sampled_from("RT"))
|
|
68
|
+
local = draw(st.sampled_from(["", "L"])) if first_character in "BCW" else ""
|
|
69
|
+
return (
|
|
70
|
+
local
|
|
71
|
+
+ first_character
|
|
72
|
+
+ second_character
|
|
73
|
+
+ third_character
|
|
74
|
+
+ fourth_character
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@st.composite
|
|
79
|
+
def summary_variables(draw: st.DrawFn) -> str:
|
|
80
|
+
"""Generator for valid summary variables.
|
|
81
|
+
|
|
82
|
+
See the OPM Flow manual section 11.1.
|
|
83
|
+
"""
|
|
84
|
+
kind = draw(
|
|
85
|
+
st.sampled_from(
|
|
86
|
+
[
|
|
87
|
+
"special",
|
|
88
|
+
"network",
|
|
89
|
+
"exceptions",
|
|
90
|
+
"directional",
|
|
91
|
+
"up_or_down",
|
|
92
|
+
"mnemonic",
|
|
93
|
+
"segment",
|
|
94
|
+
"well",
|
|
95
|
+
"region2region",
|
|
96
|
+
"mnemonic",
|
|
97
|
+
"region",
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
if kind == "special":
|
|
102
|
+
return draw(st.sampled_from(SPECIAL_KEYWORDS))
|
|
103
|
+
if kind == "exceptions":
|
|
104
|
+
return draw(
|
|
105
|
+
st.sampled_from(
|
|
106
|
+
["BAPI", "BOSAT", "BPR", "FAQR", "FPR", "FWCT", "WBHP", "WWCT", "ROFR"]
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
elif kind == "directional":
|
|
110
|
+
direction = draw(st.sampled_from("IJK"))
|
|
111
|
+
return (
|
|
112
|
+
draw(st.sampled_from(["FLOO", "VELG", "VELO", "FLOW", "VELW"])) + direction
|
|
113
|
+
)
|
|
114
|
+
elif kind == "up_or_down":
|
|
115
|
+
dimension = draw(st.sampled_from("XYZRT"))
|
|
116
|
+
direction = draw(st.sampled_from(["", "-"]))
|
|
117
|
+
return draw(st.sampled_from(["GKR", "OKR", "WKR"])) + dimension + direction
|
|
118
|
+
elif kind == "network":
|
|
119
|
+
root = draw(_root_memnonic())
|
|
120
|
+
return "N" + root
|
|
121
|
+
elif kind == "segment":
|
|
122
|
+
return draw(
|
|
123
|
+
st.sampled_from(["SALQ", "SFR", "SGFR", "SGFRF", "SGFRS", "SGFTA", "SGFT"])
|
|
124
|
+
)
|
|
125
|
+
elif kind == "well":
|
|
126
|
+
return draw(
|
|
127
|
+
st.one_of(
|
|
128
|
+
st.builds(lambda r: "W" + r, _root_memnonic()),
|
|
129
|
+
st.sampled_from(
|
|
130
|
+
[
|
|
131
|
+
"WBHP",
|
|
132
|
+
"WBP5",
|
|
133
|
+
"WBP4",
|
|
134
|
+
"WBP9",
|
|
135
|
+
"WBP",
|
|
136
|
+
"WBHPH",
|
|
137
|
+
"WBHPT",
|
|
138
|
+
"WPIG",
|
|
139
|
+
"WPIL",
|
|
140
|
+
"WPIO",
|
|
141
|
+
"WPI5",
|
|
142
|
+
]
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
elif kind == "region2region":
|
|
147
|
+
return draw(st.sampled_from(_inter_region_summary_variables))
|
|
148
|
+
elif kind == "region":
|
|
149
|
+
return draw(st.builds(lambda r: "R" + r, _root_memnonic()))
|
|
150
|
+
else:
|
|
151
|
+
return draw(_root_memnonic())
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
_unit_names = st.sampled_from(
|
|
155
|
+
["SM3/DAY", "BARSA", "SM3/SM3", "FRACTION", "DAYS", "HOURS", "SM3"]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
_names = st.text(
|
|
159
|
+
min_size=8,
|
|
160
|
+
max_size=8,
|
|
161
|
+
alphabet=st.characters(
|
|
162
|
+
min_codepoint=65,
|
|
163
|
+
max_codepoint=90,
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@unique
|
|
169
|
+
class UnitSystem(Enum):
|
|
170
|
+
"""The unit system used for summary values."""
|
|
171
|
+
|
|
172
|
+
METRIC = 1
|
|
173
|
+
FIELD = 2
|
|
174
|
+
LAB = 3
|
|
175
|
+
|
|
176
|
+
def to_smry(self) -> int:
|
|
177
|
+
return self.value
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@unique
|
|
181
|
+
class Simulator(Enum):
|
|
182
|
+
"""The simulator used to generate the summary."""
|
|
183
|
+
|
|
184
|
+
ECLIPSE_100 = 100
|
|
185
|
+
ECLIPSE_300 = 300
|
|
186
|
+
ECLIPSE_300_THERMAL = 500
|
|
187
|
+
INTERSECT = 700
|
|
188
|
+
FRONTSIM = 800
|
|
189
|
+
|
|
190
|
+
def to_smry(self) -> int:
|
|
191
|
+
return self.value
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@dataclass
|
|
195
|
+
class SmspecIntehead(GrdeclKeyword):
|
|
196
|
+
"""The values in the INTEHEAD array"""
|
|
197
|
+
|
|
198
|
+
unit: UnitSystem
|
|
199
|
+
simulator: Simulator
|
|
200
|
+
|
|
201
|
+
def to_smry(self) -> list[Any]:
|
|
202
|
+
return [value.to_smry() for value in astuple(self)]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class Date:
|
|
207
|
+
"""The date given by the STARTDAT array"""
|
|
208
|
+
|
|
209
|
+
day: conint(ge=1, le=31) # type: ignore
|
|
210
|
+
month: conint(ge=1, le=12) # type: ignore
|
|
211
|
+
year: conint(gt=1901, lt=2038) # type: ignore
|
|
212
|
+
hour: conint(ge=0, lt=24) # type: ignore
|
|
213
|
+
minutes: conint(ge=0, lt=60) # type: ignore
|
|
214
|
+
micro_seconds: conint(ge=0, lt=60000000) # type: ignore
|
|
215
|
+
|
|
216
|
+
def to_smry(self) -> tuple[int, ...]:
|
|
217
|
+
return astuple(self)
|
|
218
|
+
|
|
219
|
+
def to_datetime(self) -> datetime:
|
|
220
|
+
return datetime(
|
|
221
|
+
year=self.year,
|
|
222
|
+
month=self.month,
|
|
223
|
+
day=self.day,
|
|
224
|
+
hour=self.hour,
|
|
225
|
+
minute=self.minutes,
|
|
226
|
+
second=self.micro_seconds // 10**6,
|
|
227
|
+
microsecond=self.micro_seconds % 10**6,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def from_datetime(cls, dt: datetime) -> Self:
|
|
232
|
+
return cls(
|
|
233
|
+
year=dt.year,
|
|
234
|
+
month=dt.month,
|
|
235
|
+
day=dt.day,
|
|
236
|
+
hour=dt.hour,
|
|
237
|
+
minutes=dt.minute,
|
|
238
|
+
micro_seconds=dt.second * 10**6 + dt.microsecond,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@dataclass
|
|
243
|
+
class Smspec:
|
|
244
|
+
"""The contents of the .SMSPEC file"""
|
|
245
|
+
|
|
246
|
+
intehead: SmspecIntehead
|
|
247
|
+
restart: str
|
|
248
|
+
num_keywords: PositiveInt
|
|
249
|
+
nx: PositiveInt
|
|
250
|
+
ny: PositiveInt
|
|
251
|
+
nz: PositiveInt
|
|
252
|
+
restarted_from_step: PositiveInt
|
|
253
|
+
keywords: list[str]
|
|
254
|
+
well_names: list[str]
|
|
255
|
+
region_numbers: list[int]
|
|
256
|
+
units: list[str]
|
|
257
|
+
start_date: Date
|
|
258
|
+
lgrs: list[str] | None = None
|
|
259
|
+
numlx: list[PositiveInt] | None = None
|
|
260
|
+
numly: list[PositiveInt] | None = None
|
|
261
|
+
numlz: list[PositiveInt] | None = None
|
|
262
|
+
use_names: bool = False # whether to use the alias NAMES for WGNAMES
|
|
263
|
+
|
|
264
|
+
def to_smry(self) -> list[tuple[str, Any]]:
|
|
265
|
+
# The restart field contains 9 strings of length 8 which
|
|
266
|
+
# should contain the name of the file restarted from.
|
|
267
|
+
# If shorter than 72 characters (most likely), the rest
|
|
268
|
+
# are spaces. (opm manual table F.44, keyword name RESTART)
|
|
269
|
+
restart = self.restart.ljust(72, " ")
|
|
270
|
+
restart_list = [restart[i * 8 : i * 8 + 8] for i in range(9)]
|
|
271
|
+
return (
|
|
272
|
+
[
|
|
273
|
+
("INTEHEAD", np.array(self.intehead.to_smry(), dtype=np.int32)),
|
|
274
|
+
("RESTART ", restart_list),
|
|
275
|
+
(
|
|
276
|
+
"DIMENS ",
|
|
277
|
+
np.array(
|
|
278
|
+
[
|
|
279
|
+
self.num_keywords,
|
|
280
|
+
self.nx,
|
|
281
|
+
self.ny,
|
|
282
|
+
self.nz,
|
|
283
|
+
0,
|
|
284
|
+
self.restarted_from_step,
|
|
285
|
+
],
|
|
286
|
+
dtype=np.int32,
|
|
287
|
+
),
|
|
288
|
+
),
|
|
289
|
+
("KEYWORDS", [kw.ljust(8) for kw in self.keywords]),
|
|
290
|
+
(
|
|
291
|
+
("NAMES ", self.well_names)
|
|
292
|
+
if self.use_names
|
|
293
|
+
else ("WGNAMES ", self.well_names)
|
|
294
|
+
),
|
|
295
|
+
("NUMS ", np.array(self.region_numbers, dtype=np.int32)),
|
|
296
|
+
("UNITS ", self.units),
|
|
297
|
+
("STARTDAT", np.array(self.start_date.to_smry(), dtype=np.int32)),
|
|
298
|
+
]
|
|
299
|
+
+ ([("LGRS ", self.lgrs)] if self.lgrs is not None else [])
|
|
300
|
+
+ ([("NUMLX ", self.numlx)] if self.numlx is not None else [])
|
|
301
|
+
+ ([("NUMLY ", self.numly)] if self.numly is not None else [])
|
|
302
|
+
+ ([("NUMLZ ", self.numlz)] if self.numlz is not None else [])
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def summary_keys(self) -> Iterator[str]:
|
|
306
|
+
"""All summary keys in the SMSPEC file."""
|
|
307
|
+
|
|
308
|
+
def optional(maybe_list: list[Any] | None) -> list[Any]:
|
|
309
|
+
if maybe_list is None:
|
|
310
|
+
return []
|
|
311
|
+
else:
|
|
312
|
+
return maybe_list
|
|
313
|
+
|
|
314
|
+
for var, num, name, lgr, lx, ly, lz in zip_longest(
|
|
315
|
+
self.keywords,
|
|
316
|
+
self.region_numbers,
|
|
317
|
+
self.well_names,
|
|
318
|
+
optional(self.lgrs),
|
|
319
|
+
optional(self.numlx),
|
|
320
|
+
optional(self.numly),
|
|
321
|
+
optional(self.numlz),
|
|
322
|
+
):
|
|
323
|
+
yield make_summary_key(var, num, name, self.nx, self.ny, lgr, lx, ly, lz)
|
|
324
|
+
|
|
325
|
+
def to_file(
|
|
326
|
+
self,
|
|
327
|
+
filelike: str | PathLike[str] | IO[Any],
|
|
328
|
+
file_format: resfo.Format = resfo.Format.UNFORMATTED,
|
|
329
|
+
) -> None:
|
|
330
|
+
"""Writes the Smspec to a file"""
|
|
331
|
+
resfo.write(filelike, self.to_smry(), file_format)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
_small_ints = from_dtype(np.dtype(np.int32), min_value=1, max_value=10)
|
|
335
|
+
|
|
336
|
+
_summary_keys = st.lists(summary_variables(), min_size=1)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
@st.composite
|
|
340
|
+
def smspecs(
|
|
341
|
+
draw: st.DrawFn,
|
|
342
|
+
sum_keys: st.SearchStrategy[list[str]] | None = None,
|
|
343
|
+
start_date: st.SearchStrategy[Date] | None = None,
|
|
344
|
+
use_days: st.SearchStrategy[bool] | None = None,
|
|
345
|
+
well_names: st.SearchStrategy[str] | None = None,
|
|
346
|
+
lgr_names: st.SearchStrategy[str] | None = None,
|
|
347
|
+
restart_names: st.SearchStrategy[str] | None = None,
|
|
348
|
+
) -> Smspec:
|
|
349
|
+
"""Hypothesis strategy for ``Smspec`` s."""
|
|
350
|
+
use_days = st.booleans() if use_days is None else use_days
|
|
351
|
+
use_locals = draw(st.booleans())
|
|
352
|
+
sum_keys_ = draw(sum_keys or _summary_keys)
|
|
353
|
+
if any(sk.startswith("L") for sk in sum_keys_):
|
|
354
|
+
use_locals = True
|
|
355
|
+
n = len(sum_keys_) + 1
|
|
356
|
+
nx = draw(_small_ints)
|
|
357
|
+
ny = draw(_small_ints)
|
|
358
|
+
nz = draw(_small_ints)
|
|
359
|
+
keywords = ["TIME "] + sum_keys_
|
|
360
|
+
if draw(use_days):
|
|
361
|
+
units = [
|
|
362
|
+
"DAYS ",
|
|
363
|
+
*draw(st.lists(_unit_names, min_size=n - 1, max_size=n - 1)),
|
|
364
|
+
]
|
|
365
|
+
else:
|
|
366
|
+
units = [
|
|
367
|
+
"HOURS ",
|
|
368
|
+
*draw(st.lists(_unit_names, min_size=n - 1, max_size=n - 1)),
|
|
369
|
+
]
|
|
370
|
+
well_names_ = [
|
|
371
|
+
":+:+:+:+",
|
|
372
|
+
*draw(st.lists(well_names or _names, min_size=n - 1, max_size=n - 1)),
|
|
373
|
+
]
|
|
374
|
+
if use_locals: # use local
|
|
375
|
+
lgrs = draw(st.lists(lgr_names or _names, min_size=n, max_size=n))
|
|
376
|
+
numlx = draw(st.lists(_small_ints, min_size=n, max_size=n))
|
|
377
|
+
numly = draw(st.lists(_small_ints, min_size=n, max_size=n))
|
|
378
|
+
numlz = draw(st.lists(_small_ints, min_size=n, max_size=n))
|
|
379
|
+
else:
|
|
380
|
+
lgrs = None
|
|
381
|
+
numlx = None
|
|
382
|
+
numly = None
|
|
383
|
+
numlz = None
|
|
384
|
+
region_numbers = [-32676] + draw(
|
|
385
|
+
st.lists(
|
|
386
|
+
from_dtype(np.dtype(np.int32), min_value=1, max_value=nx * ny * nz),
|
|
387
|
+
min_size=len(sum_keys_),
|
|
388
|
+
max_size=len(sum_keys_),
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
return draw(
|
|
392
|
+
st.builds(
|
|
393
|
+
Smspec,
|
|
394
|
+
nx=st.just(nx),
|
|
395
|
+
ny=st.just(ny),
|
|
396
|
+
nz=st.just(nz),
|
|
397
|
+
# restarted_from_step is hardcoded to 0 because
|
|
398
|
+
# of a bug in enkf_obs where it assumes that
|
|
399
|
+
# ecl_sum_get_first_report_step is always 1
|
|
400
|
+
restarted_from_step=st.just(0),
|
|
401
|
+
num_keywords=st.just(n),
|
|
402
|
+
restart=restart_names or _names,
|
|
403
|
+
keywords=st.just(keywords),
|
|
404
|
+
well_names=st.just(well_names_),
|
|
405
|
+
lgrs=st.just(lgrs),
|
|
406
|
+
numlx=st.just(numlx),
|
|
407
|
+
numly=st.just(numly),
|
|
408
|
+
numlz=st.just(numlz),
|
|
409
|
+
region_numbers=st.just(region_numbers),
|
|
410
|
+
units=st.just(units),
|
|
411
|
+
start_date=start_date or st.datetimes().map(Date.from_datetime),
|
|
412
|
+
use_names=st.booleans(),
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@dataclass
|
|
418
|
+
class SummaryMiniStep:
|
|
419
|
+
"""One ministep section in a summary file."""
|
|
420
|
+
|
|
421
|
+
mini_step: int
|
|
422
|
+
params: list[float]
|
|
423
|
+
|
|
424
|
+
def to_smry(self) -> list[tuple[str, npt.NDArray[Any]]]:
|
|
425
|
+
return [
|
|
426
|
+
("MINISTEP", np.array([self.mini_step], dtype=np.int32)),
|
|
427
|
+
("PARAMS ", np.array(self.params, dtype=np.float32)),
|
|
428
|
+
]
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@dataclass
|
|
432
|
+
class SummaryStep:
|
|
433
|
+
"""One step section in a summary file.
|
|
434
|
+
|
|
435
|
+
Is just one SEQHDR followed by one or more ministeps.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
seqnum: int
|
|
439
|
+
ministeps: list[SummaryMiniStep]
|
|
440
|
+
|
|
441
|
+
def to_smry(self) -> list[tuple[str, npt.NDArray[Any]]]:
|
|
442
|
+
return [("SEQHDR ", np.array([self.seqnum], dtype=np.int32))] + [
|
|
443
|
+
i for ms in self.ministeps for i in ms.to_smry()
|
|
444
|
+
]
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@dataclass
|
|
448
|
+
class Unsmry:
|
|
449
|
+
"""The contents of a unified summary file."""
|
|
450
|
+
|
|
451
|
+
steps: list[SummaryStep]
|
|
452
|
+
|
|
453
|
+
def to_smry(self) -> list[tuple[str, npt.NDArray[Any]]]:
|
|
454
|
+
a = [i for step in self.steps for i in step.to_smry()]
|
|
455
|
+
return a
|
|
456
|
+
|
|
457
|
+
def to_file(
|
|
458
|
+
self,
|
|
459
|
+
filelike: str | PathLike[str] | IO[Any],
|
|
460
|
+
file_format: resfo.Format = resfo.Format.UNFORMATTED,
|
|
461
|
+
) -> None:
|
|
462
|
+
resfo.write(filelike, self.to_smry(), file_format)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
_positive_floats = from_dtype(
|
|
466
|
+
np.dtype(np.float32),
|
|
467
|
+
min_value=np.float32(0.1), # type: ignore
|
|
468
|
+
max_value=np.float32(1e19), # type: ignore
|
|
469
|
+
allow_nan=False,
|
|
470
|
+
allow_infinity=False,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
_start_dates = st.datetimes(
|
|
475
|
+
min_value=datetime.strptime("1969-1-1", "%Y-%m-%d"),
|
|
476
|
+
max_value=datetime.strptime("2100-1-1", "%Y-%m-%d"),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
_time_delta_lists = st.lists(
|
|
480
|
+
st.floats(
|
|
481
|
+
min_value=0.1,
|
|
482
|
+
max_value=2500.0, # in days ~= 6.8 years
|
|
483
|
+
allow_nan=False,
|
|
484
|
+
allow_infinity=False,
|
|
485
|
+
),
|
|
486
|
+
min_size=2,
|
|
487
|
+
max_size=100,
|
|
488
|
+
unique=True,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
@st.composite
|
|
493
|
+
def summaries(
|
|
494
|
+
draw: st.DrawFn,
|
|
495
|
+
start_date: st.SearchStrategy[datetime] = _start_dates,
|
|
496
|
+
time_deltas: st.SearchStrategy[list[float]] = _time_delta_lists,
|
|
497
|
+
summary_keys: st.SearchStrategy[list[str]] = _summary_keys,
|
|
498
|
+
use_days: st.SearchStrategy[bool] | None = None,
|
|
499
|
+
report_step_only: bool = False,
|
|
500
|
+
) -> tuple[Smspec, Unsmry]:
|
|
501
|
+
"""Generator of a smspec and unsmry pair.
|
|
502
|
+
|
|
503
|
+
The strategy ensures that the files matches in number of keywords.
|
|
504
|
+
"""
|
|
505
|
+
sum_keys = draw(summary_keys)
|
|
506
|
+
first_date = draw(start_date)
|
|
507
|
+
days = draw(use_days if use_days is not None else st.booleans())
|
|
508
|
+
smspec = draw(
|
|
509
|
+
smspecs(
|
|
510
|
+
sum_keys=st.just(sum_keys),
|
|
511
|
+
start_date=st.just(Date.from_datetime(first_date)),
|
|
512
|
+
use_days=st.just(days),
|
|
513
|
+
)
|
|
514
|
+
)
|
|
515
|
+
# The smspec should be unique up to summary_keys.
|
|
516
|
+
# This just mimics the behavior of simulators.
|
|
517
|
+
assume(len(set(smspec.summary_keys())) == len(smspec.keywords))
|
|
518
|
+
dates = [0.0] + draw(time_deltas)
|
|
519
|
+
try:
|
|
520
|
+
if days:
|
|
521
|
+
_ = first_date + timedelta(days=max(dates))
|
|
522
|
+
else:
|
|
523
|
+
_ = first_date + timedelta(hours=max(dates))
|
|
524
|
+
except (ValueError, OverflowError): # datetime has a max year
|
|
525
|
+
assume(False)
|
|
526
|
+
|
|
527
|
+
ds = sorted(dates, reverse=True)
|
|
528
|
+
steps = []
|
|
529
|
+
i = 0
|
|
530
|
+
j = 0
|
|
531
|
+
while len(ds) > 0:
|
|
532
|
+
minis = []
|
|
533
|
+
max_val = 1 if report_step_only else len(ds)
|
|
534
|
+
for _ in range(draw(st.integers(min_value=1, max_value=max_val))):
|
|
535
|
+
minis.append(
|
|
536
|
+
SummaryMiniStep(
|
|
537
|
+
i,
|
|
538
|
+
[
|
|
539
|
+
ds.pop(),
|
|
540
|
+
*draw(
|
|
541
|
+
st.lists(
|
|
542
|
+
_positive_floats,
|
|
543
|
+
min_size=len(sum_keys),
|
|
544
|
+
max_size=len(sum_keys),
|
|
545
|
+
)
|
|
546
|
+
),
|
|
547
|
+
],
|
|
548
|
+
)
|
|
549
|
+
)
|
|
550
|
+
i += 1
|
|
551
|
+
steps.append(SummaryStep(j, minis))
|
|
552
|
+
j += 1
|
|
553
|
+
return smspec, Unsmry(steps)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
__all__ = [
|
|
557
|
+
"summary_variables",
|
|
558
|
+
"UnitSystem",
|
|
559
|
+
"Simulator",
|
|
560
|
+
"SmspecIntehead",
|
|
561
|
+
"Date",
|
|
562
|
+
"Smspec",
|
|
563
|
+
"smspecs",
|
|
564
|
+
"SummaryMiniStep",
|
|
565
|
+
"SummaryStep",
|
|
566
|
+
"Unsmry",
|
|
567
|
+
"summaries",
|
|
568
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: resfo-utilities
|
|
3
|
+
Version: 0.3.0b0
|
|
4
|
+
Summary: A utility library for working with the output of reservoir simulators.
|
|
5
|
+
Author-email: Equinor <fg_sib-scout@equinor.com>
|
|
6
|
+
Maintainer-email: Eivind Jahren <ejah@equinor.com>, Håkon Steinkopf Søhoel <hsoho@equinor.com>
|
|
7
|
+
License: LGPL-3.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/equinor/resfo-utilities
|
|
9
|
+
Project-URL: Repository, https://github.com/equinor/resfo-utilities
|
|
10
|
+
Project-URL: Documentation, https://resfo-utilities.readthedocs.io/en/stable/
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/equinor/resfo-utilities/issues
|
|
12
|
+
Classifier: Development Status :: 1 - Planning
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: <=3.14,>=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.md
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: resfo>=5.0.0b1
|
|
23
|
+
Requires-Dist: scipy
|
|
24
|
+
Requires-Dist: natsort
|
|
25
|
+
Provides-Extra: testing
|
|
26
|
+
Requires-Dist: hypothesis; extra == "testing"
|
|
27
|
+
Requires-Dist: pydantic; extra == "testing"
|
|
28
|
+
Provides-Extra: doc
|
|
29
|
+
Requires-Dist: sphinx; extra == "doc"
|
|
30
|
+
Requires-Dist: sphinx-rtd-theme; extra == "doc"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-benchmark; extra == "dev"
|
|
36
|
+
Requires-Dist: scipy-stubs; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
resfo-utilities
|
|
40
|
+
===============
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
resfo-utilities is a library for working with output from
|
|
44
|
+
several reservoir simulators such as [opm
|
|
45
|
+
flow](https://github.com/OPM/opm-simulators).
|
|
46
|
+
|
|
47
|
+
Installation
|
|
48
|
+
============
|
|
49
|
+
|
|
50
|
+
`pip install resfo-utilities`
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
How to contribute
|
|
54
|
+
=================
|
|
55
|
+
|
|
56
|
+
We use uv to have one synchronized development environment for all packages.
|
|
57
|
+
See [installing uv](https://docs.astral.sh/uv/getting-started/installation/). We
|
|
58
|
+
recommend either installing uv using your systems package manager, or creating
|
|
59
|
+
a small virtual environment you intall base packages into (such as `uv` and `pre-commit`).
|
|
60
|
+
|
|
61
|
+
Once uv is installed, you can get a development environment by running:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
git clone https://github.com/equinor/resfo-utilities
|
|
65
|
+
cd resfo-utilities
|
|
66
|
+
uv sync --all-extras
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
You should set up `pre-commit` to ensure style checks are done as you commit:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv run pre-commit install --hook-type pre-push
|
|
74
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
resfo_utilities/__init__.py,sha256=Qg5znqkYEp11hn7Y-D3jTuUZnf19viG150qHsXPqT_s,615
|
|
2
|
+
resfo_utilities/_cornerpoint_grid.py,sha256=I453AgeOqQbsBpqtgxpatQ6op4D8Wzk9xp7emB9cVGY,21690
|
|
3
|
+
resfo_utilities/_summary_keys.py,sha256=8QZZwAQlJs3yOj9IG1iUxd3LU1CxZsjBYEWHIgN4iG4,13086
|
|
4
|
+
resfo_utilities/_summary_reader.py,sha256=BHJ-vifjoUXRBteyVdAr9asTJPUdiICtDTdOwXDE3pQ,20580
|
|
5
|
+
resfo_utilities/testing/__init__.py,sha256=z28oj49eVYt4B1DDWv4ZtzB2CL0KAEl-OnwRSIO9ilE,1834
|
|
6
|
+
resfo_utilities/testing/_egrid_generator.py,sha256=nUYrs2qLzyLpH7wVgoG1rcsila6Klpd3DiAbC32zkbY,11194
|
|
7
|
+
resfo_utilities/testing/_summary_generator.py,sha256=XzgUdPMUDJAuNqN0bFH6-w8j69SgBG-Orp1Rjb2l4Po,16570
|
|
8
|
+
resfo_utilities-0.3.0b0.dist-info/licenses/LICENSE.md,sha256=3IXI3x1RN4jDR2PonpWF1guc33zagcTgpq_VnboE3UU,7653
|
|
9
|
+
resfo_utilities-0.3.0b0.dist-info/METADATA,sha256=Uar86s8_gBDVoKnJAL-n-USAqMEl713X8Jbv8kAZN54,2515
|
|
10
|
+
resfo_utilities-0.3.0b0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
resfo_utilities-0.3.0b0.dist-info/top_level.txt,sha256=VjItoaJHqsDLhHEvCjEI5bN2sZy55tA-zlkl-CtggEU,16
|
|
12
|
+
resfo_utilities-0.3.0b0.dist-info/RECORD,,
|