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.
- resqpy/__init__.py +1 -1
- resqpy/fault/_gcs_functions.py +10 -10
- resqpy/fault/_grid_connection_set.py +277 -113
- resqpy/grid/__init__.py +2 -3
- resqpy/grid/_defined_geometry.py +3 -3
- resqpy/grid/_extract_functions.py +2 -1
- resqpy/grid/_grid.py +95 -12
- resqpy/grid/_grid_types.py +22 -7
- resqpy/grid/_points_functions.py +1 -1
- resqpy/grid/_regular_grid.py +6 -2
- resqpy/grid_surface/__init__.py +17 -38
- resqpy/grid_surface/_blocked_well_populate.py +5 -5
- resqpy/grid_surface/_find_faces.py +1349 -253
- resqpy/lines/_polyline.py +24 -33
- resqpy/model/_catalogue.py +9 -0
- resqpy/model/_forestry.py +18 -14
- resqpy/model/_hdf5.py +11 -3
- resqpy/model/_model.py +85 -10
- resqpy/model/_xml.py +38 -13
- resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
- resqpy/olio/read_nexus_fault.py +8 -2
- resqpy/olio/relperm.py +1 -1
- resqpy/olio/transmission.py +8 -8
- resqpy/olio/triangulation.py +36 -30
- resqpy/olio/vector_utilities.py +340 -6
- resqpy/olio/volume.py +0 -20
- resqpy/olio/wellspec_keywords.py +19 -13
- resqpy/olio/write_hdf5.py +1 -1
- resqpy/olio/xml_et.py +12 -0
- resqpy/property/__init__.py +6 -4
- resqpy/property/_collection_add_part.py +4 -3
- resqpy/property/_collection_create_xml.py +4 -2
- resqpy/property/_collection_get_attributes.py +4 -0
- resqpy/property/attribute_property_set.py +311 -0
- resqpy/property/grid_property_collection.py +11 -11
- resqpy/property/property_collection.py +79 -31
- resqpy/property/property_common.py +3 -8
- resqpy/rq_import/_add_surfaces.py +34 -14
- resqpy/rq_import/_grid_from_cp.py +2 -2
- resqpy/rq_import/_import_nexus.py +75 -48
- resqpy/rq_import/_import_vdb_all_grids.py +64 -52
- resqpy/rq_import/_import_vdb_ensemble.py +12 -13
- resqpy/surface/_mesh.py +4 -0
- resqpy/surface/_surface.py +593 -118
- resqpy/surface/_tri_mesh.py +13 -10
- resqpy/surface/_tri_mesh_stencil.py +4 -4
- resqpy/surface/_triangulated_patch.py +71 -51
- resqpy/time_series/_any_time_series.py +7 -4
- resqpy/time_series/_geologic_time_series.py +1 -1
- resqpy/unstructured/_hexa_grid.py +6 -2
- resqpy/unstructured/_prism_grid.py +13 -5
- resqpy/unstructured/_pyramid_grid.py +6 -2
- resqpy/unstructured/_tetra_grid.py +6 -2
- resqpy/unstructured/_unstructured_grid.py +6 -2
- resqpy/well/_blocked_well.py +1986 -1946
- resqpy/well/_deviation_survey.py +3 -3
- resqpy/well/_md_datum.py +11 -21
- resqpy/well/_trajectory.py +10 -5
- resqpy/well/_wellbore_frame.py +10 -2
- resqpy/well/blocked_well_frame.py +3 -3
- resqpy/well/well_object_funcs.py +7 -9
- resqpy/well/well_utils.py +33 -0
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/LICENSE +0 -0
resqpy/olio/vector_utilities.py
CHANGED
@@ -13,8 +13,8 @@ log = logging.getLogger(__name__)
|
|
13
13
|
import math as maths
|
14
14
|
import numpy as np
|
15
15
|
import numba # type: ignore
|
16
|
-
from numba import njit # type: ignore
|
17
|
-
from typing import Tuple, Optional
|
16
|
+
from numba import njit, prange # type: ignore
|
17
|
+
from typing import Tuple, Optional, List
|
18
18
|
|
19
19
|
|
20
20
|
def radians_from_degrees(deg):
|
@@ -952,13 +952,13 @@ def triangle_box(triangle: np.ndarray) -> Tuple[float, float, float, float]: #
|
|
952
952
|
"""
|
953
953
|
x_values = triangle[:, 0]
|
954
954
|
y_values = triangle[:, 1]
|
955
|
-
return min(x_values), max(x_values), min(y_values), max(y_values)
|
955
|
+
return np.min(x_values), np.max(x_values), np.min(y_values), np.max(y_values)
|
956
956
|
|
957
957
|
|
958
958
|
@njit
|
959
959
|
def vertical_intercept(x: float, x_values: np.ndarray, y_values: np.ndarray) -> Optional[float]: # pragma: no cover
|
960
960
|
"""Finds the y value of a straight line between two points at a given x.
|
961
|
-
|
961
|
+
|
962
962
|
If the x value given is not within the x values of the points, returns None.
|
963
963
|
|
964
964
|
arguments:
|
@@ -982,8 +982,81 @@ def vertical_intercept(x: float, x_values: np.ndarray, y_values: np.ndarray) ->
|
|
982
982
|
|
983
983
|
|
984
984
|
@njit
|
985
|
-
def
|
986
|
-
|
985
|
+
def vertical_intercept_nan(x: float, x_value_0: float, x_value_1: float, y_value_0: float,
|
986
|
+
y_value_1: float) -> float: # pragma: no cover
|
987
|
+
"""Finds the y value of a straight line between two points at a given x.
|
988
|
+
|
989
|
+
If the x value given is not within the x values of the points, returns NaN.
|
990
|
+
|
991
|
+
arguments:
|
992
|
+
x (float): x value at which to determine the y value
|
993
|
+
x_value_0 (np.ndarray): the x coordinate of point 1
|
994
|
+
x_value_1 (np.ndarray): the x coordinate of point 2
|
995
|
+
y_value_0 (np.ndarray): the y coordinate of point 1
|
996
|
+
y_value_1 (np.ndarray): the y coordinate of point 2
|
997
|
+
|
998
|
+
returns:
|
999
|
+
y (float): y value of the straight line segment between point 1 and point 2,
|
1000
|
+
evaluated at x; if x is outside the x values range, y is NaN
|
1001
|
+
"""
|
1002
|
+
y = np.nan
|
1003
|
+
if x_value_1 < x_value_0:
|
1004
|
+
x_value_0, x_value_1 = x_value_1, x_value_0
|
1005
|
+
y_value_0, y_value_1 = y_value_1, y_value_0
|
1006
|
+
if x >= x_value_0 and x <= x_value_1:
|
1007
|
+
if x_value_0 == x_value_1:
|
1008
|
+
y = y_value_0
|
1009
|
+
else:
|
1010
|
+
m = (y_value_1 - y_value_0) / (x_value_1 - x_value_0)
|
1011
|
+
c = y_value_1 - m * x_value_1
|
1012
|
+
y = m * x + c
|
1013
|
+
return y
|
1014
|
+
|
1015
|
+
|
1016
|
+
@njit
|
1017
|
+
def vertical_intercept_nan_yz(x: float, x_0: float, x_1: float, y_0: float, y_1: float, z_0: float,
|
1018
|
+
z_1: float) -> Tuple[float, float]: # pragma: no cover
|
1019
|
+
"""Finds the y & z values of a straight line between two points at a given x.
|
1020
|
+
|
1021
|
+
If the x value given is not within the x values of the points, returns NaN.
|
1022
|
+
|
1023
|
+
arguments:
|
1024
|
+
x (float): x value at which to determine the y value
|
1025
|
+
x_0 (float): the x coordinate of point a
|
1026
|
+
x_1 (float): the x coordinate of point b
|
1027
|
+
y_0 (float): the y coordinate of point a
|
1028
|
+
y_1 (float): the y coordinate of point b
|
1029
|
+
z_0 (float): the z coordinate of point a
|
1030
|
+
z_1 (float): the z coordinate of point b
|
1031
|
+
|
1032
|
+
returns:
|
1033
|
+
y, z (float, float): y & z values of the straight line segment between point a and point b,
|
1034
|
+
evaluated at x; if x is outside the x values range, y & z are NaN
|
1035
|
+
"""
|
1036
|
+
y = np.nan
|
1037
|
+
z = np.nan
|
1038
|
+
if x_1 < x_0:
|
1039
|
+
x_0, x_1 = x_1, x_0
|
1040
|
+
y_0, y_1 = y_1, y_0
|
1041
|
+
z_0, z_1 = z_1, z_0
|
1042
|
+
if x >= x_0 and x <= x_1:
|
1043
|
+
if x_0 == x_1:
|
1044
|
+
y = y_0
|
1045
|
+
z = z_0
|
1046
|
+
else:
|
1047
|
+
xr = x_1 - x_0
|
1048
|
+
m = (y_1 - y_0) / xr
|
1049
|
+
c = y_1 - m * x_1
|
1050
|
+
y = m * x + c
|
1051
|
+
m = (z_1 - z_0) / xr
|
1052
|
+
c = z_1 - m * x_1
|
1053
|
+
z = m * x + c
|
1054
|
+
return (y, z)
|
1055
|
+
|
1056
|
+
|
1057
|
+
@njit
|
1058
|
+
def points_in_triangles_aligned_optimised_old(nx: int, ny: int, dx: float, dy: float,
|
1059
|
+
triangles: np.ndarray) -> np.ndarray: # pragma: no cover
|
987
1060
|
"""Calculates which points are within which triangles in 2D for a regular mesh of aligned points.
|
988
1061
|
|
989
1062
|
arguments:
|
@@ -1023,6 +1096,249 @@ def points_in_triangles_aligned_optimised(nx: int, ny: int, dx: float, dy: float
|
|
1023
1096
|
return triangles_points
|
1024
1097
|
|
1025
1098
|
|
1099
|
+
@njit
|
1100
|
+
def points_in_triangles_aligned_optimised_serial(nx: int, ny: int, dx: float, dy: float,
|
1101
|
+
triangles: np.ndarray) -> np.ndarray: # pragma: no cover
|
1102
|
+
"""Calculates which points are within which triangles in 2D for a regular mesh of aligned points.
|
1103
|
+
|
1104
|
+
arguments:
|
1105
|
+
nx (int): number of points in x axis
|
1106
|
+
ny (int): number of points in y axis
|
1107
|
+
dx (float): spacing of points in x axis (first point is at half dx)
|
1108
|
+
dy (float): spacing of points in y axis (first point is at half dy)
|
1109
|
+
triangles (np.ndarray): float array of each triangles' vertices in 2D, shape (N, 3, 2)
|
1110
|
+
|
1111
|
+
returns:
|
1112
|
+
triangles_points (np.ndarray): 2D array (list-like) containing only the points within each triangle,
|
1113
|
+
with each row being the triangle number, points y index, and points x index
|
1114
|
+
"""
|
1115
|
+
triangles = triangles.copy()
|
1116
|
+
triangles[..., 0] /= dx
|
1117
|
+
triangles[..., 1] /= dy
|
1118
|
+
triangles -= 0.5
|
1119
|
+
triangles_points_list = []
|
1120
|
+
for triangle_num in range(len(triangles)):
|
1121
|
+
triangle = triangles[triangle_num]
|
1122
|
+
min_x, max_x, min_y, max_y = triangle_box(triangle)
|
1123
|
+
for xi in range(max(maths.ceil(min_x), 0), min(maths.floor(max_x) + 1, nx)):
|
1124
|
+
x = float(xi)
|
1125
|
+
e0_y = vertical_intercept_nan(x, triangle[1, 0], triangle[2, 0], triangle[1, 1], triangle[2, 1])
|
1126
|
+
e1_y = vertical_intercept_nan(x, triangle[0, 0], triangle[1, 0], triangle[0, 1], triangle[1, 1])
|
1127
|
+
e2_y = vertical_intercept_nan(x, triangle[0, 0], triangle[2, 0], triangle[0, 1], triangle[2, 1])
|
1128
|
+
ey_list = np.array([e0_y, e1_y, e2_y], dtype = np.float64)
|
1129
|
+
floor_y = np.nanmin(ey_list)
|
1130
|
+
ceil_y = np.nanmax(ey_list)
|
1131
|
+
triangles_points_list.extend(
|
1132
|
+
[[triangle_num, y, xi] for y in range(max(maths.ceil(floor_y), 0), min(maths.floor(ceil_y) + 1, ny))])
|
1133
|
+
|
1134
|
+
if len(triangles_points_list) == 0:
|
1135
|
+
triangles_points = np.empty((0, 3), dtype = np.int32)
|
1136
|
+
else:
|
1137
|
+
triangles_points = np.array(triangles_points_list, dtype = np.int32)
|
1138
|
+
|
1139
|
+
return triangles_points
|
1140
|
+
|
1141
|
+
|
1142
|
+
@njit(parallel = True)
|
1143
|
+
def points_in_triangles_aligned_optimised(nx: int,
|
1144
|
+
ny: int,
|
1145
|
+
dx: float,
|
1146
|
+
dy: float,
|
1147
|
+
triangles: np.ndarray,
|
1148
|
+
n_batches: int = 20) -> np.ndarray: # pragma: no cover
|
1149
|
+
"""Calculates which points are within which triangles in 2D for a regular mesh of aligned points.
|
1150
|
+
|
1151
|
+
arguments:
|
1152
|
+
nx (int): number of points in x axis
|
1153
|
+
ny (int): number of points in y axis
|
1154
|
+
dx (float): spacing of points in x axis (first point is at half dx)
|
1155
|
+
dy (float): spacing of points in y axis (first point is at half dy)
|
1156
|
+
triangles (np.ndarray): float array of each triangles' vertices in 2D, shape (N, 3, 2)
|
1157
|
+
n_batches (int, default 20): number of parallel batches
|
1158
|
+
|
1159
|
+
returns:
|
1160
|
+
triangles_points (np.ndarray): 2D array (list-like) containing only the points within each triangle,
|
1161
|
+
with each row being the triangle number, points y index, and points x index
|
1162
|
+
"""
|
1163
|
+
n_triangles = len(triangles)
|
1164
|
+
if n_triangles == 0:
|
1165
|
+
return np.empty((0, 3), dtype = np.int32)
|
1166
|
+
triangles = triangles.copy()
|
1167
|
+
triangles[..., 0] /= dx
|
1168
|
+
triangles[..., 1] /= dy
|
1169
|
+
triangles -= 0.5
|
1170
|
+
n_batches = min(n_triangles, n_batches)
|
1171
|
+
batch_size = (n_triangles - 1) // n_batches + 1
|
1172
|
+
tp = [np.empty((0, 3), dtype = np.int32)] * n_batches
|
1173
|
+
for batch in prange(n_batches):
|
1174
|
+
base = batch * batch_size
|
1175
|
+
tp[batch] = _points_in_triangles_aligned_optimised_batch(nx, ny, base, triangles[base:(batch + 1) * batch_size])
|
1176
|
+
collated = np.empty((0, 3), dtype = np.int32)
|
1177
|
+
for batch in range(n_batches):
|
1178
|
+
collated = np.concatenate((collated, tp[batch]), axis = 0)
|
1179
|
+
return collated
|
1180
|
+
|
1181
|
+
|
1182
|
+
@njit(parallel = True)
|
1183
|
+
def points_in_triangles_aligned_unified(nx: int,
|
1184
|
+
ny: int,
|
1185
|
+
ax: int,
|
1186
|
+
ay: int,
|
1187
|
+
az: int,
|
1188
|
+
triangles: np.ndarray,
|
1189
|
+
n_batches: int = 20) -> Tuple[np.ndarray, np.ndarray]: # pragma: no cover
|
1190
|
+
"""Calculates which points are within which triangles in 2D for a regular mesh of aligned points.
|
1191
|
+
|
1192
|
+
arguments:
|
1193
|
+
- nx (int): number of points in x axis
|
1194
|
+
- ny (int): number of points in y axis
|
1195
|
+
- ax (int): 'x' axis selection (0, 1, or 2)
|
1196
|
+
- ay (int): 'y' axis selection (0, 1, or 2)
|
1197
|
+
- az (int): 'z' axis selection (0, 1, or 2)
|
1198
|
+
- triangles (np.ndarray): float array of each triangles' normalised vertices in 3D, shape (N, 3, 3)
|
1199
|
+
- n_batches (int, default 20): number of parallel batches
|
1200
|
+
|
1201
|
+
returns:
|
1202
|
+
list like int array with each row being (tri number, axis 'y' int, axis 'x' int), and
|
1203
|
+
corresponding list like float array being axis 'z' sampled at point on triangle
|
1204
|
+
|
1205
|
+
notes:
|
1206
|
+
- actual axes to use for x, y, & z are determined by the ax, ay, & az arguments
|
1207
|
+
- 0, 1, & 2 must appear once each amongst the ax, ay, & az arguments
|
1208
|
+
- triangles points have already been normalised to a unit grid spacing and offset by half a cell
|
1209
|
+
- returned 'z' values are in normalised form
|
1210
|
+
- to denormalize 'z' values, add 0.5 and multiply by the actual cell length in the corresponding axis
|
1211
|
+
"""
|
1212
|
+
n_triangles = len(triangles)
|
1213
|
+
if n_triangles == 0:
|
1214
|
+
return np.empty((0, 3), dtype = np.int32), np.empty((0,), dtype = np.float64)
|
1215
|
+
n_batches = min(n_triangles, n_batches)
|
1216
|
+
batch_size = (n_triangles - 1) // n_batches + 1
|
1217
|
+
tp = [np.empty((0, 3), dtype = np.int32)] * n_batches
|
1218
|
+
tz = [np.empty((0,), dtype = np.float64)] * n_batches
|
1219
|
+
for batch in prange(n_batches):
|
1220
|
+
base = batch * batch_size
|
1221
|
+
tp[batch], tz[batch] = _points_in_triangles_aligned_unified_batch(nx, ny, base,
|
1222
|
+
triangles[base:(batch + 1) * batch_size], ax,
|
1223
|
+
ay, az)
|
1224
|
+
collated = np.empty((0, 3), dtype = np.int32)
|
1225
|
+
collated_z = np.empty((0,), dtype = np.float64)
|
1226
|
+
for batch in range(n_batches):
|
1227
|
+
collated = np.concatenate((collated, tp[batch]), axis = 0)
|
1228
|
+
collated_z = np.concatenate((collated_z, tz[batch]), axis = 0)
|
1229
|
+
return collated, collated_z
|
1230
|
+
|
1231
|
+
|
1232
|
+
@njit
|
1233
|
+
def _points_in_triangles_aligned_optimised_batch(nx: int, ny: int, base_triangle: int,
|
1234
|
+
triangles: np.ndarray) -> np.ndarray: # pragma: no cover
|
1235
|
+
triangles_points_list = []
|
1236
|
+
for triangle_num in range(len(triangles)):
|
1237
|
+
triangle = triangles[triangle_num]
|
1238
|
+
min_x, max_x, min_y, max_y = triangle_box(triangle)
|
1239
|
+
for xi in range(max(maths.ceil(min_x), 0), min(maths.floor(max_x) + 1, nx)):
|
1240
|
+
x = float(xi)
|
1241
|
+
e0_y = vertical_intercept_nan(x, triangle[1, 0], triangle[2, 0], triangle[1, 1], triangle[2, 1])
|
1242
|
+
e1_y = vertical_intercept_nan(x, triangle[0, 0], triangle[1, 0], triangle[0, 1], triangle[1, 1])
|
1243
|
+
e2_y = vertical_intercept_nan(x, triangle[0, 0], triangle[2, 0], triangle[0, 1], triangle[2, 1])
|
1244
|
+
ey_list = np.array([e0_y, e1_y, e2_y], dtype = np.float64)
|
1245
|
+
floor_y = np.nanmin(ey_list)
|
1246
|
+
ceil_y = np.nanmax(ey_list)
|
1247
|
+
triangles_points_list.extend([[triangle_num + base_triangle, y, xi]
|
1248
|
+
for y in range(max(maths.ceil(floor_y), 0), min(maths.floor(ceil_y) + 1, ny))
|
1249
|
+
])
|
1250
|
+
|
1251
|
+
if len(triangles_points_list) == 0:
|
1252
|
+
triangles_points = np.empty((0, 3), dtype = np.int32)
|
1253
|
+
else:
|
1254
|
+
triangles_points = np.array(triangles_points_list, dtype = np.int32)
|
1255
|
+
|
1256
|
+
return triangles_points
|
1257
|
+
|
1258
|
+
|
1259
|
+
@njit
|
1260
|
+
def _points_in_triangles_aligned_unified_batch(nx: int, ny: int, base_triangle: int, tp: np.ndarray, ax: int, ay: int,
|
1261
|
+
az: int) -> Tuple[np.ndarray, np.ndarray]: # pragma: no cover
|
1262
|
+
# returns list like int array with each row being (tri number, axis y int, axis x int), and
|
1263
|
+
# corresponding list like float array being axis z sampled at point on triangle
|
1264
|
+
# todo: add type subscripting once support for python 3.8 is dropped
|
1265
|
+
int_list = []
|
1266
|
+
sampled_z_list = []
|
1267
|
+
|
1268
|
+
for triangle_num in range(len(tp)):
|
1269
|
+
tri = tp[triangle_num]
|
1270
|
+
min_x = np.min(tri[:, ax])
|
1271
|
+
max_x = np.max(tri[:, ax])
|
1272
|
+
min_y = np.min(tri[:, ay])
|
1273
|
+
max_y = np.max(tri[:, ay])
|
1274
|
+
for xi in range(max(maths.ceil(min_x), 0), min(maths.floor(max_x) + 1, nx)):
|
1275
|
+
x = float(xi)
|
1276
|
+
e0_y, e0_z = vertical_intercept_nan_yz(x, tri[1, ax], tri[2, ax], tri[1, ay], tri[2, ay], tri[1, az],
|
1277
|
+
tri[2, az])
|
1278
|
+
e1_y, e1_z = vertical_intercept_nan_yz(x, tri[0, ax], tri[1, ax], tri[0, ay], tri[1, ay], tri[0, az],
|
1279
|
+
tri[1, az])
|
1280
|
+
e2_y, e2_z = vertical_intercept_nan_yz(x, tri[0, ax], tri[2, ax], tri[0, ay], tri[2, ay], tri[0, az],
|
1281
|
+
tri[2, az])
|
1282
|
+
floor_y = np.nan
|
1283
|
+
ceil_y = np.nan
|
1284
|
+
floor_z = np.nan
|
1285
|
+
ceil_z = np.nan
|
1286
|
+
if not np.isnan(e0_y):
|
1287
|
+
floor_y = e0_y
|
1288
|
+
ceil_y = e0_y
|
1289
|
+
floor_z = e0_z
|
1290
|
+
ceil_z = e0_z
|
1291
|
+
if not np.isnan(e1_y):
|
1292
|
+
if np.isnan(floor_y):
|
1293
|
+
floor_y = e1_y
|
1294
|
+
ceil_y = e1_y
|
1295
|
+
floor_z = e1_z
|
1296
|
+
ceil_z = e1_z
|
1297
|
+
else:
|
1298
|
+
if e1_y < floor_y:
|
1299
|
+
floor_y = e1_y
|
1300
|
+
floor_z = e1_z
|
1301
|
+
else:
|
1302
|
+
ceil_y = e1_y
|
1303
|
+
ceil_z = e1_z
|
1304
|
+
if not np.isnan(e2_y):
|
1305
|
+
if np.isnan(floor_y):
|
1306
|
+
floor_y = e2_y
|
1307
|
+
ceil_y = e2_y
|
1308
|
+
floor_z = e2_z
|
1309
|
+
ceil_z = e2_z
|
1310
|
+
else:
|
1311
|
+
if e2_y < floor_y:
|
1312
|
+
floor_y = e2_y
|
1313
|
+
floor_z = e2_z
|
1314
|
+
elif e2_y > ceil_y:
|
1315
|
+
ceil_y = e2_y
|
1316
|
+
ceil_z = e2_z
|
1317
|
+
y_range = ceil_y - floor_y
|
1318
|
+
z_range = ceil_z - floor_z
|
1319
|
+
t = triangle_num + base_triangle
|
1320
|
+
extend_int_list = []
|
1321
|
+
extend_z_list = []
|
1322
|
+
for y in range(max(maths.ceil(floor_y), 0), min(maths.floor(ceil_y) + 1, ny)):
|
1323
|
+
yf = float(y) - floor_y
|
1324
|
+
if y_range > 0.0:
|
1325
|
+
yf /= y_range
|
1326
|
+
z = floor_z + yf * z_range
|
1327
|
+
extend_int_list.append([triangle_num + base_triangle, y, xi])
|
1328
|
+
extend_z_list.append(z)
|
1329
|
+
int_list.extend(extend_int_list)
|
1330
|
+
sampled_z_list.extend(extend_z_list)
|
1331
|
+
|
1332
|
+
if len(int_list) == 0:
|
1333
|
+
int_array = np.empty((0, 3), dtype = np.int32)
|
1334
|
+
z_array = np.empty((0,), dtype = np.float64)
|
1335
|
+
else:
|
1336
|
+
int_array = np.array(int_list, dtype = np.int32)
|
1337
|
+
z_array = np.array(sampled_z_list, dtype = np.float64)
|
1338
|
+
|
1339
|
+
return (int_array, z_array)
|
1340
|
+
|
1341
|
+
|
1026
1342
|
def triangle_normal_vector(p3):
|
1027
1343
|
"""For a triangle in 3D space, defined by 3 vertex points, returns a unit vector normal to the plane of the triangle.
|
1028
1344
|
|
@@ -1047,6 +1363,24 @@ def triangle_normal_vector_numba(points): # pragma: no cover
|
|
1047
1363
|
return v / np.linalg.norm(v)
|
1048
1364
|
|
1049
1365
|
|
1366
|
+
@njit
|
1367
|
+
def triangles_normal_vectors(t: np.ndarray, p: np.ndarray) -> np.ndarray: # pragma: no cover
|
1368
|
+
"""For a triangulated set, return an array of unit normal vectors (one per triangle).
|
1369
|
+
|
1370
|
+
note:
|
1371
|
+
resulting vectors implicitly assume that xy & z units are the same; if this is not the case, adjust vectors
|
1372
|
+
afterwards as required
|
1373
|
+
"""
|
1374
|
+
nv = np.empty((len(t), 3), dtype = np.float64)
|
1375
|
+
v = np.zeros(3, dtype = np.float64)
|
1376
|
+
for ti in range(len(t)):
|
1377
|
+
v[:] = np.cross(p[t[ti, 0]] - p[t[ti, 1]], p[t[ti, 0]] - p[t[ti, 2]])
|
1378
|
+
if v[2] < 0.0:
|
1379
|
+
v[:] = -v
|
1380
|
+
nv[ti, :] = v / np.linalg.norm(v)
|
1381
|
+
return nv
|
1382
|
+
|
1383
|
+
|
1050
1384
|
def in_circumcircle(a, b, c, d):
|
1051
1385
|
"""Returns True if point d lies within the circumcircle pf ccw points a, b, c, projected onto xy plane.
|
1052
1386
|
|
resqpy/olio/volume.py
CHANGED
@@ -77,26 +77,6 @@ def tetra_cell_volume(cp, centre = None, off_hand = False):
|
|
77
77
|
return v / 6.0
|
78
78
|
|
79
79
|
|
80
|
-
def tetra_volumes_slow(cp, centres = None, off_hand = False):
|
81
|
-
"""Returns volume array for all hexahedral cells assuming bilinear faces, using loop over cells."""
|
82
|
-
|
83
|
-
# NB: deprecated, superceded by much faster function below
|
84
|
-
# todo: handle NaNs
|
85
|
-
# Pagoda style corner point data
|
86
|
-
assert cp.ndim == 7
|
87
|
-
|
88
|
-
flat = cp.reshape(-1, 2, 2, 2, 3)
|
89
|
-
cells = flat.shape[0]
|
90
|
-
if centres is None:
|
91
|
-
centres = np.mean(flat, axis = (1, 2, 3))
|
92
|
-
else:
|
93
|
-
centres = centres.reshape((-1, 3))
|
94
|
-
volumes = np.zeros(cells)
|
95
|
-
for cell in range(cells):
|
96
|
-
volumes[cell] = tetra_cell_volume(flat[cell], centre = centres[cell], off_hand = off_hand)
|
97
|
-
return volumes.reshape(cp.shape[0:3])
|
98
|
-
|
99
|
-
|
100
80
|
def tetra_volumes(cp, centres = None, off_hand = False):
|
101
81
|
"""Returns volume array for all hexahedral cells assuming bilinear faces, using numpy operations.
|
102
82
|
|
resqpy/olio/wellspec_keywords.py
CHANGED
@@ -75,9 +75,9 @@ wellspec_dict['DZ'] = (0, wk_preferably_not, wk_okay, None, True) # no
|
|
75
75
|
|
76
76
|
wellspec_dtype: Dict[str, Type] = { } # mapping wellspec column key to expected data type
|
77
77
|
|
78
|
-
wellspec_dtype['IW'] =
|
79
|
-
wellspec_dtype['JW'] =
|
80
|
-
wellspec_dtype['L'] =
|
78
|
+
wellspec_dtype['IW'] = np.int32
|
79
|
+
wellspec_dtype['JW'] = np.int32
|
80
|
+
wellspec_dtype['L'] = np.int32
|
81
81
|
wellspec_dtype['GRID'] = str
|
82
82
|
wellspec_dtype['RADW'] = float
|
83
83
|
wellspec_dtype['KHMULT'] = float
|
@@ -89,11 +89,11 @@ wellspec_dtype['KH'] = float
|
|
89
89
|
wellspec_dtype['SKIN'] = float
|
90
90
|
wellspec_dtype['PPERF'] = float
|
91
91
|
wellspec_dtype['ANGLE'] = float
|
92
|
-
wellspec_dtype['IRELPM'] =
|
92
|
+
wellspec_dtype['IRELPM'] = np.int32
|
93
93
|
wellspec_dtype['RADB'] = float
|
94
94
|
wellspec_dtype['WI'] = float
|
95
95
|
wellspec_dtype['K'] = float
|
96
|
-
wellspec_dtype['LAYER'] =
|
96
|
+
wellspec_dtype['LAYER'] = np.int32
|
97
97
|
wellspec_dtype['DEPTH'] = float # '#' causes nexus to use cell depth
|
98
98
|
wellspec_dtype['X'] = float # use cell X for i.p. perf
|
99
99
|
wellspec_dtype['Y'] = float # use cell Y for i.p. perf
|
@@ -109,10 +109,10 @@ wellspec_dtype['PARENT'] = str
|
|
109
109
|
wellspec_dtype['MDCON'] = float
|
110
110
|
wellspec_dtype['SECT'] = str # todo: need to check type
|
111
111
|
wellspec_dtype['FLOWSECT'] = str # todo: need to check type
|
112
|
-
wellspec_dtype['ZONE'] =
|
112
|
+
wellspec_dtype['ZONE'] = np.int32
|
113
113
|
wellspec_dtype['GROUP'] = str
|
114
114
|
wellspec_dtype['TEMP'] = float
|
115
|
-
wellspec_dtype['IPTN'] =
|
115
|
+
wellspec_dtype['IPTN'] = np.int32
|
116
116
|
wellspec_dtype['D'] = float
|
117
117
|
wellspec_dtype['ND'] = str
|
118
118
|
wellspec_dtype['DZ'] = float
|
@@ -456,7 +456,7 @@ def get_well_data(
|
|
456
456
|
line = kf.strip_trailing_comment(file.readline()).upper()
|
457
457
|
columns_present = line.split()
|
458
458
|
if selecting:
|
459
|
-
column_map = np.full((len(column_list),), -1, dtype =
|
459
|
+
column_map = np.full((len(column_list),), -1, dtype = np.int32)
|
460
460
|
for i in range(len(column_list)):
|
461
461
|
column = column_list[i].upper()
|
462
462
|
if column in columns_present:
|
@@ -482,11 +482,11 @@ def get_well_data(
|
|
482
482
|
if column_list[col_index].upper() == "GRID":
|
483
483
|
data[col].append("ROOT")
|
484
484
|
else:
|
485
|
-
data[col].append(np.
|
485
|
+
data[col].append(np.nan)
|
486
486
|
else:
|
487
487
|
value = words[column_map[col_index]]
|
488
488
|
if value == "NA":
|
489
|
-
data[col].append(np.
|
489
|
+
data[col].append(np.nan)
|
490
490
|
elif value == "#":
|
491
491
|
data[col].append(value)
|
492
492
|
elif value:
|
@@ -496,7 +496,7 @@ def get_well_data(
|
|
496
496
|
else:
|
497
497
|
for col, value in zip(columns_present, words[:len(columns_present)]):
|
498
498
|
if value == "NA":
|
499
|
-
data[col].append(np.
|
499
|
+
data[col].append(np.nan)
|
500
500
|
elif value == "#":
|
501
501
|
data[col].append(value)
|
502
502
|
elif value:
|
@@ -519,13 +519,19 @@ def get_well_data(
|
|
519
519
|
|
520
520
|
def stat_tranformation(row):
|
521
521
|
if row["STAT"] == "ON":
|
522
|
-
return 1
|
522
|
+
return np.int8(1)
|
523
523
|
else:
|
524
|
-
return 0
|
524
|
+
return np.int8(0)
|
525
525
|
|
526
526
|
if "STAT" in df.columns:
|
527
527
|
df["STAT"] = df.apply(lambda row: stat_tranformation(row), axis = 1)
|
528
528
|
|
529
|
+
int_col_dict = {}
|
530
|
+
for col in ["IW", "JW", "L", "LAYER", "STAT"]:
|
531
|
+
if col in df.columns:
|
532
|
+
int_col_dict[col] = (np.int8 if col == "STAT" else np.int32)
|
533
|
+
df = df.astype(int_col_dict)
|
534
|
+
|
529
535
|
return df
|
530
536
|
|
531
537
|
|
resqpy/olio/write_hdf5.py
CHANGED
@@ -101,7 +101,7 @@ class H5Register():
|
|
101
101
|
assert chunks is None or isinstance(chunks, str) or isinstance(chunks, tuple)
|
102
102
|
assert compression is None or (isinstance(compression, str) and compression in ['gzip', 'lzf', 'none'])
|
103
103
|
if str(dtype) == 'pack':
|
104
|
-
a = np.packbits(a, axis = -1)
|
104
|
+
a = np.packbits(a, axis = -1)
|
105
105
|
dtype = 'uint8'
|
106
106
|
elif dtype is not None:
|
107
107
|
a = a.astype(dtype, copy = copy)
|
resqpy/olio/xml_et.py
CHANGED
@@ -778,6 +778,18 @@ def load_metadata_from_xml(node):
|
|
778
778
|
return extra_metadata
|
779
779
|
|
780
780
|
|
781
|
+
def find_metadata_item_node_in_xml(node, key):
|
782
|
+
"""Returns the extra metadata node for a particular key, if present."""
|
783
|
+
|
784
|
+
if node is None:
|
785
|
+
return None
|
786
|
+
meta_nodes = list_of_tag(node, 'ExtraMetadata')
|
787
|
+
for meta in meta_nodes:
|
788
|
+
if find_tag_text(meta, 'Name') == key:
|
789
|
+
return meta
|
790
|
+
return None
|
791
|
+
|
792
|
+
|
781
793
|
def create_metadata_xml(node, extra_metadata):
|
782
794
|
"""Writes the xml for the given metadata dictionary."""
|
783
795
|
|
resqpy/property/__init__.py
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
"""Collections of properties for grids, wellbore frames, grid connection sets etc."""
|
2
2
|
|
3
3
|
__all__ = [
|
4
|
-
'PropertyCollection', 'Property', '
|
5
|
-
'WellLogCollection', 'StringLookup', 'PropertyKind', 'GridPropertyCollection',
|
4
|
+
'PropertyCollection', 'Property', 'AttributePropertySet', 'ApsProperty', 'WellLog', 'WellIntervalProperty',
|
5
|
+
'WellIntervalPropertyCollection', 'WellLogCollection', 'StringLookup', 'PropertyKind', 'GridPropertyCollection',
|
6
6
|
'property_over_time_series_from_collection', 'property_collection_for_keyword', 'infer_property_kind',
|
7
7
|
'write_hdf5_and_create_xml_for_active_property', 'reformat_column_edges_to_resqml_format',
|
8
8
|
'reformat_column_edges_from_resqml_format', 'same_property_kind', 'selective_version_of_collection',
|
9
9
|
'supported_local_property_kind_list', 'supported_property_kind_list', 'supported_facet_type_list',
|
10
10
|
'expected_facet_type_dict', 'create_transmisibility_multiplier_property_kind',
|
11
|
-
'property_kind_and_facet_from_keyword', 'guess_uom', 'property_parts', 'property_part'
|
11
|
+
'property_kind_and_facet_from_keyword', 'guess_uom', 'property_parts', 'property_part', 'make_aps_key',
|
12
|
+
'establish_zone_property_kind'
|
12
13
|
]
|
13
14
|
|
14
15
|
from .property_common import property_collection_for_keyword, \
|
@@ -27,9 +28,10 @@ from .property_common import property_collection_for_keyword, \
|
|
27
28
|
guess_uom, \
|
28
29
|
property_parts, \
|
29
30
|
property_part
|
30
|
-
from .property_kind import PropertyKind, create_transmisibility_multiplier_property_kind
|
31
|
+
from .property_kind import PropertyKind, create_transmisibility_multiplier_property_kind, establish_zone_property_kind
|
31
32
|
from .string_lookup import StringLookup
|
32
33
|
from .property_collection import PropertyCollection
|
34
|
+
from .attribute_property_set import AttributePropertySet, ApsProperty, make_aps_key
|
33
35
|
from .grid_property_collection import GridPropertyCollection
|
34
36
|
from ._property import Property
|
35
37
|
from .well_interval_property import WellIntervalProperty
|
@@ -199,7 +199,7 @@ def _process_imported_property(collection, attributes, property_kind_uuid, strin
|
|
199
199
|
extra_metadata, expand_const_arrays):
|
200
200
|
(p_uuid, p_file_name, p_keyword, p_cached_name, p_discrete, p_uom, p_time_index, p_null_value, p_min_value,
|
201
201
|
p_max_value, property_kind, facet_type, facet, realization, indexable_element, count, local_property_kind_uuid,
|
202
|
-
const_value, points, p_time_series_uuid, p_string_lookup_uuid) = attributes
|
202
|
+
const_value, points, p_time_series_uuid, p_string_lookup_uuid, pre_packed) = attributes
|
203
203
|
|
204
204
|
log.debug('processing imported property ' + str(p_keyword))
|
205
205
|
assert not points or not p_discrete
|
@@ -214,7 +214,7 @@ def _process_imported_property(collection, attributes, property_kind_uuid, strin
|
|
214
214
|
p_keyword, p_discrete, string_lookup_uuid, points)
|
215
215
|
|
216
216
|
p_array = _process_imported_property_get_p_array(collection, p_cached_name)
|
217
|
-
p_array_bool =
|
217
|
+
p_array_bool = isinstance(const_value, bool) if p_array is None else p_array.dtype in [bool, np.int8, np.uint8]
|
218
218
|
|
219
219
|
add_min_max = pcga._process_imported_property_get_add_min_max(points, property_kind, string_lookup_uuid,
|
220
220
|
local_property_kind_uuid, p_array_bool)
|
@@ -251,7 +251,8 @@ def _process_imported_property(collection, attributes, property_kind_uuid, strin
|
|
251
251
|
find_local_property_kinds = find_local_property_kinds,
|
252
252
|
extra_metadata = extra_metadata,
|
253
253
|
const_value = const_value,
|
254
|
-
expand_const_arrays = expand_const_arrays
|
254
|
+
expand_const_arrays = expand_const_arrays,
|
255
|
+
pre_packed = pre_packed)
|
255
256
|
if p_node is not None:
|
256
257
|
return p_node
|
257
258
|
else:
|
@@ -66,7 +66,7 @@ def _create_xml_property_kind(collection, p_node, find_local_property_kinds, pro
|
|
66
66
|
property_kind = 'rock permeability'
|
67
67
|
p_kind_node = rqet.SubElement(p_node, ns['resqml2'] + 'PropertyKind')
|
68
68
|
p_kind_node.text = rqet.null_xml_text
|
69
|
-
if find_local_property_kinds and property_kind not in
|
69
|
+
if find_local_property_kinds and property_kind not in wam.valid_property_kinds():
|
70
70
|
property_kind_uuid = pcga._get_property_kind_uuid(collection, property_kind_uuid, property_kind, uom, discrete)
|
71
71
|
|
72
72
|
if property_kind_uuid is None:
|
@@ -246,8 +246,10 @@ def _create_xml_facet_node(facet_type, facet, p_node):
|
|
246
246
|
facet_value_node.text = facet
|
247
247
|
|
248
248
|
|
249
|
-
def _check_shape_list(collection, indexable_element, direction, property_array, points, count):
|
249
|
+
def _check_shape_list(collection, indexable_element, direction, property_array, points, count, pre_packed):
|
250
250
|
shape_list = collection.supporting_shape(indexable_element = indexable_element, direction = direction)
|
251
|
+
if pre_packed:
|
252
|
+
shape_list[-1] = (shape_list[-1] - 1) // 8 + 1
|
251
253
|
if shape_list is not None:
|
252
254
|
if count > 1:
|
253
255
|
shape_list.append(count)
|
@@ -509,6 +509,8 @@ def _supporting_shape_surface(support, indexable_element):
|
|
509
509
|
shape_list = [support.triangle_count()]
|
510
510
|
elif indexable_element == 'nodes':
|
511
511
|
shape_list = [support.node_count()]
|
512
|
+
elif indexable_element == 'patches':
|
513
|
+
shape_list = [len(support.patch_list)]
|
512
514
|
return shape_list
|
513
515
|
|
514
516
|
|
@@ -538,6 +540,8 @@ def _supporting_shape_polylineset(support, indexable_element):
|
|
538
540
|
shape_list = [len(support.coordinates) - reduction]
|
539
541
|
elif indexable_element == 'nodes':
|
540
542
|
shape_list = [len(support.coordinates)]
|
543
|
+
elif indexable_element in ['patches', 'enumerated elements', 'contacts']: # one value per polyline within the set
|
544
|
+
shape_list = [len(support.count_perpol)]
|
541
545
|
return shape_list
|
542
546
|
|
543
547
|
|