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.
- resqpy/__init__.py +1 -1
- resqpy/fault/_gcs_functions.py +10 -10
- resqpy/fault/_grid_connection_set.py +277 -113
- resqpy/grid/__init__.py +2 -3
- resqpy/grid/_defined_geometry.py +3 -3
- resqpy/grid/_extract_functions.py +8 -2
- resqpy/grid/_grid.py +95 -12
- resqpy/grid/_grid_types.py +22 -7
- resqpy/grid/_points_functions.py +1 -1
- resqpy/grid/_regular_grid.py +6 -2
- resqpy/grid_surface/__init__.py +17 -38
- resqpy/grid_surface/_blocked_well_populate.py +5 -5
- resqpy/grid_surface/_find_faces.py +1413 -253
- resqpy/lines/_polyline.py +24 -33
- resqpy/model/_catalogue.py +9 -0
- resqpy/model/_forestry.py +18 -14
- resqpy/model/_hdf5.py +11 -3
- resqpy/model/_model.py +85 -10
- resqpy/model/_xml.py +38 -13
- resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
- resqpy/olio/read_nexus_fault.py +8 -2
- resqpy/olio/relperm.py +1 -1
- resqpy/olio/transmission.py +8 -8
- resqpy/olio/triangulation.py +36 -30
- resqpy/olio/vector_utilities.py +340 -6
- resqpy/olio/volume.py +0 -20
- resqpy/olio/wellspec_keywords.py +19 -13
- resqpy/olio/write_hdf5.py +1 -1
- resqpy/olio/xml_et.py +12 -0
- resqpy/property/__init__.py +6 -4
- resqpy/property/_collection_add_part.py +4 -3
- resqpy/property/_collection_create_xml.py +4 -2
- resqpy/property/_collection_get_attributes.py +4 -0
- resqpy/property/attribute_property_set.py +311 -0
- resqpy/property/grid_property_collection.py +11 -11
- resqpy/property/property_collection.py +79 -31
- resqpy/property/property_common.py +3 -8
- resqpy/rq_import/_add_surfaces.py +34 -14
- resqpy/rq_import/_grid_from_cp.py +2 -2
- resqpy/rq_import/_import_nexus.py +75 -48
- resqpy/rq_import/_import_vdb_all_grids.py +64 -52
- resqpy/rq_import/_import_vdb_ensemble.py +12 -13
- resqpy/surface/_mesh.py +4 -0
- resqpy/surface/_surface.py +593 -118
- resqpy/surface/_tri_mesh.py +13 -10
- resqpy/surface/_tri_mesh_stencil.py +4 -4
- resqpy/surface/_triangulated_patch.py +71 -51
- resqpy/time_series/_any_time_series.py +7 -4
- resqpy/time_series/_geologic_time_series.py +1 -1
- resqpy/unstructured/_hexa_grid.py +6 -2
- resqpy/unstructured/_prism_grid.py +13 -5
- resqpy/unstructured/_pyramid_grid.py +6 -2
- resqpy/unstructured/_tetra_grid.py +6 -2
- resqpy/unstructured/_unstructured_grid.py +6 -2
- resqpy/well/_blocked_well.py +1986 -1946
- resqpy/well/_deviation_survey.py +3 -3
- resqpy/well/_md_datum.py +11 -21
- resqpy/well/_trajectory.py +10 -5
- resqpy/well/_wellbore_frame.py +10 -2
- resqpy/well/blocked_well_frame.py +3 -3
- resqpy/well/well_object_funcs.py +7 -9
- resqpy/well/well_utils.py +33 -0
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/LICENSE +0 -0
resqpy/surface/_tri_mesh.py
CHANGED
@@ -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 =
|
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.
|
245
|
-
fy[mask] = np.
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
41
|
-
|
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.
|
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):
|