resqpy 4.14.1__py3-none-any.whl → 5.1.5__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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 +2 -1
  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 +1349 -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 +22 -12
  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.1.dist-info → resqpy-5.1.5.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/LICENSE +0 -0
@@ -120,9 +120,16 @@ 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
- def from_tri_mesh_and_z_values(cls, tri_mesh, z_values, z_uom = None, title = None, surface_role = 'map'):
126
+ def from_tri_mesh_and_z_values(cls,
127
+ tri_mesh,
128
+ z_values,
129
+ z_uom = None,
130
+ title = None,
131
+ surface_role = 'map',
132
+ extra_metadata = None):
126
133
  """Create a new tri mesh with the same xy pattern as an existing one, updating z."""
127
134
  assert z_values.shape == (tri_mesh.nj, tri_mesh.ni)
128
135
  if not z_uom:
@@ -139,7 +146,8 @@ class TriMesh(rqs.Mesh):
139
146
  z_uom = z_uom,
140
147
  surface_role = surface_role,
141
148
  crs_uuid = tri_mesh.crs_uuid,
142
- title = title)
149
+ title = title,
150
+ extra_metadata = extra_metadata)
143
151
  return tm
144
152
 
145
153
  def tji_for_xy(self, xy):
@@ -211,6 +219,7 @@ class TriMesh(rqs.Mesh):
211
219
  base edge towards point 2; f1 is component towards point 1; f0 is component towards point 0;
212
220
  the trilinear coordinates sum to one and can be used as weights to interpolate z values at points
213
221
  """
222
+ assert xy_array.ndim > 1 and 2 <= xy_array.shape[-1] <= 3
214
223
  x = xy_array[..., 0].copy()
215
224
  y = xy_array[..., 1].copy()
216
225
  if self.origin is not None:
@@ -228,14 +237,14 @@ class TriMesh(rqs.Mesh):
228
237
  mask = np.logical_or(mask, np.logical_or(i < 0, i >= self.ni - 1))
229
238
  fx = ip - i.astype(float)
230
239
  i *= 2
231
- am = np.where(fx > 1.0 - fy)
240
+ am = (fx > 1.0 - fy).astype(bool)
232
241
  i[am] += 1
233
242
  fx[am] -= 1.0 - fy[am]
234
243
  fy[am] = 1.0 - fy[am]
235
244
  j[mask] = -1
236
245
  i[mask] = -1
237
- fx[mask] = np.NaN
238
- fy[mask] = np.NaN
246
+ fx[mask] = np.nan
247
+ fy[mask] = np.nan
239
248
 
240
249
  return (np.stack((j, i), axis = -1), np.stack((1.0 - (fx + fy), fx, fy), axis = -1))
241
250
 
@@ -267,6 +276,7 @@ class TriMesh(rqs.Mesh):
267
276
  of three vertices of triangles containing xy points; float triplets contain corresponding weights (summing to
268
277
  one per triangle) which can be used to interpolate z values at points xy_array
269
278
  """
279
+ assert xy_array.ndim > 1 and 2 <= xy_array.shape[-1] <= 3
270
280
  tji, tc = self.tji_tc_for_xy_array(xy_array)
271
281
  ji = self.tri_nodes_for_tji_array(tji)
272
282
  return (ji, tc)
@@ -295,7 +305,7 @@ class TriMesh(rqs.Mesh):
295
305
  def tri_nodes_for_tji(self, tji):
296
306
  """Return mesh node indices, shape (3, 2), for triangle tji (tj, ti)."""
297
307
  j, i = tji
298
- tn = np.zeros((3, 2), dtype = int)
308
+ tn = np.zeros((3, 2), dtype = self.t_type)
299
309
  j_odd = j % 2
300
310
  i2, i_odd = divmod(i, 2)
301
311
  assert 0 <= j < self.nj - 1 and 0 <= i < 2 * (self.ni - 1)
@@ -318,7 +328,7 @@ class TriMesh(rqs.Mesh):
318
328
  j = tji_array[..., 0]
319
329
  i = tji_array[..., 1]
320
330
  tn_shape = tuple(list(tji_array.shape[:-1]) + [3, 2])
321
- tn = np.zeros(tn_shape, dtype = int)
331
+ tn = np.zeros(tn_shape, dtype = self.t_type)
322
332
  j_odd = j % 2
323
333
  i2, i_odd = np.divmod(i, 2)
324
334
  mask = np.logical_or(np.logical_or(j < 0, j >= self.nj - 1), np.logical_or(i < 0, i >= 2 * (self.ni - 1)))
@@ -335,9 +345,9 @@ class TriMesh(rqs.Mesh):
335
345
 
336
346
  def all_tri_nodes(self):
337
347
  """Returns array of mesh node indices for all triangles, shape (nj - 1, 2 * (ni - 1), 3, 2)."""
338
- 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)
339
349
  # set mesh j indices
340
- 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)
341
351
  tna[1::2, ::2, 0, 0] += 1
342
352
  tna[::2, 1::2, 0, 0] += 1
343
353
  tna[:, :, 1, 0] = tna[:, :, 0, 0]
@@ -345,7 +355,7 @@ class TriMesh(rqs.Mesh):
345
355
  tna[1::2, ::2, 2, 0] -= 2
346
356
  tna[::2, 1::2, 2, 0] -= 2
347
357
  # set mesh i indices
348
- 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)
349
359
  tna[:, 1::2, 0, 1] = tna[:, ::2, 0, 1]
350
360
  tna[:, :, 1, 1] = tna[:, :, 0, 1] + 1
351
361
  tna[:, :, 2, 1] = tna[:, :, 0, 1]
@@ -355,7 +365,7 @@ class TriMesh(rqs.Mesh):
355
365
  def triangles_and_points(self):
356
366
  """Returns node indices and xyz points in form suitable for a Surface (triangulated set)."""
357
367
  tna = self.all_tri_nodes()
358
- composite_ji = tna[:, :, :, 0] * self.ni + tna[:, :, :, 1]
368
+ composite_ji = (tna[:, :, :, 0] * self.ni + tna[:, :, :, 1]).astype(self.t_type)
359
369
  return (composite_ji.reshape((-1, 3)), self.full_array_ref().reshape((-1, 3)))
360
370
 
361
371
  def tji_for_triangle_index(self, ti):
@@ -403,7 +413,7 @@ class TriMesh(rqs.Mesh):
403
413
  tp)
404
414
  tn_a[:, 1] *= 2 # node j
405
415
 
406
- return np.concatenate((tn_a, tn_b), axis = 0)
416
+ return np.concatenate((tn_a, tn_b), axis = 0).astype(self.t_type)
407
417
 
408
418
  def edge_zero_crossings(self, z_values = None):
409
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):