resqpy 4.14.1__py3-none-any.whl → 5.1.5__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 +2 -1
- 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 +1349 -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 +22 -12
- 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.1.dist-info → resqpy-5.1.5.dist-info}/METADATA +8 -9
- {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
- {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/LICENSE +0 -0
@@ -10,6 +10,7 @@ import math as maths
|
|
10
10
|
import numpy as np
|
11
11
|
import pandas as pd
|
12
12
|
|
13
|
+
import resqpy.grid as grr
|
13
14
|
import resqpy.fault
|
14
15
|
import resqpy.olio.read_nexus_fault as rnf
|
15
16
|
import resqpy.olio.trademark as tm
|
@@ -118,7 +119,7 @@ class GridConnectionSet(BaseResqpy):
|
|
118
119
|
self.cell_index_pairs = None #: shape (count, 2); dtype int; index normalized for flattened array
|
119
120
|
self.cell_index_pairs_null_value = -1 #: integer null value for array above
|
120
121
|
self.grid_index_pairs = None #: shape (count, 2); dtype int; optional; used if more than one grid referenced
|
121
|
-
self.face_index_pairs = None #: shape (count, 2); dtype
|
122
|
+
self.face_index_pairs = None #: shape (count, 2); dtype int8; local to cell, ie. range 0 to 5
|
122
123
|
self.face_index_pairs_null_value = -1 #: integer null value for array above
|
123
124
|
# NB face index values 0..5 usually mean [K-, K+, J+, I+, J-, I-] respectively but there is some ambiguity
|
124
125
|
# over I & J in the Energistics RESQML Usage Guide; see comments in DevOps backlog item 269001 for more info
|
@@ -129,17 +130,18 @@ class GridConnectionSet(BaseResqpy):
|
|
129
130
|
self.feature_list = None #: ordered list, actually of interpretations, indexed by feature_indices
|
130
131
|
# feature list contains tuples: (content_type, uuid, title) for fault features (or other interpretations)
|
131
132
|
self.property_collection = None #: optional property.PropertyCollection
|
133
|
+
self.cell_index_dtype = np.int32 #: set to int64 if any grid is big, otherwise int32
|
132
134
|
|
133
135
|
# NB: RESQML documentation is not clear which order is correct; should be kept consistent with same data in property.py
|
134
136
|
# face_index_map maps from (axis, p01) to face index value in range 0..5
|
135
137
|
# this is the default as indicated on page 139 (but not p. 180) of the RESQML Usage Gude v2.0.1
|
136
138
|
# also assumes K is generally increasing downwards
|
137
139
|
# see DevOps backlog item 269001 discussion for more information
|
138
|
-
# self.face_index_map = np.array([[0, 1], [4, 2], [5, 3]], dtype =
|
139
|
-
self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype =
|
140
|
+
# self.face_index_map = np.array([[0, 1], [4, 2], [5, 3]], dtype = np.int8)
|
141
|
+
self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype = np.int8) # order: top, base, J-, I+, J+, I-
|
140
142
|
# and the inverse, maps from 0..5 to (axis, p01)
|
141
|
-
# self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 1], [2, 1], [1, 0], [2, 0]], dtype =
|
142
|
-
self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype =
|
143
|
+
# self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 1], [2, 1], [1, 0], [2, 0]], dtype = np.int8)
|
144
|
+
self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype = np.int8)
|
143
145
|
# note: the rework_face_pairs() method, below, overwrites the face indices based on I, J cell indices
|
144
146
|
if not title:
|
145
147
|
title = feature_name
|
@@ -196,6 +198,110 @@ class GridConnectionSet(BaseResqpy):
|
|
196
198
|
self.cache_arrays()
|
197
199
|
if find_properties:
|
198
200
|
self.extract_property_collection()
|
201
|
+
self._set_cell_index_dtype()
|
202
|
+
|
203
|
+
@classmethod
|
204
|
+
def from_faces_indices(cls,
|
205
|
+
grid,
|
206
|
+
k_faces_kji0,
|
207
|
+
j_faces_kji0,
|
208
|
+
i_faces_kji0,
|
209
|
+
remove_duplicates = True,
|
210
|
+
k_properties = None,
|
211
|
+
j_properties = None,
|
212
|
+
i_properties = None,
|
213
|
+
feature_name = None,
|
214
|
+
feature_type = 'fault',
|
215
|
+
create_organizing_objects_where_needed = True,
|
216
|
+
title = None,
|
217
|
+
originator = None,
|
218
|
+
extra_metadata = None):
|
219
|
+
"""Create a GridConnectionSet given a grid and 3 list-like arrays identifying faces by indices.
|
220
|
+
|
221
|
+
arguments:
|
222
|
+
- grid (Grid): the single grid to be referenced by the grid connection set
|
223
|
+
- k_faces_kji0 (numpy int array of shape (Nk, 3)): indices of cells on negative side of desired K faces
|
224
|
+
- j_faces_kji0 (numpy int array of shape (Nj, 3)): indices of cells on negative side of desired J faces
|
225
|
+
- i_faces_kji0 (numpy int array of shape (Ni, 3)): indices of cells on negative side of desired I faces
|
226
|
+
- remove_duplicates (bool, default True): if True, indices are sorted and duplicates removed
|
227
|
+
- k_properties (list of 1D numpy arrays, optional): if present and remove_duplicates is True, each array
|
228
|
+
is sorted and has elements removed to keep them compatible with the indices
|
229
|
+
- j_properties (list of 1D numpy arrays, optional): if present and remove_duplicates is True, each array
|
230
|
+
is sorted and has elements removed to keep them compatible with the indices
|
231
|
+
- i_properties (list of 1D numpy arrays, optional): if present and remove_duplicates is True, each array
|
232
|
+
is sorted and has elements removed to keep them compatible with the indices
|
233
|
+
- feature_name (string, optional): the feature name to use when setting from faces
|
234
|
+
- feature_type (string, default 'fault'): 'fault', 'horizon' or 'geobody boundary'
|
235
|
+
- create_organizing_objects_where_needed (boolean, default True): if True, a fault interpretation object
|
236
|
+
and tectonic boundary feature object will be created if such objects do not exist for the feature;
|
237
|
+
if False, missing organizational objects will cause an error to be logged
|
238
|
+
- title (str, optional): the citation title to use for a new grid connection set
|
239
|
+
- originator (str, optional): the name of the person creating the new grid connection set, defaults to login id
|
240
|
+
- extra_metadata (dict, optional): string key, value pairs to add as extra metadata for the grid connection set
|
241
|
+
|
242
|
+
returns:
|
243
|
+
- a new GridConnectionSet populated based on the faces indices
|
244
|
+
|
245
|
+
notes:
|
246
|
+
- this method only supports creation of single grid connection sets
|
247
|
+
- the faces indices are for cells on the negative side of the face
|
248
|
+
- the paired cell is implicitly the neighbouring cell in the positive direction of the axis
|
249
|
+
- the indices must therefore not include the last cell in the axis, though this is not checked
|
250
|
+
- if properties are passed, they should be passed in list variables which have their elements
|
251
|
+
replaced; individual property arrays should therefore be extracted from the lists afterwards
|
252
|
+
"""
|
253
|
+
assert isinstance(grid, grr.Grid)
|
254
|
+
|
255
|
+
gcs = cls(grid.model, title = title, originator = originator, extra_metadata = extra_metadata)
|
256
|
+
|
257
|
+
gcs._sort_out_organizing_objects(feature_type, feature_name, create_organizing_objects_where_needed)
|
258
|
+
|
259
|
+
nj_ni = grid.nj * grid.ni
|
260
|
+
if k_faces_kji0 is not None and len(k_faces_kji0) > 0:
|
261
|
+
ci = grid.natural_cell_indices(k_faces_kji0)
|
262
|
+
if remove_duplicates:
|
263
|
+
ci = _sort_and_remove_duplicates(ci, k_properties)
|
264
|
+
cip = np.empty((ci.size, 2), dtype = gcs.cell_index_dtype)
|
265
|
+
cip[:, 0] = ci
|
266
|
+
cip[:, 1] = ci + nj_ni
|
267
|
+
fip = np.empty(cip.shape, dtype = np.int8)
|
268
|
+
fip[:, 0] = gcs.face_index_map[0, 1]
|
269
|
+
fip[:, 1] = gcs.face_index_map[0, 0]
|
270
|
+
else:
|
271
|
+
cip = np.empty((0, 2), dtype = gcs.cell_index_dtype)
|
272
|
+
fip = np.empty((0, 2), dtype = np.int8)
|
273
|
+
if j_faces_kji0 is not None and len(j_faces_kji0) > 0:
|
274
|
+
ci = grid.natural_cell_indices(j_faces_kji0)
|
275
|
+
if remove_duplicates:
|
276
|
+
ci = _sort_and_remove_duplicates(ci, j_properties)
|
277
|
+
j_cip = np.empty((ci.size, 2), dtype = gcs.cell_index_dtype)
|
278
|
+
j_cip[:, 0] = ci
|
279
|
+
j_cip[:, 1] = ci + grid.ni
|
280
|
+
j_fip = np.empty(j_cip.shape, dtype = np.int8)
|
281
|
+
j_fip[:, 0] = gcs.face_index_map[1, 1]
|
282
|
+
j_fip[:, 1] = gcs.face_index_map[1, 0]
|
283
|
+
cip = np.concatenate((cip, j_cip), axis = 0)
|
284
|
+
fip = np.concatenate((fip, j_fip), axis = 0)
|
285
|
+
del j_cip, j_fip
|
286
|
+
if i_faces_kji0 is not None and len(i_faces_kji0) > 0:
|
287
|
+
ci = grid.natural_cell_indices(i_faces_kji0)
|
288
|
+
if remove_duplicates:
|
289
|
+
ci = _sort_and_remove_duplicates(ci, i_properties)
|
290
|
+
i_cip = np.empty((ci.size, 2), dtype = gcs.cell_index_dtype)
|
291
|
+
i_cip[:, 0] = ci
|
292
|
+
i_cip[:, 1] = ci + 1
|
293
|
+
i_fip = np.empty(i_cip.shape, dtype = np.int8)
|
294
|
+
i_fip[:, 0] = gcs.face_index_map[2, 1]
|
295
|
+
i_fip[:, 1] = gcs.face_index_map[2, 0]
|
296
|
+
cip = np.concatenate((cip, i_cip), axis = 0)
|
297
|
+
fip = np.concatenate((fip, i_fip), axis = 0)
|
298
|
+
del i_cip, i_fip
|
299
|
+
gcs.cell_index_pairs = cip
|
300
|
+
gcs.face_index_pairs = fip
|
301
|
+
gcs.count = len(gcs.cell_index_pairs)
|
302
|
+
gcs.feature_indices = np.zeros(gcs.count, dtype = np.int8)
|
303
|
+
assert len(gcs.face_index_pairs) == gcs.count
|
304
|
+
return gcs
|
199
305
|
|
200
306
|
@classmethod
|
201
307
|
def from_gcs_uuid_list(cls,
|
@@ -249,6 +355,7 @@ class GridConnectionSet(BaseResqpy):
|
|
249
355
|
gcs.model = parent_model
|
250
356
|
gcs.uuid = bu.new_uuid() # not strictly necessary as append will cause a new uuid as well
|
251
357
|
gcs.title = title
|
358
|
+
gcs.property_collection = None
|
252
359
|
# append data from the remaining grid connection sets
|
253
360
|
for another_uuid in gcs_uuid_list[1:]:
|
254
361
|
another_gcs = GridConnectionSet(source_model, uuid = another_uuid)
|
@@ -356,6 +463,14 @@ class GridConnectionSet(BaseResqpy):
|
|
356
463
|
self.property_collection = rqp.PropertyCollection(support = self)
|
357
464
|
return self.property_collection
|
358
465
|
|
466
|
+
def _set_cell_index_dtype(self):
|
467
|
+
"""Determines whether to use int32 or int64 for normalised cell indices."""
|
468
|
+
self.cell_index_dtype = np.int32
|
469
|
+
for g in self.grid_list:
|
470
|
+
if g.is_big():
|
471
|
+
self.cell_index_dtype = np.int64
|
472
|
+
break
|
473
|
+
|
359
474
|
def set_pairs_from_kelp(self,
|
360
475
|
kelp_0,
|
361
476
|
kelp_1,
|
@@ -393,7 +508,7 @@ class GridConnectionSet(BaseResqpy):
|
|
393
508
|
k_layer = np.zeros((grid.nk - 1, grid.ni), dtype = bool)
|
394
509
|
else:
|
395
510
|
k_layer = np.zeros((grid.nk - 1, grid.nj), dtype = bool)
|
396
|
-
kelp_a = np.array(kelp_k, dtype =
|
511
|
+
kelp_a = np.array(kelp_k, dtype = np.int32).T
|
397
512
|
k_layer[kelp_a[0], kelp_a[1]] = True
|
398
513
|
k_faces = np.zeros((grid.nk - 1, grid.nj, grid.ni), dtype = bool)
|
399
514
|
if axis == 'J':
|
@@ -407,7 +522,7 @@ class GridConnectionSet(BaseResqpy):
|
|
407
522
|
j_layer = np.zeros((grid.nj - 1, grid.ni), dtype = bool)
|
408
523
|
else:
|
409
524
|
j_layer = np.zeros((grid.nk, grid.nj - 1), dtype = bool)
|
410
|
-
kelp_a = np.array(kelp_j, dtype =
|
525
|
+
kelp_a = np.array(kelp_j, dtype = np.int32).T
|
411
526
|
j_layer[kelp_a[0], kelp_a[1]] = True
|
412
527
|
j_faces = np.zeros((grid.nk, grid.nj - 1, grid.ni), dtype = bool)
|
413
528
|
if axis == 'K':
|
@@ -421,7 +536,7 @@ class GridConnectionSet(BaseResqpy):
|
|
421
536
|
i_layer = np.zeros((grid.nj, grid.ni - 1), dtype = bool)
|
422
537
|
else:
|
423
538
|
i_layer = np.zeros((grid.nk, grid.ni - 1), dtype = bool)
|
424
|
-
kelp_a = np.array(kelp_i, dtype =
|
539
|
+
kelp_a = np.array(kelp_i, dtype = np.int32).T
|
425
540
|
i_layer[kelp_a[0], kelp_a[1]] = True
|
426
541
|
i_faces = np.zeros((grid.nk, grid.nj, grid.ni - 1), dtype = bool)
|
427
542
|
if axis == 'K':
|
@@ -437,24 +552,13 @@ class GridConnectionSet(BaseResqpy):
|
|
437
552
|
create_organizing_objects_where_needed,
|
438
553
|
feature_type = feature_type)
|
439
554
|
|
440
|
-
def
|
441
|
-
|
442
|
-
k_faces,
|
443
|
-
j_faces,
|
444
|
-
i_faces,
|
445
|
-
feature_name,
|
446
|
-
create_organizing_objects_where_needed,
|
447
|
-
feature_type = 'fault', # other feature_type values: 'horizon', 'geobody boundary'
|
448
|
-
k_sides = None,
|
449
|
-
j_sides = None,
|
450
|
-
i_sides = None):
|
451
|
-
"""Sets cell_index_pairs and face_index_pairs based on triple face masks, using simple no throw pairing."""
|
452
|
-
|
555
|
+
def _sort_out_organizing_objects(self, feature_type, feature_name, create_organizing_objects_where_needed):
|
556
|
+
"""Finds or creates interpretation and feature objects."""
|
453
557
|
assert feature_type in ['fault', 'horizon', 'geobody boundary']
|
454
558
|
if feature_name is None:
|
455
|
-
feature_name = 'feature from
|
559
|
+
feature_name = 'feature from faces' # not sure this default is wise
|
456
560
|
if len(self.grid_list) > 1:
|
457
|
-
log.warning('setting grid connection set pairs from
|
561
|
+
log.warning('setting grid connection set pairs from faces for first grid in list only')
|
458
562
|
grid = self.grid_list[0]
|
459
563
|
if feature_type == 'fault':
|
460
564
|
feature_flavour = 'TectonicBoundaryFeature'
|
@@ -511,6 +615,23 @@ class GridConnectionSet(BaseResqpy):
|
|
511
615
|
log.error('no interpretation found for feature: ' + feature_name)
|
512
616
|
return
|
513
617
|
self.feature_list = [('obj_' + interpretation_flavour, fi_uuid, str(feature_name))]
|
618
|
+
|
619
|
+
def set_pairs_from_face_masks(
|
620
|
+
self,
|
621
|
+
k_faces,
|
622
|
+
j_faces,
|
623
|
+
i_faces,
|
624
|
+
feature_name,
|
625
|
+
create_organizing_objects_where_needed,
|
626
|
+
feature_type = 'fault', # other feature_type values: 'horizon', 'geobody boundary'
|
627
|
+
k_sides = None,
|
628
|
+
j_sides = None,
|
629
|
+
i_sides = None):
|
630
|
+
"""Sets cell_index_pairs and face_index_pairs based on triple face masks, using simple no throw pairing."""
|
631
|
+
|
632
|
+
self._sort_out_organizing_objects(feature_type, feature_name, create_organizing_objects_where_needed)
|
633
|
+
|
634
|
+
grid = self.grid_list[0]
|
514
635
|
cell_pair_list = []
|
515
636
|
face_pair_list = []
|
516
637
|
nj_ni = grid.nj * grid.ni
|
@@ -547,10 +668,10 @@ class GridConnectionSet(BaseResqpy):
|
|
547
668
|
else:
|
548
669
|
cell_pair_list.append((cell, cell + 1))
|
549
670
|
face_pair_list.append((self.face_index_map[2, 1], self.face_index_map[2, 0]))
|
550
|
-
self.cell_index_pairs = np.array(cell_pair_list, dtype =
|
551
|
-
self.face_index_pairs = np.array(face_pair_list, dtype =
|
671
|
+
self.cell_index_pairs = np.array(cell_pair_list, dtype = self.cell_index_dtype)
|
672
|
+
self.face_index_pairs = np.array(face_pair_list, dtype = np.int8)
|
552
673
|
self.count = len(self.cell_index_pairs)
|
553
|
-
self.feature_indices = np.zeros(self.count, dtype =
|
674
|
+
self.feature_indices = np.zeros(self.count, dtype = np.int8)
|
554
675
|
assert len(self.face_index_pairs) == self.count
|
555
676
|
|
556
677
|
def set_pairs_from_faces_df(self,
|
@@ -608,9 +729,9 @@ class GridConnectionSet(BaseResqpy):
|
|
608
729
|
if success:
|
609
730
|
feature_index += 1
|
610
731
|
|
611
|
-
self.feature_indices = np.array(fi_list, dtype =
|
612
|
-
self.cell_index_pairs = np.array(cell_pair_list, dtype =
|
613
|
-
self.face_index_pairs = np.array(face_pair_list, dtype =
|
732
|
+
self.feature_indices = np.array(fi_list, dtype = np.int32)
|
733
|
+
self.cell_index_pairs = np.array(cell_pair_list, dtype = self.cell_index_dtype)
|
734
|
+
self.face_index_pairs = np.array(face_pair_list, dtype = np.int8)
|
614
735
|
self.count = len(self.cell_index_pairs)
|
615
736
|
assert len(self.face_index_pairs) == self.count
|
616
737
|
if create_mult_prop and self.count > 0:
|
@@ -695,7 +816,7 @@ class GridConnectionSet(BaseResqpy):
|
|
695
816
|
singleton.cell_index_pairs, singleton.face_index_pairs = \
|
696
817
|
self.raw_list_of_cell_face_pairs_for_feature_index(feature_index)
|
697
818
|
singleton.count = singleton.cell_index_pairs.shape[0]
|
698
|
-
singleton.feature_indices = np.zeros((singleton.count,), dtype =
|
819
|
+
singleton.feature_indices = np.zeros((singleton.count,), dtype = np.int32)
|
699
820
|
singleton.feature_list = [self.feature_list[feature_index]]
|
700
821
|
return singleton
|
701
822
|
|
@@ -822,7 +943,7 @@ class GridConnectionSet(BaseResqpy):
|
|
822
943
|
cache_array = True,
|
823
944
|
object = self,
|
824
945
|
array_attribute = 'cell_index_pairs',
|
825
|
-
dtype = '
|
946
|
+
dtype = 'int64' if self.cell_index_dtype is np.int64 else 'int32')
|
826
947
|
|
827
948
|
if self.face_index_pairs is None:
|
828
949
|
log.debug('caching face index pairs from hdf5')
|
@@ -833,7 +954,7 @@ class GridConnectionSet(BaseResqpy):
|
|
833
954
|
cache_array = True,
|
834
955
|
object = self,
|
835
956
|
array_attribute = 'face_index_pairs',
|
836
|
-
dtype = '
|
957
|
+
dtype = 'int8')
|
837
958
|
|
838
959
|
if len(self.grid_list) > 1 and self.grid_index_pairs is None:
|
839
960
|
grid_index_node = rqet.find_tag(self.root, 'GridIndexPairs')
|
@@ -844,7 +965,7 @@ class GridConnectionSet(BaseResqpy):
|
|
844
965
|
cache_array = True,
|
845
966
|
object = self,
|
846
967
|
array_attribute = 'grid_index_pairs',
|
847
|
-
dtype = '
|
968
|
+
dtype = 'int32' if len(self.grid_list) > 127 else 'int8')
|
848
969
|
|
849
970
|
if self.feature_list is None:
|
850
971
|
return
|
@@ -853,18 +974,18 @@ class GridConnectionSet(BaseResqpy):
|
|
853
974
|
if self.feature_indices is None:
|
854
975
|
log.debug('caching feature indices')
|
855
976
|
elements_node = rqet.find_nested_tags(interp_root, ['InterpretationIndices', 'Elements'])
|
856
|
-
#
|
977
|
+
# elements_node = rqet.find_nested_tags(interp_root, ['FaultIndices', 'Elements'])
|
857
978
|
h5_key_pair = self.model.h5_uuid_and_path_for_node(elements_node, tag = 'Values')
|
858
979
|
assert h5_key_pair is not None
|
859
980
|
self.model.h5_array_element(h5_key_pair,
|
860
981
|
cache_array = True,
|
861
982
|
object = self,
|
862
983
|
array_attribute = 'feature_indices',
|
863
|
-
dtype = '
|
984
|
+
dtype = 'int32')
|
864
985
|
assert self.feature_indices.shape == (self.count,)
|
865
986
|
|
866
987
|
cl_node = rqet.find_nested_tags(interp_root, ['InterpretationIndices', 'CumulativeLength'])
|
867
|
-
#
|
988
|
+
# cl_node = rqet.find_nested_tags(interp_root, ['FaultIndices', 'CumulativeLength'])
|
868
989
|
h5_key_pair = self.model.h5_uuid_and_path_for_node(cl_node, tag = 'Values')
|
869
990
|
assert h5_key_pair is not None
|
870
991
|
self.model.h5_array_element(h5_key_pair,
|
@@ -872,8 +993,8 @@ class GridConnectionSet(BaseResqpy):
|
|
872
993
|
object = self,
|
873
994
|
array_attribute = 'fi_cl',
|
874
995
|
dtype = 'uint32')
|
875
|
-
assert self.fi_cl.shape == (
|
876
|
-
|
996
|
+
assert self.fi_cl.shape == (self.count,), \
|
997
|
+
'connection set face pair(s) not assigned to exactly one feature' # rough check
|
877
998
|
|
878
999
|
# delattr(self, 'fi_cl') # assumed to be one-to-one mapping, so cumulative length is discarded
|
879
1000
|
|
@@ -1195,7 +1316,7 @@ class GridConnectionSet(BaseResqpy):
|
|
1195
1316
|
# uuid/InterpretationIndices/elements (N,) uint32
|
1196
1317
|
h5_reg.register_dataset(self.uuid, 'InterpretationIndices/elements', self.feature_indices)
|
1197
1318
|
# uuid/InterpretationIndices/cumulativeLength (N,) uint32
|
1198
|
-
one_to_one = np.arange(1, self.count + 1, dtype =
|
1319
|
+
one_to_one = np.arange(1, self.count + 1, dtype = np.uint32)
|
1199
1320
|
h5_reg.register_dataset(self.uuid, 'InterpretationIndices/cumulativeLength', one_to_one)
|
1200
1321
|
|
1201
1322
|
h5_reg.write(file_name, mode = mode)
|
@@ -1370,11 +1491,16 @@ class GridConnectionSet(BaseResqpy):
|
|
1370
1491
|
include_both_sides = False,
|
1371
1492
|
use_minus = False,
|
1372
1493
|
trans_mult_uuid = None):
|
1373
|
-
"""Creates a Nexus include file holding MULT keywords and data.
|
1494
|
+
"""Creates a Nexus include file holding MULT keywords and data.
|
1495
|
+
|
1496
|
+
note:
|
1497
|
+
trans_mult_uuid (optional) is the uuid of a property on the gcs containing transmissibility multiplier values;
|
1498
|
+
If not provided values of 1.0 will be used
|
1499
|
+
"""
|
1374
1500
|
if trans_mult_uuid is not None:
|
1375
1501
|
self.extract_property_collection()
|
1376
|
-
assert self.property_collection.part_in_collection(self.model.part_for_uuid(
|
1377
|
-
|
1502
|
+
assert self.property_collection.part_in_collection(self.model.part_for_uuid(trans_mult_uuid)), \
|
1503
|
+
f'trans_mult_uuid provided is not part of collection {trans_mult_uuid}'
|
1378
1504
|
tmult_array = self.property_collection.cached_part_array_ref(self.model.part_for_uuid(trans_mult_uuid))
|
1379
1505
|
assert tmult_array is not None
|
1380
1506
|
else:
|
@@ -1420,26 +1546,39 @@ class GridConnectionSet(BaseResqpy):
|
|
1420
1546
|
feature_name = self.feature_list[feature_index][2].split()[0].upper()
|
1421
1547
|
cell_index_pairs, face_index_pairs = self.list_of_cell_face_pairs_for_feature_index(feature_index)
|
1422
1548
|
if tmult_array is not None:
|
1423
|
-
feature_mask =
|
1549
|
+
feature_mask = (self.feature_indices == feature_index)
|
1424
1550
|
feat_mult_array = np.extract(feature_mask, tmult_array)
|
1425
1551
|
else:
|
1426
1552
|
feat_mult_array = np.ones(shape = (cell_index_pairs.shape[0],), dtype = float)
|
1427
1553
|
for side in sides:
|
1428
|
-
both = np.empty((cell_index_pairs.shape[0],
|
1554
|
+
both = np.empty((cell_index_pairs.shape[0], 5), dtype = np.int32) # axis, polarity, k, j, i
|
1429
1555
|
both[:, :2] = face_index_pairs[:, side, :] # axis, polarity
|
1430
|
-
both[:, 2
|
1431
|
-
both[:, -1] = feat_mult_array.flatten()
|
1432
|
-
df = pd.DataFrame(both, columns = ['axis', 'polarity', 'k', 'j', 'i', 'tmult'])
|
1433
|
-
df = df.sort_values(by = ['axis', 'polarity', 'j', 'i', 'k', 'tmult'])
|
1434
|
-
both_sorted = np.empty(both.shape, dtype =
|
1435
|
-
both_sorted[:] = df
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1556
|
+
both[:, 2:] = cell_index_pairs[:, side, :] # k, j, i
|
1557
|
+
# both[:, -1] = feat_mult_array.flatten()
|
1558
|
+
# df = pd.DataFrame(both, columns = ['axis', 'polarity', 'k', 'j', 'i', 'tmult'])
|
1559
|
+
# df = df.sort_values(by = ['axis', 'polarity', 'j', 'i', 'k', 'tmult'])
|
1560
|
+
# both_sorted = np.empty(both.shape, dtype = np.int32)
|
1561
|
+
# both_sorted[:] = df
|
1562
|
+
si = np.argsort(both[:, 2]) # k
|
1563
|
+
msi = si
|
1564
|
+
both = both[si]
|
1565
|
+
si = np.argsort(both[:, 4], kind = 'stable') # i
|
1566
|
+
msi = msi[si]
|
1567
|
+
both = both[si]
|
1568
|
+
si = np.argsort(both[:, 3], kind = 'stable') # j
|
1569
|
+
msi = msi[si]
|
1570
|
+
both = both[si]
|
1571
|
+
si = np.argsort(both[:, 1], kind = 'stable') # polarity
|
1572
|
+
msi = msi[si]
|
1573
|
+
both = both[si]
|
1574
|
+
si = np.argsort(both[:, 0], kind = 'stable') # axis
|
1575
|
+
msi = msi[si]
|
1576
|
+
both = both[si]
|
1577
|
+
cell_indices = both[:, 2:]
|
1578
|
+
face_indices = np.empty((both.shape[0], 2), dtype = np.int8)
|
1579
|
+
face_indices[:, :] = both[:, :2]
|
1580
|
+
tmult_values = feat_mult_array[msi]
|
1441
1581
|
del both
|
1442
|
-
del df
|
1443
1582
|
k = None
|
1444
1583
|
i = j = k2 = axis = polarity = None # only needed to placate flake8 which whinges incorrectly otherwise
|
1445
1584
|
for row in range(cell_indices.shape[0]):
|
@@ -1557,7 +1696,7 @@ class GridConnectionSet(BaseResqpy):
|
|
1557
1696
|
log.info(
|
1558
1697
|
f'Property name {property_name} not found in extra_metadata for {self.model.citation_title_for_part(self.model.part_for_uuid(feature_uuid))}'
|
1559
1698
|
)
|
1560
|
-
value_list.append(np.
|
1699
|
+
value_list.append(np.nan)
|
1561
1700
|
else:
|
1562
1701
|
value_list.append(float(feat.extra_metadata[property_name]))
|
1563
1702
|
return value_list
|
@@ -1691,7 +1830,7 @@ class GridConnectionSet(BaseResqpy):
|
|
1691
1830
|
combined_values = property_value_by_column_edge.copy()
|
1692
1831
|
else:
|
1693
1832
|
combined_values = None
|
1694
|
-
combined_index = np.full((fault_by_column_edge_mask.shape), -1, dtype =
|
1833
|
+
combined_index = np.full((fault_by_column_edge_mask.shape), -1, dtype = np.int32)
|
1695
1834
|
combined_index = np.where(fault_by_column_edge_mask, feature, combined_index)
|
1696
1835
|
sum_unmasked = np.sum(fault_by_column_edge_mask)
|
1697
1836
|
else:
|
@@ -1801,7 +1940,7 @@ class GridConnectionSet(BaseResqpy):
|
|
1801
1940
|
def sorted_paired_cell_face_index_position(cell_face_index, a_or_b):
|
1802
1941
|
# pair one side (a_or_b) of cell_face_index with its position, then sort
|
1803
1942
|
count = len(cell_face_index)
|
1804
|
-
sp = np.empty((count, 2), dtype =
|
1943
|
+
sp = np.empty((count, 2), dtype = np.int32)
|
1805
1944
|
sp[:, 0] = cell_face_index[:, a_or_b]
|
1806
1945
|
sp[:, 1] = np.arange(count)
|
1807
1946
|
t = [tuple(r) for r in sp] # could use numpy fields based sort instead of tuple list?
|
@@ -1984,19 +2123,19 @@ class GridConnectionSet(BaseResqpy):
|
|
1984
2123
|
for k0 in range(entry['k1'], entry['k2'] + 1):
|
1985
2124
|
for j0 in range(entry['j1'], entry['j2'] + 1):
|
1986
2125
|
for i0 in range(entry['i1'], entry['i2'] + 1):
|
1987
|
-
neighbour = np.array([k0, j0, i0], dtype =
|
2126
|
+
neighbour = np.array([k0, j0, i0], dtype = np.int32)
|
1988
2127
|
if fp:
|
1989
2128
|
neighbour[axis] += 1
|
1990
2129
|
else:
|
1991
2130
|
neighbour[axis] -= 1
|
1992
2131
|
fi_list.append(feature_index)
|
1993
|
-
cell_pair_list.append((grid.natural_cell_index(
|
1994
|
-
|
2132
|
+
cell_pair_list.append((grid.natural_cell_index((k0, j0, i0)), \
|
2133
|
+
grid.natural_cell_index(neighbour)))
|
1995
2134
|
face_pair_list.append((self.face_index_map[axis, fp], self.face_index_map[axis, 1 - fp]))
|
1996
2135
|
if create_mult_prop:
|
1997
2136
|
mult_list.append(multiplier)
|
1998
2137
|
if fi_root is not None and fault_const_mult and fault_mult_value is not None:
|
1999
|
-
#patch extra_metadata into xml for new fault interpretation object
|
2138
|
+
# patch extra_metadata into xml for new fault interpretation object
|
2000
2139
|
rqet.create_metadata_xml(fi_root, {"Transmissibility multiplier": str(fault_mult_value)})
|
2001
2140
|
return True, const_mult
|
2002
2141
|
|
@@ -2042,14 +2181,15 @@ class GridConnectionSet(BaseResqpy):
|
|
2042
2181
|
feature_index = None,
|
2043
2182
|
active_only = True,
|
2044
2183
|
lazy = False,
|
2045
|
-
baffle_uuid = None
|
2184
|
+
baffle_uuid = None,
|
2185
|
+
dtype = None):
|
2046
2186
|
"""Creates a triplet of grid face numpy arrays populated from a property for this gcs.
|
2047
2187
|
|
2048
2188
|
arguments:
|
2049
2189
|
property_uuid (UUID): the uuid of the gcs property
|
2050
2190
|
default_value (float or int, optional): the value to use in the grid property
|
2051
2191
|
on faces that do not appear in the grid connection set; will default to
|
2052
|
-
np.
|
2192
|
+
np.nan for continuous properties, -1 for categorical or discrete
|
2053
2193
|
feature_index (int, optional): if present, only faces for this feature are used
|
2054
2194
|
active_only (bool, default True): if True and an active property exists for the
|
2055
2195
|
grid connection set, then only active faces are used when populating the
|
@@ -2060,6 +2200,8 @@ class GridConnectionSet(BaseResqpy):
|
|
2060
2200
|
baffle_uuid (uuid, optional): if present, the uuid of a discrete (bool) property
|
2061
2201
|
of the gcs holding baffle flags; where True the output face value is set
|
2062
2202
|
to zero regardless of the main property value
|
2203
|
+
dtype (type or str, optional): the element type for the returned arrays; defaults
|
2204
|
+
to float for continuous properties or int for discrete properties; see notes
|
2063
2205
|
|
2064
2206
|
returns:
|
2065
2207
|
triple numpy arrays: identifying the K, J & I direction grid face property values;
|
@@ -2069,7 +2211,9 @@ class GridConnectionSet(BaseResqpy):
|
|
2069
2211
|
can only be used on single grid gcs; gcs property must have indexable of faces;
|
2070
2212
|
at present generates grid properties with indexable 'faces' not 'faces per cell',
|
2071
2213
|
which might not be appropriate for grids with split pillars (structural faults);
|
2072
|
-
points properties not currently supported; count must be 1
|
2214
|
+
points properties not currently supported; count must be 1;
|
2215
|
+
if the property is a boolean array and may have been written to hdf5 using packing,
|
2216
|
+
then the dtype argument must be set to bool or np.uint8 to ensure unpacking
|
2073
2217
|
"""
|
2074
2218
|
|
2075
2219
|
assert self.number_of_grids() == 1
|
@@ -2077,7 +2221,7 @@ class GridConnectionSet(BaseResqpy):
|
|
2077
2221
|
active_mask = None
|
2078
2222
|
if active_only:
|
2079
2223
|
pc = self.extract_property_collection()
|
2080
|
-
active_mask = pc.single_array_ref(property_kind = 'active')
|
2224
|
+
active_mask = pc.single_array_ref(property_kind = 'active', dtype = bool)
|
2081
2225
|
if active_mask is not None:
|
2082
2226
|
assert active_mask.shape == (self.count,)
|
2083
2227
|
gcs_prop = rqp.Property(self.model, uuid = property_uuid)
|
@@ -2085,10 +2229,12 @@ class GridConnectionSet(BaseResqpy):
|
|
2085
2229
|
assert bu.matching_uuids(gcs_prop.collection.support_uuid, self.uuid)
|
2086
2230
|
assert gcs_prop.count() == 1
|
2087
2231
|
assert not gcs_prop.is_points()
|
2088
|
-
|
2232
|
+
if dtype is None:
|
2233
|
+
dtype = float if gcs_prop.is_continuous() else int
|
2089
2234
|
if default_value is None:
|
2090
|
-
default_value = -1 if dtype is int else np.
|
2091
|
-
gcs_prop_array = gcs_prop.array_ref()
|
2235
|
+
default_value = -1 if dtype is int else np.nan
|
2236
|
+
gcs_prop_array = gcs_prop.array_ref(dtype = dtype)
|
2237
|
+
assert gcs_prop_array.shape == (self.count,)
|
2092
2238
|
log.debug(f'preparing grid face arrays from gcs property: {gcs_prop.title}; from gcs:{self.title}')
|
2093
2239
|
|
2094
2240
|
baffle_mask = None
|
@@ -2100,55 +2246,49 @@ class GridConnectionSet(BaseResqpy):
|
|
2100
2246
|
ak = np.full((nk + 1, nj, ni), default_value, dtype = dtype)
|
2101
2247
|
aj = np.full((nk, nj + 1, ni), default_value, dtype = dtype)
|
2102
2248
|
ai = np.full((nk, nj, ni + 1), default_value, dtype = dtype)
|
2103
|
-
# mk = np.zeros((nk + 1, nj, ni), dtype = bool)
|
2104
|
-
# mj = np.zeros((nk, nj + 1, ni), dtype = bool)
|
2105
|
-
# mi = np.zeros((nk, nj, ni + 1), dtype = bool)
|
2106
2249
|
|
2107
2250
|
# populate arrays from faces of gcs, optionally filtered by feature index
|
2108
|
-
cip, fip = self.list_of_cell_face_pairs_for_feature_index(
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
2251
|
+
cip, fip = self.list_of_cell_face_pairs_for_feature_index(feature_index)
|
2252
|
+
|
2253
|
+
value_array = gcs_prop_array.copy()
|
2254
|
+
|
2255
|
+
if baffle_mask is not None:
|
2256
|
+
value_array[baffle_mask] = 0 # will be cast to float (or bool) if needed
|
2257
|
+
|
2258
|
+
if feature_index is not None:
|
2114
2259
|
indices = self.indices_for_feature_index(feature_index)
|
2260
|
+
value_array = value_array[indices]
|
2261
|
+
if active_mask is not None:
|
2262
|
+
active_mask = active_mask[indices]
|
2263
|
+
|
2264
|
+
if active_mask is not None:
|
2265
|
+
cip = cip[active_mask, :, :]
|
2266
|
+
fip = fip[active_mask, :, :]
|
2267
|
+
value_array = value_array[active_mask]
|
2115
2268
|
|
2116
|
-
# opposing_count = 0
|
2117
2269
|
side_list = ([0] if lazy else [0, 1])
|
2118
|
-
for fi in indices:
|
2119
|
-
# fi = int(i)
|
2120
|
-
if active_mask is not None and not active_mask[fi]:
|
2121
|
-
continue
|
2122
|
-
value = gcs_prop_array[fi]
|
2123
|
-
if baffle_mask is not None and baffle_mask[fi]:
|
2124
|
-
value = 0 # will be cast to float (or bool) if needed when assigned below
|
2125
|
-
for side in side_list:
|
2126
|
-
cell_kji0 = cip[fi, side].copy()
|
2127
|
-
# opposing = cell_kji0.copy()
|
2128
|
-
axis, polarity = fip[fi, side]
|
2129
|
-
assert 0 <= axis <= 2 and 0 <= polarity <= 1
|
2130
|
-
cell_kji0[axis] += polarity
|
2131
|
-
# opposing[axis] += (1 - polarity)
|
2132
|
-
if axis == 0:
|
2133
|
-
ak[tuple(cell_kji0)] = value
|
2134
|
-
# mk[tuple(cell_kji0)] = True
|
2135
|
-
# if mk[tuple(opposing)]:
|
2136
|
-
# opposing_count += 1
|
2137
|
-
elif axis == 1:
|
2138
|
-
aj[tuple(cell_kji0)] = value
|
2139
|
-
# mj[tuple(cell_kji0)] = True
|
2140
|
-
# if mj[tuple(opposing)]:
|
2141
|
-
# opposing_count += 1
|
2142
|
-
else:
|
2143
|
-
ai[tuple(cell_kji0)] = value
|
2144
|
-
# mi[tuple(cell_kji0)] = True
|
2145
|
-
# if mi[tuple(opposing)]:
|
2146
|
-
# opposing_count += 1
|
2147
2270
|
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2271
|
+
for side in side_list:
|
2272
|
+
cell_kji0 = cip[:, side].copy() # shape (N, 3)
|
2273
|
+
axis = fip[:, side, 0] # shape (N,)
|
2274
|
+
polarity = fip[:, side, 1] # shape (N,)
|
2275
|
+
# assert 0 <= np.min(axis) and np.max(axis) <= 2
|
2276
|
+
# assert 0 <= np.min(polarity) and np.max(polarity) <= 1
|
2277
|
+
|
2278
|
+
axis_mask = (axis == 0).astype(bool)
|
2279
|
+
ak_kji0 = cell_kji0[axis_mask, :]
|
2280
|
+
ak_kji0[:, 0] += polarity[axis_mask]
|
2281
|
+
ak[ak_kji0[:, 0], ak_kji0[:, 1], ak_kji0[:, 2]] = value_array[axis_mask]
|
2282
|
+
|
2283
|
+
axis_mask = (axis == 1).astype(bool)
|
2284
|
+
aj_kji0 = cell_kji0[axis_mask, :]
|
2285
|
+
aj_kji0[:, 1] += polarity[axis_mask]
|
2286
|
+
aj[aj_kji0[:, 0], aj_kji0[:, 1], aj_kji0[:, 2]] = value_array[axis_mask]
|
2287
|
+
|
2288
|
+
axis_mask = (axis == 2).astype(bool)
|
2289
|
+
ai_kji0 = cell_kji0[axis_mask, :]
|
2290
|
+
ai_kji0[:, 2] += polarity[axis_mask]
|
2291
|
+
ai[ai_kji0[:, 0], ai_kji0[:, 1], ai_kji0[:, 2]] = value_array[axis_mask]
|
2152
2292
|
|
2153
2293
|
return (ak, aj, ai)
|
2154
2294
|
|
@@ -2160,3 +2300,27 @@ def _copy_organisation_objects(target_model, source_model, gcs):
|
|
2160
2300
|
for _, uuid, _ in gcs.feature_list:
|
2161
2301
|
target_model.copy_uuid_from_other_model(source_model,
|
2162
2302
|
uuid) # will copy related features as well as interpretations
|
2303
|
+
|
2304
|
+
|
2305
|
+
def _sort_and_remove_duplicates(a, props = None):
|
2306
|
+
"""Return copy of 1D array a, sorted and with duplicates removed; secondary arrays can be kept in alignment."""
|
2307
|
+
if a is None or a.size <= 1:
|
2308
|
+
return a
|
2309
|
+
assert a.ndim == 1
|
2310
|
+
si = None
|
2311
|
+
no_props = (props is None or len(props) == 0)
|
2312
|
+
if no_props:
|
2313
|
+
a = np.sort(a)
|
2314
|
+
else:
|
2315
|
+
si = np.argsort(a)
|
2316
|
+
a = a[si]
|
2317
|
+
m = np.empty(a.size, dtype = bool)
|
2318
|
+
m[0] = True
|
2319
|
+
m[1:] = (a[1:] != a[:-1])
|
2320
|
+
if np.all(m):
|
2321
|
+
return a
|
2322
|
+
if not no_props:
|
2323
|
+
for i in range(len(props)):
|
2324
|
+
p = props[i][si]
|
2325
|
+
props[i] = p[m]
|
2326
|
+
return a[m]
|