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.
- 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]
|