resqpy 4.14.2__py3-none-any.whl → 5.1.5__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|