resqpy 4.5.0__py3-none-any.whl → 4.6.3__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/__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
|