resqpy 4.14.2__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 +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.5.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/LICENSE +0 -0
@@ -4,8 +4,10 @@ import logging
|
|
4
4
|
|
5
5
|
log = logging.getLogger(__name__)
|
6
6
|
|
7
|
+
import os
|
7
8
|
import numpy as np
|
8
9
|
import uuid
|
10
|
+
import ast
|
9
11
|
from typing import Tuple, Union, List, Optional, Callable
|
10
12
|
from pathlib import Path
|
11
13
|
from uuid import UUID
|
@@ -34,16 +36,22 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
34
36
|
trimmed: bool = False,
|
35
37
|
is_curtain = False,
|
36
38
|
extend_fault_representation: bool = False,
|
37
|
-
flange_inner_ring = False,
|
38
|
-
saucer_parameter = None,
|
39
|
+
flange_inner_ring: bool = False,
|
40
|
+
saucer_parameter: Optional[float] = None,
|
39
41
|
retriangulate: bool = False,
|
40
|
-
related_uuid = None,
|
42
|
+
related_uuid: Optional[Union[UUID, str]] = None,
|
41
43
|
progress_fn: Optional[Callable] = None,
|
42
44
|
extra_metadata = None,
|
43
45
|
return_properties: Optional[List[str]] = None,
|
44
46
|
raw_bisector: bool = False,
|
45
47
|
use_pack: bool = False,
|
46
|
-
flange_radius = None
|
48
|
+
flange_radius: Optional[float] = None,
|
49
|
+
reorient: bool = True,
|
50
|
+
n_threads: int = 20,
|
51
|
+
patchwork: bool = False,
|
52
|
+
grid_patching_property_uuid: Optional[Union[UUID, str]] = None,
|
53
|
+
surface_patching_property_uuid: Optional[Union[UUID, str]] = None) -> \
|
54
|
+
Tuple[int, bool, str, List[Union[UUID, str]]]:
|
47
55
|
"""Multiprocessing wrapper function of find_faces_to_represent_surface_regular_optimised.
|
48
56
|
|
49
57
|
arguments:
|
@@ -92,10 +100,22 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
92
100
|
the returned dictionary has the passed strings as keys and numpy arrays as values
|
93
101
|
raw_bisector (bool, default False): if True and grid bisector is requested then it is left in a raw
|
94
102
|
form without assessing which side is shallower (True values indicate same side as origin cell)
|
95
|
-
use_pack (bool, default False): if True, boolean properties will be stored in numpy
|
96
|
-
which will only be readable by resqpy based applications
|
103
|
+
use_pack (bool, default False): if True, boolean properties will be generated and stored in numpy
|
104
|
+
packed format, which will only be readable by resqpy based applications
|
97
105
|
flange_radius (float, optional): the radial distance to use for outer flange extension points; if None,
|
98
106
|
a large value will be calculated from the grid size; units are xy units of grid crs
|
107
|
+
reorient (bool, default True): if True, the points are reoriented to minimise the
|
108
|
+
z range prior to retriangulation (ie. z axis is approximate normal to plane of points), to enhace the triangulation
|
109
|
+
n_threads (int, default 20): the number of parallel threads to use in numba points in triangles function
|
110
|
+
patchwork (bool, default False): if True and grid bisector is included in return properties, a compostite
|
111
|
+
bisector is generated, based on individual ones for each patch of the surface; the following two
|
112
|
+
arguments must be set if patchwork is True
|
113
|
+
grid_patching_property_uuid (uuid, optional): required if patchwork is True, the uuid of a discrete or
|
114
|
+
categorical cells property on the grid which will be used to determine which patch of the surface is
|
115
|
+
relevant to a cell
|
116
|
+
surface_patching_property_uuid (uuid, optional): required if patchwork is True, the uuid of a discrete or
|
117
|
+
categorical property on the patches of the surface, identifying the value of the grid patching property
|
118
|
+
that each patch relates to
|
99
119
|
|
100
120
|
returns:
|
101
121
|
Tuple containing:
|
@@ -105,18 +125,15 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
105
125
|
- uuid_list (List[str]): list of UUIDs of relevant objects
|
106
126
|
|
107
127
|
notes:
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
the saucer_parameter is
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
+ve angles result in the shift being in the direction of the -ve z hemisphere; -ve angles result in
|
118
|
-
the shift being in the +ve z hemisphere; in either case the direction of the shift is perpendicular
|
119
|
-
to the average plane of the original points
|
128
|
+
- use this function as argument to the multiprocessing function; it will create a new model that is saved
|
129
|
+
in a temporary epc file and returns the required values, which are used in the multiprocessing function to
|
130
|
+
recombine all the objects into a single epc file
|
131
|
+
- the saucer_parameter is between -90.0 and 90.0 and is interpreted as an angle to apply out of
|
132
|
+
the plane of the original points, to give a simple saucer shape;
|
133
|
+
+ve angles result in the shift being in the direction of the -ve z hemisphere; -ve angles result in
|
134
|
+
the shift being in the +ve z hemisphere; in either case the direction of the shift is perpendicular
|
135
|
+
to the average plane of the original points
|
136
|
+
- patchwork is not compatible with re-triangulation
|
120
137
|
"""
|
121
138
|
tmp_dir = Path(parent_tmp_dir) / f"{uuid.uuid4()}"
|
122
139
|
tmp_dir.mkdir(parents = True, exist_ok = True)
|
@@ -144,12 +161,20 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
144
161
|
if flange_radius is None:
|
145
162
|
flange_radius = 5.0 * np.sum(np.array(grid.extent_kji, dtype = float) * np.array(grid.aligned_dxyz()))
|
146
163
|
s_model = rq.Model(surface_epc, quiet = True)
|
147
|
-
|
164
|
+
surface_uuid = str(surface_uuid)
|
165
|
+
model.copy_uuid_from_other_model(s_model, uuid = surface_uuid)
|
166
|
+
if surface_patching_property_uuid is not None:
|
167
|
+
model.copy_uuid_from_other_model(s_model, uuid = surface_patching_property_uuid)
|
168
|
+
uuid_list.append(surface_patching_property_uuid)
|
148
169
|
repr_type = model.type_of_part(model.part(uuid = surface_uuid), strip_obj = True)
|
149
170
|
assert repr_type in ['TriangulatedSetRepresentation', 'PointSetRepresentation']
|
171
|
+
assert repr_type == 'TriangulatedSetRepresentation' or not patchwork, \
|
172
|
+
'patchwork only implemented for triangulated set surfaces'
|
173
|
+
|
150
174
|
extended = False
|
151
175
|
retriangulated = False
|
152
176
|
flange_bool = None
|
177
|
+
|
153
178
|
if repr_type == 'PointSetRepresentation':
|
154
179
|
# trim pointset to grid xyz box
|
155
180
|
pset = rqs.PointSet(model, uuid = surface_uuid)
|
@@ -175,7 +200,7 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
175
200
|
surf = rqs.Surface(model, crs_uuid = grid.crs.uuid, title = surf_title)
|
176
201
|
flange_bool = surf.set_from_point_set(pset,
|
177
202
|
convexity_parameter = 2.0,
|
178
|
-
reorient =
|
203
|
+
reorient = reorient,
|
179
204
|
extend_with_flange = extend_fault_representation,
|
180
205
|
flange_inner_ring = flange_inner_ring,
|
181
206
|
saucer_parameter = saucer_parameter,
|
@@ -189,19 +214,26 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
189
214
|
inherit_interpretation_relationship(model, surface_uuid, surf.uuid)
|
190
215
|
surface_uuid = surf.uuid
|
191
216
|
|
192
|
-
surface = rqs.Surface(parent_model = model, uuid =
|
217
|
+
surface = rqs.Surface(parent_model = model, uuid = surface_uuid)
|
193
218
|
surf_title = surface.title
|
194
219
|
assert surf_title
|
195
220
|
surface.change_crs(grid.crs)
|
221
|
+
normal_vector = None
|
222
|
+
if reorient:
|
223
|
+
normal_vector = surface.normal()
|
224
|
+
if patchwork: # disable trimming as whole patches could be trimmed out, changing the patch indexing from that expected
|
225
|
+
trimmed = True
|
196
226
|
if not trimmed and surface.triangle_count() > 100:
|
197
227
|
if not surf_title.endswith('trimmed'):
|
198
228
|
surf_title += ' trimmed'
|
199
229
|
trimmed_surf = rqs.Surface(model, crs_uuid = grid.crs.uuid, title = surf_title)
|
200
230
|
# trimmed_surf.set_to_trimmed_surface(surf, xyz_box = xyz_box, xy_polygon = parent_seg.polygon)
|
201
231
|
trimmed_surf.set_to_trimmed_surface(surface, xyz_box = grid.xyz_box(local = True))
|
232
|
+
trimmed_surf.extra_metadata = surface.extra_metadata
|
202
233
|
surface = trimmed_surf
|
203
234
|
trimmed = True
|
204
235
|
if (extend_fault_representation and not extended) or (retriangulate and not retriangulated):
|
236
|
+
assert not patchwork, 'extension or re-triangulation are not compatible with patchwork'
|
205
237
|
_, p = surface.triangles_and_points()
|
206
238
|
pset = rqs.PointSet(model, points_array = p, crs_uuid = grid.crs.uuid, title = surf_title)
|
207
239
|
if extend_fault_representation and not surf_title.endswith('extended'):
|
@@ -209,12 +241,13 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
209
241
|
surface = rqs.Surface(model, crs_uuid = grid.crs.uuid, title = surf_title)
|
210
242
|
flange_bool = surface.set_from_point_set(pset,
|
211
243
|
convexity_parameter = 2.0,
|
212
|
-
reorient =
|
244
|
+
reorient = reorient,
|
213
245
|
extend_with_flange = extend_fault_representation,
|
214
246
|
flange_inner_ring = flange_inner_ring,
|
215
247
|
saucer_parameter = saucer_parameter,
|
216
248
|
flange_radial_distance = flange_radius,
|
217
|
-
make_clockwise = False
|
249
|
+
make_clockwise = False,
|
250
|
+
normal_vector = normal_vector)
|
218
251
|
del pset
|
219
252
|
extended = extend_fault_representation
|
220
253
|
retriangulated = True
|
@@ -235,9 +268,26 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
235
268
|
property_kind = 'flange bool',
|
236
269
|
find_local_property_kind = True,
|
237
270
|
indexable_element = 'faces',
|
238
|
-
discrete = True
|
271
|
+
discrete = True,
|
272
|
+
dtype = np.uint8)
|
239
273
|
uuid_list.append(flange_p.uuid)
|
240
|
-
|
274
|
+
|
275
|
+
if not patchwork:
|
276
|
+
uuid_list.append(surface_uuid)
|
277
|
+
|
278
|
+
patch_indices = None
|
279
|
+
if patchwork: # generate a patch indices array over grid cells based on supplied patching properties
|
280
|
+
assert grid_patching_property_uuid is not None and surface_patching_property_uuid is not None
|
281
|
+
g_patching_array = rqp.Property(g_model, uuid = grid_patching_property_uuid).array_ref()
|
282
|
+
assert g_patching_array.shape == tuple(grid.extent_kji)
|
283
|
+
s_patches_array = rqp.Property(model, uuid = surface_patching_property_uuid).array_ref()
|
284
|
+
patch_count = surface.number_of_patches()
|
285
|
+
assert s_patches_array.shape == (patch_count,)
|
286
|
+
p_dtype = (np.int8 if s_patches_array.shape[0] < 127 else np.int32)
|
287
|
+
patch_indices = np.full(g_patching_array.shape, -1, dtype = p_dtype)
|
288
|
+
for patch in range(patch_count):
|
289
|
+
gp = s_patches_array[patch]
|
290
|
+
patch_indices[(g_patching_array == gp).astype(bool)] = patch
|
241
291
|
|
242
292
|
returns = rqgs.find_faces_to_represent_surface_regular_optimised(grid,
|
243
293
|
surface,
|
@@ -249,7 +299,10 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
249
299
|
is_curtain,
|
250
300
|
progress_fn,
|
251
301
|
return_properties,
|
252
|
-
raw_bisector = raw_bisector
|
302
|
+
raw_bisector = raw_bisector,
|
303
|
+
n_batches = n_threads,
|
304
|
+
packed_bisectors = use_pack,
|
305
|
+
patch_indices = patch_indices)
|
253
306
|
|
254
307
|
success = False
|
255
308
|
|
@@ -279,6 +332,7 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
279
332
|
|
280
333
|
if success and return_properties is not None and len(return_properties):
|
281
334
|
log.debug(f'{name} requested properties: {return_properties}')
|
335
|
+
assert isinstance(returns, tuple)
|
282
336
|
properties = returns[1]
|
283
337
|
realisation = index if use_index_as_realisation else None
|
284
338
|
property_collection = rqp.PropertyCollection(support = gcs)
|
@@ -339,17 +393,18 @@ def find_faces_to_represent_surface_regular_wrapper(
|
|
339
393
|
if grid_pc is None:
|
340
394
|
grid_pc = rqp.PropertyCollection()
|
341
395
|
grid_pc.set_support(support = grid)
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
396
|
+
assert array.ndim == (2 if is_curtain else 3)
|
397
|
+
grid_pc.add_cached_array_to_imported_list(array,
|
398
|
+
f"from find_faces function for {surface.title}",
|
399
|
+
f'{surface.title} {p_name}',
|
400
|
+
discrete = True,
|
401
|
+
property_kind = "grid bisector",
|
402
|
+
facet_type = 'direction',
|
403
|
+
facet = 'raw' if raw_bisector else
|
404
|
+
('vertical' if is_curtain else 'sloping'),
|
405
|
+
realization = realisation,
|
406
|
+
indexable_element = "columns" if is_curtain else "cells",
|
407
|
+
pre_packed = False if is_curtain else use_pack)
|
353
408
|
elif p_name == 'grid shadow':
|
354
409
|
if grid_pc is None:
|
355
410
|
grid_pc = rqp.PropertyCollection()
|
resqpy/olio/read_nexus_fault.py
CHANGED
@@ -64,7 +64,10 @@ def load_nexus_fault_mult_table_from_list(file_as_list):
|
|
64
64
|
outdata[mask] = np.concatenate(d_elems)
|
65
65
|
df = pd.DataFrame(outdata)
|
66
66
|
for column in df.columns:
|
67
|
-
|
67
|
+
try:
|
68
|
+
df[column] = pd.to_numeric(df[column], errors = "coerce")
|
69
|
+
except ValueError:
|
70
|
+
pass
|
68
71
|
df.columns = ['i1', 'i2', 'j1', 'j2', 'k1', 'k2', 'mult']
|
69
72
|
df['grid'] = grid
|
70
73
|
df['name'] = name
|
@@ -114,7 +117,10 @@ def load_nexus_fault_mult_table_from_list(file_as_list):
|
|
114
117
|
outdata[mask] = np.concatenate(d_elems)
|
115
118
|
df = pd.DataFrame(outdata)
|
116
119
|
for column in df.columns:
|
117
|
-
|
120
|
+
try:
|
121
|
+
df[column] = pd.to_numeric(df[column], errors = "coerce")
|
122
|
+
except ValueError:
|
123
|
+
pass
|
118
124
|
df.columns = ['i1', 'i2', 'j1', 'j2', 'k1', 'k2', 'mult']
|
119
125
|
df['grid'] = grid
|
120
126
|
df['name'] = name
|
resqpy/olio/relperm.py
CHANGED
@@ -91,7 +91,7 @@ class RelPerm(DataFrame):
|
|
91
91
|
processed_phase_combo_checks.get(processed_phase_combo)(df)
|
92
92
|
# ensure that missing capillary pressure values are stored as np.nan
|
93
93
|
if 'Pc' in df.columns:
|
94
|
-
df['Pc'].replace('None', np.nan
|
94
|
+
df['Pc'] = df['Pc'].replace('None', np.nan)
|
95
95
|
# convert all values in the dataframe to numeric type
|
96
96
|
df[df.columns] = df[df.columns].apply(pd.to_numeric, errors = 'coerce')
|
97
97
|
# ensure that no other column besides Pc has missing values
|
resqpy/olio/transmission.py
CHANGED
@@ -260,10 +260,10 @@ def half_cell_t_irregular(grid,
|
|
260
260
|
zero_length_mask = np.logical_or(np.any(np.isnan(half_axis_vectors), axis = -1),
|
261
261
|
half_axis_length_sqr < tolerance_sqr)
|
262
262
|
minus_face_t = np.where(
|
263
|
-
zero_length_mask, np.
|
263
|
+
zero_length_mask, np.nan,
|
264
264
|
perm_k * np.sum(half_axis_vectors * minus_face_areas, axis = -1) / half_axis_length_sqr)
|
265
265
|
plus_face_t = np.where(
|
266
|
-
zero_length_mask, np.
|
266
|
+
zero_length_mask, np.nan,
|
267
267
|
perm_k * np.sum(half_axis_vectors * plus_face_areas, axis = -1) / half_axis_length_sqr)
|
268
268
|
|
269
269
|
elif axis == 1: # j
|
@@ -303,10 +303,10 @@ def half_cell_t_irregular(grid,
|
|
303
303
|
half_axis_length_sqr = np.sum(half_axis_vectors * half_axis_vectors, axis = -1)
|
304
304
|
zero_length_mask = (half_axis_length_sqr < tolerance_sqr)
|
305
305
|
minus_face_t = np.where(
|
306
|
-
zero_length_mask, np.
|
306
|
+
zero_length_mask, np.nan,
|
307
307
|
perm_j * np.sum(half_axis_vectors * minus_face_areas, axis = -1) / half_axis_length_sqr)
|
308
308
|
plus_face_t = np.where(
|
309
|
-
zero_length_mask, np.
|
309
|
+
zero_length_mask, np.nan,
|
310
310
|
perm_j * np.sum(half_axis_vectors * plus_face_areas, axis = -1) / half_axis_length_sqr)
|
311
311
|
|
312
312
|
else: # i
|
@@ -345,10 +345,10 @@ def half_cell_t_irregular(grid,
|
|
345
345
|
half_axis_length_sqr = np.sum(half_axis_vectors * half_axis_vectors, axis = -1)
|
346
346
|
zero_length_mask = (half_axis_length_sqr < tolerance_sqr)
|
347
347
|
minus_face_t = np.where(
|
348
|
-
zero_length_mask, np.
|
348
|
+
zero_length_mask, np.nan,
|
349
349
|
perm_i * np.sum(half_axis_vectors * minus_face_areas, axis = -1) / half_axis_length_sqr)
|
350
350
|
plus_face_t = np.where(
|
351
|
-
zero_length_mask, np.
|
351
|
+
zero_length_mask, np.nan,
|
352
352
|
perm_i * np.sum(half_axis_vectors * plus_face_areas, axis = -1) / half_axis_length_sqr)
|
353
353
|
|
354
354
|
if axis != 0 and ntg is not None:
|
@@ -417,14 +417,14 @@ def half_cell_t_vertical_prism(vpg,
|
|
417
417
|
# compute transmissibilities
|
418
418
|
tr = np.zeros((vpg.cell_count, 5), dtype = float)
|
419
419
|
# vertical
|
420
|
-
tr[:, 0] = np.where(half_thickness < tolerance, np.
|
420
|
+
tr[:, 0] = np.where(half_thickness < tolerance, np.nan, (perm_k.reshape(
|
421
421
|
(vpg.nk, -1)) * triangle_areas / half_thickness)).flatten()
|
422
422
|
tr[:, 1] = tr[:, 0]
|
423
423
|
# horizontal
|
424
424
|
# TODO: compute dip adjustments for non-vertical transmissibilities
|
425
425
|
dt_full = np.empty((vpg.nk, vpg.cell_count // vpg.nk, 3), dtype = float)
|
426
426
|
dt_full[:] = d_t
|
427
|
-
tr[:, 2:] = np.where(dt_full < tolerance, np.
|
427
|
+
tr[:, 2:] = np.where(dt_full < tolerance, np.nan,
|
428
428
|
triple_perm_horizontal.reshape((vpg.nk, -1, 3)) * a_t.reshape((1, -1, 3)) / dt_full).reshape(
|
429
429
|
(-1, 3))
|
430
430
|
if ntg is not None:
|
resqpy/olio/triangulation.py
CHANGED
@@ -25,7 +25,7 @@ import resqpy.olio.vector_utilities as vec
|
|
25
25
|
|
26
26
|
def _dt_scipy(points: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
27
27
|
"""Calculates the Delaunay triangulation for an array of points and the convex hull indices.
|
28
|
-
|
28
|
+
|
29
29
|
arguments:
|
30
30
|
points (np.ndarray): coordinates of the points to triangulate; array has shape
|
31
31
|
(npoints, ndim)
|
@@ -48,7 +48,7 @@ def _dt_scipy(points: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
|
48
48
|
|
49
49
|
|
50
50
|
def _dt_simple(po, plot_fn = None, progress_fn = None, container_size_factor = None):
|
51
|
-
# returns
|
51
|
+
# returns Delaunay triangulation of po and list of hull point indices, using a simple algorithm
|
52
52
|
|
53
53
|
def flip(ei):
|
54
54
|
nonlocal fm, e, t, te, p, nt, p_i, ne
|
@@ -115,7 +115,7 @@ def _dt_simple(po, plot_fn = None, progress_fn = None, container_size_factor = N
|
|
115
115
|
if n_p < 3:
|
116
116
|
return None, None # not enough points
|
117
117
|
elif n_p == 3:
|
118
|
-
return np.array([0, 1, 2], dtype =
|
118
|
+
return np.array([0, 1, 2], dtype = np.int32).reshape((1, 3)), np.array([0, 1, 2], dtype = np.int32)
|
119
119
|
|
120
120
|
if progress_fn is not None:
|
121
121
|
progress_fn(0.0)
|
@@ -133,19 +133,20 @@ def _dt_simple(po, plot_fn = None, progress_fn = None, container_size_factor = N
|
|
133
133
|
p[-1] = (min_xy[0] + 0.5 * csf * dxy[0], max_xy[1] + 0.8 * csf * dxy[1])
|
134
134
|
|
135
135
|
# triangle vertex indices
|
136
|
-
|
136
|
+
t_type = np.int32 if n_p < 2_147_483_648 else np.int64
|
137
|
+
t = np.empty((2 * n_p + 2, 3), dtype = t_type) # empty space for triangle vertex indices
|
137
138
|
t[0] = (n_p, n_p + 1, n_p + 2) # initial set of one containing triangle
|
138
139
|
nt = 1 # number of triangles so far populated
|
139
140
|
|
140
141
|
# edges: list of indices of triangles and edge within triangle; -1 indicates no triangle using edge
|
141
|
-
e = np.full((3 * n_p + 6, 2, 2), fill_value = -1, dtype =
|
142
|
+
e = np.full((3 * n_p + 6, 2, 2), fill_value = -1, dtype = t_type)
|
142
143
|
e[:3, 0, 0] = 0
|
143
144
|
for edge in range(3):
|
144
145
|
e[edge, 0, 1] = edge
|
145
146
|
ne = 3 # number of edges so far in use
|
146
147
|
|
147
148
|
# edge indices (in e) for each triangle, first axis indexed in sync with t
|
148
|
-
te = np.empty((2 * n_p + 2, 3), dtype =
|
149
|
+
te = np.empty((2 * n_p + 2, 3), dtype = t_type) # empty space for triangle edge indices
|
149
150
|
te[0] = (0, 1, 2)
|
150
151
|
|
151
152
|
# mask tracking which edges have been flipped
|
@@ -233,7 +234,7 @@ def _dt_simple(po, plot_fn = None, progress_fn = None, container_size_factor = N
|
|
233
234
|
|
234
235
|
|
235
236
|
def dt(p, algorithm = "scipy", plot_fn = None, progress_fn = None, container_size_factor = 100.0, return_hull = False):
|
236
|
-
"""Returns the
|
237
|
+
"""Returns the Delaunay Triangulation of 2D point set p.
|
237
238
|
|
238
239
|
arguments:
|
239
240
|
p (numpy float array of shape (N, 2): the x,y coordinates of the points
|
@@ -252,7 +253,7 @@ def dt(p, algorithm = "scipy", plot_fn = None, progress_fn = None, container_siz
|
|
252
253
|
|
253
254
|
returns:
|
254
255
|
numpy int array of shape (M, 3) - being the indices into the first axis of p of the 3 points
|
255
|
-
per triangle in the
|
256
|
+
per triangle in the Delaunay Triangulation - and if return_hull is True, another int array
|
256
257
|
of shape (B,) - being indices into p of the clockwise ordered points on the boundary of
|
257
258
|
the triangulation
|
258
259
|
|
@@ -260,7 +261,7 @@ def dt(p, algorithm = "scipy", plot_fn = None, progress_fn = None, container_siz
|
|
260
261
|
the plot_fn, progress_fn and container_size_factor arguments are only used by the 'simple' algorithm;
|
261
262
|
if points p are 3D, the projection onto the xy plane is used for the triangulation
|
262
263
|
"""
|
263
|
-
assert p.ndim == 2 and p.shape[1] >= 2, 'bad points shape for 2D
|
264
|
+
assert p.ndim == 2 and p.shape[1] >= 2, 'bad points shape for 2D Delaunay Triangulation'
|
264
265
|
|
265
266
|
if not algorithm:
|
266
267
|
algorithm = 'scipy'
|
@@ -273,7 +274,7 @@ def dt(p, algorithm = "scipy", plot_fn = None, progress_fn = None, container_siz
|
|
273
274
|
progress_fn = progress_fn,
|
274
275
|
container_size_factor = container_size_factor)
|
275
276
|
else:
|
276
|
-
raise Exception(f'unrecognised
|
277
|
+
raise Exception(f'unrecognised Delaunay Triangulation algorithm name: {algorithm}')
|
277
278
|
|
278
279
|
assert tri.ndim == 2 and tri.shape[1] == 3
|
279
280
|
|
@@ -303,11 +304,11 @@ def ccc(p1, p2, p3):
|
|
303
304
|
|
304
305
|
|
305
306
|
def voronoi(p, t, b, aoi: rql.Polyline):
|
306
|
-
"""Returns dual Voronoi diagram for a
|
307
|
+
"""Returns dual Voronoi diagram for a Delaunay triangulation.
|
307
308
|
|
308
309
|
arguments:
|
309
|
-
p (numpy float array of shape (N, 2)): seed points used in the
|
310
|
-
t (numpy int array of shape (M, 3)): the
|
310
|
+
p (numpy float array of shape (N, 2)): seed points used in the Delaunay triangulation
|
311
|
+
t (numpy int array of shape (M, 3)): the Delaunay triangulation of p as returned by dt()
|
311
312
|
b (numpy int array of shape (B,)): clockwise sorted list of indices into p of the boundary
|
312
313
|
points of the triangulation t
|
313
314
|
aoi (lines.Polyline): area of interest; a closed clockwise polyline that must strictly contain
|
@@ -328,7 +329,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
328
329
|
|
329
330
|
# this code assumes that the Voronoi polygon for a seed point visits the circumcentres of
|
330
331
|
# all the triangles that make use of the point – currently understood to be always the case
|
331
|
-
# for a
|
332
|
+
# for a Delaunay triangulation
|
332
333
|
|
333
334
|
def __aoi_intervening_nodes(aoi_count, c_count, seg_a, seg_c):
|
334
335
|
nodes = []
|
@@ -501,7 +502,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
501
502
|
return trimmed_ci
|
502
503
|
|
503
504
|
def __veroni_cells(aoi_count, aoi_intersect_segments, b, c, c_count, ca_count, cah_count, caho_count, cahon_count,
|
504
|
-
hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi):
|
505
|
+
hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi, t_type):
|
505
506
|
# list of voronoi cells (each a numpy list of node indices into c extended with aoi points etc)
|
506
507
|
v = []
|
507
508
|
# for each seed point build the voronoi cell
|
@@ -550,7 +551,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
550
551
|
ci_for_p, out_pair_intersect_segments)
|
551
552
|
|
552
553
|
# remove circumcircle centres that are outwith area of interest
|
553
|
-
ci_for_p = np.array([ti for ti in ci_for_p if ti >= c_count or ti not in tc_outwith_aoi], dtype =
|
554
|
+
ci_for_p = np.array([ti for ti in ci_for_p if ti >= c_count or ti not in tc_outwith_aoi], dtype = t_type)
|
554
555
|
|
555
556
|
# find azimuths of vectors from seed point to circumcircle centres and aoi boundary points
|
556
557
|
azi = [vec.azimuth(centre - p[p_i, :2]) for centre in c[ci_for_p, :2]]
|
@@ -581,7 +582,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
581
582
|
|
582
583
|
# check for concavities in hull
|
583
584
|
if not hull.is_convex():
|
584
|
-
log.warning('
|
585
|
+
log.warning('Delaunay triangulation is not convex; Voronoi diagram construction might fail')
|
585
586
|
|
586
587
|
# compute circumcircle centres
|
587
588
|
c = np.zeros((t.shape[0], 2))
|
@@ -612,11 +613,12 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
612
613
|
caho_count = cah_count + 2 * o_count
|
613
614
|
cahon_count = caho_count + hull_count
|
614
615
|
assert cahon_count + hull_count == len(c)
|
616
|
+
t_type = np.int32 if len(c) < 2_147_483_648 else np.int64
|
615
617
|
|
616
618
|
# compute intersection points between hull edge normals and aoi polyline
|
617
619
|
# also extended virtual centres for hull edges
|
618
620
|
extension_scaling = 1000.0 * np.sum((np.max(aoi.coordinates, axis = 0) - np.min(aoi.coordinates, axis = 0))[:2])
|
619
|
-
aoi_intersect_segments = np.empty((hull_count,), dtype =
|
621
|
+
aoi_intersect_segments = np.empty((hull_count,), dtype = t_type)
|
620
622
|
for ei in range(hull_count):
|
621
623
|
# use segment midpoint and normal methods of hull to project out
|
622
624
|
m = hull.segment_midpoint(ei)[:2] # midpoint
|
@@ -638,8 +640,8 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
638
640
|
c[cahon_count + ei] = hull.coordinates[ei, :2] + extension_scaling * vector
|
639
641
|
|
640
642
|
# where cicrumcircle centres are outwith aoi, compute intersections of normals of wing edges with aoi
|
641
|
-
out_pair_intersect_segments = np.empty((o_count, 2), dtype =
|
642
|
-
wing_hull_segments = np.empty((o_count, 2), dtype =
|
643
|
+
out_pair_intersect_segments = np.empty((o_count, 2), dtype = t_type)
|
644
|
+
wing_hull_segments = np.empty((o_count, 2), dtype = t_type)
|
643
645
|
for oi, ti in enumerate(tc_outwith_aoi):
|
644
646
|
tpi = __shorter_sides_p_i(p[t[ti]])
|
645
647
|
for wing in range(2):
|
@@ -658,7 +660,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
|
|
658
660
|
tpi = (tpi + 1) % 3
|
659
661
|
|
660
662
|
v = __veroni_cells(aoi_count, aoi_intersect_segments, b, c, c_count, ca_count, cah_count, caho_count, cahon_count,
|
661
|
-
hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi)
|
663
|
+
hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi, t_type)
|
662
664
|
|
663
665
|
return c[:caho_count], v
|
664
666
|
|
@@ -703,7 +705,8 @@ def triangulated_polygons(p, v, centres = None):
|
|
703
705
|
points[len(p):] = centres
|
704
706
|
|
705
707
|
t_count = sum([len(x) for x in v])
|
706
|
-
|
708
|
+
t_type = np.int32 if len(points) < 2_147_483_648 else np.int64
|
709
|
+
triangles = np.empty((t_count, 3), dtype = t_type)
|
707
710
|
t_index = 0
|
708
711
|
|
709
712
|
for cell, poly_vertices in enumerate(v):
|
@@ -711,7 +714,7 @@ def triangulated_polygons(p, v, centres = None):
|
|
711
714
|
centre_i = len(p) + cell
|
712
715
|
if centres is None:
|
713
716
|
polygon = rql.Polyline(model,
|
714
|
-
set_coord = p[np.array(poly_vertices, dtype =
|
717
|
+
set_coord = p[np.array(poly_vertices, dtype = t_type)],
|
715
718
|
is_closed = True,
|
716
719
|
set_crs = crs.uuid,
|
717
720
|
title = 'v cell')
|
@@ -747,7 +750,7 @@ def reorient(points, rough = True, max_dip = None, use_linalg = True, sample = 5
|
|
747
750
|
notes:
|
748
751
|
the original points array is not modified by this function;
|
749
752
|
implicit xy & z units for points are assumed to be the same;
|
750
|
-
the function may typically be called prior to the
|
753
|
+
the function may typically be called prior to the Delaunay triangulation, which uses an xy projection to
|
751
754
|
determine the triangulation;
|
752
755
|
the numpy linear algebra option seems to be memory intensive, not recommended;
|
753
756
|
downsampling will occur (for normal vector determination) when the number of points exceeds double that
|
@@ -865,9 +868,11 @@ def surrounding_xy_ring(p,
|
|
865
868
|
being an angle to determine a z offset for the ring(s); a +ve angle results in a -ve z shift
|
866
869
|
|
867
870
|
returns:
|
868
|
-
numpy float array
|
869
|
-
|
870
|
-
|
871
|
+
(numpy float array, float) pair:
|
872
|
+
- numpy float array of shape (N, 3) being xyz points in surrounding ring(s); z is set constant to
|
873
|
+
mean value of z in p (optionally adjussted based on saucer_angle);
|
874
|
+
N is count if inner_ring is False, 3 * count if True
|
875
|
+
- radius used for ring of additional points
|
871
876
|
"""
|
872
877
|
|
873
878
|
def make_ring(count, centre, radius, saucer_angle):
|
@@ -895,8 +900,8 @@ def surrounding_xy_ring(p,
|
|
895
900
|
inner_radius = p_radius * 1.1
|
896
901
|
assert radius > inner_radius
|
897
902
|
in_ring = make_ring(2 * count, centre, inner_radius, saucer_angle)
|
898
|
-
return np.concatenate((in_ring, ring), axis = 0)
|
899
|
-
return ring
|
903
|
+
return np.concatenate((in_ring, ring), axis = 0), radius
|
904
|
+
return ring, radius
|
900
905
|
|
901
906
|
|
902
907
|
def edges(t):
|
@@ -917,7 +922,7 @@ def edges(t):
|
|
917
922
|
"""
|
918
923
|
|
919
924
|
assert t.ndim == 2 and t.shape[1] == 3
|
920
|
-
all_edges = np.empty((len(t), 3, 2), dtype =
|
925
|
+
all_edges = np.empty((len(t), 3, 2), dtype = t.dtype)
|
921
926
|
all_edges[:, :, 0] = t
|
922
927
|
all_edges[:, :2, 1] = t[:, 1:]
|
923
928
|
all_edges[:, 2, 1] = t[:, 0]
|
@@ -946,6 +951,7 @@ def triangles_using_edges(t, edges):
|
|
946
951
|
"""Returns int array of shape (len(edges), 2) with indices of upto 2 triangles using each edge (-1 for unused)."""
|
947
952
|
|
948
953
|
assert t.ndim == 2 and t.shape[1] == 3 and edges.ndim == 2 and edges.shape[1] == 2
|
954
|
+
t_type = np.int32 if len(t) < 2_147_483_648 else np.int64
|
949
955
|
ti = np.full((len(edges), 2), -1, dtype = int)
|
950
956
|
for i in range(len(edges)):
|
951
957
|
te = triangles_using_edge(t, edges[i, 0], edges[i, 1])
|