resfo-utilities 0.0.3__py3-none-any.whl → 0.1.1__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.

@@ -1,3 +1,8 @@
1
- from ._cornerpoint_grid import CornerpointGrid, InvalidEgridFileError, MapAxes
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
- self.mesh[self.i, self.j],
298
- self.mesh[self.i + 1, self.j],
299
- self.mesh[self.i + 1, self.j + 1],
300
- self.mesh[self.i, self.j + 1],
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 self.zcorn.shape[0] <= 0 or self.zcorn.shape[1] <= 0:
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 = [Quad(mesh, 0, 0, p)]
368
+ queue = [
369
+ Quad(dims[0] // 2, dims[1] // 2, p, dims[0] // 2, dims[1] // 2)
370
+ ]
345
371
  else:
346
- queue = [Quad(mesh, *prev_ij, p)]
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 <= tolerance and Path(
357
- vertices
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() - tolerance <= z <= zcorn.max() + tolerance
365
- and self.point_in_cell(p, i, j, k, map_coordinates=False)
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
- for di in (-1, 0, 1):
375
- ni = i + di
376
- if ni < 0 or ni >= self.zcorn.shape[0]:
377
- continue
378
- for dj in (-1, 0, 1):
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(queue, Quad(mesh, ni, nj, p))
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
- t = (self.zcorn[i, j, k] - twice(top_z)) / twice(bot_z - top_z)
412
- return twice(top) + t[:, np.newaxis] * twice(bot - top)
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).astype(np.float64)
497
+ vertices = self.cell_corners(i, j, k)
448
498
 
449
499
  corner_signs = np.array(
450
500
  [
@@ -480,9 +530,13 @@ class CornerpointGrid:
480
530
 
481
531
  solutions = []
482
532
  for point in points:
483
- point = point.astype(np.float64)
484
- initial_guess = 2 * (point - vertices[0]) / (vertices[6] - vertices[0]) - 1
485
- initial_guess = np.clip(initial_guess, -1, 1)
533
+ point = point
534
+ with warnings.catch_warnings():
535
+ warnings.simplefilter("ignore")
536
+ initial_guess = (
537
+ 2 * (point - vertices[0]) / (vertices[7] - vertices[0]) - 1
538
+ )
539
+ initial_guess = np.clip(initial_guess, -1, 1)
486
540
  np.nan_to_num(initial_guess, copy=False)
487
541
  sol = scipy.optimize.least_squares(
488
542
  residual(point),
@@ -498,7 +552,7 @@ class CornerpointGrid:
498
552
  solutions.append(
499
553
  bool(
500
554
  np.all(np.abs(sol.x) <= 1.0 + tolerance)
501
- and np.linalg.norm(residual(point)(sol.x)) <= 1e-4 + tolerance
555
+ and np.linalg.norm(residual(point)(sol.x)) <= tolerance
502
556
  )
503
557
  )
504
558
  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
3
+ Version: 0.1.1
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=bBgCT1nEVl7jir3REGxQ20dKS9FAGqnrQLZEpAvoDBg,21801
3
+ resfo_utilities-0.1.1.dist-info/licenses/LICENSE.md,sha256=3IXI3x1RN4jDR2PonpWF1guc33zagcTgpq_VnboE3UU,7653
4
+ resfo_utilities-0.1.1.dist-info/METADATA,sha256=ju1jI3YTp-579bYJAIUGqv4424CCjWEswsouKARW994,2330
5
+ resfo_utilities-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ resfo_utilities-0.1.1.dist-info/top_level.txt,sha256=VjItoaJHqsDLhHEvCjEI5bN2sZy55tA-zlkl-CtggEU,16
7
+ resfo_utilities-0.1.1.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,,