resqpy 4.5.0__py3-none-any.whl → 4.6.3__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- resqpy/__init__.py +1 -1
- resqpy/grid/_defined_geometry.py +15 -9
- resqpy/grid_surface/__init__.py +61 -40
- resqpy/grid_surface/_find_faces.py +351 -243
- resqpy/grid_surface/grid_surface_cuda.py +172 -125
- resqpy/lines/_common.py +10 -7
- resqpy/lines/_polyline.py +20 -0
- resqpy/lines/_polyline_set.py +64 -34
- resqpy/model/_hdf5.py +17 -7
- resqpy/model/_model.py +2 -1
- resqpy/model/_xml.py +4 -4
- resqpy/multi_processing/_multiprocessing.py +1 -0
- resqpy/multi_processing/wrappers/grid_surface_mp.py +12 -3
- resqpy/olio/intersection.py +2 -3
- resqpy/olio/read_nexus_fault.py +71 -67
- resqpy/olio/triangulation.py +66 -22
- resqpy/olio/vector_utilities.py +175 -71
- resqpy/olio/wellspec_keywords.py +5 -4
- resqpy/olio/write_hdf5.py +16 -8
- resqpy/olio/xml_et.py +3 -3
- resqpy/property/_collection_get_attributes.py +11 -5
- resqpy/property/_property.py +16 -5
- resqpy/property/property_collection.py +36 -11
- resqpy/surface/__init__.py +2 -2
- resqpy/surface/_surface.py +69 -6
- resqpy/time_series/__init__.py +3 -2
- resqpy/time_series/_time_series.py +10 -0
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/METADATA +3 -3
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/RECORD +31 -31
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/WHEEL +1 -1
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/LICENSE +0 -0
resqpy/olio/triangulation.py
CHANGED
@@ -751,9 +751,6 @@ def reorient(points, rough = True, max_dip = None, use_linalg = False):
|
|
751
751
|
the numpy linear algebra option seems to be memory intensive, not recommended
|
752
752
|
"""
|
753
753
|
|
754
|
-
def z_range(p):
|
755
|
-
return np.nanmax(p[..., 2]) - np.nanmin(p[..., 2])
|
756
|
-
|
757
754
|
def best_angles(points, mid_x, mid_y, steps, d_theta):
|
758
755
|
best_range = None
|
759
756
|
best_x_rotation = None
|
@@ -766,7 +763,7 @@ def reorient(points, rough = True, max_dip = None, use_linalg = False):
|
|
766
763
|
rotation_m = vec.rotation_3d_matrix((x_degrees, 0.0, y_degrees))
|
767
764
|
p = points.copy()
|
768
765
|
rotated_p = vec.rotate_array(rotation_m, p)
|
769
|
-
z_r =
|
766
|
+
z_r = np.nanmax(rotated_p[..., 2]) - np.nanmin(rotated_p[..., 2])
|
770
767
|
if best_range is None or z_r < best_range:
|
771
768
|
best_range = z_r
|
772
769
|
best_x_rotation = x_degrees
|
@@ -777,14 +774,13 @@ def reorient(points, rough = True, max_dip = None, use_linalg = False):
|
|
777
774
|
assert p.ndim >= 2 and p.shape[-1] == 3
|
778
775
|
p = p.reshape((-1, 3))
|
779
776
|
centre = p.sum(axis = 0) / p.shape[0]
|
780
|
-
|
777
|
+
_, _, vh = np.linalg.svd(p - centre)
|
781
778
|
# unit normal vector
|
782
779
|
return vh[2, :]
|
783
780
|
|
784
781
|
assert points.ndim >= 2 and points.shape[-1] == 3
|
785
782
|
|
786
783
|
if use_linalg:
|
787
|
-
|
788
784
|
normal_vector = linalg_normal_vector(points)
|
789
785
|
incl = vec.inclination(normal_vector)
|
790
786
|
if incl == 0.0:
|
@@ -792,10 +788,8 @@ def reorient(points, rough = True, max_dip = None, use_linalg = False):
|
|
792
788
|
else:
|
793
789
|
azi = vec.azimuth(normal_vector)
|
794
790
|
rotation_m = vec.tilt_3d_matrix(azi, incl)
|
795
|
-
|
796
791
|
else:
|
797
|
-
|
798
|
-
# coarse iteration trying a few different angles
|
792
|
+
# coarse iteration trying a few different angles
|
799
793
|
best_x_rotation, best_y_rotation = best_angles(points, 0.0, 0.0, 7, 30.0)
|
800
794
|
|
801
795
|
# finer iteration searching around the best coarse rotation
|
@@ -837,7 +831,7 @@ def make_all_clockwise_xy(t, p):
|
|
837
831
|
return t
|
838
832
|
|
839
833
|
|
840
|
-
def surrounding_xy_ring(p, count = 12, radial_factor = 10.0, radial_distance = None):
|
834
|
+
def surrounding_xy_ring(p, count = 12, radial_factor = 10.0, radial_distance = None, inner_ring = False):
|
841
835
|
"""Creates a set of points surrounding the point set p, in the xy plane.
|
842
836
|
|
843
837
|
arguments:
|
@@ -847,12 +841,23 @@ def surrounding_xy_ring(p, count = 12, radial_factor = 10.0, radial_distance = N
|
|
847
841
|
the 'radius' of the outermost points in p
|
848
842
|
radial_distance (float): if present, the radius of the ring of points, unless radial_factor
|
849
843
|
results in a greater distance in which case that is used
|
844
|
+
inner_ring (bool, default False): if True, an inner ring of points, with double count, is created
|
845
|
+
at a radius just outside that of the furthest flung original point; this improves triangulation
|
846
|
+
of the extended point set when the original has a non-convex hull
|
850
847
|
|
851
848
|
returns:
|
852
|
-
numpy float array of shape (
|
853
|
-
mean value of z in p
|
849
|
+
numpy float array of shape (N, 3) being xyz points in surrounding ring(s); z is set constant to
|
850
|
+
mean value of z in p; N is count if inner_ring is False, 3 * count if True
|
854
851
|
"""
|
855
852
|
|
853
|
+
def make_ring(count, centre, radius):
|
854
|
+
delta_theta = 2.0 * maths.pi / float(count)
|
855
|
+
ring = np.zeros((count, 3))
|
856
|
+
for i in range(count):
|
857
|
+
theta = float(i) * delta_theta
|
858
|
+
ring[i] = centre + radius * np.array([maths.cos(theta), maths.sin(theta), 0.0])
|
859
|
+
return ring
|
860
|
+
|
856
861
|
assert p.shape[-1] == 3
|
857
862
|
assert radial_factor >= 1.0
|
858
863
|
centre = np.nanmean(p.reshape((-1, 3)), axis = 0)
|
@@ -861,11 +866,12 @@ def surrounding_xy_ring(p, count = 12, radial_factor = 10.0, radial_distance = N
|
|
861
866
|
radius = p_radius * radial_factor
|
862
867
|
if radial_distance is not None and radial_distance > radius:
|
863
868
|
radius = radial_distance
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
+
ring = make_ring(count, centre, radius)
|
870
|
+
if inner_ring:
|
871
|
+
inner_radius = p_radius * 1.1
|
872
|
+
assert radius > inner_radius
|
873
|
+
in_ring = make_ring(2 * count, centre, inner_radius)
|
874
|
+
return np.concatenate((in_ring, ring), axis = 0)
|
869
875
|
return ring
|
870
876
|
|
871
877
|
|
@@ -896,6 +902,36 @@ def edges(t):
|
|
896
902
|
return np.unique(all_edges, axis = 0, return_counts = True)
|
897
903
|
|
898
904
|
|
905
|
+
def triangles_using_point(t, point_index):
|
906
|
+
"""Returns list-like 1D int array of indices of triangles using vertex identified by point_index."""
|
907
|
+
|
908
|
+
assert t.ndim == 2 and t.shape[1] == 3 and isinstance(point_index, int)
|
909
|
+
mask = np.any(t == point_index, axis = -1)
|
910
|
+
return np.where(mask)[0]
|
911
|
+
|
912
|
+
|
913
|
+
def triangles_using_edge(t, p1, p2):
|
914
|
+
"""Returns list-like 1D int array of indices of triangles using edge identified by pair of point indices."""
|
915
|
+
|
916
|
+
assert t.ndim == 2 and t.shape[1] == 3 and p1 != p2
|
917
|
+
mask = np.logical_and(np.any(t == p1, axis = -1), np.any(t == p2, axis = -1))
|
918
|
+
return np.where(mask)[0]
|
919
|
+
|
920
|
+
|
921
|
+
def triangles_using_edges(t, edges):
|
922
|
+
"""Returns int array of shape (len(edges), 2) with indices of upto 2 triangles using each edge (-1 for unused)."""
|
923
|
+
|
924
|
+
assert t.ndim == 2 and t.shape[1] == 3 and edges.ndim == 2 and edges.shape[1] == 2
|
925
|
+
ti = np.full((len(edges), 2), -1, dtype = int)
|
926
|
+
for i in range(len(edges)):
|
927
|
+
te = triangles_using_edge(t, edges[i, 0], edges[i, 1])
|
928
|
+
c = len(te)
|
929
|
+
assert 0 <= c <= 2
|
930
|
+
if c:
|
931
|
+
ti[i, :c] = te
|
932
|
+
return ti
|
933
|
+
|
934
|
+
|
899
935
|
def rim_edges(all_edges, edge_counts):
|
900
936
|
"""Returns a subset of all edges where the edge count is 1."""
|
901
937
|
|
@@ -904,6 +940,14 @@ def rim_edges(all_edges, edge_counts):
|
|
904
940
|
return all_edges[edge_counts == 1, :]
|
905
941
|
|
906
942
|
|
943
|
+
def internal_edges(all_edges, edge_counts):
|
944
|
+
"""Returns a subset of all edges where the edge count is 2."""
|
945
|
+
|
946
|
+
assert all_edges.ndim == 2 and all_edges.shape[1] == 2
|
947
|
+
assert edge_counts.ndim == 1 and edge_counts.size == len(all_edges)
|
948
|
+
return all_edges[edge_counts == 2, :]
|
949
|
+
|
950
|
+
|
907
951
|
def rims(all_rim_edges):
|
908
952
|
"""Returns edge index and points index lists of distinct rims.
|
909
953
|
|
@@ -949,7 +993,7 @@ def rims(all_rim_edges):
|
|
949
993
|
return rim_edges_list, rim_points_list
|
950
994
|
|
951
995
|
|
952
|
-
@njit
|
996
|
+
@njit # pragma: no cover
|
953
997
|
def _find_unused(ap: np.ndarray, used_mask: np.ndarray, v: int): # type: ignore
|
954
998
|
"""Finds the first unused occurence of v in pair list ap, returning index and paired value."""
|
955
999
|
for idx, val in np.ndenumerate(ap[:, 0]):
|
@@ -961,18 +1005,18 @@ def _find_unused(ap: np.ndarray, used_mask: np.ndarray, v: int): # type: ignore
|
|
961
1005
|
return ap.shape[0], -1
|
962
1006
|
|
963
1007
|
|
964
|
-
@njit
|
1008
|
+
@njit # pragma: no cover
|
965
1009
|
def _first_false(array: np.ndarray) -> Optional[int]: # type: ignore
|
966
|
-
"""Returns the index of the first False value in the array."""
|
1010
|
+
"""Returns the index of the first False (or zero) value in the 1D array."""
|
967
1011
|
for idx, val in np.ndenumerate(array):
|
968
1012
|
if not val:
|
969
1013
|
return idx[0]
|
970
1014
|
return array.size
|
971
1015
|
|
972
1016
|
|
973
|
-
@njit
|
1017
|
+
@njit # pragma: no cover
|
974
1018
|
def _first_match(array: np.ndarray, v: int) -> Optional[int]: # type: ignore
|
975
|
-
"""Returns the index of the first
|
1019
|
+
"""Returns the index of the first occurrence of value v in the 1D array."""
|
976
1020
|
for idx, val in np.ndenumerate(array):
|
977
1021
|
if val == v:
|
978
1022
|
return idx[0]
|
resqpy/olio/vector_utilities.py
CHANGED
@@ -75,10 +75,22 @@ def amplify(v, scaling): # note: could just use numpy a * scalar facility
|
|
75
75
|
def unit_vector(v):
|
76
76
|
"""Returns vector with same direction as v but with unit length."""
|
77
77
|
assert 2 <= len(v) <= 3
|
78
|
-
v = np.array(v, dtype =
|
79
|
-
|
78
|
+
v = np.array(v, dtype = np.float64)
|
79
|
+
norm = np.linalg.norm(v)
|
80
|
+
if norm == 0.0:
|
80
81
|
return v
|
81
|
-
|
82
|
+
v = v / norm
|
83
|
+
return v
|
84
|
+
|
85
|
+
|
86
|
+
@njit
|
87
|
+
def unit_vector_njit(v): # pragma: no cover
|
88
|
+
"""Returns vector with same direction as v but with unit length."""
|
89
|
+
norm = np.linalg.norm(v)
|
90
|
+
if norm == 0.0:
|
91
|
+
return v
|
92
|
+
v = v / norm
|
93
|
+
return v
|
82
94
|
|
83
95
|
|
84
96
|
def unit_vectors(v):
|
@@ -189,6 +201,7 @@ def nan_inclinations(a, already_unit_vectors = False):
|
|
189
201
|
|
190
202
|
def points_direction_vector(a, axis):
|
191
203
|
"""Returns an average direction vector based on first and last non-NaN points or slices in given axis."""
|
204
|
+
# note: as currently coded, might give poor results with some patterns of NaNs
|
192
205
|
|
193
206
|
assert a.ndim > 1 and 0 <= axis < a.ndim - 1 and a.shape[-1] > 1 and a.shape[axis] > 1
|
194
207
|
if np.all(np.isnan(a)):
|
@@ -196,17 +209,18 @@ def points_direction_vector(a, axis):
|
|
196
209
|
start = 0
|
197
210
|
start_slicing = [slice(None)] * a.ndim
|
198
211
|
while True:
|
199
|
-
start_slicing[axis] = slice(start)
|
212
|
+
start_slicing[axis] = slice(start, start + 1)
|
200
213
|
if not np.all(np.isnan(a[tuple(start_slicing)])):
|
201
214
|
break
|
202
215
|
start += 1
|
216
|
+
assert start < a.shape[axis]
|
203
217
|
finish = a.shape[axis] - 1
|
204
218
|
finish_slicing = [slice(None)] * a.ndim
|
205
219
|
while True:
|
206
|
-
finish_slicing[axis] = slice(finish)
|
220
|
+
finish_slicing[axis] = slice(finish, finish + 1)
|
207
221
|
if not np.all(np.isnan(a[tuple(finish_slicing)])):
|
208
222
|
break
|
209
|
-
finish
|
223
|
+
finish -= 1
|
210
224
|
if start >= finish:
|
211
225
|
return None
|
212
226
|
if a.ndim > 2:
|
@@ -220,24 +234,24 @@ def points_direction_vector(a, axis):
|
|
220
234
|
return finish_p - start_p
|
221
235
|
|
222
236
|
|
223
|
-
def dot_product(a, b):
|
237
|
+
def dot_product(a, b): # pragma: no cover
|
224
238
|
"""Returns the dot product (scalar product) of the two vectors."""
|
225
239
|
return np.dot(a, b)
|
226
240
|
|
227
241
|
|
228
|
-
def dot_products(a, b):
|
242
|
+
def dot_products(a, b): # pragma: no cover
|
229
243
|
"""Returns the dot products of pairs of vectors; last axis covers element of a vector."""
|
230
244
|
return np.sum(a * b, axis = -1)
|
231
245
|
|
232
246
|
|
233
|
-
def cross_product(a, b):
|
247
|
+
def cross_product(a, b): # pragma: no cover
|
234
248
|
"""Returns the cross product (vector product) of the two vectors."""
|
235
249
|
return np.cross(a, b)
|
236
250
|
|
237
251
|
|
238
|
-
def naive_length(v):
|
252
|
+
def naive_length(v): # pragma: no cover
|
239
253
|
"""Returns the length of the vector assuming consistent units."""
|
240
|
-
return
|
254
|
+
return np.linalg.norm(v)
|
241
255
|
|
242
256
|
|
243
257
|
def naive_lengths(v):
|
@@ -245,9 +259,9 @@ def naive_lengths(v):
|
|
245
259
|
return np.sqrt(np.sum(v * v, axis = -1))
|
246
260
|
|
247
261
|
|
248
|
-
def naive_2d_length(v):
|
262
|
+
def naive_2d_length(v): # pragma: no cover
|
249
263
|
"""Returns the length of the vector projected onto xy plane, assuming consistent units."""
|
250
|
-
return
|
264
|
+
return np.linalg.norm(v[0:2])
|
251
265
|
|
252
266
|
|
253
267
|
def naive_2d_lengths(v):
|
@@ -257,9 +271,20 @@ def naive_2d_lengths(v):
|
|
257
271
|
|
258
272
|
|
259
273
|
def unit_corrected_length(v, unit_conversion):
|
260
|
-
"""Returns the length of the vector v after applying the unit_conversion factors.
|
261
|
-
|
262
|
-
|
274
|
+
"""Returns the length of the vector v after applying the unit_conversion factors.
|
275
|
+
|
276
|
+
arguments:
|
277
|
+
v (1D numpy float array): vector with mixed units of measure
|
278
|
+
unit_conversion (1D numpy float array): vector to multiply elements of v by, prior to finding length
|
279
|
+
|
280
|
+
returns:
|
281
|
+
float, being the length of v after adjustment by unit_conversion
|
282
|
+
|
283
|
+
notes:
|
284
|
+
example unit_conversion might be:
|
285
|
+
[1.0, 1.0, 0.3048] to convert z from feet to metres, or
|
286
|
+
[3.28084, 3.28084, 1.0] to convert x and y from metres to feet
|
287
|
+
"""
|
263
288
|
converted = elemental_multiply(v, unit_conversion)
|
264
289
|
return naive_length(converted)
|
265
290
|
|
@@ -293,14 +318,12 @@ def rotation_matrix_3d_axial(axis, angle):
|
|
293
318
|
this function follows the mathematical convention: a positive angle results in anti-clockwise rotation
|
294
319
|
when viewed in direction of positive axis
|
295
320
|
"""
|
296
|
-
|
297
321
|
axis_a = (axis + 1) % 3
|
298
322
|
axis_b = (axis_a + 1) % 3
|
299
|
-
matrix = np.
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
sine = maths.sin(radians)
|
323
|
+
matrix = np.eye(3)
|
324
|
+
radians = np.radians(angle)
|
325
|
+
cosine = np.cos(radians)
|
326
|
+
sine = np.sin(radians)
|
304
327
|
matrix[axis_a, axis_a] = cosine
|
305
328
|
matrix[axis_b, axis_b] = cosine
|
306
329
|
matrix[axis_a, axis_b] = -sine # left handed coordinate system, eg. UTM & depth
|
@@ -308,45 +331,66 @@ def rotation_matrix_3d_axial(axis, angle):
|
|
308
331
|
return matrix
|
309
332
|
|
310
333
|
|
311
|
-
def no_rotation_matrix():
|
312
|
-
"""Returns a rotation matrix which will not move points."""
|
313
|
-
|
314
|
-
for axis in range(3):
|
315
|
-
matrix[axis, axis] = 1.0
|
316
|
-
return matrix
|
334
|
+
def no_rotation_matrix(): # pragma: no cover
|
335
|
+
"""Returns a rotation matrix which will not move points (identity matrix)."""
|
336
|
+
return np.eye(3)
|
317
337
|
|
318
338
|
|
319
339
|
def rotation_3d_matrix(xzy_axis_angles):
|
320
|
-
"""Returns a rotation matrix which will rotate points about
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
340
|
+
"""Returns a rotation matrix which will rotate points about the x, z, then y axis by angles in degrees."""
|
341
|
+
angles = np.radians(xzy_axis_angles)
|
342
|
+
cos_c, cos_a, cos_b = np.cos(angles)
|
343
|
+
sin_c, sin_a, sin_b = np.sin(angles)
|
344
|
+
|
345
|
+
sin_a_sin_c = sin_a * sin_c
|
346
|
+
sin_a_cos_c = sin_a * cos_c
|
347
|
+
|
348
|
+
rotation_matrix = np.array([
|
349
|
+
[cos_a * cos_b, -sin_a_cos_c * cos_b + sin_b * sin_c, cos_b * sin_a_sin_c + sin_b * cos_c],
|
350
|
+
[sin_a, cos_a * cos_c, -sin_c * cos_a],
|
351
|
+
[-sin_b * cos_a, sin_a_cos_c * sin_b + cos_b * sin_c, -sin_b * sin_a_sin_c + cos_b * cos_c],
|
352
|
+
])
|
353
|
+
return rotation_matrix
|
354
|
+
|
355
|
+
|
356
|
+
@njit
|
357
|
+
def rotation_3d_matrix_njit(xzy_axis_angles): # pragma: no cover
|
358
|
+
"""Returns a rotation matrix which will rotate points about the x, z, then y axis by angles in degrees."""
|
359
|
+
angles = np.radians(xzy_axis_angles)
|
360
|
+
cos_c, cos_a, cos_b = np.cos(angles)
|
361
|
+
sin_c, sin_a, sin_b = np.sin(angles)
|
362
|
+
|
363
|
+
sin_a_sin_c = sin_a * sin_c
|
364
|
+
sin_a_cos_c = sin_a * cos_c
|
331
365
|
|
366
|
+
rotation_matrix = np.array([
|
367
|
+
[cos_a * cos_b, -sin_a_cos_c * cos_b + sin_b * sin_c, cos_b * sin_a_sin_c + sin_b * cos_c],
|
368
|
+
[sin_a, cos_a * cos_c, -sin_c * cos_a],
|
369
|
+
[-sin_b * cos_a, sin_a_cos_c * sin_b + cos_b * sin_c, -sin_b * sin_a_sin_c + cos_b * cos_c],
|
370
|
+
])
|
371
|
+
return rotation_matrix
|
332
372
|
|
333
|
-
|
334
|
-
|
373
|
+
|
374
|
+
def reverse_rotation_3d_matrix(xzy_axis_angles): # pragma: no cover
|
375
|
+
"""Returns a rotation matrix which will rotate points about the y, z, then x axis by angles in degrees."""
|
335
376
|
|
336
377
|
return rotation_3d_matrix(xzy_axis_angles).T
|
337
378
|
|
338
379
|
|
339
|
-
def rotate_vector(rotation_matrix, vector):
|
380
|
+
def rotate_vector(rotation_matrix, vector): # pragma: no cover
|
340
381
|
"""Returns the rotated vector."""
|
341
|
-
|
342
382
|
return np.dot(rotation_matrix, vector)
|
343
383
|
|
344
384
|
|
345
385
|
def rotate_array(rotation_matrix, a):
|
346
386
|
"""Returns a copy of array a with each vector rotated by the rotation matrix."""
|
387
|
+
return np.matmul(rotation_matrix, a.reshape(-1, 3).T).T.reshape(a.shape)
|
347
388
|
|
348
|
-
|
349
|
-
|
389
|
+
|
390
|
+
@njit
|
391
|
+
def rotate_array_njit(rotation_matrix, a): # pragma: no cover
|
392
|
+
"""Returns a copy of array a with each vector rotated by the rotation matrix."""
|
393
|
+
return np.dot(rotation_matrix, a.reshape(-1, 3).T).T.reshape(a.shape)
|
350
394
|
|
351
395
|
|
352
396
|
def rotate_xyz_array_around_z_axis(a, target_xy_vector):
|
@@ -399,15 +443,32 @@ def tilt_3d_matrix(azimuth, dip):
|
|
399
443
|
|
400
444
|
|
401
445
|
def rotation_matrix_3d_vector(v):
|
446
|
+
"""Returns a rotation matrix which will rotate vector v to the vertical (z) axis.
|
447
|
+
|
448
|
+
note:
|
449
|
+
the returned matrix will map a positive z axis vector onto v
|
450
|
+
"""
|
451
|
+
v = v / np.linalg.norm(v)
|
452
|
+
kmat = np.array([[0, 0, -v[0]], [0, 0, -v[1]], [v[0], v[1], 0]])
|
453
|
+
rotation_matrix = np.eye(3) + kmat + kmat.dot(kmat) * (1 / (1 + v[2]))
|
454
|
+
|
455
|
+
rotation_matrix[:2] = -rotation_matrix[:2]
|
456
|
+
return rotation_matrix
|
457
|
+
|
458
|
+
|
459
|
+
@njit
|
460
|
+
def rotation_matrix_3d_vector_njit(v): # pragma: no cover
|
402
461
|
"""Returns a rotation matrix which will rotate points by inclination and azimuth of vector.
|
403
462
|
|
404
463
|
note:
|
405
464
|
the returned matrix will map a positive z axis vector onto v
|
406
465
|
"""
|
466
|
+
v = v / np.linalg.norm(v)
|
467
|
+
kmat = np.array([[0, 0, -v[0]], [0, 0, -v[1]], [v[0], v[1], 0]])
|
468
|
+
rotation_matrix = np.eye(3) + kmat + kmat.dot(kmat) * (1 / (1 + v[2]))
|
407
469
|
|
408
|
-
|
409
|
-
|
410
|
-
return m
|
470
|
+
rotation_matrix[:2] = -rotation_matrix[:2]
|
471
|
+
return rotation_matrix
|
411
472
|
|
412
473
|
|
413
474
|
def tilt_points(pivot_xyz, azimuth, dip, points):
|
@@ -562,7 +623,7 @@ def points_in_triangles(p, t, da, projection = 'xy', edged = False):
|
|
562
623
|
|
563
624
|
|
564
625
|
@njit
|
565
|
-
def point_in_polygon(x, y, polygon):
|
626
|
+
def point_in_polygon(x, y, polygon): # pragma: no cover
|
566
627
|
"""Calculates if a point in within a polygon in 2D.
|
567
628
|
|
568
629
|
arguments:
|
@@ -576,14 +637,14 @@ def point_in_polygon(x, y, polygon):
|
|
576
637
|
note:
|
577
638
|
the polygon is assumed closed, the closing point should not be repeated
|
578
639
|
"""
|
579
|
-
|
640
|
+
n = len(polygon)
|
580
641
|
inside = False
|
581
642
|
p2x = 0.0
|
582
643
|
p2y = 0.0
|
583
644
|
xints = 0.0
|
584
645
|
p1x, p1y = polygon[0]
|
585
|
-
for i in
|
586
|
-
p2x, p2y = polygon[i %
|
646
|
+
for i in range(n + 1):
|
647
|
+
p2x, p2y = polygon[i % n]
|
587
648
|
if y > min(p1y, p2y):
|
588
649
|
if y <= max(p1y, p2y):
|
589
650
|
if x <= max(p1x, p2x):
|
@@ -592,11 +653,12 @@ def point_in_polygon(x, y, polygon):
|
|
592
653
|
if p1x == p2x or x <= xints:
|
593
654
|
inside = not inside
|
594
655
|
p1x, p1y = p2x, p2y
|
656
|
+
|
595
657
|
return inside
|
596
658
|
|
597
659
|
|
598
660
|
@njit
|
599
|
-
def point_in_triangle(x, y, triangle):
|
661
|
+
def point_in_triangle(x, y, triangle): # pragma: no cover
|
600
662
|
"""Calculates if a point in within a triangle in 2D.
|
601
663
|
|
602
664
|
arguments:
|
@@ -666,7 +728,7 @@ def point_in_triangle(x, y, triangle):
|
|
666
728
|
return inside
|
667
729
|
|
668
730
|
|
669
|
-
@njit
|
731
|
+
@njit(parallel = True) # pragma: no cover
|
670
732
|
def points_in_polygon(points: np.ndarray, polygon: np.ndarray, points_xlen: int, polygon_num: int = 0) -> np.ndarray:
|
671
733
|
"""Calculates which points are within a polygon in 2D.
|
672
734
|
|
@@ -683,17 +745,18 @@ def points_in_polygon(points: np.ndarray, polygon: np.ndarray, points_xlen: int,
|
|
683
745
|
note:
|
684
746
|
the polygon is assumed closed, the closing point should not be repeated
|
685
747
|
"""
|
686
|
-
polygon_points = np.
|
748
|
+
polygon_points = np.full((len(points), 3), -1, dtype = np.int32)
|
687
749
|
for point_num in numba.prange(len(points)):
|
688
|
-
|
750
|
+
x, y = points[point_num]
|
751
|
+
p = point_in_polygon(x, y, polygon)
|
689
752
|
if p is True:
|
690
753
|
j, i = divmod(point_num, points_xlen)
|
691
|
-
polygon_points =
|
754
|
+
polygon_points[point_num] = [polygon_num, j, i]
|
692
755
|
|
693
|
-
return polygon_points
|
756
|
+
return polygon_points[polygon_points[:, 0] != -1]
|
694
757
|
|
695
758
|
|
696
|
-
@njit
|
759
|
+
@njit # pragma: no cover
|
697
760
|
def points_in_triangle(points: np.ndarray, triangle: np.ndarray, points_xlen: int, triangle_num: int = 0) -> np.ndarray:
|
698
761
|
"""Calculates which points are within a triangle in 2D.
|
699
762
|
|
@@ -707,19 +770,17 @@ def points_in_triangle(points: np.ndarray, triangle: np.ndarray, points_xlen: in
|
|
707
770
|
triangle_points (np.ndarray): 2D array containing only the points within the triangle,
|
708
771
|
with each row being the triangle number, points y index, and points x index.
|
709
772
|
"""
|
710
|
-
triangle_points = np.
|
773
|
+
triangle_points = np.full((len(points), 3), -1, dtype = np.int32)
|
711
774
|
for point_num in numba.prange(len(points)):
|
712
775
|
p = point_in_triangle(points[point_num, 0], points[point_num, 1], triangle)
|
713
776
|
if p is True:
|
714
777
|
yi, xi = divmod(point_num, points_xlen)
|
715
|
-
triangle_points =
|
716
|
-
np.array([[triangle_num, yi, xi]], dtype = numba.int32),
|
717
|
-
axis = 0)
|
778
|
+
triangle_points[point_num] = [triangle_num, yi, xi]
|
718
779
|
|
719
|
-
return triangle_points
|
780
|
+
return triangle_points[triangle_points[:, 0] != -1]
|
720
781
|
|
721
782
|
|
722
|
-
@njit
|
783
|
+
@njit # pragma: no cover
|
723
784
|
def mesh_points_in_triangle(triangle: np.ndarray,
|
724
785
|
points_xlen: int,
|
725
786
|
points_ylen: int,
|
@@ -751,7 +812,7 @@ def mesh_points_in_triangle(triangle: np.ndarray,
|
|
751
812
|
return triangle_points
|
752
813
|
|
753
814
|
|
754
|
-
@njit
|
815
|
+
@njit # pragma: no cover
|
755
816
|
def points_in_polygons(points: np.ndarray, polygons: np.ndarray, points_xlen: int) -> np.ndarray:
|
756
817
|
"""Calculates which points are within which polygons in 2D.
|
757
818
|
|
@@ -793,7 +854,7 @@ def points_in_triangles_njit(points: np.ndarray, triangles: np.ndarray, points_x
|
|
793
854
|
return triangles_points
|
794
855
|
|
795
856
|
|
796
|
-
@njit
|
857
|
+
@njit # pragma: no cover
|
797
858
|
def meshgrid(x: np.ndarray, y: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
798
859
|
"""Returns coordinate matrices from coordinate vectors x and y.
|
799
860
|
|
@@ -861,7 +922,7 @@ def points_in_triangles_aligned(nx: int, ny: int, dx: float, dy: float, triangle
|
|
861
922
|
return triangles_points
|
862
923
|
|
863
924
|
|
864
|
-
@njit
|
925
|
+
@njit # pragma: no cover
|
865
926
|
def triangle_box(triangle: np.ndarray) -> Tuple[float, float, float, float]:
|
866
927
|
"""Finds the minimum and maximum x and y values of a single traingle.
|
867
928
|
|
@@ -907,7 +968,7 @@ def vertical_intercept(x: float, x_values: np.ndarray, y_values: np.ndarray) ->
|
|
907
968
|
return y
|
908
969
|
|
909
970
|
|
910
|
-
@njit
|
971
|
+
@njit # pragma: no cover
|
911
972
|
def points_in_triangles_aligned_optimised(nx: int, ny: int, dx: float, dy: float, triangles: np.ndarray) -> np.ndarray:
|
912
973
|
"""Calculates which points are within which triangles in 2D for a regular mesh of aligned points.
|
913
974
|
|
@@ -961,7 +1022,7 @@ def triangle_normal_vector(p3):
|
|
961
1022
|
|
962
1023
|
|
963
1024
|
@njit
|
964
|
-
def triangle_normal_vector_numba(points):
|
1025
|
+
def triangle_normal_vector_numba(points): # pragma: no cover
|
965
1026
|
"""For a triangle in 3D space, defined by 3 vertex points, returns a unit vector normal to the plane of the triangle.
|
966
1027
|
|
967
1028
|
note:
|
@@ -1120,13 +1181,56 @@ def xy_sorted(p, axis = None):
|
|
1120
1181
|
"""
|
1121
1182
|
assert p.ndim >= 2 and p.shape[-1] >= 2
|
1122
1183
|
p = p.reshape((-1, p.shape[-1]))
|
1123
|
-
xy_range = np.nanmax(p, axis = 0) - np.nanmin(p, axis = 0)
|
1124
1184
|
if axis is None:
|
1185
|
+
xy_range = np.nanmax(p, axis = 0) - np.nanmin(p, axis = 0)
|
1125
1186
|
axis = (1 if xy_range[1] > xy_range[0] else 0)
|
1126
1187
|
spi = np.argsort(p[:, axis])
|
1127
1188
|
return p[spi], axis
|
1128
1189
|
|
1129
1190
|
|
1191
|
+
@njit
|
1192
|
+
def xy_sorted_njit(p, axis = -1): # pragma: no cover
|
1193
|
+
"""Returns copy of points p sorted according to x or y (whichever has greater range)."""
|
1194
|
+
assert p.ndim >= 2 and p.shape[-1] >= 2
|
1195
|
+
p = p.reshape((-1, p.shape[-1]))
|
1196
|
+
if axis == -1:
|
1197
|
+
xy_range = _nanmax(p) - _nanmin(p)
|
1198
|
+
axis = (1 if xy_range[1] > xy_range[0] else 0)
|
1199
|
+
points = p[:, axis]
|
1200
|
+
spi = np.argsort(points)
|
1201
|
+
return p[spi], axis
|
1202
|
+
|
1203
|
+
|
1204
|
+
@njit
|
1205
|
+
def _nanmax(array): # pragma: no cover
|
1206
|
+
"""Numba implementation of np.nanmax with axis = 0."""
|
1207
|
+
len0 = array.shape[1]
|
1208
|
+
max_array = np.empty(len0)
|
1209
|
+
for col in range(len0):
|
1210
|
+
col_array = array[:, col]
|
1211
|
+
max_val = col_array[0]
|
1212
|
+
for val in col_array:
|
1213
|
+
if val > max_val and not np.isnan(val):
|
1214
|
+
max_val = val
|
1215
|
+
max_array[col] = max_val
|
1216
|
+
return max_array
|
1217
|
+
|
1218
|
+
|
1219
|
+
@njit
|
1220
|
+
def _nanmin(array): # pragma: no cover
|
1221
|
+
"""Numba implementation of np.nanmin with axis = 0."""
|
1222
|
+
len0 = array.shape[1]
|
1223
|
+
min_array = np.empty(len0)
|
1224
|
+
for col in range(len0):
|
1225
|
+
col_array = array[:, col]
|
1226
|
+
min_val = col_array[0]
|
1227
|
+
for val in col_array:
|
1228
|
+
if val < min_val and not np.isnan(val):
|
1229
|
+
min_val = val
|
1230
|
+
min_array[col] = min_val
|
1231
|
+
return min_array
|
1232
|
+
|
1233
|
+
|
1130
1234
|
def _projected_xyz_axes(projection):
|
1131
1235
|
assert projection in ['xy', 'xz', 'yz'], f'invalid projection {projection}'
|
1132
1236
|
a0 = 'xyz'.index(projection[0])
|
resqpy/olio/wellspec_keywords.py
CHANGED
@@ -382,6 +382,7 @@ def get_well_pointers(
|
|
382
382
|
words = line.split()
|
383
383
|
assert len(words) >= 2, "Missing date after TIME keyword."
|
384
384
|
date = words[1]
|
385
|
+
date_obj = None
|
385
386
|
if '(' in date:
|
386
387
|
# sometimes user specifies (HH:MM:SS) along with date - can separate time from date with this check
|
387
388
|
date = date.split('(')[0]
|
@@ -390,13 +391,13 @@ def get_well_pointers(
|
|
390
391
|
date_obj = datetime.datetime.strptime(date, "%m/%d/%Y").date()
|
391
392
|
else:
|
392
393
|
date_obj = datetime.datetime.strptime(date, "%d/%m/%Y").date()
|
393
|
-
if no_date_replacement is not None and date_obj < no_date_replacement:
|
394
|
-
raise ValueError(
|
395
|
-
f"The Zero Date {no_date_replacement} must be before the first wellspec TIME {date_obj}.")
|
396
|
-
date = date_obj.isoformat()
|
397
394
|
except ValueError:
|
398
395
|
raise ValueError(f"The date found '{date}' does not match the correct format (usa_date_format "
|
399
396
|
f"is {usa_date_format}).")
|
397
|
+
if no_date_replacement is not None and date_obj < no_date_replacement:
|
398
|
+
raise ValueError(
|
399
|
+
f"The Zero Date {no_date_replacement} must be before the first wellspec TIME {date_obj}.")
|
400
|
+
date = date_obj.isoformat()
|
400
401
|
time_pointers[file.tell()] = date
|
401
402
|
|
402
403
|
current_date = None # Before first TIME
|