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.
Files changed (67) hide show
  1. resqpy/__init__.py +1 -1
  2. resqpy/fault/_gcs_functions.py +10 -10
  3. resqpy/fault/_grid_connection_set.py +277 -113
  4. resqpy/grid/__init__.py +2 -3
  5. resqpy/grid/_defined_geometry.py +3 -3
  6. resqpy/grid/_extract_functions.py +2 -1
  7. resqpy/grid/_grid.py +95 -12
  8. resqpy/grid/_grid_types.py +22 -7
  9. resqpy/grid/_points_functions.py +1 -1
  10. resqpy/grid/_regular_grid.py +6 -2
  11. resqpy/grid_surface/__init__.py +17 -38
  12. resqpy/grid_surface/_blocked_well_populate.py +5 -5
  13. resqpy/grid_surface/_find_faces.py +1349 -253
  14. resqpy/lines/_polyline.py +24 -33
  15. resqpy/model/_catalogue.py +9 -0
  16. resqpy/model/_forestry.py +18 -14
  17. resqpy/model/_hdf5.py +11 -3
  18. resqpy/model/_model.py +85 -10
  19. resqpy/model/_xml.py +38 -13
  20. resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
  21. resqpy/olio/read_nexus_fault.py +8 -2
  22. resqpy/olio/relperm.py +1 -1
  23. resqpy/olio/transmission.py +8 -8
  24. resqpy/olio/triangulation.py +36 -30
  25. resqpy/olio/vector_utilities.py +340 -6
  26. resqpy/olio/volume.py +0 -20
  27. resqpy/olio/wellspec_keywords.py +19 -13
  28. resqpy/olio/write_hdf5.py +1 -1
  29. resqpy/olio/xml_et.py +12 -0
  30. resqpy/property/__init__.py +6 -4
  31. resqpy/property/_collection_add_part.py +4 -3
  32. resqpy/property/_collection_create_xml.py +4 -2
  33. resqpy/property/_collection_get_attributes.py +4 -0
  34. resqpy/property/attribute_property_set.py +311 -0
  35. resqpy/property/grid_property_collection.py +11 -11
  36. resqpy/property/property_collection.py +79 -31
  37. resqpy/property/property_common.py +3 -8
  38. resqpy/rq_import/_add_surfaces.py +34 -14
  39. resqpy/rq_import/_grid_from_cp.py +2 -2
  40. resqpy/rq_import/_import_nexus.py +75 -48
  41. resqpy/rq_import/_import_vdb_all_grids.py +64 -52
  42. resqpy/rq_import/_import_vdb_ensemble.py +12 -13
  43. resqpy/surface/_mesh.py +4 -0
  44. resqpy/surface/_surface.py +593 -118
  45. resqpy/surface/_tri_mesh.py +13 -10
  46. resqpy/surface/_tri_mesh_stencil.py +4 -4
  47. resqpy/surface/_triangulated_patch.py +71 -51
  48. resqpy/time_series/_any_time_series.py +7 -4
  49. resqpy/time_series/_geologic_time_series.py +1 -1
  50. resqpy/unstructured/_hexa_grid.py +6 -2
  51. resqpy/unstructured/_prism_grid.py +13 -5
  52. resqpy/unstructured/_pyramid_grid.py +6 -2
  53. resqpy/unstructured/_tetra_grid.py +6 -2
  54. resqpy/unstructured/_unstructured_grid.py +6 -2
  55. resqpy/well/_blocked_well.py +1986 -1946
  56. resqpy/well/_deviation_survey.py +3 -3
  57. resqpy/well/_md_datum.py +11 -21
  58. resqpy/well/_trajectory.py +10 -5
  59. resqpy/well/_wellbore_frame.py +10 -2
  60. resqpy/well/blocked_well_frame.py +3 -3
  61. resqpy/well/well_object_funcs.py +7 -9
  62. resqpy/well/well_utils.py +33 -0
  63. {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {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) -> Tuple[int, bool, str, List[Union[UUID, str]]]:
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 packed format,
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
- Use this function as argument to the multiprocessing function; it will create a new model that is saved
109
- in a temporary epc file and returns the required values, which are used in the multiprocessing function to
110
- recombine all the objects into a single epc file;
111
- the saucer_parameter is interpreted in one of two ways: (1) +ve fractoinal values between zero and one
112
- are the fractional distance from the centre of the points to its rim at which to sample the surface for
113
- extrapolation and thereby modify the recumbent z of flange points; 0 will usually give shallower and
114
- smoother saucer; larger values (must be less than one) will lead to stronger and more erratic saucer
115
- shape in flange; (2) other values between -90.0 and 90.0 are interpreted as an angle to apply out of
116
- the plane of the original points, to give a simple (and less computationally demanding) saucer shape;
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
- model.copy_uuid_from_other_model(s_model, uuid = str(surface_uuid))
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 = True,
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 = str(surface_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 = True,
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
- uuid_list.append(surface_uuid)
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
- grid_pc.add_cached_array_to_imported_list(
343
- array,
344
- f"from find_faces function for {surface.title}",
345
- f'{surface.title} {p_name}',
346
- discrete = True,
347
- property_kind = "grid bisector",
348
- facet_type = 'direction',
349
- facet = 'raw' if raw_bisector else ('vertical' if is_curtain else 'sloping'),
350
- realization = realisation,
351
- indexable_element = "columns" if is_curtain else "cells",
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()
@@ -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
- df[column] = pd.to_numeric(df[column], errors = 'ignore')
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
- df[column] = pd.to_numeric(df[column], errors = 'ignore')
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, inplace = True)
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
@@ -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.NaN,
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.NaN,
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.NaN,
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.NaN,
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.NaN,
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.NaN,
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.NaN, (perm_k.reshape(
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.NaN,
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:
@@ -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 Delauney triangulation of po and list of hull point indices, using a simple algorithm
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 = int).reshape((1, 3)), np.array([0, 1, 2], dtype = int)
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
- t = np.empty((2 * n_p + 2, 3), dtype = int) # empty space for triangle vertex indices
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 = int)
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 = int) # empty space for triangle edge indices
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 Delauney Triangulation of 2D point set p.
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 Delauney Triangulation - and if return_hull is True, another int array
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 Delauney Triangulation'
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 Delauney Triangulation algorithm name: {algorithm}')
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 Delauney triangulation.
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 Delauney triangulation
310
- t (numpy int array of shape (M, 3)): the Delauney triangulation of p as returned by dt()
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 Delauney triangulation
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 = int)
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('Delauney triangulation is not convex; Voronoi diagram construction might fail')
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 = int)
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 = int)
642
- wing_hull_segments = np.empty((o_count, 2), dtype = int)
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
- triangles = np.empty((t_count, 3), dtype = int)
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 = int)],
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 Delauney triangulation, which uses an xy projection to
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 of shape (N, 3) being xyz points in surrounding ring(s); z is set constant to
869
- mean value of z in p (optionally adjussted based on saucer_angle);
870
- N is count if inner_ring is False, 3 * count if True
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 = int)
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])