resfo-utilities 0.0.3__py3-none-any.whl → 0.1.0__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 +7 -2
- resfo_utilities/_cornerpoint_grid.py +81 -31
- {resfo_utilities-0.0.3.dist-info → resfo_utilities-0.1.0.dist-info}/METADATA +1 -2
- resfo_utilities-0.1.0.dist-info/RECORD +7 -0
- resfo_utilities-0.0.3.dist-info/RECORD +0 -7
- {resfo_utilities-0.0.3.dist-info → resfo_utilities-0.1.0.dist-info}/WHEEL +0 -0
- {resfo_utilities-0.0.3.dist-info → resfo_utilities-0.1.0.dist-info}/licenses/LICENSE.md +0 -0
- {resfo_utilities-0.0.3.dist-info → resfo_utilities-0.1.0.dist-info}/top_level.txt +0 -0
resfo_utilities/__init__.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
from ._cornerpoint_grid import
|
|
1
|
+
from ._cornerpoint_grid import (
|
|
2
|
+
CornerpointGrid,
|
|
3
|
+
InvalidEgridFileError,
|
|
4
|
+
MapAxes,
|
|
5
|
+
InvalidGridError,
|
|
6
|
+
)
|
|
2
7
|
|
|
3
|
-
__all__ = ["CornerpointGrid", "InvalidEgridFileError", "MapAxes"]
|
|
8
|
+
__all__ = ["CornerpointGrid", "InvalidEgridFileError", "MapAxes", "InvalidGridError"]
|
|
@@ -7,7 +7,6 @@ import numpy as np
|
|
|
7
7
|
import resfo
|
|
8
8
|
import scipy.optimize
|
|
9
9
|
import warnings
|
|
10
|
-
from matplotlib.path import Path
|
|
11
10
|
import heapq
|
|
12
11
|
from functools import cached_property
|
|
13
12
|
|
|
@@ -16,6 +15,10 @@ class InvalidEgridFileError(ValueError):
|
|
|
16
15
|
pass
|
|
17
16
|
|
|
18
17
|
|
|
18
|
+
class InvalidGridError(ValueError):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
19
22
|
@dataclass
|
|
20
23
|
class MapAxes:
|
|
21
24
|
"""The axes of the map coordinate system.
|
|
@@ -108,6 +111,19 @@ class CornerpointGrid:
|
|
|
108
111
|
zcorn: npt.NDArray[np.float32]
|
|
109
112
|
map_axes: MapAxes | None = None
|
|
110
113
|
|
|
114
|
+
def __post_init__(self) -> None:
|
|
115
|
+
if len(self.coord.shape) != 4 or self.coord.shape[2:4] != (2, 3):
|
|
116
|
+
raise InvalidGridError(f"coord had invalid dimensions {self.coord.shape}")
|
|
117
|
+
if len(self.zcorn.shape) != 4 or self.zcorn.shape[-1] != 8:
|
|
118
|
+
raise InvalidGridError(f"zcorn had invalid dimensions {self.zcorn.shape}")
|
|
119
|
+
ni = self.coord.shape[0] - 1
|
|
120
|
+
nj = self.coord.shape[1] - 1
|
|
121
|
+
if self.zcorn.shape[0] != ni or self.zcorn.shape[1] != nj:
|
|
122
|
+
raise InvalidGridError(
|
|
123
|
+
"zcorn and coord dimensions do not match:"
|
|
124
|
+
f" {self.zcorn.shape} vs {self.coord.shape}"
|
|
125
|
+
)
|
|
126
|
+
|
|
111
127
|
@classmethod
|
|
112
128
|
def read_egrid(cls, file_like: str | os.PathLike[str] | IO[Any]) -> Self:
|
|
113
129
|
"""Read the global grid from an .EGRID or .FEGRID file.
|
|
@@ -272,6 +288,10 @@ class CornerpointGrid:
|
|
|
272
288
|
if map_coordinates and self.map_axes is not None:
|
|
273
289
|
points = self.map_axes.transform_map_points(points)
|
|
274
290
|
|
|
291
|
+
dims = self.zcorn.shape[0:3]
|
|
292
|
+
top = self._pillars_z_plane_intersection(self.zcorn.min())
|
|
293
|
+
bot = self._pillars_z_plane_intersection(self.zcorn.max())
|
|
294
|
+
|
|
275
295
|
# This algorithm will for each point p calculate the mesh surface that
|
|
276
296
|
# is the intersection of the pillars with the plane z=p[2]. Then it searches
|
|
277
297
|
# through the quad with a heuristical search that orders each neighbour by
|
|
@@ -285,19 +305,24 @@ class CornerpointGrid:
|
|
|
285
305
|
class Quad:
|
|
286
306
|
"""The quad at index i,j"""
|
|
287
307
|
|
|
288
|
-
mesh: npt.NDArray[np.float32]
|
|
289
308
|
i: int
|
|
290
309
|
j: int
|
|
291
310
|
p: npt.NDArray[np.float32]
|
|
311
|
+
i_neighbourhood: int
|
|
312
|
+
j_neighbourhood: int
|
|
292
313
|
|
|
293
314
|
@cached_property
|
|
294
315
|
def vertices(self) -> npt.NDArray[np.float32]:
|
|
295
316
|
return np.array(
|
|
296
317
|
[
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
318
|
+
top[self.i, self.j],
|
|
319
|
+
top[self.i + 1, self.j],
|
|
320
|
+
top[self.i + 1, self.j + 1],
|
|
321
|
+
top[self.i, self.j + 1],
|
|
322
|
+
bot[self.i, self.j],
|
|
323
|
+
bot[self.i + 1, self.j],
|
|
324
|
+
bot[self.i + 1, self.j + 1],
|
|
325
|
+
bot[self.i, self.j + 1],
|
|
301
326
|
],
|
|
302
327
|
dtype=np.float32,
|
|
303
328
|
)
|
|
@@ -334,53 +359,63 @@ class CornerpointGrid:
|
|
|
334
359
|
return False
|
|
335
360
|
return bool(self.distance_from_bounds < other.distance_from_bounds)
|
|
336
361
|
|
|
337
|
-
if
|
|
362
|
+
if dims[0] <= 0 or dims[1] <= 0:
|
|
338
363
|
return [None] * len(points)
|
|
339
364
|
|
|
340
365
|
for p in points:
|
|
341
366
|
found = False
|
|
342
|
-
mesh = self._pillars_z_plane_intersection(p[2])
|
|
343
367
|
if prev_ij is None:
|
|
344
|
-
queue = [
|
|
368
|
+
queue = [
|
|
369
|
+
Quad(dims[0] // 2, dims[1] // 2, p, dims[0] // 2, dims[1] // 2)
|
|
370
|
+
]
|
|
345
371
|
else:
|
|
346
|
-
queue = [Quad(
|
|
372
|
+
queue = [Quad(*prev_ij, p, 1, 1)]
|
|
347
373
|
visited = set([(queue[0].i, queue[0].j)])
|
|
348
374
|
while queue:
|
|
349
375
|
node = heapq.heappop(queue)
|
|
350
|
-
vertices = node.vertices
|
|
351
376
|
i = node.i
|
|
352
377
|
j = node.j
|
|
353
378
|
|
|
354
379
|
# If the quad contains the point then search through each k index
|
|
355
380
|
# for that quad
|
|
356
|
-
if node.distance_from_bounds <=
|
|
357
|
-
|
|
358
|
-
).contains_points([p[0:2]], radius=tolerance):
|
|
359
|
-
for k in range(self.zcorn.shape[2]):
|
|
381
|
+
if node.distance_from_bounds <= 2 * tolerance:
|
|
382
|
+
for k in range(dims[2]):
|
|
360
383
|
zcorn = self.zcorn[i, j, k]
|
|
361
384
|
z = p[2]
|
|
362
385
|
# Prune by bounding box first then check whether point_in_cell
|
|
363
386
|
if (
|
|
364
|
-
zcorn.min() -
|
|
365
|
-
|
|
387
|
+
zcorn.min() - 2 * tolerance
|
|
388
|
+
<= z
|
|
389
|
+
<= zcorn.max() + 2 * tolerance
|
|
390
|
+
and self.point_in_cell(
|
|
391
|
+
p, i, j, k, tolerance=tolerance, map_coordinates=False
|
|
392
|
+
)
|
|
366
393
|
):
|
|
367
394
|
prev_ij = (i, j)
|
|
368
395
|
result.append((i, j, k))
|
|
369
396
|
found = True
|
|
370
397
|
break
|
|
398
|
+
if found:
|
|
371
399
|
break
|
|
372
400
|
|
|
373
401
|
# Add each neighbour to the queue if not visited
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
for dj in (-1, 0,
|
|
379
|
-
nj = j + dj
|
|
380
|
-
if nj < 0 or nj >= self.zcorn.shape[1]:
|
|
381
|
-
continue
|
|
402
|
+
size_i = node.i_neighbourhood
|
|
403
|
+
for di in (-1 * size_i, 0, size_i):
|
|
404
|
+
ni = np.clip(i + di, 0, dims[0] - 1)
|
|
405
|
+
size_j = node.j_neighbourhood
|
|
406
|
+
for dj in (-1 * size_j, 0, size_j):
|
|
407
|
+
nj = np.clip(j + dj, 0, dims[1] - 1)
|
|
382
408
|
if (ni, nj) not in visited:
|
|
383
|
-
heapq.heappush(
|
|
409
|
+
heapq.heappush(
|
|
410
|
+
queue,
|
|
411
|
+
Quad(
|
|
412
|
+
ni,
|
|
413
|
+
nj,
|
|
414
|
+
p,
|
|
415
|
+
max(size_i // 2, 1),
|
|
416
|
+
max(size_j // 2, 1),
|
|
417
|
+
),
|
|
418
|
+
)
|
|
384
419
|
visited.add((ni, nj))
|
|
385
420
|
if not found:
|
|
386
421
|
result.append(None)
|
|
@@ -408,8 +443,23 @@ class CornerpointGrid:
|
|
|
408
443
|
def twice(a: npt.NDArray[Any]) -> npt.NDArray[Any]:
|
|
409
444
|
return np.concatenate([a, a])
|
|
410
445
|
|
|
411
|
-
|
|
412
|
-
|
|
446
|
+
height_diff = twice(bot_z - top_z)
|
|
447
|
+
|
|
448
|
+
if np.any(height_diff == 0):
|
|
449
|
+
raise InvalidGridError(
|
|
450
|
+
f"Grid contains zero height pillars with different for cell {i, j, k}"
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
t = (self.zcorn[i, j, k] - twice(top_z)) / height_diff
|
|
454
|
+
|
|
455
|
+
result = twice(top) + t[:, np.newaxis] * twice(bot - top)
|
|
456
|
+
|
|
457
|
+
if not np.all(np.isfinite(result)):
|
|
458
|
+
raise InvalidGridError(
|
|
459
|
+
f"The corners of the cell at {i, j, k} is not well defined"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
return result
|
|
413
463
|
|
|
414
464
|
def point_in_cell(
|
|
415
465
|
self,
|
|
@@ -444,7 +494,7 @@ class CornerpointGrid:
|
|
|
444
494
|
if map_coordinates and self.map_axes is not None:
|
|
445
495
|
points = self.map_axes.transform_map_points(points)
|
|
446
496
|
|
|
447
|
-
vertices = self.cell_corners(i, j, k)
|
|
497
|
+
vertices = self.cell_corners(i, j, k)
|
|
448
498
|
|
|
449
499
|
corner_signs = np.array(
|
|
450
500
|
[
|
|
@@ -480,7 +530,7 @@ class CornerpointGrid:
|
|
|
480
530
|
|
|
481
531
|
solutions = []
|
|
482
532
|
for point in points:
|
|
483
|
-
point = point
|
|
533
|
+
point = point
|
|
484
534
|
initial_guess = 2 * (point - vertices[0]) / (vertices[6] - vertices[0]) - 1
|
|
485
535
|
initial_guess = np.clip(initial_guess, -1, 1)
|
|
486
536
|
np.nan_to_num(initial_guess, copy=False)
|
|
@@ -498,7 +548,7 @@ class CornerpointGrid:
|
|
|
498
548
|
solutions.append(
|
|
499
549
|
bool(
|
|
500
550
|
np.all(np.abs(sol.x) <= 1.0 + tolerance)
|
|
501
|
-
and np.linalg.norm(residual(point)(sol.x)) <=
|
|
551
|
+
and np.linalg.norm(residual(point)(sol.x)) <= tolerance
|
|
502
552
|
)
|
|
503
553
|
)
|
|
504
554
|
return np.array(solutions, dtype=np.bool_)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: resfo-utilities
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: A utility library for working with the output of reservoir simulators.
|
|
5
5
|
Author-email: Equinor <fg_sib-scout@equinor.com>
|
|
6
6
|
Maintainer-email: Eivind Jahren <ejah@equinor.com>, Håkon Steinkopf Søhoel <hsoho@equinor.com>
|
|
@@ -19,7 +19,6 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE.md
|
|
20
20
|
Requires-Dist: numpy
|
|
21
21
|
Requires-Dist: resfo
|
|
22
|
-
Requires-Dist: matplotlib
|
|
23
22
|
Requires-Dist: scipy
|
|
24
23
|
Provides-Extra: doc
|
|
25
24
|
Requires-Dist: sphinx; extra == "doc"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
resfo_utilities/__init__.py,sha256=AymNFhNg2Q8fQg2HfOFPDBlX2irU2fmizsSXf9YhWMg,205
|
|
2
|
+
resfo_utilities/_cornerpoint_grid.py,sha256=aY5af8X7n_A6rIribiI14vvzLUHd_EWZxP_ez6365PE,21661
|
|
3
|
+
resfo_utilities-0.1.0.dist-info/licenses/LICENSE.md,sha256=3IXI3x1RN4jDR2PonpWF1guc33zagcTgpq_VnboE3UU,7653
|
|
4
|
+
resfo_utilities-0.1.0.dist-info/METADATA,sha256=PgySjtZ9GZQKa4AGJGuB6RiGCNck5-U_j-C4hwQYltA,2330
|
|
5
|
+
resfo_utilities-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
resfo_utilities-0.1.0.dist-info/top_level.txt,sha256=VjItoaJHqsDLhHEvCjEI5bN2sZy55tA-zlkl-CtggEU,16
|
|
7
|
+
resfo_utilities-0.1.0.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
resfo_utilities/__init__.py,sha256=_O7IbN2VxBuoWsSeVlQl6LSi_N0Tnb1YAOwiPNcy2i0,146
|
|
2
|
-
resfo_utilities/_cornerpoint_grid.py,sha256=ZJleszWHKEeVyIsW0If3H3OWX64Tjdg_tgo-etlenA0,19905
|
|
3
|
-
resfo_utilities-0.0.3.dist-info/licenses/LICENSE.md,sha256=3IXI3x1RN4jDR2PonpWF1guc33zagcTgpq_VnboE3UU,7653
|
|
4
|
-
resfo_utilities-0.0.3.dist-info/METADATA,sha256=gRqzsGS6p87TNZQ6G57WI5exXkF4ICAwATdLN3ulUtk,2356
|
|
5
|
-
resfo_utilities-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
-
resfo_utilities-0.0.3.dist-info/top_level.txt,sha256=VjItoaJHqsDLhHEvCjEI5bN2sZy55tA-zlkl-CtggEU,16
|
|
7
|
-
resfo_utilities-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|