resqpy 4.5.0__py3-none-any.whl → 4.6.3__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- resqpy/__init__.py +1 -1
- resqpy/grid/_defined_geometry.py +15 -9
- resqpy/grid_surface/__init__.py +61 -40
- resqpy/grid_surface/_find_faces.py +351 -243
- resqpy/grid_surface/grid_surface_cuda.py +172 -125
- resqpy/lines/_common.py +10 -7
- resqpy/lines/_polyline.py +20 -0
- resqpy/lines/_polyline_set.py +64 -34
- resqpy/model/_hdf5.py +17 -7
- resqpy/model/_model.py +2 -1
- resqpy/model/_xml.py +4 -4
- resqpy/multi_processing/_multiprocessing.py +1 -0
- resqpy/multi_processing/wrappers/grid_surface_mp.py +12 -3
- resqpy/olio/intersection.py +2 -3
- resqpy/olio/read_nexus_fault.py +71 -67
- resqpy/olio/triangulation.py +66 -22
- resqpy/olio/vector_utilities.py +175 -71
- resqpy/olio/wellspec_keywords.py +5 -4
- resqpy/olio/write_hdf5.py +16 -8
- resqpy/olio/xml_et.py +3 -3
- resqpy/property/_collection_get_attributes.py +11 -5
- resqpy/property/_property.py +16 -5
- resqpy/property/property_collection.py +36 -11
- resqpy/surface/__init__.py +2 -2
- resqpy/surface/_surface.py +69 -6
- resqpy/time_series/__init__.py +3 -2
- resqpy/time_series/_time_series.py +10 -0
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/METADATA +3 -3
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/RECORD +31 -31
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/WHEEL +1 -1
- {resqpy-4.5.0.dist-info → resqpy-4.6.3.dist-info}/LICENSE +0 -0
@@ -24,7 +24,7 @@ import resqpy.olio.vector_utilities as vec
|
|
24
24
|
# note: resqpy.grid_surface._grid_surface_cuda will be imported by the find_faces_to_represent_surface() function if needed
|
25
25
|
|
26
26
|
|
27
|
-
def find_faces_to_represent_surface_staffa(grid, surface, name, feature_type =
|
27
|
+
def find_faces_to_represent_surface_staffa(grid, surface, name, feature_type = "fault", progress_fn = None):
|
28
28
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
29
29
|
|
30
30
|
note:
|
@@ -92,11 +92,11 @@ def find_faces_to_represent_surface_staffa(grid, surface, name, feature_type = '
|
|
92
92
|
# log.debug('finding surface triangle boxes')
|
93
93
|
t, p = surface.triangles_and_points()
|
94
94
|
if not bu.matching_uuids(grid.crs_uuid, surface.crs_uuid):
|
95
|
-
log.debug(
|
95
|
+
log.debug("converting from surface crs to grid crs")
|
96
96
|
s_crs = rqc.Crs(surface.model, uuid = surface.crs_uuid)
|
97
97
|
s_crs.convert_array_to(grid.crs, p)
|
98
98
|
triangles = p[t]
|
99
|
-
assert triangles.size > 0,
|
99
|
+
assert triangles.size > 0, "no triangles in surface"
|
100
100
|
triangle_boxes = np.empty((triangles.shape[0], 2, 3))
|
101
101
|
triangle_boxes[:, 0, :] = np.amin(triangles, axis = 1)
|
102
102
|
triangle_boxes[:, 1, :] = np.amax(triangles, axis = 1)
|
@@ -120,26 +120,32 @@ def find_faces_to_represent_surface_staffa(grid, surface, name, feature_type = '
|
|
120
120
|
progress_fn(progress_base + progress_batch * (float(j) / float(grid.nj)))
|
121
121
|
for i in range(grid.ni):
|
122
122
|
if column_k_vector_boxes is not None and bx.boxes_overlap(batch_box, column_k_vector_boxes[j, i]):
|
123
|
-
full_intersects = meet.line_set_triangles_intersects(
|
124
|
-
|
125
|
-
|
126
|
-
|
123
|
+
full_intersects = meet.line_set_triangles_intersects(
|
124
|
+
centre_points[:-1, j, i],
|
125
|
+
k_vectors[:, j, i],
|
126
|
+
triangles[ti_base:ti_end],
|
127
|
+
line_segment = True,
|
128
|
+
)
|
127
129
|
distilled_intersects, _, _ = meet.distilled_intersects(full_intersects)
|
128
130
|
k_faces[distilled_intersects, j, i] = True
|
129
|
-
if j < grid.nj - 1 and column_j_vector_boxes is not None and
|
130
|
-
batch_box, column_j_vector_boxes[j, i]):
|
131
|
-
full_intersects = meet.line_set_triangles_intersects(
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
if (j < grid.nj - 1 and column_j_vector_boxes is not None and
|
132
|
+
bx.boxes_overlap(batch_box, column_j_vector_boxes[j, i])):
|
133
|
+
full_intersects = meet.line_set_triangles_intersects(
|
134
|
+
centre_points[:, j, i],
|
135
|
+
j_vectors[:, j, i],
|
136
|
+
triangles[ti_base:ti_end],
|
137
|
+
line_segment = True,
|
138
|
+
)
|
135
139
|
distilled_intersects, _, _ = meet.distilled_intersects(full_intersects)
|
136
140
|
j_faces[distilled_intersects, j, i] = True
|
137
|
-
if i < grid.ni - 1 and column_i_vector_boxes is not None and
|
138
|
-
batch_box, column_i_vector_boxes[j, i]):
|
139
|
-
full_intersects = meet.line_set_triangles_intersects(
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
if (i < grid.ni - 1 and column_i_vector_boxes is not None and
|
142
|
+
bx.boxes_overlap(batch_box, column_i_vector_boxes[j, i])):
|
143
|
+
full_intersects = meet.line_set_triangles_intersects(
|
144
|
+
centre_points[:, j, i],
|
145
|
+
i_vectors[:, j, i],
|
146
|
+
triangles[ti_base:ti_end],
|
147
|
+
line_segment = True,
|
148
|
+
)
|
143
149
|
distilled_intersects, _, _ = meet.distilled_intersects(full_intersects)
|
144
150
|
i_faces[distilled_intersects, j, i] = True
|
145
151
|
ti_base = ti_end
|
@@ -152,14 +158,16 @@ def find_faces_to_represent_surface_staffa(grid, surface, name, feature_type = '
|
|
152
158
|
# log.debug('face counts: K: ' + str(np.count_nonzero(k_faces)) +
|
153
159
|
# '; J: ' + str(np.count_nonzero(j_faces)) +
|
154
160
|
# '; I: ' + str(np.count_nonzero(i_faces)))
|
155
|
-
gcs = rqf.GridConnectionSet(
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
161
|
+
gcs = rqf.GridConnectionSet(
|
162
|
+
grid.model,
|
163
|
+
grid = grid,
|
164
|
+
k_faces = k_faces,
|
165
|
+
j_faces = j_faces,
|
166
|
+
i_faces = i_faces,
|
167
|
+
feature_name = name,
|
168
|
+
feature_type = feature_type,
|
169
|
+
create_organizing_objects_where_needed = True,
|
170
|
+
)
|
163
171
|
|
164
172
|
if progress_fn is not None:
|
165
173
|
progress_fn(1.0)
|
@@ -167,16 +175,18 @@ def find_faces_to_represent_surface_staffa(grid, surface, name, feature_type = '
|
|
167
175
|
return gcs
|
168
176
|
|
169
177
|
|
170
|
-
def find_faces_to_represent_surface_regular(
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
178
|
+
def find_faces_to_represent_surface_regular(
|
179
|
+
grid,
|
180
|
+
surface,
|
181
|
+
name,
|
182
|
+
title = None,
|
183
|
+
centres = None,
|
184
|
+
agitate = False,
|
185
|
+
feature_type = "fault",
|
186
|
+
progress_fn = None,
|
187
|
+
consistent_side = False,
|
188
|
+
return_properties = None,
|
189
|
+
):
|
180
190
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
181
191
|
|
182
192
|
arguments:
|
@@ -224,9 +234,9 @@ def find_faces_to_represent_surface_regular(grid,
|
|
224
234
|
return_normal_vectors = False
|
225
235
|
return_offsets = False
|
226
236
|
if return_properties:
|
227
|
-
assert all([p in [
|
228
|
-
return_normal_vectors =
|
229
|
-
return_offsets =
|
237
|
+
assert all([p in ["offset", "normal vector"] for p in return_properties])
|
238
|
+
return_normal_vectors = "normal vector" in return_properties
|
239
|
+
return_offsets = "offset" in return_properties
|
230
240
|
|
231
241
|
if title is None:
|
232
242
|
title = name
|
@@ -234,34 +244,38 @@ def find_faces_to_represent_surface_regular(grid,
|
|
234
244
|
if progress_fn is not None:
|
235
245
|
progress_fn(0.0)
|
236
246
|
|
237
|
-
log.debug(f
|
238
|
-
log.debug(f
|
247
|
+
log.debug(f"intersecting surface {surface.title} with regular grid {grid.title}")
|
248
|
+
log.debug(f"grid extent kji: {grid.extent_kji}")
|
239
249
|
|
240
|
-
grid_dxyz = (
|
250
|
+
grid_dxyz = (
|
251
|
+
grid.block_dxyz_dkji[2, 0],
|
252
|
+
grid.block_dxyz_dkji[1, 1],
|
253
|
+
grid.block_dxyz_dkji[0, 2],
|
254
|
+
)
|
241
255
|
if centres is None:
|
242
256
|
centres = grid.centre_point(use_origin = True)
|
243
257
|
if consistent_side:
|
244
|
-
log.debug(
|
258
|
+
log.debug("making all triangles clockwise")
|
245
259
|
# note: following will shuffle order of vertices within t cached in surface
|
246
260
|
surface.make_all_clockwise_xy(reorient = True)
|
247
261
|
t, p = surface.triangles_and_points()
|
248
|
-
assert t is not None and p is not None, f
|
262
|
+
assert t is not None and p is not None, f"surface {surface.title} is empty"
|
249
263
|
if agitate:
|
250
264
|
p += 1.0e-5 * (np.random.random(p.shape) - 0.5)
|
251
|
-
log.debug(f
|
252
|
-
log.debug(f
|
253
|
-
log.debug(f
|
265
|
+
log.debug(f"surface: {surface.title}; p0: {p[0]}; crs uuid: {surface.crs_uuid}")
|
266
|
+
log.debug(f"surface min xyz: {np.min(p, axis = 0)}")
|
267
|
+
log.debug(f"surface max xyz: {np.max(p, axis = 0)}")
|
254
268
|
if not bu.matching_uuids(grid.crs_uuid, surface.crs_uuid):
|
255
|
-
log.debug(
|
269
|
+
log.debug("converting from surface crs to grid crs")
|
256
270
|
s_crs = rqc.Crs(surface.model, uuid = surface.crs_uuid)
|
257
271
|
s_crs.convert_array_to(grid.crs, p)
|
258
272
|
surface.crs_uuid = grid.crs.uuid
|
259
|
-
log.debug(f
|
260
|
-
log.debug(f
|
261
|
-
log.debug(f
|
273
|
+
log.debug(f"surface: {surface.title}; p0: {p[0]}; crs uuid: {surface.crs_uuid}")
|
274
|
+
log.debug(f"surface min xyz: {np.min(p, axis = 0)}")
|
275
|
+
log.debug(f"surface max xyz: {np.max(p, axis = 0)}")
|
262
276
|
|
263
|
-
log.debug(f
|
264
|
-
log.debug(f
|
277
|
+
log.debug(f"centres min xyz: {np.min(centres.reshape((-1, 3)), axis = 0)}")
|
278
|
+
log.debug(f"centres max xyz: {np.max(centres.reshape((-1, 3)), axis = 0)}")
|
265
279
|
|
266
280
|
t_count = len(t)
|
267
281
|
|
@@ -269,23 +283,25 @@ def find_faces_to_represent_surface_regular(grid,
|
|
269
283
|
|
270
284
|
# K direction (xy projection)
|
271
285
|
if grid.nk > 1:
|
272
|
-
log.debug(
|
286
|
+
log.debug("searching for k faces")
|
273
287
|
k_faces = np.zeros((grid.nk - 1, grid.nj, grid.ni), dtype = bool)
|
274
288
|
k_sides = np.zeros((grid.nk - 1, grid.nj, grid.ni), dtype = bool)
|
275
|
-
k_offsets = np.full((grid.nk - 1, grid.nj, grid.ni), np.nan) if return_offsets else None
|
276
|
-
k_normals = np.full((grid.nk - 1, grid.nj, grid.ni, 3), np.nan) if return_normal_vectors else None
|
289
|
+
k_offsets = (np.full((grid.nk - 1, grid.nj, grid.ni), np.nan) if return_offsets else None)
|
290
|
+
k_normals = (np.full((grid.nk - 1, grid.nj, grid.ni, 3), np.nan) if return_normal_vectors else None)
|
277
291
|
k_centres = centres[0, :, :].reshape((-1, 3))
|
278
|
-
k_hits = vec.points_in_triangles(p, t, k_centres, projection =
|
292
|
+
k_hits = vec.points_in_triangles(p, t, k_centres, projection = "xy", edged = True).reshape(
|
279
293
|
(t_count, grid.nj, grid.ni))
|
280
294
|
del k_centres
|
281
295
|
if consistent_side:
|
282
|
-
cwt =
|
296
|
+
cwt = vec.clockwise_triangles(p, t, projection = "xy") >= 0.0
|
283
297
|
for k_t, k_j, k_i in np.stack(np.where(k_hits), axis = -1):
|
284
|
-
xyz = meet.line_triangle_intersect(
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
298
|
+
xyz = meet.line_triangle_intersect(
|
299
|
+
centres[0, k_j, k_i],
|
300
|
+
centres[-1, k_j, k_i] - centres[0, k_j, k_i],
|
301
|
+
p[t[k_t]],
|
302
|
+
line_segment = True,
|
303
|
+
t_tol = 1.0e-6,
|
304
|
+
)
|
289
305
|
if xyz is None: # meeting point is outwith grid
|
290
306
|
continue
|
291
307
|
k_face = int((xyz[2] - centres[0, k_j, k_i, 2]) / grid_dxyz[2])
|
@@ -299,15 +315,15 @@ def find_faces_to_represent_surface_regular(grid,
|
|
299
315
|
k_sides[k_face, k_j, k_i] = cwt[k_t]
|
300
316
|
if return_offsets:
|
301
317
|
# compute offset as z diff between xyz and face
|
302
|
-
k_offsets[k_face, k_j,
|
303
|
-
|
318
|
+
k_offsets[k_face, k_j,
|
319
|
+
k_i] = xyz[2] - 0.5 * (centres[k_face, k_j, k_i, 2] + centres[k_face + 1, k_j, k_i, 2])
|
304
320
|
if return_normal_vectors:
|
305
321
|
k_normals[k_face, k_j, k_i] = vec.triangle_normal_vector(p[t[k_t]])
|
306
322
|
# todo: if consistent side, could deliver information about horizon surface inversion
|
307
323
|
if k_normals[k_face, k_j, k_i, 2] > 0.0:
|
308
324
|
k_normals[k_face, k_j, k_i] = -k_normals[k_face, k_j, k_i] # -ve z hemisphere normal
|
309
325
|
del k_hits
|
310
|
-
log.debug(f
|
326
|
+
log.debug(f"k face count: {np.count_nonzero(k_faces)}")
|
311
327
|
else:
|
312
328
|
k_faces = None
|
313
329
|
k_sides = None
|
@@ -318,23 +334,25 @@ def find_faces_to_represent_surface_regular(grid,
|
|
318
334
|
|
319
335
|
# J direction (xz projection)
|
320
336
|
if grid.nj > 1:
|
321
|
-
log.debug(
|
337
|
+
log.debug("searching for j faces")
|
322
338
|
j_faces = np.zeros((grid.nk, grid.nj - 1, grid.ni), dtype = bool)
|
323
339
|
j_sides = np.zeros((grid.nk, grid.nj - 1, grid.ni), dtype = bool)
|
324
|
-
j_offsets = np.full((grid.nk, grid.nj - 1, grid.ni), np.nan) if return_offsets else None
|
325
|
-
j_normals = np.full((grid.nk, grid.nj - 1, grid.ni, 3), np.nan) if return_normal_vectors else None
|
340
|
+
j_offsets = (np.full((grid.nk, grid.nj - 1, grid.ni), np.nan) if return_offsets else None)
|
341
|
+
j_normals = (np.full((grid.nk, grid.nj - 1, grid.ni, 3), np.nan) if return_normal_vectors else None)
|
326
342
|
j_centres = centres[:, 0, :].reshape((-1, 3))
|
327
|
-
j_hits = vec.points_in_triangles(p, t, j_centres, projection =
|
343
|
+
j_hits = vec.points_in_triangles(p, t, j_centres, projection = "xz", edged = True).reshape(
|
328
344
|
(t_count, grid.nk, grid.ni))
|
329
345
|
del j_centres
|
330
346
|
if consistent_side:
|
331
|
-
cwt =
|
347
|
+
cwt = vec.clockwise_triangles(p, t, projection = "xz") >= 0.0
|
332
348
|
for j_t, j_k, j_i in np.stack(np.where(j_hits), axis = -1):
|
333
|
-
xyz = meet.line_triangle_intersect(
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
349
|
+
xyz = meet.line_triangle_intersect(
|
350
|
+
centres[j_k, 0, j_i],
|
351
|
+
centres[j_k, -1, j_i] - centres[j_k, 0, j_i],
|
352
|
+
p[t[j_t]],
|
353
|
+
line_segment = True,
|
354
|
+
t_tol = 1.0e-6,
|
355
|
+
)
|
338
356
|
if xyz is None: # meeting point is outwith grid
|
339
357
|
continue
|
340
358
|
j_face = int((xyz[1] - centres[j_k, 0, j_i, 1]) / grid_dxyz[1])
|
@@ -348,14 +366,14 @@ def find_faces_to_represent_surface_regular(grid,
|
|
348
366
|
j_sides[j_k, j_face, j_i] = cwt[j_t]
|
349
367
|
if return_offsets:
|
350
368
|
# compute offset as y diff between xyz and face
|
351
|
-
j_offsets[j_k, j_face,
|
352
|
-
|
369
|
+
j_offsets[j_k, j_face,
|
370
|
+
j_i] = xyz[1] - 0.5 * (centres[j_k, j_face, j_i, 1] + centres[j_k, j_face + 1, j_i, 1])
|
353
371
|
if return_normal_vectors:
|
354
372
|
j_normals[j_k, j_face, j_i] = vec.triangle_normal_vector(p[t[j_t]])
|
355
373
|
if j_normals[j_k, j_face, j_i, 2] > 0.0:
|
356
374
|
j_normals[j_k, j_face, j_i] = -j_normals[j_k, j_face, j_i] # -ve z hemisphere normal
|
357
375
|
del j_hits
|
358
|
-
log.debug(f
|
376
|
+
log.debug(f"j face count: {np.count_nonzero(j_faces)}")
|
359
377
|
else:
|
360
378
|
j_faces = None
|
361
379
|
j_sides = None
|
@@ -366,23 +384,25 @@ def find_faces_to_represent_surface_regular(grid,
|
|
366
384
|
|
367
385
|
# I direction (yz projection)
|
368
386
|
if grid.ni > 1:
|
369
|
-
log.debug(
|
387
|
+
log.debug("searching for i faces")
|
370
388
|
i_faces = np.zeros((grid.nk, grid.nj, grid.ni - 1), dtype = bool)
|
371
389
|
i_sides = np.zeros((grid.nk, grid.nj, grid.ni - 1), dtype = bool)
|
372
|
-
i_offsets = np.full((grid.nk, grid.nj, grid.ni - 1), np.nan) if return_offsets else None
|
373
|
-
i_normals = np.full((grid.nk, grid.nj, grid.ni - 1, 3), np.nan) if return_normal_vectors else None
|
390
|
+
i_offsets = (np.full((grid.nk, grid.nj, grid.ni - 1), np.nan) if return_offsets else None)
|
391
|
+
i_normals = (np.full((grid.nk, grid.nj, grid.ni - 1, 3), np.nan) if return_normal_vectors else None)
|
374
392
|
i_centres = centres[:, :, 0].reshape((-1, 3))
|
375
|
-
i_hits = vec.points_in_triangles(p, t, i_centres, projection =
|
393
|
+
i_hits = vec.points_in_triangles(p, t, i_centres, projection = "yz", edged = True).reshape(
|
376
394
|
(t_count, grid.nk, grid.nj))
|
377
395
|
del i_centres
|
378
396
|
if consistent_side:
|
379
|
-
cwt =
|
397
|
+
cwt = vec.clockwise_triangles(p, t, projection = "yz") >= 0.0
|
380
398
|
for i_t, i_k, i_j in np.stack(np.where(i_hits), axis = -1):
|
381
|
-
xyz = meet.line_triangle_intersect(
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
399
|
+
xyz = meet.line_triangle_intersect(
|
400
|
+
centres[i_k, i_j, 0],
|
401
|
+
centres[i_k, i_j, -1] - centres[i_k, i_j, 0],
|
402
|
+
p[t[i_t]],
|
403
|
+
line_segment = True,
|
404
|
+
t_tol = 1.0e-6,
|
405
|
+
)
|
386
406
|
if xyz is None: # meeting point is outwith grid
|
387
407
|
continue
|
388
408
|
i_face = int((xyz[0] - centres[i_k, i_j, 0, 0]) / grid_dxyz[0])
|
@@ -396,14 +416,14 @@ def find_faces_to_represent_surface_regular(grid,
|
|
396
416
|
i_sides[i_k, i_j, i_face] = cwt[i_t]
|
397
417
|
if return_offsets:
|
398
418
|
# compute offset as x diff between xyz and face
|
399
|
-
i_offsets[i_k, i_j,
|
400
|
-
|
419
|
+
i_offsets[i_k, i_j,
|
420
|
+
i_face] = xyz[0] - 0.5 * (centres[i_k, i_j, i_face, 0] + centres[i_k, i_j, i_face + 1, 0])
|
401
421
|
if return_normal_vectors:
|
402
422
|
i_normals[i_k, i_j, i_face] = vec.triangle_normal_vector(p[t[i_t]])
|
403
423
|
if i_normals[i_k, i_j, i_face, 2] > 0.0:
|
404
424
|
i_normals[i_k, i_j, i_face] = -i_normals[i_k, i_j, i_face] # -ve z hemisphere normal
|
405
425
|
del i_hits
|
406
|
-
log.debug(f
|
426
|
+
log.debug(f"i face count: {np.count_nonzero(i_faces)}")
|
407
427
|
else:
|
408
428
|
i_faces = None
|
409
429
|
i_sides = None
|
@@ -417,36 +437,38 @@ def find_faces_to_represent_surface_regular(grid,
|
|
417
437
|
j_sides = None
|
418
438
|
i_sides = None
|
419
439
|
|
420
|
-
log.debug(
|
421
|
-
gcs = rqf.GridConnectionSet(
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
440
|
+
log.debug("converting face sets into grid connection set")
|
441
|
+
gcs = rqf.GridConnectionSet(
|
442
|
+
grid.model,
|
443
|
+
grid = grid,
|
444
|
+
k_faces = k_faces,
|
445
|
+
j_faces = j_faces,
|
446
|
+
i_faces = i_faces,
|
447
|
+
k_sides = k_sides,
|
448
|
+
j_sides = j_sides,
|
449
|
+
i_sides = i_sides,
|
450
|
+
feature_name = name,
|
451
|
+
feature_type = feature_type,
|
452
|
+
title = title,
|
453
|
+
create_organizing_objects_where_needed = True,
|
454
|
+
)
|
433
455
|
|
434
456
|
# NB. following assumes faces have been added to gcs in a particular order!
|
435
457
|
if return_offsets:
|
436
|
-
k_offsets_list = np.empty((0,)) if k_offsets is None else k_offsets[np.where(k_faces)]
|
437
|
-
j_offsets_list = np.empty((0,)) if j_offsets is None else j_offsets[np.where(j_faces)]
|
438
|
-
i_offsets_list = np.empty((0,)) if i_offsets is None else i_offsets[np.where(i_faces)]
|
458
|
+
k_offsets_list = (np.empty((0,)) if k_offsets is None else k_offsets[np.where(k_faces)])
|
459
|
+
j_offsets_list = (np.empty((0,)) if j_offsets is None else j_offsets[np.where(j_faces)])
|
460
|
+
i_offsets_list = (np.empty((0,)) if i_offsets is None else i_offsets[np.where(i_faces)])
|
439
461
|
all_offsets = _all_offsets(grid.crs, k_offsets_list, j_offsets_list, i_offsets_list)
|
440
|
-
log.debug(f
|
462
|
+
log.debug(f"gcs count: {gcs.count}; all offsets shape: {all_offsets.shape}")
|
441
463
|
assert all_offsets.shape == (gcs.count,)
|
442
464
|
|
443
465
|
# NB. following assumes faces have been added to gcs in a particular order!
|
444
466
|
if return_normal_vectors:
|
445
|
-
k_normals_list = np.empty((0, 3)) if k_normals is None else k_normals[np.where(k_faces)]
|
446
|
-
j_normals_list = np.empty((0, 3)) if j_normals is None else j_normals[np.where(j_faces)]
|
447
|
-
i_normals_list = np.empty((0, 3)) if i_normals is None else i_normals[np.where(i_faces)]
|
467
|
+
k_normals_list = (np.empty((0, 3)) if k_normals is None else k_normals[np.where(k_faces)])
|
468
|
+
j_normals_list = (np.empty((0, 3)) if j_normals is None else j_normals[np.where(j_faces)])
|
469
|
+
i_normals_list = (np.empty((0, 3)) if i_normals is None else i_normals[np.where(i_faces)])
|
448
470
|
all_normals = np.concatenate((k_normals_list, j_normals_list, i_normals_list), axis = 0)
|
449
|
-
log.debug(f
|
471
|
+
log.debug(f"gcs count: {gcs.count}; all normals shape: {all_normals.shape}")
|
450
472
|
assert all_normals.shape == (gcs.count, 3)
|
451
473
|
if grid.crs.xy_units != grid.crs.z_units:
|
452
474
|
wam.convert_lengths(all_normals[:, 2], grid.crs.z_units, grid.crs.xy_units)
|
@@ -459,24 +481,26 @@ def find_faces_to_represent_surface_regular(grid,
|
|
459
481
|
if return_properties:
|
460
482
|
props_dict = {}
|
461
483
|
if return_offsets:
|
462
|
-
props_dict[
|
484
|
+
props_dict["offset"] = all_offsets
|
463
485
|
if return_normal_vectors:
|
464
|
-
props_dict[
|
486
|
+
props_dict["normal vector"] = all_normals
|
465
487
|
return (gcs, props_dict)
|
466
488
|
|
467
489
|
return gcs
|
468
490
|
|
469
491
|
|
470
|
-
def find_faces_to_represent_surface_regular_optimised(
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
492
|
+
def find_faces_to_represent_surface_regular_optimised(
|
493
|
+
grid,
|
494
|
+
surface,
|
495
|
+
name,
|
496
|
+
title = None,
|
497
|
+
agitate = False,
|
498
|
+
feature_type = "fault",
|
499
|
+
is_curtain = False,
|
500
|
+
progress_fn = None,
|
501
|
+
return_properties = None,
|
502
|
+
raw_bisector = False,
|
503
|
+
):
|
480
504
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
481
505
|
|
482
506
|
argumants:
|
@@ -532,15 +556,21 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
532
556
|
return_flange_bool = False
|
533
557
|
if return_properties:
|
534
558
|
assert all([
|
535
|
-
p in [
|
536
|
-
|
559
|
+
p in [
|
560
|
+
"triangle",
|
561
|
+
"depth",
|
562
|
+
"offset",
|
563
|
+
"grid bisector",
|
564
|
+
"grid shadow",
|
565
|
+
"flange bool",
|
566
|
+
] for p in return_properties
|
537
567
|
])
|
538
|
-
return_triangles =
|
539
|
-
return_depths =
|
540
|
-
return_offsets =
|
541
|
-
return_bisector =
|
542
|
-
return_shadow =
|
543
|
-
return_flange_bool =
|
568
|
+
return_triangles = "triangle" in return_properties
|
569
|
+
return_depths = "depth" in return_properties
|
570
|
+
return_offsets = "offset" in return_properties
|
571
|
+
return_bisector = "grid bisector" in return_properties
|
572
|
+
return_shadow = "grid shadow" in return_properties
|
573
|
+
return_flange_bool = "flange bool" in return_properties
|
544
574
|
if return_flange_bool:
|
545
575
|
return_triangles = True
|
546
576
|
|
@@ -550,25 +580,29 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
550
580
|
if progress_fn is not None:
|
551
581
|
progress_fn(0.0)
|
552
582
|
|
553
|
-
log.debug(f
|
583
|
+
log.debug(f"intersecting surface {surface.title} with regular grid {grid.title}")
|
554
584
|
# log.debug(f'grid extent kji: {grid.extent_kji}')
|
555
585
|
|
556
|
-
grid_dxyz = (
|
586
|
+
grid_dxyz = (
|
587
|
+
grid.block_dxyz_dkji[2, 0],
|
588
|
+
grid.block_dxyz_dkji[1, 1],
|
589
|
+
grid.block_dxyz_dkji[0, 2],
|
590
|
+
)
|
557
591
|
triangles, points = surface.triangles_and_points()
|
558
|
-
assert triangles is not None and points is not None, f
|
592
|
+
assert (triangles is not None and points is not None), f"surface {surface.title} is empty"
|
559
593
|
if agitate:
|
560
594
|
points += 1.0e-5 * (np.random.random(points.shape) - 0.5)
|
561
|
-
#
|
595
|
+
# log.debug(f'surface: {surface.title}; p0: {points[0]}; crs uuid: {surface.crs_uuid}')
|
562
596
|
# log.debug(f'surface min xyz: {np.min(points, axis = 0)}')
|
563
597
|
# log.debug(f'surface max xyz: {np.max(points, axis = 0)}')
|
564
598
|
if not bu.matching_uuids(grid.crs_uuid, surface.crs_uuid):
|
565
|
-
log.debug(
|
599
|
+
log.debug("converting from surface crs to grid crs")
|
566
600
|
s_crs = rqc.Crs(surface.model, uuid = surface.crs_uuid)
|
567
601
|
s_crs.convert_array_to(grid.crs, points)
|
568
602
|
surface.crs_uuid = grid.crs.uuid
|
569
603
|
# log.debug(f'surface: {surface.title}; p0: {points[0]}; crs uuid: {surface.crs_uuid}')
|
570
604
|
# log.debug(f'surface min xyz: {np.min(points, axis = 0)}')
|
571
|
-
#
|
605
|
+
# log.debug(f'surface max xyz: {np.max(points, axis = 0)}')
|
572
606
|
|
573
607
|
nk = 1 if is_curtain else grid.nk
|
574
608
|
# K direction (xy projection)
|
@@ -586,11 +620,23 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
586
620
|
axis = 2
|
587
621
|
index1 = 1
|
588
622
|
index2 = 2
|
589
|
-
k_faces, k_offsets, k_triangles =
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
623
|
+
k_faces, k_offsets, k_triangles = intersect_numba(
|
624
|
+
axis,
|
625
|
+
index1,
|
626
|
+
index2,
|
627
|
+
k_hits,
|
628
|
+
nk,
|
629
|
+
points,
|
630
|
+
triangles,
|
631
|
+
grid_dxyz,
|
632
|
+
k_faces,
|
633
|
+
return_depths,
|
634
|
+
k_depths,
|
635
|
+
return_offsets,
|
636
|
+
k_offsets,
|
637
|
+
return_triangles,
|
638
|
+
k_triangles,
|
639
|
+
)
|
594
640
|
del k_hits
|
595
641
|
del p_xy
|
596
642
|
log.debug(f"k face count: {np.count_nonzero(k_faces)}")
|
@@ -617,11 +663,23 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
617
663
|
axis = 1
|
618
664
|
index1 = 0
|
619
665
|
index2 = 2
|
620
|
-
j_faces, j_offsets, j_triangles =
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
666
|
+
j_faces, j_offsets, j_triangles = intersect_numba(
|
667
|
+
axis,
|
668
|
+
index1,
|
669
|
+
index2,
|
670
|
+
j_hits,
|
671
|
+
grid.nj,
|
672
|
+
points,
|
673
|
+
triangles,
|
674
|
+
grid_dxyz,
|
675
|
+
j_faces,
|
676
|
+
return_depths,
|
677
|
+
j_depths,
|
678
|
+
return_offsets,
|
679
|
+
j_offsets,
|
680
|
+
return_triangles,
|
681
|
+
j_triangles,
|
682
|
+
)
|
625
683
|
del j_hits
|
626
684
|
del p_xz
|
627
685
|
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
@@ -653,11 +711,23 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
653
711
|
axis = 0
|
654
712
|
index1 = 0
|
655
713
|
index2 = 1
|
656
|
-
i_faces, i_offsets, i_triangles =
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
714
|
+
i_faces, i_offsets, i_triangles = intersect_numba(
|
715
|
+
axis,
|
716
|
+
index1,
|
717
|
+
index2,
|
718
|
+
i_hits,
|
719
|
+
grid.ni,
|
720
|
+
points,
|
721
|
+
triangles,
|
722
|
+
grid_dxyz,
|
723
|
+
i_faces,
|
724
|
+
return_depths,
|
725
|
+
i_depths,
|
726
|
+
return_offsets,
|
727
|
+
i_offsets,
|
728
|
+
return_triangles,
|
729
|
+
i_triangles,
|
730
|
+
)
|
661
731
|
del i_hits
|
662
732
|
del p_yz
|
663
733
|
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
@@ -696,9 +766,9 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
696
766
|
# NB. following assumes faces have been added to gcs in a particular order!
|
697
767
|
if return_triangles:
|
698
768
|
# log.debug('preparing triangles array')
|
699
|
-
k_tri_list = np.empty((0,)) if k_triangles is None else k_triangles[
|
700
|
-
j_tri_list = np.empty((0,)) if j_triangles is None else j_triangles[
|
701
|
-
i_tri_list = np.empty((0,)) if i_triangles is None else i_triangles[
|
769
|
+
k_tri_list = (np.empty((0,)) if k_triangles is None else k_triangles[_where_true(k_faces)])
|
770
|
+
j_tri_list = (np.empty((0,)) if j_triangles is None else j_triangles[_where_true(j_faces)])
|
771
|
+
i_tri_list = (np.empty((0,)) if i_triangles is None else i_triangles[_where_true(i_faces)])
|
702
772
|
all_tris = np.concatenate((k_tri_list, j_tri_list, i_tri_list), axis = 0)
|
703
773
|
# log.debug(f'gcs count: {gcs.count}; all triangles shape: {all_tris.shape}')
|
704
774
|
assert all_tris.shape == (gcs.count,)
|
@@ -706,9 +776,9 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
706
776
|
# NB. following assumes faces have been added to gcs in a particular order!
|
707
777
|
if return_depths:
|
708
778
|
# log.debug('preparing depths array')
|
709
|
-
k_depths_list = np.empty((0,)) if k_depths is None else k_depths[
|
710
|
-
j_depths_list = np.empty((0,)) if j_depths is None else j_depths[
|
711
|
-
i_depths_list = np.empty((0,)) if i_depths is None else i_depths[
|
779
|
+
k_depths_list = (np.empty((0,)) if k_depths is None else k_depths[_where_true(k_faces)])
|
780
|
+
j_depths_list = (np.empty((0,)) if j_depths is None else j_depths[_where_true(j_faces)])
|
781
|
+
i_depths_list = (np.empty((0,)) if i_depths is None else i_depths[_where_true(i_faces)])
|
712
782
|
all_depths = np.concatenate((k_depths_list, j_depths_list, i_depths_list), axis = 0)
|
713
783
|
# log.debug(f'gcs count: {gcs.count}; all depths shape: {all_depths.shape}')
|
714
784
|
assert all_depths.shape == (gcs.count,)
|
@@ -716,19 +786,19 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
716
786
|
# NB. following assumes faces have been added to gcs in a particular order!
|
717
787
|
if return_offsets:
|
718
788
|
# log.debug('preparing offsets array')
|
719
|
-
k_offsets_list = np.empty((0,)) if k_offsets is None else k_offsets[
|
720
|
-
j_offsets_list = np.empty((0,)) if j_offsets is None else j_offsets[
|
721
|
-
i_offsets_list = np.empty((0,)) if i_offsets is None else i_offsets[
|
789
|
+
k_offsets_list = (np.empty((0,)) if k_offsets is None else k_offsets[_where_true(k_faces)])
|
790
|
+
j_offsets_list = (np.empty((0,)) if j_offsets is None else j_offsets[_where_true(j_faces)])
|
791
|
+
i_offsets_list = (np.empty((0,)) if i_offsets is None else i_offsets[_where_true(i_faces)])
|
722
792
|
all_offsets = _all_offsets(grid.crs, k_offsets_list, j_offsets_list, i_offsets_list)
|
723
793
|
# log.debug(f'gcs count: {gcs.count}; all offsets shape: {all_offsets.shape}')
|
724
794
|
assert all_offsets.shape == (gcs.count,)
|
725
795
|
|
726
796
|
if return_flange_bool:
|
727
797
|
# log.debug('preparing flange array')
|
728
|
-
flange_bool_uuid = surface.model.uuid(title =
|
729
|
-
obj_type =
|
798
|
+
flange_bool_uuid = surface.model.uuid(title = "flange bool",
|
799
|
+
obj_type = "DiscreteProperty",
|
730
800
|
related_uuid = surface.uuid)
|
731
|
-
assert flange_bool_uuid is not None, f"No flange bool property found for surface: {surface.title}"
|
801
|
+
assert (flange_bool_uuid is not None), f"No flange bool property found for surface: {surface.title}"
|
732
802
|
flange_bool = rqp.Property(surface.model, uuid = flange_bool_uuid)
|
733
803
|
flange_array = flange_bool.array_ref()
|
734
804
|
all_flange = np.take(flange_array, all_tris)
|
@@ -737,46 +807,46 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
737
807
|
# note: following is a grid cells property, not a gcs property
|
738
808
|
if return_bisector:
|
739
809
|
if is_curtain:
|
740
|
-
log.debug(
|
810
|
+
log.debug("preparing columns bisector")
|
741
811
|
bisector = column_bisector_from_faces((grid.nj, grid.ni), j_faces[0], i_faces[0])
|
742
812
|
# log.debug('finished preparing columns bisector')
|
743
813
|
else:
|
744
|
-
log.debug(
|
814
|
+
log.debug("preparing cells bisector")
|
745
815
|
bisector, is_curtain = bisector_from_faces(tuple(grid.extent_kji), k_faces, j_faces, i_faces, raw_bisector)
|
746
816
|
if is_curtain:
|
747
817
|
bisector = bisector[0] # reduce to a columns property
|
748
818
|
|
749
819
|
# note: following is a grid cells property, not a gcs property
|
750
820
|
if return_shadow:
|
751
|
-
log.debug(
|
821
|
+
log.debug("preparing cells shadow")
|
752
822
|
shadow = shadow_from_faces(tuple(grid.extent_kji), k_faces)
|
753
823
|
|
754
824
|
if progress_fn is not None:
|
755
825
|
progress_fn(1.0)
|
756
826
|
|
757
|
-
log.debug(f
|
827
|
+
log.debug(f"finishing find_faces_to_represent_surface_regular_optimised for {name}")
|
758
828
|
|
759
829
|
# if returning properties, construct dictionary
|
760
830
|
if return_properties:
|
761
831
|
props_dict = {}
|
762
832
|
if return_triangles:
|
763
|
-
props_dict[
|
833
|
+
props_dict["triangle"] = all_tris
|
764
834
|
if return_depths:
|
765
|
-
props_dict[
|
835
|
+
props_dict["depth"] = all_depths
|
766
836
|
if return_offsets:
|
767
|
-
props_dict[
|
837
|
+
props_dict["offset"] = all_offsets
|
768
838
|
if return_bisector:
|
769
|
-
props_dict[
|
839
|
+
props_dict["grid bisector"] = (bisector, is_curtain)
|
770
840
|
if return_shadow:
|
771
|
-
props_dict[
|
841
|
+
props_dict["grid shadow"] = shadow
|
772
842
|
if return_flange_bool:
|
773
|
-
props_dict[
|
843
|
+
props_dict["flange bool"] = all_flange
|
774
844
|
return (gcs, props_dict)
|
775
845
|
|
776
846
|
return gcs
|
777
847
|
|
778
848
|
|
779
|
-
def find_faces_to_represent_surface(grid, surface, name, mode =
|
849
|
+
def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_type = "fault", progress_fn = None):
|
780
850
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
781
851
|
|
782
852
|
arguments:
|
@@ -797,44 +867,49 @@ def find_faces_to_represent_surface(grid, surface, name, mode = 'auto', feature_
|
|
797
867
|
this is a wrapper function selecting between more specialised functions; use those directly for more options
|
798
868
|
"""
|
799
869
|
|
800
|
-
log.debug(
|
801
|
-
if mode ==
|
870
|
+
log.debug("finding cell faces for surface")
|
871
|
+
if mode == "auto":
|
802
872
|
if isinstance(grid, grr.RegularGrid) and grid.is_aligned:
|
803
|
-
mode =
|
873
|
+
mode = "regular_optimised"
|
804
874
|
else:
|
805
|
-
mode =
|
806
|
-
if mode ==
|
875
|
+
mode = "staffa"
|
876
|
+
if mode == "staffa":
|
807
877
|
return find_faces_to_represent_surface_staffa(grid,
|
808
878
|
surface,
|
809
879
|
name,
|
810
880
|
feature_type = feature_type,
|
811
881
|
progress_fn = progress_fn)
|
812
|
-
elif mode ==
|
882
|
+
elif mode == "regular":
|
813
883
|
return find_faces_to_represent_surface_regular(grid,
|
814
884
|
surface,
|
815
885
|
name,
|
816
886
|
feature_type = feature_type,
|
817
887
|
progress_fn = progress_fn)
|
818
|
-
elif mode ==
|
888
|
+
elif mode == "regular_optimised":
|
819
889
|
return find_faces_to_represent_surface_regular_optimised(grid,
|
820
890
|
surface,
|
821
891
|
name,
|
822
892
|
feature_type = feature_type,
|
823
893
|
progress_fn = progress_fn)
|
824
|
-
elif mode ==
|
894
|
+
elif mode == "regular_cuda":
|
825
895
|
import resqpy.grid_surface.grid_surface_cuda as rgs_c
|
896
|
+
|
826
897
|
return rgs_c.find_faces_to_represent_surface_regular_cuda_mgpu(grid,
|
827
898
|
surface,
|
828
899
|
name,
|
829
900
|
feature_type = feature_type,
|
830
901
|
progress_fn = progress_fn)
|
831
|
-
log.critical(
|
902
|
+
log.critical("unrecognised mode: " + str(mode))
|
832
903
|
return None
|
833
904
|
|
834
905
|
|
835
906
|
def bisector_from_faces( # type: ignore
|
836
|
-
|
837
|
-
|
907
|
+
grid_extent_kji: Tuple[int, int, int],
|
908
|
+
k_faces: np.ndarray,
|
909
|
+
j_faces: np.ndarray,
|
910
|
+
i_faces: np.ndarray,
|
911
|
+
raw_bisector: bool,
|
912
|
+
) -> Tuple[np.ndarray, bool]:
|
838
913
|
"""Creates a boolean array denoting the bisection of the grid by the face sets.
|
839
914
|
|
840
915
|
arguments:
|
@@ -861,9 +936,14 @@ def bisector_from_faces( # type: ignore
|
|
861
936
|
boundary = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
862
937
|
|
863
938
|
# Setting up the bisector array for the bounding box.
|
864
|
-
bounding_array = np.zeros(
|
865
|
-
|
866
|
-
|
939
|
+
bounding_array = np.zeros(
|
940
|
+
(
|
941
|
+
boundary["k_max"] - boundary["k_min"] + 1,
|
942
|
+
boundary["j_max"] - boundary["j_min"] + 1,
|
943
|
+
boundary["i_max"] - boundary["i_min"] + 1,
|
944
|
+
),
|
945
|
+
dtype = np.bool_,
|
946
|
+
)
|
867
947
|
|
868
948
|
# Seeding the bisector array from (0, 0, 0) up to the first faces that represent the surface.
|
869
949
|
boundary_values = tuple(boundary.values())
|
@@ -886,19 +966,25 @@ def bisector_from_faces( # type: ignore
|
|
886
966
|
point = tuple(point) # type: ignore
|
887
967
|
if point not in points:
|
888
968
|
points.add(point)
|
889
|
-
bounding_array, _, _, _ = _seed_array(
|
890
|
-
|
969
|
+
bounding_array, _, _, _ = _seed_array(
|
970
|
+
point,
|
971
|
+
k_faces,
|
972
|
+
j_faces,
|
973
|
+
i_faces,
|
974
|
+
boundary_values,
|
975
|
+
bounding_array,
|
976
|
+
)
|
891
977
|
|
892
978
|
# Setting up the array for the changing values.
|
893
979
|
changing_array = np.zeros_like(bounding_array, dtype = np.bool_)
|
894
980
|
|
895
981
|
# Repeatedly spreading True values to neighbouring cells that are not the other side of a face.
|
896
982
|
open_k = np.logical_not(k_faces)[boundary["k_min"]:boundary["k_max"], boundary["j_min"]:boundary["j_max"] + 1,
|
897
|
-
boundary["i_min"]:boundary["i_max"] + 1]
|
983
|
+
boundary["i_min"]:boundary["i_max"] + 1,]
|
898
984
|
open_j = np.logical_not(j_faces)[boundary["k_min"]:boundary["k_max"] + 1, boundary["j_min"]:boundary["j_max"],
|
899
|
-
boundary["i_min"]:boundary["i_max"] + 1]
|
985
|
+
boundary["i_min"]:boundary["i_max"] + 1,]
|
900
986
|
open_i = np.logical_not(i_faces)[boundary["k_min"]:boundary["k_max"] + 1, boundary["j_min"]:boundary["j_max"] + 1,
|
901
|
-
boundary["i_min"]:boundary["i_max"]]
|
987
|
+
boundary["i_min"]:boundary["i_max"],]
|
902
988
|
while True:
|
903
989
|
changing_array[:] = False
|
904
990
|
|
@@ -927,7 +1013,7 @@ def bisector_from_faces( # type: ignore
|
|
927
1013
|
# Setting up the full bisectors array and assigning the bounding box values.
|
928
1014
|
array = np.zeros(grid_extent_kji, dtype = np.bool_)
|
929
1015
|
array[boundary["k_min"]:boundary["k_max"] + 1, boundary["j_min"]:boundary["j_max"] + 1,
|
930
|
-
boundary["i_min"]:boundary["i_max"] + 1] = bounding_array
|
1016
|
+
boundary["i_min"]:boundary["i_max"] + 1,] = bounding_array
|
931
1017
|
|
932
1018
|
# Setting values outside of the bounding box.
|
933
1019
|
if boundary["k_max"] != grid_extent_kji[0] - 1 and np.any(bounding_array[-1, :, :]):
|
@@ -946,7 +1032,7 @@ def bisector_from_faces( # type: ignore
|
|
946
1032
|
# Check all array elements are not the same.
|
947
1033
|
true_count = np.count_nonzero(array)
|
948
1034
|
cell_count = array.size
|
949
|
-
assert 0 < true_count < cell_count,
|
1035
|
+
assert (0 < true_count < cell_count), "Face set for surface is leaky or empty (surface does not intersect grid)."
|
950
1036
|
|
951
1037
|
# Negate the array if it minimises the mean k and determine if the surface is a curtain.
|
952
1038
|
layer_cell_count = grid_extent_kji[1] * grid_extent_kji[2]
|
@@ -1005,7 +1091,7 @@ def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndar
|
|
1005
1091
|
# j faces
|
1006
1092
|
c[1:, :] = np.logical_or(c[1:, :], np.logical_and(a[:-1, :], open_j))
|
1007
1093
|
c[:-1, :] = np.logical_or(c[:-1, :], np.logical_and(a[1:, :], open_j))
|
1008
|
-
#
|
1094
|
+
# i faces
|
1009
1095
|
c[:, 1:] = np.logical_or(c[:, 1:], np.logical_and(a[:, :-1], open_i))
|
1010
1096
|
c[:, :-1] = np.logical_or(c[:, :-1], np.logical_and(a[:, 1:], open_i))
|
1011
1097
|
c[:] = np.logical_and(c, np.logical_not(a))
|
@@ -1013,7 +1099,7 @@ def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndar
|
|
1013
1099
|
break
|
1014
1100
|
a[:] = np.logical_or(a, c)
|
1015
1101
|
if np.all(a):
|
1016
|
-
log.warning(
|
1102
|
+
log.warning("curtain is leaky or misses grid when setting column bisector")
|
1017
1103
|
# log.debug(f'returning bisector with count: {np.count_nonzero(a)} of {a.size}; shape: {a.shape}')
|
1018
1104
|
return a
|
1019
1105
|
|
@@ -1055,8 +1141,11 @@ def shadow_from_faces(extent_kji, k_faces):
|
|
1055
1141
|
|
1056
1142
|
|
1057
1143
|
def get_boundary( # type: ignore
|
1058
|
-
|
1059
|
-
|
1144
|
+
k_faces: np.ndarray,
|
1145
|
+
j_faces: np.ndarray,
|
1146
|
+
i_faces: np.ndarray,
|
1147
|
+
grid_extent_kji: Tuple[int, int, int],
|
1148
|
+
) -> Dict[str, int]:
|
1060
1149
|
"""Cretaes a dictionary of the indices that bound the surface (where the faces are True).
|
1061
1150
|
|
1062
1151
|
arguments:
|
@@ -1086,24 +1175,24 @@ def get_boundary( # type: ignore
|
|
1086
1175
|
|
1087
1176
|
for f_i, faces in enumerate([k_faces, j_faces, i_faces]):
|
1088
1177
|
|
1089
|
-
#
|
1178
|
+
# NB. k, j & i for rest of loop refer to indices of faces, regardless of which face set is being processed
|
1090
1179
|
|
1091
|
-
where_k, where_j, where_i =
|
1180
|
+
where_k, where_j, where_i = _where_true(faces)
|
1092
1181
|
|
1093
1182
|
if len(where_k) == 0:
|
1094
1183
|
continue
|
1095
1184
|
|
1096
1185
|
min_k = where_k[0] # optimisation if np.where() guaranteed to return results in natural order
|
1097
1186
|
max_k = where_k[-1]
|
1098
|
-
#
|
1099
|
-
#
|
1187
|
+
# min_k = np.amin(where_k)
|
1188
|
+
# max_k = np.amax(where_k)
|
1100
1189
|
min_j = np.amin(where_j)
|
1101
1190
|
max_j = np.amax(where_j)
|
1102
1191
|
min_i = np.amin(where_i)
|
1103
1192
|
max_i = np.amax(where_i)
|
1104
1193
|
|
1105
1194
|
# include cells on both sides of internal faces
|
1106
|
-
#
|
1195
|
+
# and add buffer slice where edge of grid not reached by surface faces
|
1107
1196
|
if f_i == 0:
|
1108
1197
|
max_k += 1
|
1109
1198
|
else:
|
@@ -1127,38 +1216,38 @@ def get_boundary( # type: ignore
|
|
1127
1216
|
max_i += 1
|
1128
1217
|
|
1129
1218
|
if starting:
|
1130
|
-
boundary[
|
1131
|
-
boundary[
|
1132
|
-
boundary[
|
1133
|
-
boundary[
|
1134
|
-
boundary[
|
1135
|
-
boundary[
|
1219
|
+
boundary["k_min"] = min_k
|
1220
|
+
boundary["k_max"] = max_k
|
1221
|
+
boundary["j_min"] = min_j
|
1222
|
+
boundary["j_max"] = max_j
|
1223
|
+
boundary["i_min"] = min_i
|
1224
|
+
boundary["i_max"] = max_i
|
1136
1225
|
starting = False
|
1137
1226
|
else:
|
1138
|
-
if min_k < boundary[
|
1139
|
-
boundary[
|
1140
|
-
if max_k > boundary[
|
1141
|
-
boundary[
|
1142
|
-
if min_j < boundary[
|
1143
|
-
boundary[
|
1144
|
-
if max_j > boundary[
|
1145
|
-
boundary[
|
1146
|
-
if min_i < boundary[
|
1147
|
-
boundary[
|
1148
|
-
if max_i > boundary[
|
1149
|
-
boundary[
|
1227
|
+
if min_k < boundary["k_min"]:
|
1228
|
+
boundary["k_min"] = min_k
|
1229
|
+
if max_k > boundary["k_max"]:
|
1230
|
+
boundary["k_max"] = max_k
|
1231
|
+
if min_j < boundary["j_min"]:
|
1232
|
+
boundary["j_min"] = min_j
|
1233
|
+
if max_j > boundary["j_max"]:
|
1234
|
+
boundary["j_max"] = max_j
|
1235
|
+
if min_i < boundary["i_min"]:
|
1236
|
+
boundary["i_min"] = min_i
|
1237
|
+
if max_i > boundary["i_max"]:
|
1238
|
+
boundary["i_max"] = max_i
|
1150
1239
|
|
1151
1240
|
return boundary # type: ignore
|
1152
1241
|
|
1153
1242
|
|
1154
|
-
@njit
|
1155
|
-
def
|
1243
|
+
@njit # pragma: no cover
|
1244
|
+
def _where_true(data: np.ndarray):
|
1156
1245
|
"""Jitted NumPy 'where' function to improve performance on subsequent calls."""
|
1157
1246
|
return np.where(data)
|
1158
1247
|
|
1159
1248
|
|
1160
|
-
@njit
|
1161
|
-
def
|
1249
|
+
@njit # pragma: no cover
|
1250
|
+
def _first_true(array: np.ndarray) -> Optional[int]: # type: ignore
|
1162
1251
|
"""Returns the index + 1 of the first True value in the array."""
|
1163
1252
|
for idx, val in np.ndenumerate(array):
|
1164
1253
|
if val:
|
@@ -1166,11 +1255,24 @@ def first_true(array: np.ndarray) -> Optional[int]: # type: ignore
|
|
1166
1255
|
return array.size
|
1167
1256
|
|
1168
1257
|
|
1169
|
-
@njit
|
1170
|
-
def intersect_numba(
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1258
|
+
@njit # pragma: no cover
|
1259
|
+
def intersect_numba(
|
1260
|
+
axis: int,
|
1261
|
+
index1: int,
|
1262
|
+
index2: int,
|
1263
|
+
hits: np.ndarray,
|
1264
|
+
n_axis: int,
|
1265
|
+
points: np.ndarray,
|
1266
|
+
triangles: np.ndarray,
|
1267
|
+
grid_dxyz: Tuple[float],
|
1268
|
+
faces: np.ndarray,
|
1269
|
+
return_depths: bool,
|
1270
|
+
depths: np.ndarray,
|
1271
|
+
return_offsets: bool,
|
1272
|
+
offsets: np.ndarray,
|
1273
|
+
return_triangles: bool,
|
1274
|
+
triangle_per_face: np.ndarray,
|
1275
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
1174
1276
|
"""Finds the faces that intersect the surface in 3D.
|
1175
1277
|
|
1176
1278
|
arguments:
|
@@ -1204,12 +1306,12 @@ def intersect_numba(axis: int, index1: int, index2: int, hits: np.ndarray, n_axi
|
|
1204
1306
|
for i in prange(len(hits)):
|
1205
1307
|
tri, d1, d2 = hits[i]
|
1206
1308
|
|
1207
|
-
#
|
1309
|
+
# Line start point in 3D which had a projection hit.
|
1208
1310
|
centre_point_start = np.zeros(3, dtype = np.float64) + grid_dxyz[axis] / 2
|
1209
1311
|
centre_point_start[2 - index1] = (d1 + 0.5) * grid_dxyz[2 - index1]
|
1210
1312
|
centre_point_start[2 - index2] = (d2 + 0.5) * grid_dxyz[2 - index2]
|
1211
1313
|
|
1212
|
-
#
|
1314
|
+
# Line end point in 3D.
|
1213
1315
|
centre_point_end = np.copy(centre_point_start)
|
1214
1316
|
centre_point_end[axis] = grid_dxyz[axis] * (n_axis - 0.5)
|
1215
1317
|
|
@@ -1248,9 +1350,15 @@ def intersect_numba(axis: int, index1: int, index2: int, hits: np.ndarray, n_axi
|
|
1248
1350
|
return faces, offsets, triangle_per_face
|
1249
1351
|
|
1250
1352
|
|
1251
|
-
@njit
|
1252
|
-
def _seed_array(
|
1253
|
-
|
1353
|
+
@njit # pragma: no cover
|
1354
|
+
def _seed_array(
|
1355
|
+
point: Tuple[int, int, int],
|
1356
|
+
k_faces: np.ndarray,
|
1357
|
+
j_faces: np.ndarray,
|
1358
|
+
i_faces: np.ndarray,
|
1359
|
+
boundary: Tuple[int, int, int, int, int, int],
|
1360
|
+
array: np.ndarray,
|
1361
|
+
) -> Tuple[np.ndarray, int, int, int]:
|
1254
1362
|
"""Sets values of the array True up until a face is hit in each direction.
|
1255
1363
|
|
1256
1364
|
arguments:
|
@@ -1279,17 +1387,17 @@ def _seed_array(point: Tuple[int, int, int], k_faces: np.ndarray, j_faces: np.nd
|
|
1279
1387
|
|
1280
1388
|
first_k = 0
|
1281
1389
|
if k == 0:
|
1282
|
-
first_k =
|
1390
|
+
first_k = _first_true(k_faces[boundary[0]:boundary[1], boundary[2] + j, boundary[4] + i])
|
1283
1391
|
array[:first_k, j, i] = True
|
1284
1392
|
|
1285
1393
|
first_j = 0
|
1286
1394
|
if j == 0:
|
1287
|
-
first_j =
|
1395
|
+
first_j = _first_true(j_faces[boundary[0] + k, boundary[2]:boundary[3], boundary[4] + i])
|
1288
1396
|
array[k, :first_j, i] = True
|
1289
1397
|
|
1290
1398
|
first_i = 0
|
1291
1399
|
if i == 0:
|
1292
|
-
first_i =
|
1400
|
+
first_i = _first_true(i_faces[boundary[0] + k, boundary[2] + j, boundary[4]:boundary[5]])
|
1293
1401
|
array[k, j, :first_i] = True
|
1294
1402
|
|
1295
1403
|
return array, first_k, first_j, first_i
|