resqpy 4.16.11__py3-none-any.whl → 4.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
resqpy/model/_hdf5.py CHANGED
@@ -262,11 +262,11 @@ def _h5_array_element(model,
262
262
  if dtype is None:
263
263
  return result
264
264
  if result.size == 1:
265
- if dtype is float or (isinstance(dtype, str) and dtype.startswith('float')):
265
+ if dtype is float or (isinstance(dtype, str) and ('float' in dtype)):
266
266
  return float(result)
267
- elif dtype is int or (isinstance(dtype, str) and dtype.startswith('int')):
267
+ elif dtype is int or (isinstance(dtype, str) and ('int' in dtype)):
268
268
  return int(result)
269
- elif dtype is bool or (isinstance(dtype, str) and dtype.startswith('bool')):
269
+ elif dtype is bool or (isinstance(dtype, str) and ('bool' in dtype)):
270
270
  return bool(result)
271
271
  return np.array(result, dtype = dtype)
272
272
 
@@ -115,7 +115,7 @@ def _dt_simple(po, plot_fn = None, progress_fn = None, container_size_factor = N
115
115
  if n_p < 3:
116
116
  return None, None # not enough points
117
117
  elif n_p == 3:
118
- return np.array([0, 1, 2], dtype = int).reshape((1, 3)), np.array([0, 1, 2], dtype = int)
118
+ return np.array([0, 1, 2], dtype = np.int32).reshape((1, 3)), np.array([0, 1, 2], dtype = np.int32)
119
119
 
120
120
  if progress_fn is not None:
121
121
  progress_fn(0.0)
@@ -133,19 +133,20 @@ def _dt_simple(po, plot_fn = None, progress_fn = None, container_size_factor = N
133
133
  p[-1] = (min_xy[0] + 0.5 * csf * dxy[0], max_xy[1] + 0.8 * csf * dxy[1])
134
134
 
135
135
  # triangle vertex indices
136
- t = np.empty((2 * n_p + 2, 3), dtype = int) # empty space for triangle vertex indices
136
+ t_type = np.int32 if n_p < 2_147_483_648 else np.int64
137
+ t = np.empty((2 * n_p + 2, 3), dtype = t_type) # empty space for triangle vertex indices
137
138
  t[0] = (n_p, n_p + 1, n_p + 2) # initial set of one containing triangle
138
139
  nt = 1 # number of triangles so far populated
139
140
 
140
141
  # edges: list of indices of triangles and edge within triangle; -1 indicates no triangle using edge
141
- e = np.full((3 * n_p + 6, 2, 2), fill_value = -1, dtype = int)
142
+ e = np.full((3 * n_p + 6, 2, 2), fill_value = -1, dtype = t_type)
142
143
  e[:3, 0, 0] = 0
143
144
  for edge in range(3):
144
145
  e[edge, 0, 1] = edge
145
146
  ne = 3 # number of edges so far in use
146
147
 
147
148
  # edge indices (in e) for each triangle, first axis indexed in sync with t
148
- te = np.empty((2 * n_p + 2, 3), dtype = int) # empty space for triangle edge indices
149
+ te = np.empty((2 * n_p + 2, 3), dtype = t_type) # empty space for triangle edge indices
149
150
  te[0] = (0, 1, 2)
150
151
 
151
152
  # mask tracking which edges have been flipped
@@ -501,7 +502,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
501
502
  return trimmed_ci
502
503
 
503
504
  def __veroni_cells(aoi_count, aoi_intersect_segments, b, c, c_count, ca_count, cah_count, caho_count, cahon_count,
504
- hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi):
505
+ hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi, t_type):
505
506
  # list of voronoi cells (each a numpy list of node indices into c extended with aoi points etc)
506
507
  v = []
507
508
  # for each seed point build the voronoi cell
@@ -550,7 +551,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
550
551
  ci_for_p, out_pair_intersect_segments)
551
552
 
552
553
  #  remove circumcircle centres that are outwith area of interest
553
- ci_for_p = np.array([ti for ti in ci_for_p if ti >= c_count or ti not in tc_outwith_aoi], dtype = int)
554
+ ci_for_p = np.array([ti for ti in ci_for_p if ti >= c_count or ti not in tc_outwith_aoi], dtype = t_type)
554
555
 
555
556
  # find azimuths of vectors from seed point to circumcircle centres and aoi boundary points
556
557
  azi = [vec.azimuth(centre - p[p_i, :2]) for centre in c[ci_for_p, :2]]
@@ -612,11 +613,12 @@ def voronoi(p, t, b, aoi: rql.Polyline):
612
613
  caho_count = cah_count + 2 * o_count
613
614
  cahon_count = caho_count + hull_count
614
615
  assert cahon_count + hull_count == len(c)
616
+ t_type = np.int32 if len(c) < 2_147_483_648 else np.int64
615
617
 
616
618
  #  compute intersection points between hull edge normals and aoi polyline
617
619
  # also extended virtual centres for hull edges
618
620
  extension_scaling = 1000.0 * np.sum((np.max(aoi.coordinates, axis = 0) - np.min(aoi.coordinates, axis = 0))[:2])
619
- aoi_intersect_segments = np.empty((hull_count,), dtype = int)
621
+ aoi_intersect_segments = np.empty((hull_count,), dtype = t_type)
620
622
  for ei in range(hull_count):
621
623
  # use segment midpoint and normal methods of hull to project out
622
624
  m = hull.segment_midpoint(ei)[:2] # midpoint
@@ -638,8 +640,8 @@ def voronoi(p, t, b, aoi: rql.Polyline):
638
640
  c[cahon_count + ei] = hull.coordinates[ei, :2] + extension_scaling * vector
639
641
 
640
642
  # where cicrumcircle centres are outwith aoi, compute intersections of normals of wing edges with aoi
641
- out_pair_intersect_segments = np.empty((o_count, 2), dtype = int)
642
- wing_hull_segments = np.empty((o_count, 2), dtype = int)
643
+ out_pair_intersect_segments = np.empty((o_count, 2), dtype = t_type)
644
+ wing_hull_segments = np.empty((o_count, 2), dtype = t_type)
643
645
  for oi, ti in enumerate(tc_outwith_aoi):
644
646
  tpi = __shorter_sides_p_i(p[t[ti]])
645
647
  for wing in range(2):
@@ -658,7 +660,7 @@ def voronoi(p, t, b, aoi: rql.Polyline):
658
660
  tpi = (tpi + 1) % 3
659
661
 
660
662
  v = __veroni_cells(aoi_count, aoi_intersect_segments, b, c, c_count, ca_count, cah_count, caho_count, cahon_count,
661
- hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi)
663
+ hull_count, out_pair_intersect_segments, p, t, tc_outwith_aoi, t_type)
662
664
 
663
665
  return c[:caho_count], v
664
666
 
@@ -703,7 +705,8 @@ def triangulated_polygons(p, v, centres = None):
703
705
  points[len(p):] = centres
704
706
 
705
707
  t_count = sum([len(x) for x in v])
706
- triangles = np.empty((t_count, 3), dtype = int)
708
+ t_type = np.int32 if len(points) < 2_147_483_648 else np.int64
709
+ triangles = np.empty((t_count, 3), dtype = t_type)
707
710
  t_index = 0
708
711
 
709
712
  for cell, poly_vertices in enumerate(v):
@@ -711,7 +714,7 @@ def triangulated_polygons(p, v, centres = None):
711
714
  centre_i = len(p) + cell
712
715
  if centres is None:
713
716
  polygon = rql.Polyline(model,
714
- set_coord = p[np.array(poly_vertices, dtype = int)],
717
+ set_coord = p[np.array(poly_vertices, dtype = t_type)],
715
718
  is_closed = True,
716
719
  set_crs = crs.uuid,
717
720
  title = 'v cell')
@@ -917,7 +920,7 @@ def edges(t):
917
920
  """
918
921
 
919
922
  assert t.ndim == 2 and t.shape[1] == 3
920
- all_edges = np.empty((len(t), 3, 2), dtype = int)
923
+ all_edges = np.empty((len(t), 3, 2), dtype = t.dtype)
921
924
  all_edges[:, :, 0] = t
922
925
  all_edges[:, :2, 1] = t[:, 1:]
923
926
  all_edges[:, 2, 1] = t[:, 0]
@@ -946,6 +949,7 @@ def triangles_using_edges(t, edges):
946
949
  """Returns int array of shape (len(edges), 2) with indices of upto 2 triangles using each edge (-1 for unused)."""
947
950
 
948
951
  assert t.ndim == 2 and t.shape[1] == 3 and edges.ndim == 2 and edges.shape[1] == 2
952
+ t_type = np.int32 if len(t) < 2_147_483_648 else np.int64
949
953
  ti = np.full((len(edges), 2), -1, dtype = int)
950
954
  for i in range(len(edges)):
951
955
  te = triangles_using_edge(t, edges[i, 0], edges[i, 1])
@@ -14,7 +14,7 @@ import math as maths
14
14
  import numpy as np
15
15
  import numba # type: ignore
16
16
  from numba import njit, prange # type: ignore
17
- from typing import Tuple, Optional
17
+ from typing import Tuple, Optional, List
18
18
 
19
19
 
20
20
  def radians_from_degrees(deg):
@@ -1013,6 +1013,47 @@ def vertical_intercept_nan(x: float, x_value_0: float, x_value_1: float, y_value
1013
1013
  return y
1014
1014
 
1015
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
+
1016
1057
  @njit
1017
1058
  def points_in_triangles_aligned_optimised_old(nx: int, ny: int, dx: float, dy: float,
1018
1059
  triangles: np.ndarray) -> np.ndarray: # pragma: no cover
@@ -1138,6 +1179,56 @@ def points_in_triangles_aligned_optimised(nx: int,
1138
1179
  return collated
1139
1180
 
1140
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
+
1141
1232
  @njit
1142
1233
  def _points_in_triangles_aligned_optimised_batch(nx: int, ny: int, base_triangle: int,
1143
1234
  triangles: np.ndarray) -> np.ndarray: # pragma: no cover
@@ -1165,6 +1256,89 @@ def _points_in_triangles_aligned_optimised_batch(nx: int, ny: int, base_triangle
1165
1256
  return triangles_points
1166
1257
 
1167
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
+
1168
1342
  def triangle_normal_vector(p3):
1169
1343
  """For a triangle in 3D space, defined by 3 vertex points, returns a unit vector normal to the plane of the triangle.
1170
1344
 
@@ -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:
@@ -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
 
@@ -51,11 +51,11 @@ class GridPropertyCollection(rqp.PropertyCollection):
51
51
 
52
52
  # NB: RESQML documentation is not clear which order is correct; should be kept consistent with same data in fault.py
53
53
  # face_index_map maps from (axis, p01) to face index value in range 0..5
54
- # self.face_index_map = np.array([[0, 1], [4, 2], [5, 3]], dtype = int)
55
- self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype = int) # order: top, base, J-, I+, J+, I-
54
+ # self.face_index_map = np.array([[0, 1], [4, 2], [5, 3]], dtype = np.int8)
55
+ self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype = np.int8) # order: top, base, J-, I+, J+, I-
56
56
  # and the inverse, maps from 0..5 to (axis, p01)
57
- # self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 1], [2, 1], [1, 0], [2, 0]], dtype = int)
58
- self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype = int)
57
+ # self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 1], [2, 1], [1, 0], [2, 0]], dtype = np.int8)
58
+ self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype = np.int8)
59
59
 
60
60
  def _copy_support_to_grid_attributes(self):
61
61
  # following three pseudonyms are for backward compatibility
@@ -266,11 +266,11 @@ class GridPropertyCollection(rqp.PropertyCollection):
266
266
  dtype = None
267
267
  if keyword[0].upper() == 'I' or keyword in ['KID', 'UID', 'UNPACK', 'DAD']:
268
268
  # coerce to integer values (vdb stores integer data as reals!)
269
- dtype = 'int32'
269
+ dtype = np.int32
270
270
  elif keyword in ['DEADCELL', 'LIVECELL']:
271
- dtype = 'bool' # could use the default dtype of 64 bit integer
271
+ dtype = bool # could use the default dtype of 64 bit integer
272
272
  else:
273
- dtype = 'float' # convert to 64 bit; could omit but RESQML states 64 bit
273
+ dtype = np.float32
274
274
  discrete = False
275
275
  import_array = vdbase.grid_static_property(grid_name, keyword, dtype = dtype)
276
276
  assert import_array is not None
@@ -335,7 +335,7 @@ class GridPropertyCollection(rqp.PropertyCollection):
335
335
  time_index = timestep
336
336
  keyword = keyword.upper()
337
337
  try:
338
- import_array = vdbase.grid_recurrent_property_for_timestep(grid_name, keyword, timestep, dtype = 'float')
338
+ import_array = vdbase.grid_recurrent_property_for_timestep(grid_name, keyword, timestep, dtype = np.float32)
339
339
  assert import_array is not None
340
340
  except Exception:
341
341
  # could raise an exception (as for static properties)
@@ -489,9 +489,9 @@ class GridPropertyCollection(rqp.PropertyCollection):
489
489
  for k0 in range(self.grid.nk):
490
490
  for j0 in range(self.grid.nj):
491
491
  for i0 in range(self.grid.ni):
492
- # if decoarsen_array[k0, j0, i0] < 0:
492
+ # if decoarsen_array[k0, j0, i0] < 0:
493
493
  if kid[k0, j0, i0] == 0:
494
- # assert not kid_mask[k0, j0, i0]
494
+ # assert not kid_mask[k0, j0, i0]
495
495
  ke = k0 + 1
496
496
  while ke < self.grid.nk and kid_mask[ke, j0, i0]:
497
497
  ke += 1
@@ -225,11 +225,11 @@ def import_vdb_ensemble(
225
225
  vdb_file = ensemble_list[realisation]
226
226
  log.info('processing realisation ' + str(realisation) + ' from: ' + str(vdb_file))
227
227
  vdbase = vdb.VDB(vdb_file)
228
- # case_list = vdbase.cases()
229
- # assert len(case_list) > 0, 'no cases found in vdb: ' + str(vdb_file)
230
- # if len(case_list) > 1: log.warning('more than one case found in vdb (using first): ' + str(vdb_file))
231
- # vdb_case = case_list[0]
232
- # vdbase.set_use_case(vdb_case)
228
+ # case_list = vdbase.cases()
229
+ # assert len(case_list) > 0, 'no cases found in vdb: ' + str(vdb_file)
230
+ # if len(case_list) > 1: log.warning('more than one case found in vdb (using first): ' + str(vdb_file))
231
+ # vdb_case = case_list[0]
232
+ # vdbase.set_use_case(vdb_case)
233
233
  vdbase.set_extent_kji(grid.extent_kji)
234
234
 
235
235
  prop_import_collection = rp.GridPropertyCollection(realization = realisation)
@@ -243,9 +243,8 @@ def import_vdb_ensemble(
243
243
  if keyword_list is not None and keyword not in keyword_list:
244
244
  continue
245
245
  prop_kind, facet_type, facet = rp.property_kind_and_facet_from_keyword(keyword)
246
- if property_kind_list is not None and prop_kind not in property_kind_list and prop_kind not in [
247
- 'active', 'region initialization'
248
- ]:
246
+ if property_kind_list is not None and prop_kind not in property_kind_list and \
247
+ prop_kind not in ['active', 'region initialization']:
249
248
  continue
250
249
  prop_import_collection.import_vdb_static_property_to_cache(vdbase,
251
250
  keyword,
@@ -312,9 +311,9 @@ def import_vdb_ensemble(
312
311
  if decoarsen_array is not None:
313
312
  step_import_collection.decoarsen_imported_list(decoarsen_array = decoarsen_array)
314
313
  # extend hdf5 with cached arrays for this timestep
315
- # log.info('number of recurrent grid property arrays for timestep: ' + str(timestep_number) +
316
- # ' is: ' + str(step_import_collection.number_of_imports()))
317
- # log.info('extending hdf5 file with recurrent properties for timestep: ' + str(timestep_number))
314
+ # log.info('number of recurrent grid property arrays for timestep: ' + str(timestep_number) +
315
+ # ' is: ' + str(step_import_collection.number_of_imports()))
316
+ # log.info('extending hdf5 file with recurrent properties for timestep: ' + str(timestep_number))
318
317
  grid.write_hdf5_from_caches(hdf5_file,
319
318
  mode = 'a',
320
319
  geometry = False,
@@ -322,8 +321,8 @@ def import_vdb_ensemble(
322
321
  write_active = False)
323
322
  # add imported list for this timestep to full imported list
324
323
  prop_import_collection.inherit_imported_list_from_other_collection(step_import_collection)
325
- # log.debug('total number of property arrays after timestep: ' + str(timestep_number) +
326
- # ' is: ' + str(prop_import_collection.number_of_imports()))
324
+ # log.debug('total number of property arrays after timestep: ' + str(timestep_number) +
325
+ # ' is: ' + str(prop_import_collection.number_of_imports()))
327
326
  # remove cached copies of arrays
328
327
  step_import_collection.remove_all_cached_arrays()
329
328
 
resqpy/surface/_mesh.py CHANGED
@@ -207,6 +207,10 @@ class Mesh(rqsb.BaseSurface):
207
207
  extra_metadata = em)
208
208
  return mesh
209
209
 
210
+ def is_big(self):
211
+ """Returns True if the number of nodes exceeds 2^31 - 1, False otherwise."""
212
+ return (self.ni * self.nj >= 2_147_483_648)
213
+
210
214
  def set_represented_interpretation_root(self, interp_root):
211
215
  """Makes a note of the xml root of the represented interpretation."""
212
216
 
@@ -279,11 +279,13 @@ class Surface(rqsb.BaseSurface):
279
279
  self.extract_patches(self.root)
280
280
  return len(self.patch_list)
281
281
 
282
- def triangles_and_points(self, patch = None):
282
+ def triangles_and_points(self, patch = None, copy = False):
283
283
  """Returns arrays representing one patch or a combination of all the patches in the surface.
284
284
 
285
285
  arguments:
286
286
  patch (int, optional): patch index; if None, combined arrays for all patches are returned
287
+ copy (bool, default False): if True, a copy of the arrays is returned; if False, the cached
288
+ arrays are returned
287
289
 
288
290
  returns:
289
291
  tuple (triangles, points):
@@ -296,21 +298,30 @@ class Surface(rqsb.BaseSurface):
296
298
 
297
299
  self.extract_patches(self.root)
298
300
  if patch is None:
299
- if self.triangles is None:
300
- points_offset = 0
301
- for triangulated_patch in self.patch_list:
302
- (t, p) = triangulated_patch.triangles_and_points()
303
- if points_offset == 0:
304
- self.triangles = t.copy()
305
- self.points = p.copy()
306
- else:
307
- self.triangles = np.concatenate((self.triangles, t.copy() + points_offset))
308
- self.points = np.concatenate((self.points, p.copy()))
309
- points_offset += p.shape[0]
310
- return (self.triangles, self.points)
301
+ if self.triangles is None or self.points is None:
302
+ if self.triangles is None:
303
+ points_offset = 0
304
+ for triangulated_patch in self.patch_list:
305
+ (t, p) = triangulated_patch.triangles_and_points()
306
+ if points_offset == 0:
307
+ self.triangles = t
308
+ self.points = p
309
+ else:
310
+ self.triangles = np.concatenate((self.triangles, t.copy() + points_offset))
311
+ self.points = np.concatenate((self.points, p))
312
+ points_offset += p.shape[0]
313
+ if copy:
314
+ return (self.triangles.copy(), self.points.copy())
315
+ else:
316
+ return (self.triangles, self.points)
311
317
  assert 0 <= patch < len(self.patch_list), \
312
318
  ValueError(f'patch index {patch} out of range for surface with {len(self.patch_list)} patches')
313
- return self.patch_list[patch].triangles_and_points()
319
+ return self.patch_list[patch].triangles_and_points(copy = copy)
320
+
321
+ def decache_triangles_and_points(self):
322
+ """Removes the cached composite triangles and points arrays."""
323
+ self.points = None
324
+ self.triangles = None
314
325
 
315
326
  def triangle_count(self, patch = None):
316
327
  """Return the numner of triangles in this surface, or in one patch.
@@ -438,10 +449,11 @@ class Surface(rqsb.BaseSurface):
438
449
  t, p = large_surface.triangles_and_points()
439
450
  assert p.ndim == 2 and p.shape[1] == 3
440
451
  pp = np.concatenate((p, line), axis = 0)
441
- tp = np.empty(p.shape, dtype = int)
452
+ t_type = np.int32 if len(pp) <= 2_147_483_647 else np.int64
453
+ tp = np.empty(p.shape, dtype = t_type)
442
454
  tp[:, 0] = len(p)
443
455
  tp[:, 1] = len(p) + 1
444
- tp[:, 2] = np.arange(len(p), dtype = int)
456
+ tp[:, 2] = np.arange(len(p), dtype = t_type)
445
457
  cw = vec.clockwise_triangles(pp, tp)
446
458
  pai = (cw >= 0.0) # bool mask over p
447
459
  pbi = (cw <= 0.0) # bool mask over p
@@ -453,11 +465,11 @@ class Surface(rqsb.BaseSurface):
453
465
  # here we stick the two halves together into a single patch
454
466
  # todo: keep as two patches as required by RESQML business rules
455
467
  p_combo = np.empty((0, 3))
456
- t_combo = np.empty((0, 3), dtype = int)
468
+ t_combo = np.empty((0, 3), dtype = t_type)
457
469
  for i, tab in enumerate((ta, tb)):
458
470
  p_keep = np.unique(t[tab])
459
471
  # note new point index for each old point that is being kept
460
- p_map = np.full(len(p), -1, dtype = int)
472
+ p_map = np.full(len(p), -1, dtype = t_type)
461
473
  p_map[p_keep] = np.arange(len(p_keep))
462
474
  # copy those unique points into a trimmed points array
463
475
  points_trimmed = p[p_keep].copy()
@@ -950,7 +962,8 @@ class Surface(rqsb.BaseSurface):
950
962
  triangles.append(line.rstrip().split(" ")[1:4])
951
963
  assert len(vertices) >= 3, 'vertices missing'
952
964
  assert len(triangles) > 0, 'triangles missing'
953
- triangles = np.array(triangles, dtype = int) - index_offset
965
+ t_type = np.int32 if len(vertices) <= 2_147_483_647 else np.int64
966
+ triangles = np.array(triangles, dtype = t_type) - index_offset
954
967
  vertices = np.array(vertices, dtype = float)
955
968
  assert np.all(triangles >= 0) and np.all(triangles < len(vertices)), 'triangle vertex indices out of range'
956
969
  self.set_from_triangles_and_points(triangles = triangles, points = vertices)
@@ -1139,8 +1152,9 @@ class Surface(rqsb.BaseSurface):
1139
1152
 
1140
1153
  # TODO: implement alternate solution using edge functions in olio triangulation to optimise
1141
1154
  points_unique, inverse = np.unique(allpoints, axis = 0, return_inverse = True)
1142
- tris = np.array(tris)
1143
- tris_unique = np.empty(shape = tris.shape, dtype = int)
1155
+ t_type = np.int32 if len(allpoints) <= 2_147_483_647 else np.int64
1156
+ tris = np.array(tris, dtype = t_type)
1157
+ tris_unique = np.empty(shape = tris.shape, dtype = t_type)
1144
1158
  tris_unique[:, 0] = inverse[tris[:, 0]]
1145
1159
  tris_unique[:, 1] = inverse[tris[:, 1]]
1146
1160
  tris_unique[:, 2] = inverse[tris[:, 2]]
@@ -1333,7 +1347,8 @@ def distill_triangle_points(t, p):
1333
1347
  # find unique points used by triangles
1334
1348
  p_keep = np.unique(t)
1335
1349
  # note new point index for each old point that is being kept
1336
- p_map = np.full(len(p), -1, dtype = int)
1350
+ t_type = np.int32 if len(p) <= 2_147_483_647 else np.int64
1351
+ p_map = np.full(len(p), -1, dtype = t_type)
1337
1352
  p_map[p_keep] = np.arange(len(p_keep))
1338
1353
  # copy those unique points into a trimmed points array
1339
1354
  points_distilled = p[p_keep]
@@ -1360,8 +1375,9 @@ def nan_removed_triangles_and_points(t, p):
1360
1375
  expanded_mask[:] = np.expand_dims(np.logical_not(t_nan_mask), axis = -1)
1361
1376
  t_filtered = t[expanded_mask].reshape((-1, 3))
1362
1377
  # modified the filtered t values to adjust for the compression of filtered p
1363
- p_map = np.full(len(p), -1, dtype = int)
1364
- p_map[p_non_nan_mask] = np.arange(len(p_filtered), dtype = int)
1378
+ t_type = np.int32 if len(p) <= 2_147_483_647 else np.int64
1379
+ p_map = np.full(len(p), -1, dtype = t_type)
1380
+ p_map[p_non_nan_mask] = np.arange(len(p_filtered), dtype = t_type)
1365
1381
  t_filtered = p_map[t_filtered]
1366
1382
  assert t_filtered.ndim == 2 and t_filtered.shape[1] == 3
1367
1383
  assert not np.any(t_filtered < 0) and not np.any(t_filtered >= len(p_filtered))