resfo-utilities 0.0.3__tar.gz → 0.1.1__tar.gz

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.

Files changed (40) hide show
  1. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/PKG-INFO +1 -2
  2. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/pyproject.toml +1 -1
  3. resfo_utilities-0.1.1/src/resfo_utilities/__init__.py +8 -0
  4. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/src/resfo_utilities/_cornerpoint_grid.py +87 -33
  5. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/src/resfo_utilities.egg-info/PKG-INFO +1 -2
  6. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/src/resfo_utilities.egg-info/requires.txt +0 -1
  7. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/unit/test_cornerpoint_grid.py +215 -59
  8. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/uv.lock +337 -647
  9. resfo_utilities-0.0.3/src/resfo_utilities/__init__.py +0 -3
  10. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.github/dependabot.yml +0 -0
  12. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.github/mypy-matcher.json +0 -0
  13. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.github/workflows/publish_to_pypi.yml +0 -0
  14. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.github/workflows/testing.yml +0 -0
  15. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.github/workflows/typing.yml +0 -0
  16. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.gitignore +0 -0
  17. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.mypy.ini +0 -0
  18. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.pre-commit-config.yaml +0 -0
  19. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/.readthedocs.yaml +0 -0
  20. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/CONTRIBUTING.md +0 -0
  21. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/LICENSE.md +0 -0
  22. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/README.md +0 -0
  23. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/SECURITY.md +0 -0
  24. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/docs/requirements.txt +0 -0
  25. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/docs/source/api_reference.rst +0 -0
  26. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/docs/source/conf.py +0 -0
  27. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/docs/source/glossary.rst +0 -0
  28. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/docs/source/index.rst +0 -0
  29. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/setup.cfg +0 -0
  30. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/src/resfo_utilities.egg-info/SOURCES.txt +0 -0
  31. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/src/resfo_utilities.egg-info/dependency_links.txt +0 -0
  32. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/src/resfo_utilities.egg-info/top_level.txt +0 -0
  33. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/__init__.py +0 -0
  34. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/conftest.py +0 -0
  35. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/integration/__init__.py +0 -0
  36. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/integration/conftest.py +0 -0
  37. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/integration/test_grid.py +0 -0
  38. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/performance/__init__.py +0 -0
  39. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/performance/test_find_cell_performance.py +0 -0
  40. {resfo_utilities-0.0.3 → resfo_utilities-0.1.1}/tests/unit/__init__.py +0 -0
@@ -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"
@@ -15,7 +15,7 @@ classifiers=[
15
15
  "Programming Language :: Python :: 3.12",
16
16
  "Programming Language :: Python :: 3.13",
17
17
  ]
18
- dependencies = ["numpy","resfo", "matplotlib", "scipy"]
18
+ dependencies = ["numpy","resfo", "scipy"]
19
19
  dynamic=["version"]
20
20
 
21
21
  authors = [
@@ -0,0 +1,8 @@
1
+ from ._cornerpoint_grid import (
2
+ CornerpointGrid,
3
+ InvalidEgridFileError,
4
+ MapAxes,
5
+ InvalidGridError,
6
+ )
7
+
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"
@@ -1,11 +1,17 @@
1
- from resfo_utilities import CornerpointGrid, InvalidEgridFileError, MapAxes
1
+ from resfo_utilities import (
2
+ CornerpointGrid,
3
+ InvalidEgridFileError,
4
+ MapAxes,
5
+ InvalidGridError,
6
+ )
2
7
  import resfo
3
8
  import pytest
4
9
  from io import BytesIO
5
10
  import numpy as np
6
11
  from numpy.testing import assert_allclose
7
- from hypothesis import given, assume
12
+ from hypothesis import given, assume, example
8
13
  import hypothesis.strategies as st
14
+ from hypothesis.extra.numpy import arrays, from_dtype
9
15
  from itertools import product
10
16
 
11
17
 
@@ -80,6 +86,47 @@ def test_that_read_egrid_raises_invalid_egrid_file_when_mapaxes_has_too_many_val
80
86
  CornerpointGrid.read_egrid(write_to_buffer([("MAPAXES ", [1.0])]))
81
87
 
82
88
 
89
+ def test_that_grid_with_invalid_coord_shape_raises():
90
+ with pytest.raises(InvalidGridError, match="coord had invalid dimensions"):
91
+ CornerpointGrid(
92
+ coord=np.array([], dtype=np.float32),
93
+ zcorn=np.array([[[[0, 0, 0, 0, 1, 1, 1, 1]]]], dtype=np.float32),
94
+ )
95
+
96
+
97
+ def test_that_grid_with_invalid_zcorn_shape_raises():
98
+ with pytest.raises(InvalidGridError, match="zcorn had invalid dimensions"):
99
+ CornerpointGrid(
100
+ coord=np.array(
101
+ [
102
+ [[[0, 0, 0], [0, 0, 1]], [[0, 1, 0], [0, 1, 1]]],
103
+ [[[1, 0, 0], [1, 0, 1]], [[1, 1, 0], [1, 1, 1]]],
104
+ ],
105
+ dtype=np.float32,
106
+ ),
107
+ zcorn=np.array([], dtype=np.float32),
108
+ )
109
+
110
+
111
+ def test_that_grid_with_zcorn_and_coord_shape_mismatch_raises():
112
+ with pytest.raises(
113
+ InvalidGridError, match="zcorn and coord dimensions do not match"
114
+ ):
115
+ CornerpointGrid(
116
+ coord=np.array(
117
+ [
118
+ [[[0, 0, 0], [0, 0, 1]], [[0, 1, 0], [0, 1, 1]]],
119
+ [[[1, 0, 0], [1, 0, 1]], [[1, 1, 0], [1, 1, 1]]],
120
+ ],
121
+ dtype=np.float32,
122
+ ),
123
+ zcorn=np.array(
124
+ [[[[0, 0, 0, 0, 1, 1, 1, 1]]], [[[0, 0, 0, 0, 1, 1, 1, 1]]]],
125
+ dtype=np.float32,
126
+ ),
127
+ )
128
+
129
+
83
130
  @pytest.mark.parametrize(
84
131
  "contents_after_global_grid",
85
132
  [
@@ -193,61 +240,6 @@ def test_that_read_egrid_fetches_the_geometry_from_the_global_grid_in_the_file(
193
240
  ]
194
241
 
195
242
 
196
- def test_that_pillars_z_plane_intersection_returns_meshgrid():
197
- coord = np.array(
198
- [
199
- [
200
- [[0.0, 0.0, 0.0], [1.0, 10.0, 100.0]],
201
- [[10.0, 20.0, 0.0], [20.0, 30.0, 100.0]],
202
- ]
203
- ]
204
- )
205
- grid = CornerpointGrid(coord, None, None)
206
- assert grid._pillars_z_plane_intersection(50.0).tolist() == [
207
- [[0.5, 5.0], [15.0, 25.0]]
208
- ]
209
-
210
-
211
- def test_that_pillars_z_plane_intersection_keeps_same_shape_as_coord_in_i_j_dimensions():
212
- coord = np.array(
213
- [
214
- [
215
- [[0.0, 1.0, 100.0], [0.0, 1.0, 200.0]],
216
- [[2.0, 3.0, 101.0], [2.0, 3.0, 201.0]],
217
- [[4.0, 5.0, 102.0], [4.0, 5.0, 202.0]],
218
- ],
219
- [
220
- [[6.0, 7.0, 103.0], [6.0, 7.0, 203.0]],
221
- [[8.0, 9.0, 104.0], [8.0, 9.0, 204.0]],
222
- [[10.0, 11.0, 105.0], [10.0, 11.0, 205.0]],
223
- ],
224
- [
225
- [[12.0, 13.0, 106.0], [12.0, 13.0, 206.0]],
226
- [[14.0, 15.0, 107.0], [14.0, 15.0, 207.0]],
227
- [[16.0, 17.0, 108.0], [16.0, 17.0, 208.0]],
228
- ],
229
- ]
230
- )
231
- grid = CornerpointGrid(coord, None, None)
232
- assert grid._pillars_z_plane_intersection(50.0).tolist() == [
233
- [
234
- [0.0, 1.0],
235
- [2.0, 3.0],
236
- [4.0, 5.0],
237
- ],
238
- [
239
- [6.0, 7.0],
240
- [8.0, 9.0],
241
- [10.0, 11.0],
242
- ],
243
- [
244
- [12.0, 13.0],
245
- [14.0, 15.0],
246
- [16.0, 17.0],
247
- ],
248
- ]
249
-
250
-
251
243
  @pytest.fixture
252
244
  def unit_cell_grid():
253
245
  """A Corner point grid which just contains the unit cube as a cell"""
@@ -362,9 +354,12 @@ def regular_grids(draw):
362
354
  return CornerpointGrid(coord, zcorn)
363
355
 
364
356
 
357
+ points = st.tuples(coordinates, coordinates, coordinates)
358
+
359
+
365
360
  @given(
366
361
  grid=regular_grids(),
367
- point=st.tuples(coordinates, coordinates, coordinates),
362
+ point=points,
368
363
  data=st.data(),
369
364
  )
370
365
  def test_that_found_cell_contains_point(grid, point, data):
@@ -386,7 +381,7 @@ def test_that_found_cell_contains_point(grid, point, data):
386
381
 
387
382
  @given(
388
383
  grid=regular_grids(),
389
- point=st.tuples(coordinates, coordinates, coordinates),
384
+ point=points,
390
385
  data=st.data(),
391
386
  )
392
387
  def test_that_on_regular_grids_point_in_cell_is_the_same_as_in_bounding_box(
@@ -560,3 +555,164 @@ def test_point_in_cell_considers_cells_as_trilinear_shapes(bottom_heights, x, y,
560
555
  assert grid.point_in_cell([x, y, z], 0, 0, 0, tolerance) == (
561
556
  z <= bottom_face_depth(x, y) and in_bounding_box((x, y, z))
562
557
  )
558
+
559
+
560
+ def test_that_zero_height_pillar_is_invalid():
561
+ grid = CornerpointGrid(
562
+ coord=np.array(
563
+ [
564
+ [
565
+ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
566
+ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
567
+ ],
568
+ [
569
+ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
570
+ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
571
+ ],
572
+ ],
573
+ dtype=np.float32,
574
+ ),
575
+ zcorn=np.array(
576
+ [[[[0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]]]], dtype=np.float32
577
+ ),
578
+ map_axes=None,
579
+ )
580
+ with pytest.raises(InvalidGridError, match="Grid contains zero height pillars"):
581
+ grid.cell_corners(0, 0, 0)
582
+
583
+
584
+ def test_that_cells_with_infinite_pillars_are_invalid():
585
+ grid = CornerpointGrid(
586
+ coord=np.array(
587
+ [
588
+ [
589
+ [[np.inf, np.inf, np.inf], [np.inf, np.inf, np.inf]],
590
+ [[np.inf, np.inf, np.inf], [np.inf, np.inf, np.inf]],
591
+ ],
592
+ [
593
+ [[np.inf, np.inf, np.inf], [np.inf, np.inf, np.inf]],
594
+ [[np.inf, np.inf, np.inf], [np.inf, np.inf, np.inf]],
595
+ ],
596
+ ],
597
+ dtype=np.float32,
598
+ ),
599
+ zcorn=np.array(
600
+ [[[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]]], dtype=np.float32
601
+ ),
602
+ map_axes=None,
603
+ )
604
+
605
+ with pytest.raises(InvalidGridError, match="The corners of the cell"):
606
+ grid.cell_corners(0, 0, 0)
607
+
608
+
609
+ @st.composite
610
+ def single_cell_grids(draw):
611
+ nice_elements = dict(
612
+ allow_nan=False, allow_infinity=False, max_value=2**12, min_value=-(2**12)
613
+ )
614
+ coord = draw(
615
+ arrays(
616
+ np.float32,
617
+ (2, 2, 2, 3),
618
+ elements=nice_elements,
619
+ )
620
+ )
621
+
622
+ for i in range(coord.shape[0]):
623
+ for j in range(coord.shape[1]):
624
+ bot_pillar_z = np.float32(coord[i, j, 0, 2] + 0.1)
625
+ coord[i, j, 1] = draw(
626
+ from_dtype(
627
+ np.dtype(np.float32),
628
+ **{
629
+ **nice_elements,
630
+ **dict(min_value=bot_pillar_z, max_value=2**14),
631
+ },
632
+ )
633
+ )
634
+
635
+ grid = CornerpointGrid(
636
+ coord=coord,
637
+ zcorn=draw(arrays(np.float32, (1, 1, 1, 8), elements=nice_elements)),
638
+ )
639
+ try:
640
+ grid.cell_corners(0, 0, 0)
641
+ except InvalidGridError:
642
+ assume(False)
643
+ return grid
644
+
645
+
646
+ @given(single_cell_grids(), points)
647
+ @example(
648
+ grid=CornerpointGrid(
649
+ coord=np.array(
650
+ [
651
+ [
652
+ [[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]],
653
+ [[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]],
654
+ ],
655
+ [
656
+ [[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]],
657
+ [[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]],
658
+ ],
659
+ ],
660
+ dtype=np.float32,
661
+ ),
662
+ zcorn=np.array(
663
+ [[[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]]], dtype=np.float32
664
+ ),
665
+ map_axes=None,
666
+ ),
667
+ point=(0.0, 0.0, 0.0),
668
+ ).via("discovered failure")
669
+ @example(
670
+ grid=CornerpointGrid(
671
+ coord=np.array(
672
+ [
673
+ [
674
+ [[0.0, 0.0, -1.0], [0.0, 0.0, 1.0]],
675
+ [[0.0, 1.0, 0.0], [2.0, 1.0, 2.0]],
676
+ ],
677
+ [
678
+ [[1.0, 0.0, -1.0], [1.0, 0.0, 1.0]],
679
+ [[0.0, 1.0, -1.0], [0.0, 1.0, 1.0]],
680
+ ],
681
+ ],
682
+ dtype=np.float32,
683
+ ),
684
+ zcorn=np.array(
685
+ [[[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]]], dtype=np.float32
686
+ ),
687
+ map_axes=None,
688
+ ),
689
+ point=(0.0, 0.0, 0.0),
690
+ ).via("constructed grid with bulging face")
691
+ @example(
692
+ grid=CornerpointGrid(
693
+ coord=np.array(
694
+ [
695
+ [
696
+ [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]],
697
+ [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]],
698
+ ],
699
+ [
700
+ [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]],
701
+ [[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]],
702
+ ],
703
+ ],
704
+ dtype=np.float32,
705
+ ),
706
+ zcorn=np.array(
707
+ [[[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]]], dtype=np.float32
708
+ ),
709
+ map_axes=None,
710
+ ),
711
+ point=(0.0, 0.0, 9.999999747378752e-06),
712
+ ).via("discovered failure")
713
+ def test_that_in_single_cell_grids_found_and_contains_are_the_same(
714
+ grid: CornerpointGrid, point: tuple[float, float, float]
715
+ ):
716
+ assert bool(grid.find_cell_containing_point([point])[0]) == grid.point_in_cell(
717
+ point, 0, 0, 0
718
+ )