resqpy 4.14.2__py3-none-any.whl → 5.1.6__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.
Files changed (67) hide show
  1. resqpy/__init__.py +1 -1
  2. resqpy/fault/_gcs_functions.py +10 -10
  3. resqpy/fault/_grid_connection_set.py +277 -113
  4. resqpy/grid/__init__.py +2 -3
  5. resqpy/grid/_defined_geometry.py +3 -3
  6. resqpy/grid/_extract_functions.py +8 -2
  7. resqpy/grid/_grid.py +95 -12
  8. resqpy/grid/_grid_types.py +22 -7
  9. resqpy/grid/_points_functions.py +1 -1
  10. resqpy/grid/_regular_grid.py +6 -2
  11. resqpy/grid_surface/__init__.py +17 -38
  12. resqpy/grid_surface/_blocked_well_populate.py +5 -5
  13. resqpy/grid_surface/_find_faces.py +1413 -253
  14. resqpy/lines/_polyline.py +24 -33
  15. resqpy/model/_catalogue.py +9 -0
  16. resqpy/model/_forestry.py +18 -14
  17. resqpy/model/_hdf5.py +11 -3
  18. resqpy/model/_model.py +85 -10
  19. resqpy/model/_xml.py +38 -13
  20. resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
  21. resqpy/olio/read_nexus_fault.py +8 -2
  22. resqpy/olio/relperm.py +1 -1
  23. resqpy/olio/transmission.py +8 -8
  24. resqpy/olio/triangulation.py +36 -30
  25. resqpy/olio/vector_utilities.py +340 -6
  26. resqpy/olio/volume.py +0 -20
  27. resqpy/olio/wellspec_keywords.py +19 -13
  28. resqpy/olio/write_hdf5.py +1 -1
  29. resqpy/olio/xml_et.py +12 -0
  30. resqpy/property/__init__.py +6 -4
  31. resqpy/property/_collection_add_part.py +4 -3
  32. resqpy/property/_collection_create_xml.py +4 -2
  33. resqpy/property/_collection_get_attributes.py +4 -0
  34. resqpy/property/attribute_property_set.py +311 -0
  35. resqpy/property/grid_property_collection.py +11 -11
  36. resqpy/property/property_collection.py +79 -31
  37. resqpy/property/property_common.py +3 -8
  38. resqpy/rq_import/_add_surfaces.py +34 -14
  39. resqpy/rq_import/_grid_from_cp.py +2 -2
  40. resqpy/rq_import/_import_nexus.py +75 -48
  41. resqpy/rq_import/_import_vdb_all_grids.py +64 -52
  42. resqpy/rq_import/_import_vdb_ensemble.py +12 -13
  43. resqpy/surface/_mesh.py +4 -0
  44. resqpy/surface/_surface.py +593 -118
  45. resqpy/surface/_tri_mesh.py +13 -10
  46. resqpy/surface/_tri_mesh_stencil.py +4 -4
  47. resqpy/surface/_triangulated_patch.py +71 -51
  48. resqpy/time_series/_any_time_series.py +7 -4
  49. resqpy/time_series/_geologic_time_series.py +1 -1
  50. resqpy/unstructured/_hexa_grid.py +6 -2
  51. resqpy/unstructured/_prism_grid.py +13 -5
  52. resqpy/unstructured/_pyramid_grid.py +6 -2
  53. resqpy/unstructured/_tetra_grid.py +6 -2
  54. resqpy/unstructured/_unstructured_grid.py +6 -2
  55. resqpy/well/_blocked_well.py +1986 -1946
  56. resqpy/well/_deviation_survey.py +3 -3
  57. resqpy/well/_md_datum.py +11 -21
  58. resqpy/well/_trajectory.py +10 -5
  59. resqpy/well/_wellbore_frame.py +10 -2
  60. resqpy/well/blocked_well_frame.py +3 -3
  61. resqpy/well/well_object_funcs.py +7 -9
  62. resqpy/well/well_utils.py +33 -0
  63. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/LICENSE +0 -0
@@ -120,6 +120,7 @@ class TriMesh(rqs.Mesh):
120
120
  self.origin = None
121
121
  else:
122
122
  self.origin = origin
123
+ self.t_type = np.int32 if self.is_big() else np.int64
123
124
 
124
125
  @classmethod
125
126
  def from_tri_mesh_and_z_values(cls,
@@ -218,6 +219,7 @@ class TriMesh(rqs.Mesh):
218
219
  base edge towards point 2; f1 is component towards point 1; f0 is component towards point 0;
219
220
  the trilinear coordinates sum to one and can be used as weights to interpolate z values at points
220
221
  """
222
+ assert xy_array.ndim > 1 and 2 <= xy_array.shape[-1] <= 3
221
223
  x = xy_array[..., 0].copy()
222
224
  y = xy_array[..., 1].copy()
223
225
  if self.origin is not None:
@@ -235,14 +237,14 @@ class TriMesh(rqs.Mesh):
235
237
  mask = np.logical_or(mask, np.logical_or(i < 0, i >= self.ni - 1))
236
238
  fx = ip - i.astype(float)
237
239
  i *= 2
238
- am = np.where(fx > 1.0 - fy)
240
+ am = (fx > 1.0 - fy).astype(bool)
239
241
  i[am] += 1
240
242
  fx[am] -= 1.0 - fy[am]
241
243
  fy[am] = 1.0 - fy[am]
242
244
  j[mask] = -1
243
245
  i[mask] = -1
244
- fx[mask] = np.NaN
245
- fy[mask] = np.NaN
246
+ fx[mask] = np.nan
247
+ fy[mask] = np.nan
246
248
 
247
249
  return (np.stack((j, i), axis = -1), np.stack((1.0 - (fx + fy), fx, fy), axis = -1))
248
250
 
@@ -274,6 +276,7 @@ class TriMesh(rqs.Mesh):
274
276
  of three vertices of triangles containing xy points; float triplets contain corresponding weights (summing to
275
277
  one per triangle) which can be used to interpolate z values at points xy_array
276
278
  """
279
+ assert xy_array.ndim > 1 and 2 <= xy_array.shape[-1] <= 3
277
280
  tji, tc = self.tji_tc_for_xy_array(xy_array)
278
281
  ji = self.tri_nodes_for_tji_array(tji)
279
282
  return (ji, tc)
@@ -302,7 +305,7 @@ class TriMesh(rqs.Mesh):
302
305
  def tri_nodes_for_tji(self, tji):
303
306
  """Return mesh node indices, shape (3, 2), for triangle tji (tj, ti)."""
304
307
  j, i = tji
305
- tn = np.zeros((3, 2), dtype = int)
308
+ tn = np.zeros((3, 2), dtype = self.t_type)
306
309
  j_odd = j % 2
307
310
  i2, i_odd = divmod(i, 2)
308
311
  assert 0 <= j < self.nj - 1 and 0 <= i < 2 * (self.ni - 1)
@@ -325,7 +328,7 @@ class TriMesh(rqs.Mesh):
325
328
  j = tji_array[..., 0]
326
329
  i = tji_array[..., 1]
327
330
  tn_shape = tuple(list(tji_array.shape[:-1]) + [3, 2])
328
- tn = np.zeros(tn_shape, dtype = int)
331
+ tn = np.zeros(tn_shape, dtype = self.t_type)
329
332
  j_odd = j % 2
330
333
  i2, i_odd = np.divmod(i, 2)
331
334
  mask = np.logical_or(np.logical_or(j < 0, j >= self.nj - 1), np.logical_or(i < 0, i >= 2 * (self.ni - 1)))
@@ -342,9 +345,9 @@ class TriMesh(rqs.Mesh):
342
345
 
343
346
  def all_tri_nodes(self):
344
347
  """Returns array of mesh node indices for all triangles, shape (nj - 1, 2 * (ni - 1), 3, 2)."""
345
- tna = np.zeros((self.nj - 1, 2 * (self.ni - 1), 3, 2), dtype = int)
348
+ tna = np.zeros((self.nj - 1, 2 * (self.ni - 1), 3, 2), dtype = self.t_type)
346
349
  # set mesh j indices
347
- tna[:, :, 0, 0] = np.expand_dims(np.arange(self.nj - 1, dtype = int), axis = -1)
350
+ tna[:, :, 0, 0] = np.expand_dims(np.arange(self.nj - 1, dtype = self.t_type), axis = -1)
348
351
  tna[1::2, ::2, 0, 0] += 1
349
352
  tna[::2, 1::2, 0, 0] += 1
350
353
  tna[:, :, 1, 0] = tna[:, :, 0, 0]
@@ -352,7 +355,7 @@ class TriMesh(rqs.Mesh):
352
355
  tna[1::2, ::2, 2, 0] -= 2
353
356
  tna[::2, 1::2, 2, 0] -= 2
354
357
  # set mesh i indices
355
- tna[:, ::2, 0, 1] = np.expand_dims(np.arange(self.ni - 1, dtype = int), axis = 0)
358
+ tna[:, ::2, 0, 1] = np.expand_dims(np.arange(self.ni - 1, dtype = self.t_type), axis = 0)
356
359
  tna[:, 1::2, 0, 1] = tna[:, ::2, 0, 1]
357
360
  tna[:, :, 1, 1] = tna[:, :, 0, 1] + 1
358
361
  tna[:, :, 2, 1] = tna[:, :, 0, 1]
@@ -362,7 +365,7 @@ class TriMesh(rqs.Mesh):
362
365
  def triangles_and_points(self):
363
366
  """Returns node indices and xyz points in form suitable for a Surface (triangulated set)."""
364
367
  tna = self.all_tri_nodes()
365
- composite_ji = tna[:, :, :, 0] * self.ni + tna[:, :, :, 1]
368
+ composite_ji = (tna[:, :, :, 0] * self.ni + tna[:, :, :, 1]).astype(self.t_type)
366
369
  return (composite_ji.reshape((-1, 3)), self.full_array_ref().reshape((-1, 3)))
367
370
 
368
371
  def tji_for_triangle_index(self, ti):
@@ -410,7 +413,7 @@ class TriMesh(rqs.Mesh):
410
413
  tp)
411
414
  tn_a[:, 1] *= 2 # node j
412
415
 
413
- return np.concatenate((tn_a, tn_b), axis = 0)
416
+ return np.concatenate((tn_a, tn_b), axis = 0).astype(self.t_type)
414
417
 
415
418
  def edge_zero_crossings(self, z_values = None):
416
419
  """Returns numpy list of points from edges where z values cross zero (or given value).
@@ -94,7 +94,7 @@ class TriMeshStencil:
94
94
  # convert into representation more njit & numpy friendly
95
95
  self.start_ip = np.array([ip for (ip, _, _) in half_hex], dtype = int)
96
96
  self.row_length = np.array([rl for (_, rl, _) in half_hex], dtype = int)
97
- self.half_hex = np.full((self.n, 2 * self.n - 1), np.NaN, dtype = float)
97
+ self.half_hex = np.full((self.n, 2 * self.n - 1), np.nan, dtype = float)
98
98
  for jp in range(self.n):
99
99
  self.half_hex[jp, :self.row_length[jp]] = half_hex[jp][2]
100
100
 
@@ -210,7 +210,7 @@ class TriMeshStencil:
210
210
 
211
211
  # todo: class methods for mexican hat
212
212
 
213
- def apply(self, tri_mesh, handle_nan = True, border_value = np.NaN, preserve_nan = False, title = None):
213
+ def apply(self, tri_mesh, handle_nan = True, border_value = np.nan, preserve_nan = False, title = None):
214
214
  """Return a new tri mesh with z values generated by applying the stencil to z values of an existing tri mesh.
215
215
 
216
216
  arguments:
@@ -250,7 +250,7 @@ class TriMeshStencil:
250
250
  if not np.any(nan_mask):
251
251
  nan_mask = None
252
252
  z_values[border:border + tri_mesh.nj, border:border + tri_mesh.ni] = tm_z
253
- applied = np.full((e_nj, e_ni), np.NaN, dtype = float)
253
+ applied = np.full((e_nj, e_ni), np.nan, dtype = float)
254
254
 
255
255
  if handle_nan:
256
256
  _apply_stencil_nanmean(self.n, self.start_ip, self.row_length, self.half_hex, z_values, applied, border,
@@ -262,7 +262,7 @@ class TriMeshStencil:
262
262
  # restore NaN values where they were present in input, if requested
263
263
  tm_z_applied = applied[border:border + tri_mesh.nj, border:border + tri_mesh.ni]
264
264
  if nan_mask is not None:
265
- tm_z_applied[nan_mask] = np.NaN
265
+ tm_z_applied[nan_mask] = np.nan
266
266
 
267
267
  # create a new tri mesh object using the values from the applied array for z
268
268
  tm = rqs.TriMesh(tri_mesh.model,
@@ -31,6 +31,7 @@ class TriangulatedPatch:
31
31
  self.ni = None # used to convert a triangle index back into a (j, i) pair when freshly built from mesh
32
32
  self.points = None
33
33
  self.crs_uuid = crs_uuid
34
+ self.t_type = np.int32 # gets set to int64 if number of points requires it
34
35
  if patch_node is not None:
35
36
  xml_patch_index = rqet.find_tag_int(patch_node, 'PatchIndex')
36
37
  assert xml_patch_index is not None
@@ -56,52 +57,61 @@ class TriangulatedPatch:
56
57
  crs_root = self.model.root_for_uuid(self.crs_uuid)
57
58
  return crs_root, self.crs_uuid
58
59
 
59
- def triangles_and_points(self):
60
+ def triangles_and_points(self, copy = False):
60
61
  """Returns arrays representing the patch.
61
62
 
62
- Returns:
63
+ arguments:
64
+ copy (bool, default False): if True, a copy of the arrays is returned; if False, the cached
65
+ arrays are returned
66
+
67
+ returns:
63
68
  Tuple (triangles, points):
64
69
 
65
70
  * triangles (int array of shape[:, 3]): integer indices into points array,
66
71
  being the nodes of the corners of the triangles
67
72
  * points (float array of shape[:, 3]): flat array of xyz points, indexed by triangles
68
73
  """
69
- if self.triangles is not None:
74
+ if self.triangles is None:
75
+
76
+ assert self.triangle_count is not None and self.node_count is not None
77
+
78
+ geometry_node = rqet.find_tag(self.node, 'Geometry')
79
+ assert geometry_node is not None
80
+ p_root = rqet.find_tag(geometry_node, 'Points')
81
+ assert p_root is not None, 'Points xml node not found for triangle patch'
82
+ assert rqet.node_type(p_root) == 'Point3dHdf5Array'
83
+ h5_key_pair = self.model.h5_uuid_and_path_for_node(p_root, tag = 'Coordinates')
84
+ if h5_key_pair is None:
85
+ return (None, None)
86
+ try:
87
+ self.model.h5_array_element(h5_key_pair,
88
+ cache_array = True,
89
+ object = self,
90
+ array_attribute = 'points',
91
+ dtype = 'float')
92
+ except Exception:
93
+ log.error('hdf5 points failure for triangle patch ' + str(self.patch_index))
94
+ raise
95
+ self._set_t_type()
96
+ triangles_node = rqet.find_tag(self.node, 'Triangles')
97
+ h5_key_pair = self.model.h5_uuid_and_path_for_node(triangles_node)
98
+ if h5_key_pair is None:
99
+ log.warning('No Triangles found in xml for patch index: ' + str(self.patch_index))
100
+ return (None, None)
101
+ try:
102
+ self.model.h5_array_element(h5_key_pair,
103
+ cache_array = True,
104
+ object = self,
105
+ array_attribute = 'triangles',
106
+ dtype = self.t_type)
107
+ except Exception:
108
+ log.error('hdf5 triangles failure for triangle patch ' + str(self.patch_index))
109
+ raise
110
+
111
+ if copy:
112
+ return (self.triangles.copy(), self.points.copy())
113
+ else:
70
114
  return (self.triangles, self.points)
71
- assert self.triangle_count is not None and self.node_count is not None
72
-
73
- geometry_node = rqet.find_tag(self.node, 'Geometry')
74
- assert geometry_node is not None
75
- p_root = rqet.find_tag(geometry_node, 'Points')
76
- assert p_root is not None, 'Points xml node not found for triangle patch'
77
- assert rqet.node_type(p_root) == 'Point3dHdf5Array'
78
- h5_key_pair = self.model.h5_uuid_and_path_for_node(p_root, tag = 'Coordinates')
79
- if h5_key_pair is None:
80
- return (None, None)
81
- try:
82
- self.model.h5_array_element(h5_key_pair,
83
- cache_array = True,
84
- object = self,
85
- array_attribute = 'points',
86
- dtype = 'float')
87
- except Exception:
88
- log.error('hdf5 points failure for triangle patch ' + str(self.patch_index))
89
- raise
90
- triangles_node = rqet.find_tag(self.node, 'Triangles')
91
- h5_key_pair = self.model.h5_uuid_and_path_for_node(triangles_node)
92
- if h5_key_pair is None:
93
- log.warning('No Triangles found in xml for patch index: ' + str(self.patch_index))
94
- return (None, None)
95
- try:
96
- self.model.h5_array_element(h5_key_pair,
97
- cache_array = True,
98
- object = self,
99
- array_attribute = 'triangles',
100
- dtype = 'int')
101
- except Exception:
102
- log.error('hdf5 triangles failure for triangle patch ' + str(self.patch_index))
103
- raise
104
- return (self.triangles, self.points)
105
115
 
106
116
  def set_to_trimmed_patch(self, larger_patch, xyz_box = None, xy_polygon = None, internal = False):
107
117
  """Populate this (empty) patch with triangles and points that overlap with a trimming volume.
@@ -147,7 +157,7 @@ class TriangulatedPatch:
147
157
  # find unique points used by those triangles
148
158
  p_keep = np.unique(large_t[t_in])
149
159
  # note new point index for each old point that is being kept
150
- p_map = np.full(len(points_in), -1, dtype = int)
160
+ p_map = np.full(len(points_in), -1, dtype = large_t.dtype)
151
161
  p_map[p_keep] = np.arange(len(p_keep))
152
162
  # copy those unique points into a trimmed points array
153
163
  points_trimmed = large_p[p_keep]
@@ -190,10 +200,10 @@ class TriangulatedPatch:
190
200
  # create pair of triangles
191
201
  if quad_triangles:
192
202
  self.triangle_count = 4
193
- self.triangles = np.array([[0, 2, 4], [2, 1, 4], [1, 3, 4], [3, 0, 4]], dtype = int)
203
+ self.triangles = np.array([[0, 2, 4], [2, 1, 4], [1, 3, 4], [3, 0, 4]], dtype = self.t_type)
194
204
  else:
195
205
  self.triangle_count = 2
196
- self.triangles = np.array([[0, 1, 2], [0, 3, 1]], dtype = int)
206
+ self.triangles = np.array([[0, 1, 2], [0, 3, 1]], dtype = self.t_type)
197
207
 
198
208
  def set_to_triangle(self, corners):
199
209
  """Populate this (empty) patch with a single triangle."""
@@ -202,12 +212,12 @@ class TriangulatedPatch:
202
212
  self.node_count = 3
203
213
  self.points = corners.copy()
204
214
  self.triangle_count = 1
205
- self.triangles = np.array([[0, 1, 2]], dtype = int)
215
+ self.triangles = np.array([[0, 1, 2]], dtype = self.t_type)
206
216
 
207
217
  def set_to_triangle_pair(self, corners):
208
218
  """Populate this (empty) patch with a pair of triangles."""
209
219
 
210
- self.set_from_triangles_and_points(np.array([[0, 1, 3], [0, 3, 2]], dtype = int), corners)
220
+ self.set_from_triangles_and_points(np.array([[0, 1, 3], [0, 3, 2]], dtype = self.t_type), corners)
211
221
 
212
222
  def set_from_triangles_and_points(self, triangles, points):
213
223
  """Populate this (empty) patch from triangle node indices and points from elsewhere."""
@@ -240,7 +250,7 @@ class TriangulatedPatch:
240
250
  self.node_count = (n + 1) * (n + 2) // 2
241
251
  self.points = np.empty((self.node_count, 3))
242
252
  self.triangle_count = n * n
243
- self.triangles = np.empty((self.triangle_count, 3), dtype = int)
253
+ self.triangles = np.empty((self.triangle_count, 3), dtype = self.t_type)
244
254
  self.points[0] = sail_point(centre, radius, azimuth, 0.0).copy()
245
255
  p = 0
246
256
  t = 0
@@ -282,11 +292,12 @@ class TriangulatedPatch:
282
292
  quad_centres[:, :] = 0.25 * (mesh_xyz[:-1, :-1, :] + mesh_xyz[:-1, 1:, :] + mesh_xyz[1:, :-1, :] +
283
293
  mesh_xyz[1:, 1:, :]).reshape((-1, 3))
284
294
  self.points = np.concatenate((mesh_xyz.copy().reshape((-1, 3)), quad_centres))
295
+ self._set_t_type()
285
296
  mesh_size = mesh_xyz.size // 3
286
297
  self.node_count = self.points.size // 3
287
298
  self.triangle_count = 4 * (mesh_shape[0] - 1) * (mesh_shape[1] - 1)
288
299
  self.quad_triangles = True
289
- triangles = np.empty((mesh_shape[0] - 1, mesh_shape[1] - 1, 4, 3), dtype = int) # flatten later
300
+ triangles = np.empty((mesh_shape[0] - 1, mesh_shape[1] - 1, 4, 3), dtype = self.t_type) # flatten later
290
301
  nic = ni - 1
291
302
  for j in range(mesh_shape[0] - 1):
292
303
  for i in range(nic):
@@ -298,10 +309,11 @@ class TriangulatedPatch:
298
309
  triangles[j, i, 3, 2] = j * ni + i
299
310
  else:
300
311
  self.points = mesh_xyz.copy().reshape((-1, 3))
312
+ self._set_t_type()
301
313
  self.node_count = mesh_shape[0] * mesh_shape[1]
302
314
  self.triangle_count = 2 * (mesh_shape[0] - 1) * (mesh_shape[1] - 1)
303
315
  self.quad_triangles = False
304
- triangles = np.empty((mesh_shape[0] - 1, mesh_shape[1] - 1, 2, 3), dtype = int) # flatten later
316
+ triangles = np.empty((mesh_shape[0] - 1, mesh_shape[1] - 1, 2, 3), dtype = self.t_type) # flatten later
305
317
  for j in range(mesh_shape[0] - 1):
306
318
  for i in range(mesh_shape[1] - 1):
307
319
  triangles[j, i, 0, 0] = j * ni + i
@@ -321,7 +333,7 @@ class TriangulatedPatch:
321
333
 
322
334
  indices = self.get_indices_from_sparse_meshxyz(mesh_xyz)
323
335
 
324
- triangles = np.zeros((2 * (mesh_shape[0] - 1) * (mesh_shape[1] - 1), 3), dtype = int) # truncate later
336
+ triangles = np.zeros((2 * (mesh_shape[0] - 1) * (mesh_shape[1] - 1), 3), dtype = self.t_type) # truncate later
325
337
  nt = 0
326
338
  for j in range(mesh_shape[0] - 1):
327
339
  for i in range(mesh_shape[1] - 1):
@@ -357,7 +369,7 @@ class TriangulatedPatch:
357
369
  else:
358
370
  raise Exception('code failure in sparse mesh processing')
359
371
  self.ni = None
360
- self.triangles = triangles[:nt, :]
372
+ self.triangles = triangles[:nt, :].copy()
361
373
  self.triangle_count = nt
362
374
 
363
375
  def get_indices_from_sparse_meshxyz(self, mesh_xyz):
@@ -373,6 +385,7 @@ class TriangulatedPatch:
373
385
  points[i] = mesh_xyz[non_nans[0][i], non_nans[1][i]]
374
386
  indices[non_nans[0][i], non_nans[1][i]] = i
375
387
  self.points = points[:len(non_nans[0]), :]
388
+ self._set_t_type()
376
389
  self.node_count = len(non_nans[0])
377
390
 
378
391
  return indices
@@ -389,11 +402,12 @@ class TriangulatedPatch:
389
402
  quad_centres = np.empty((nj, ni, 3))
390
403
  quad_centres[:, :, :] = 0.25 * np.sum(mesh_xyz, axis = (2, 3))
391
404
  self.points = np.concatenate((mesh_xyz.copy().reshape((-1, 3)), quad_centres.reshape((-1, 3))))
405
+ self._set_t_type()
392
406
  mesh_size = mesh_xyz.size // 3
393
407
  self.node_count = 5 * nj * ni
394
408
  self.triangle_count = 4 * nj * ni
395
409
  self.quad_triangles = True
396
- triangles = np.empty((nj, ni, 4, 3), dtype = int) # flatten later
410
+ triangles = np.empty((nj, ni, 4, 3), dtype = self.t_type) # flatten later
397
411
  for j in range(nj):
398
412
  for i in range(ni):
399
413
  base_p = 4 * (j * ni + i)
@@ -405,10 +419,11 @@ class TriangulatedPatch:
405
419
  triangles[j, i, 3, 2] = base_p
406
420
  else:
407
421
  self.points = mesh_xyz.copy().reshape((-1, 3))
422
+ self._set_t_type()
408
423
  self.node_count = 4 * nj * ni
409
424
  self.triangle_count = 2 * nj * ni
410
425
  self.quad_triangles = False
411
- triangles = np.empty((nj, ni, 2, 3), dtype = int) # flatten later
426
+ triangles = np.empty((nj, ni, 2, 3), dtype = self.t_type) # flatten later
412
427
  for j in range(nj):
413
428
  for i in range(ni):
414
429
  base_p = 4 * (j * ni + i)
@@ -469,7 +484,8 @@ class TriangulatedPatch:
469
484
  self.triangle_count = 12
470
485
  self.node_count = 8
471
486
  self.points = cp.copy().reshape((-1, 3))
472
- triangles = np.empty((3, 2, 2, 3), dtype = int) # flatten later
487
+ self._set_t_type()
488
+ triangles = np.empty((3, 2, 2, 3), dtype = self.t_type) # flatten later
473
489
  for axis in range(3):
474
490
  if axis == 0:
475
491
  ip1, ip2 = 2, 1
@@ -500,7 +516,8 @@ class TriangulatedPatch:
500
516
  quad_centres[2, 1, :] = 0.25 * np.sum(cp[:, :, 1, :], axis = (0, 1)) # I+
501
517
  self.node_count = 14
502
518
  self.points = np.concatenate((cp.copy().reshape((-1, 3)), quad_centres.reshape((-1, 3))))
503
- triangles = np.empty((3, 2, 4, 3), dtype = int) # flatten later
519
+ self._set_t_type()
520
+ triangles = np.empty((3, 2, 4, 3), dtype = self.t_type) # flatten later
504
521
  for axis in range(3):
505
522
  if axis == 0:
506
523
  ip1, ip2 = 2, 1
@@ -544,3 +561,6 @@ class TriangulatedPatch:
544
561
  _, _ = self.triangles_and_points() # ensure points are loaded
545
562
  z_values = self.points[:, 2].copy()
546
563
  self.points[:, 2] = ref_depth + scaling_factor * (z_values - ref_depth)
564
+
565
+ def _set_t_type(self):
566
+ self.t_type = np.int64 if len(self.points) > 2_147_483_648 else np.int32
@@ -34,12 +34,15 @@ class AnyTimeSeries(BaseResqpy):
34
34
  dt_text = rqet.find_tag_text(child, 'DateTime')
35
35
  assert dt_text, 'missing DateTime field in xml for time series'
36
36
  year_offset = rqet.find_tag_int(child, 'YearOffset')
37
- if year_offset:
38
- assert self.timeframe == 'geologic'
37
+ if self.timeframe == 'geologic' and year_offset is not None:
38
+ if year_offset > 0:
39
+ log.warning(f'positive year offset in xml indicates future geological time: {year_offset}')
39
40
  self.timestamps.append(year_offset) # todo: trim and check timestamp
40
- else:
41
- assert self.timeframe == 'human'
41
+ elif self.timeframe == 'human' and not year_offset:
42
+ # year_offset can be 0 for "human" time frames, indicating None.
42
43
  self.timestamps.append(dt_text) # todo: trim and check timestamp
44
+ else:
45
+ raise AssertionError(f'Invalid combination of timeframe {self.timeframe} and year_offset {year_offset}')
43
46
  self.timestamps.sort()
44
47
 
45
48
  def is_equivalent(self, other_ts):
@@ -29,7 +29,7 @@ class GeologicTimeSeries(ats.AnyTimeSeries):
29
29
 
30
30
  note:
31
31
  if instantiating from an existing RESQML time series, its Time entries must all have YearOffset data
32
- which should be large negative integers
32
+ which should be large negative integers (or zero if reaching the current era)
33
33
 
34
34
  :meta common:
35
35
  """
@@ -23,7 +23,8 @@ class HexaGrid(rug.UnstructuredGrid):
23
23
  cache_geometry = False,
24
24
  title = None,
25
25
  originator = None,
26
- extra_metadata = {}):
26
+ extra_metadata = {},
27
+ load_inactive = True):
27
28
  """Creates a new resqpy HexaGrid object (RESQML UnstructuredGrid with cell shape hexahedral)
28
29
 
29
30
  arguments:
@@ -39,6 +40,8 @@ class HexaGrid(rug.UnstructuredGrid):
39
40
  ignored if uuid is present
40
41
  extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid;
41
42
  ignored if uuid is present
43
+ load_inactive (bool, default True): if True and uuid is provided, the inactive attribubte is
44
+ populated if a property of kind 'active' is found for the grid
42
45
 
43
46
  returns:
44
47
  a newly created HexaGrid object
@@ -52,7 +55,8 @@ class HexaGrid(rug.UnstructuredGrid):
52
55
  cell_shape = 'hexahedral',
53
56
  title = title,
54
57
  originator = originator,
55
- extra_metadata = extra_metadata)
58
+ extra_metadata = extra_metadata,
59
+ load_inactive = load_inactive)
56
60
 
57
61
  if self.root is not None:
58
62
  assert grr.grid_flavour(self.root) == 'HexaGrid'
@@ -34,7 +34,8 @@ class PrismGrid(rug.UnstructuredGrid):
34
34
  cache_geometry = False,
35
35
  title = None,
36
36
  originator = None,
37
- extra_metadata = {}):
37
+ extra_metadata = {},
38
+ load_inactive = True):
38
39
  """Creates a new resqpy PrismGrid object (RESQML UnstructuredGrid with cell shape trisngular prism)
39
40
 
40
41
  arguments:
@@ -50,6 +51,8 @@ class PrismGrid(rug.UnstructuredGrid):
50
51
  ignored if uuid is present
51
52
  extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid;
52
53
  ignored if uuid is present
54
+ load_inactive (bool, default True): if True and uuid is provided, the inactive attribubte is
55
+ populated if a property of kind 'active' is found for the grid
53
56
 
54
57
  returns:
55
58
  a newly created PrismGrid object
@@ -63,7 +66,8 @@ class PrismGrid(rug.UnstructuredGrid):
63
66
  cell_shape = 'prism',
64
67
  title = title,
65
68
  originator = originator,
66
- extra_metadata = extra_metadata)
69
+ extra_metadata = extra_metadata,
70
+ load_inactive = load_inactive)
67
71
 
68
72
  if self.root is not None:
69
73
  assert grr.grid_flavour(self.root) in ['PrismGrid', 'VerticalPrismGrid']
@@ -110,7 +114,8 @@ class VerticalPrismGrid(PrismGrid):
110
114
  cache_geometry = False,
111
115
  title = None,
112
116
  originator = None,
113
- extra_metadata = {}):
117
+ extra_metadata = {},
118
+ load_inactive = True):
114
119
  """Creates a new resqpy VerticalPrismGrid object.
115
120
 
116
121
  arguments:
@@ -126,6 +131,8 @@ class VerticalPrismGrid(PrismGrid):
126
131
  ignored if uuid is present
127
132
  extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid;
128
133
  ignored if uuid is present
134
+ load_inactive (bool, default True): if True and uuid is provided, the inactive attribubte is
135
+ populated if a property of kind 'active' is found for the grid
129
136
 
130
137
  returns:
131
138
  a newly created VerticalPrismGrid object
@@ -139,7 +146,8 @@ class VerticalPrismGrid(PrismGrid):
139
146
  cache_geometry = cache_geometry,
140
147
  title = title,
141
148
  originator = originator,
142
- extra_metadata = extra_metadata)
149
+ extra_metadata = extra_metadata,
150
+ load_inactive = load_inactive)
143
151
 
144
152
  if self.root is not None:
145
153
  assert grr.grid_flavour(self.root) in ['VerticalPrismGrid', 'PrismGrid']
@@ -335,7 +343,7 @@ class VerticalPrismGrid(PrismGrid):
335
343
  if surf == 0:
336
344
  # allow NaN entries to handle unused distant circumcentres in Voronoi graph data
337
345
  # assert not np.any(nan_lines), 'top surface does not cover all column points'
338
- single_intersects[nan_lines] = np.NaN
346
+ single_intersects[nan_lines] = np.nan
339
347
  else:
340
348
  single_intersects[nan_lines] = points[surf - 1][nan_lines]
341
349
  # populate z values for layer of points
@@ -22,7 +22,8 @@ class PyramidGrid(rug.UnstructuredGrid):
22
22
  cache_geometry = False,
23
23
  title = None,
24
24
  originator = None,
25
- extra_metadata = {}):
25
+ extra_metadata = {},
26
+ load_inactive = True):
26
27
  """Creates a new resqpy PyramidGrid object (RESQML UnstructuredGrid with cell shape pyramidal)
27
28
 
28
29
  arguments:
@@ -38,6 +39,8 @@ class PyramidGrid(rug.UnstructuredGrid):
38
39
  ignored if uuid is present
39
40
  extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid;
40
41
  ignored if uuid is present
42
+ load_inactive (bool, default True): if True and uuid is provided, the inactive attribubte is
43
+ populated if a property of kind 'active' is found for the grid
41
44
 
42
45
  returns:
43
46
  a newly created PyramidGrid object
@@ -51,7 +54,8 @@ class PyramidGrid(rug.UnstructuredGrid):
51
54
  cell_shape = 'pyramidal',
52
55
  title = title,
53
56
  originator = originator,
54
- extra_metadata = extra_metadata)
57
+ extra_metadata = extra_metadata,
58
+ load_inactive = load_inactive)
55
59
 
56
60
  if self.root is not None:
57
61
  assert grr.grid_flavour(self.root) == 'PyramidGrid'
@@ -24,7 +24,8 @@ class TetraGrid(rug.UnstructuredGrid):
24
24
  cache_geometry = False,
25
25
  title = None,
26
26
  originator = None,
27
- extra_metadata = {}):
27
+ extra_metadata = {},
28
+ load_inactive = True):
28
29
  """Creates a new resqpy TetraGrid object (RESQML UnstructuredGrid with cell shape tetrahedral)
29
30
 
30
31
  arguments:
@@ -40,6 +41,8 @@ class TetraGrid(rug.UnstructuredGrid):
40
41
  ignored if uuid is present
41
42
  extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid;
42
43
  ignored if uuid is present
44
+ load_inactive (bool, default True): if True and uuid is provided, the inactive attribubte is
45
+ populated if a property of kind 'active' is found for the grid
43
46
 
44
47
  returns:
45
48
  a newly created TetraGrid object
@@ -53,7 +56,8 @@ class TetraGrid(rug.UnstructuredGrid):
53
56
  cell_shape = 'tetrahedral',
54
57
  title = title,
55
58
  originator = originator,
56
- extra_metadata = extra_metadata)
59
+ extra_metadata = extra_metadata,
60
+ load_inactive = load_inactive)
57
61
 
58
62
  if self.root is not None:
59
63
  assert grr.grid_flavour(self.root) == 'TetraGrid'
@@ -35,7 +35,8 @@ class UnstructuredGrid(BaseResqpy):
35
35
  cell_shape = 'polyhedral',
36
36
  title = None,
37
37
  originator = None,
38
- extra_metadata = {}):
38
+ extra_metadata = {},
39
+ load_inactive = True):
39
40
  """Create an Unstructured Grid object and optionally populate from xml tree.
40
41
 
41
42
  arguments:
@@ -55,6 +56,8 @@ class UnstructuredGrid(BaseResqpy):
55
56
  ignored if uuid is present
56
57
  extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid;
57
58
  ignored if uuid is present
59
+ load_inactive (bool, default True): if True and uuid is provided, the inactive attribubte is
60
+ populated if a property of kind 'active' is found for the grid
58
61
 
59
62
  returns:
60
63
  a newly created Unstructured Grid object
@@ -103,6 +106,8 @@ class UnstructuredGrid(BaseResqpy):
103
106
  self.title = 'ROOT'
104
107
 
105
108
  if uuid is not None:
109
+ if load_inactive:
110
+ self.extract_inactive_mask()
106
111
  if geometry_required:
107
112
  assert self.geometry_root is not None, 'unstructured grid geometry not present in xml'
108
113
  if cache_geometry and self.geometry_root is not None:
@@ -127,7 +132,6 @@ class UnstructuredGrid(BaseResqpy):
127
132
  assert self.node_count > 3
128
133
  self.face_count = rqet.find_tag_int(self.geometry_root, 'FaceCount')
129
134
  assert self.face_count > 3
130
- self.extract_inactive_mask()
131
135
  # note: geometry arrays not loaded until demanded; see cache_all_geometry_arrays()
132
136
 
133
137
  def set_cell_count(self, n: int):