resqpy 4.14.2__py3-none-any.whl → 5.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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])