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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. resqpy/__init__.py +1 -1
  2. resqpy/fault/_gcs_functions.py +10 -10
  3. resqpy/fault/_grid_connection_set.py +277 -113
  4. resqpy/grid/__init__.py +2 -3
  5. resqpy/grid/_defined_geometry.py +3 -3
  6. resqpy/grid/_extract_functions.py +8 -2
  7. resqpy/grid/_grid.py +95 -12
  8. resqpy/grid/_grid_types.py +22 -7
  9. resqpy/grid/_points_functions.py +1 -1
  10. resqpy/grid/_regular_grid.py +6 -2
  11. resqpy/grid_surface/__init__.py +17 -38
  12. resqpy/grid_surface/_blocked_well_populate.py +5 -5
  13. resqpy/grid_surface/_find_faces.py +1413 -253
  14. resqpy/lines/_polyline.py +24 -33
  15. resqpy/model/_catalogue.py +9 -0
  16. resqpy/model/_forestry.py +18 -14
  17. resqpy/model/_hdf5.py +11 -3
  18. resqpy/model/_model.py +85 -10
  19. resqpy/model/_xml.py +38 -13
  20. resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
  21. resqpy/olio/read_nexus_fault.py +8 -2
  22. resqpy/olio/relperm.py +1 -1
  23. resqpy/olio/transmission.py +8 -8
  24. resqpy/olio/triangulation.py +36 -30
  25. resqpy/olio/vector_utilities.py +340 -6
  26. resqpy/olio/volume.py +0 -20
  27. resqpy/olio/wellspec_keywords.py +19 -13
  28. resqpy/olio/write_hdf5.py +1 -1
  29. resqpy/olio/xml_et.py +12 -0
  30. resqpy/property/__init__.py +6 -4
  31. resqpy/property/_collection_add_part.py +4 -3
  32. resqpy/property/_collection_create_xml.py +4 -2
  33. resqpy/property/_collection_get_attributes.py +4 -0
  34. resqpy/property/attribute_property_set.py +311 -0
  35. resqpy/property/grid_property_collection.py +11 -11
  36. resqpy/property/property_collection.py +79 -31
  37. resqpy/property/property_common.py +3 -8
  38. resqpy/rq_import/_add_surfaces.py +34 -14
  39. resqpy/rq_import/_grid_from_cp.py +2 -2
  40. resqpy/rq_import/_import_nexus.py +75 -48
  41. resqpy/rq_import/_import_vdb_all_grids.py +64 -52
  42. resqpy/rq_import/_import_vdb_ensemble.py +12 -13
  43. resqpy/surface/_mesh.py +4 -0
  44. resqpy/surface/_surface.py +593 -118
  45. resqpy/surface/_tri_mesh.py +13 -10
  46. resqpy/surface/_tri_mesh_stencil.py +4 -4
  47. resqpy/surface/_triangulated_patch.py +71 -51
  48. resqpy/time_series/_any_time_series.py +7 -4
  49. resqpy/time_series/_geologic_time_series.py +1 -1
  50. resqpy/unstructured/_hexa_grid.py +6 -2
  51. resqpy/unstructured/_prism_grid.py +13 -5
  52. resqpy/unstructured/_pyramid_grid.py +6 -2
  53. resqpy/unstructured/_tetra_grid.py +6 -2
  54. resqpy/unstructured/_unstructured_grid.py +6 -2
  55. resqpy/well/_blocked_well.py +1986 -1946
  56. resqpy/well/_deviation_survey.py +3 -3
  57. resqpy/well/_md_datum.py +11 -21
  58. resqpy/well/_trajectory.py +10 -5
  59. resqpy/well/_wellbore_frame.py +10 -2
  60. resqpy/well/blocked_well_frame.py +3 -3
  61. resqpy/well/well_object_funcs.py +7 -9
  62. resqpy/well/well_utils.py +33 -0
  63. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/LICENSE +0 -0
@@ -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 points_in_triangles_aligned_optimised(nx: int, ny: int, dx: float, dy: float,
986
- triangles: np.ndarray) -> np.ndarray: # pragma: no cover
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
 
@@ -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'] = int
79
- wellspec_dtype['JW'] = int
80
- wellspec_dtype['L'] = int
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'] = int
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'] = int
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'] = int
112
+ wellspec_dtype['ZONE'] = np.int32
113
113
  wellspec_dtype['GROUP'] = str
114
114
  wellspec_dtype['TEMP'] = float
115
- wellspec_dtype['IPTN'] = int
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 = int)
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.NaN)
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.NaN)
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.NaN)
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) # todo: check this returns uint8 array
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
 
@@ -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', 'WellLog', 'WellIntervalProperty', 'WellIntervalPropertyCollection',
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 = None if p_array is None else p_array.dtype in [bool, np.int8]
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 rqp_c.supported_property_kind_list:
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