resqpy 4.14.2__py3-none-any.whl → 5.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resqpy/__init__.py +1 -1
- resqpy/fault/_gcs_functions.py +10 -10
- resqpy/fault/_grid_connection_set.py +277 -113
- resqpy/grid/__init__.py +2 -3
- resqpy/grid/_defined_geometry.py +3 -3
- resqpy/grid/_extract_functions.py +8 -2
- resqpy/grid/_grid.py +95 -12
- resqpy/grid/_grid_types.py +22 -7
- resqpy/grid/_points_functions.py +1 -1
- resqpy/grid/_regular_grid.py +6 -2
- resqpy/grid_surface/__init__.py +17 -38
- resqpy/grid_surface/_blocked_well_populate.py +5 -5
- resqpy/grid_surface/_find_faces.py +1413 -253
- resqpy/lines/_polyline.py +24 -33
- resqpy/model/_catalogue.py +9 -0
- resqpy/model/_forestry.py +18 -14
- resqpy/model/_hdf5.py +11 -3
- resqpy/model/_model.py +85 -10
- resqpy/model/_xml.py +38 -13
- resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
- resqpy/olio/read_nexus_fault.py +8 -2
- resqpy/olio/relperm.py +1 -1
- resqpy/olio/transmission.py +8 -8
- resqpy/olio/triangulation.py +36 -30
- resqpy/olio/vector_utilities.py +340 -6
- resqpy/olio/volume.py +0 -20
- resqpy/olio/wellspec_keywords.py +19 -13
- resqpy/olio/write_hdf5.py +1 -1
- resqpy/olio/xml_et.py +12 -0
- resqpy/property/__init__.py +6 -4
- resqpy/property/_collection_add_part.py +4 -3
- resqpy/property/_collection_create_xml.py +4 -2
- resqpy/property/_collection_get_attributes.py +4 -0
- resqpy/property/attribute_property_set.py +311 -0
- resqpy/property/grid_property_collection.py +11 -11
- resqpy/property/property_collection.py +79 -31
- resqpy/property/property_common.py +3 -8
- resqpy/rq_import/_add_surfaces.py +34 -14
- resqpy/rq_import/_grid_from_cp.py +2 -2
- resqpy/rq_import/_import_nexus.py +75 -48
- resqpy/rq_import/_import_vdb_all_grids.py +64 -52
- resqpy/rq_import/_import_vdb_ensemble.py +12 -13
- resqpy/surface/_mesh.py +4 -0
- resqpy/surface/_surface.py +593 -118
- resqpy/surface/_tri_mesh.py +13 -10
- resqpy/surface/_tri_mesh_stencil.py +4 -4
- resqpy/surface/_triangulated_patch.py +71 -51
- resqpy/time_series/_any_time_series.py +7 -4
- resqpy/time_series/_geologic_time_series.py +1 -1
- resqpy/unstructured/_hexa_grid.py +6 -2
- resqpy/unstructured/_prism_grid.py +13 -5
- resqpy/unstructured/_pyramid_grid.py +6 -2
- resqpy/unstructured/_tetra_grid.py +6 -2
- resqpy/unstructured/_unstructured_grid.py +6 -2
- resqpy/well/_blocked_well.py +1986 -1946
- resqpy/well/_deviation_survey.py +3 -3
- resqpy/well/_md_datum.py +11 -21
- resqpy/well/_trajectory.py +10 -5
- resqpy/well/_wellbore_frame.py +10 -2
- resqpy/well/blocked_well_frame.py +3 -3
- resqpy/well/well_object_funcs.py +7 -9
- resqpy/well/well_utils.py +33 -0
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/LICENSE +0 -0
@@ -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])
|