resqpy 4.5.0__py3-none-any.whl → 4.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resqpy/__init__.py +1 -1
- resqpy/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
|