resqpy 4.14.1__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.
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 +2 -1
  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 +1349 -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 +22 -12
  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.1.dist-info → resqpy-5.1.5.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.1.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {resqpy-4.14.1.dist-info → resqpy-5.1.5.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