resqpy 4.14.2__py3-none-any.whl → 5.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resqpy/__init__.py +1 -1
- resqpy/fault/_gcs_functions.py +10 -10
- resqpy/fault/_grid_connection_set.py +277 -113
- resqpy/grid/__init__.py +2 -3
- resqpy/grid/_defined_geometry.py +3 -3
- resqpy/grid/_extract_functions.py +8 -2
- resqpy/grid/_grid.py +95 -12
- resqpy/grid/_grid_types.py +22 -7
- resqpy/grid/_points_functions.py +1 -1
- resqpy/grid/_regular_grid.py +6 -2
- resqpy/grid_surface/__init__.py +17 -38
- resqpy/grid_surface/_blocked_well_populate.py +5 -5
- resqpy/grid_surface/_find_faces.py +1413 -253
- resqpy/lines/_polyline.py +24 -33
- resqpy/model/_catalogue.py +9 -0
- resqpy/model/_forestry.py +18 -14
- resqpy/model/_hdf5.py +11 -3
- resqpy/model/_model.py +85 -10
- resqpy/model/_xml.py +38 -13
- resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
- resqpy/olio/read_nexus_fault.py +8 -2
- resqpy/olio/relperm.py +1 -1
- resqpy/olio/transmission.py +8 -8
- resqpy/olio/triangulation.py +36 -30
- resqpy/olio/vector_utilities.py +340 -6
- resqpy/olio/volume.py +0 -20
- resqpy/olio/wellspec_keywords.py +19 -13
- resqpy/olio/write_hdf5.py +1 -1
- resqpy/olio/xml_et.py +12 -0
- resqpy/property/__init__.py +6 -4
- resqpy/property/_collection_add_part.py +4 -3
- resqpy/property/_collection_create_xml.py +4 -2
- resqpy/property/_collection_get_attributes.py +4 -0
- resqpy/property/attribute_property_set.py +311 -0
- resqpy/property/grid_property_collection.py +11 -11
- resqpy/property/property_collection.py +79 -31
- resqpy/property/property_common.py +3 -8
- resqpy/rq_import/_add_surfaces.py +34 -14
- resqpy/rq_import/_grid_from_cp.py +2 -2
- resqpy/rq_import/_import_nexus.py +75 -48
- resqpy/rq_import/_import_vdb_all_grids.py +64 -52
- resqpy/rq_import/_import_vdb_ensemble.py +12 -13
- resqpy/surface/_mesh.py +4 -0
- resqpy/surface/_surface.py +593 -118
- resqpy/surface/_tri_mesh.py +13 -10
- resqpy/surface/_tri_mesh_stencil.py +4 -4
- resqpy/surface/_triangulated_patch.py +71 -51
- resqpy/time_series/_any_time_series.py +7 -4
- resqpy/time_series/_geologic_time_series.py +1 -1
- resqpy/unstructured/_hexa_grid.py +6 -2
- resqpy/unstructured/_prism_grid.py +13 -5
- resqpy/unstructured/_pyramid_grid.py +6 -2
- resqpy/unstructured/_tetra_grid.py +6 -2
- resqpy/unstructured/_unstructured_grid.py +6 -2
- resqpy/well/_blocked_well.py +1986 -1946
- resqpy/well/_deviation_survey.py +3 -3
- resqpy/well/_md_datum.py +11 -21
- resqpy/well/_trajectory.py +10 -5
- resqpy/well/_wellbore_frame.py +10 -2
- resqpy/well/blocked_well_frame.py +3 -3
- resqpy/well/well_object_funcs.py +7 -9
- resqpy/well/well_utils.py +33 -0
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/LICENSE +0 -0
@@ -8,9 +8,9 @@ import numpy as np
|
|
8
8
|
import warnings
|
9
9
|
import numba # type: ignore
|
10
10
|
from numba import njit, prange # type: ignore
|
11
|
-
from typing import Tuple,
|
11
|
+
from typing import Tuple, Union, Dict
|
12
12
|
|
13
|
-
import resqpy as rq
|
13
|
+
import resqpy.model as rq
|
14
14
|
import resqpy.crs as rqc
|
15
15
|
import resqpy.grid as grr
|
16
16
|
import resqpy.fault as rqf
|
@@ -495,19 +495,18 @@ def find_faces_to_represent_surface_regular(
|
|
495
495
|
return gcs
|
496
496
|
|
497
497
|
|
498
|
-
def
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
):
|
498
|
+
def find_faces_to_represent_surface_regular_dense_optimised(grid,
|
499
|
+
surface,
|
500
|
+
name,
|
501
|
+
title = None,
|
502
|
+
agitate = False,
|
503
|
+
random_agitation = False,
|
504
|
+
feature_type = "fault",
|
505
|
+
is_curtain = False,
|
506
|
+
progress_fn = None,
|
507
|
+
return_properties = None,
|
508
|
+
raw_bisector = False,
|
509
|
+
n_batches = 20):
|
511
510
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
512
511
|
|
513
512
|
argumants:
|
@@ -539,6 +538,8 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
539
538
|
the returned dictionary has the passed strings as keys and numpy arrays as values
|
540
539
|
raw_bisector (bool, default False): if True and grid bisector is requested then it is left in a raw
|
541
540
|
form without assessing which side is shallower (True values indicate same side as origin cell)
|
541
|
+
n_batches (int, default 20): the number of batches of triangles to use at the low level (numba multi
|
542
|
+
threading allows some parallelism between the batches)
|
542
543
|
|
543
544
|
returns:
|
544
545
|
gcs or (gcs, gcs_props)
|
@@ -552,8 +553,13 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
552
553
|
no trimming of the surface is carried out here: for computational efficiency, it is recommended
|
553
554
|
to trim first;
|
554
555
|
organisational objects for the feature are created if needed;
|
555
|
-
if the offset return property is requested, the implicit units will be the z units of the grid's crs
|
556
|
+
if the offset return property is requested, the implicit units will be the z units of the grid's crs;
|
557
|
+
this version of the function uses fully explicit boolean arrays to capture the faces before conversion
|
558
|
+
to a grid connection set; use the non-dense version of the function for a reduced memory footprint;
|
559
|
+
this function is DEPRECATED pending proving of newer find_faces_to_represent_surface_regular_optimised()
|
556
560
|
"""
|
561
|
+
warnings.warn('DEPRECATED: grid_surface.find_faces_to_represent_surface_regular_dense_optimised() function; ' +
|
562
|
+
'use find_faces_to_represent_surface_regular_optimised() instead')
|
557
563
|
|
558
564
|
assert isinstance(grid, grr.RegularGrid)
|
559
565
|
assert grid.is_aligned
|
@@ -598,8 +604,10 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
598
604
|
grid.block_dxyz_dkji[0, 2],
|
599
605
|
)
|
600
606
|
triangles, points = surface.triangles_and_points()
|
607
|
+
t_dtype = np.int32 if len(triangles) < 2_000_000_000 else np.int64
|
601
608
|
assert (triangles is not None and points is not None), f"surface {surface.title} is empty"
|
602
609
|
if agitate:
|
610
|
+
points = points.copy()
|
603
611
|
if random_agitation:
|
604
612
|
points += 1.0e-5 * (np.random.random(points.shape) - 0.5)
|
605
613
|
else:
|
@@ -621,14 +629,15 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
621
629
|
if nk > 1:
|
622
630
|
# log.debug("searching for k faces")
|
623
631
|
k_faces = np.zeros((nk - 1, grid.nj, grid.ni), dtype = bool)
|
624
|
-
k_triangles = np.full((nk - 1, grid.nj, grid.ni), -1, dtype =
|
632
|
+
k_triangles = np.full((nk - 1, grid.nj, grid.ni), -1, dtype = t_dtype)
|
625
633
|
k_depths = np.full((nk - 1, grid.nj, grid.ni), np.nan)
|
626
634
|
k_offsets = np.full((nk - 1, grid.nj, grid.ni), np.nan)
|
627
635
|
p_xy = np.delete(points, 2, 1)
|
628
636
|
|
629
637
|
k_hits = vec.points_in_triangles_aligned_optimised(grid.ni, grid.nj, grid_dxyz[0], grid_dxyz[1],
|
630
|
-
p_xy[triangles])
|
638
|
+
p_xy[triangles], n_batches)
|
631
639
|
|
640
|
+
del p_xy
|
632
641
|
axis = 2
|
633
642
|
index1 = 1
|
634
643
|
index2 = 2
|
@@ -650,7 +659,6 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
650
659
|
k_triangles,
|
651
660
|
)
|
652
661
|
del k_hits
|
653
|
-
del p_xy
|
654
662
|
log.debug(f"k face count: {np.count_nonzero(k_faces)}")
|
655
663
|
else:
|
656
664
|
k_faces = None
|
@@ -665,13 +673,15 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
665
673
|
if grid.nj > 1:
|
666
674
|
# log.debug("searching for j faces")
|
667
675
|
j_faces = np.zeros((nk, grid.nj - 1, grid.ni), dtype = bool)
|
668
|
-
j_triangles = np.full((nk, grid.nj - 1, grid.ni), -1, dtype =
|
676
|
+
j_triangles = np.full((nk, grid.nj - 1, grid.ni), -1, dtype = t_dtype)
|
669
677
|
j_depths = np.full((nk, grid.nj - 1, grid.ni), np.nan)
|
670
678
|
j_offsets = np.full((nk, grid.nj - 1, grid.ni), np.nan)
|
671
679
|
p_xz = np.delete(points, 1, 1)
|
672
680
|
|
673
|
-
j_hits = vec.points_in_triangles_aligned_optimised(grid.ni, nk, grid_dxyz[0], grid_dxyz[2], p_xz[triangles]
|
681
|
+
j_hits = vec.points_in_triangles_aligned_optimised(grid.ni, nk, grid_dxyz[0], grid_dxyz[2], p_xz[triangles],
|
682
|
+
n_batches)
|
674
683
|
|
684
|
+
del p_xz
|
675
685
|
axis = 1
|
676
686
|
index1 = 0
|
677
687
|
index2 = 2
|
@@ -693,7 +703,6 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
693
703
|
j_triangles,
|
694
704
|
)
|
695
705
|
del j_hits
|
696
|
-
del p_xz
|
697
706
|
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
698
707
|
j_faces = np.repeat(j_faces, grid.nk, axis = 0)
|
699
708
|
j_triangles = np.repeat(j_triangles, grid.nk, axis = 0)
|
@@ -713,13 +722,15 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
713
722
|
if grid.ni > 1:
|
714
723
|
# log.debug("searching for i faces")
|
715
724
|
i_faces = np.zeros((nk, grid.nj, grid.ni - 1), dtype = bool)
|
716
|
-
i_triangles = np.full((nk, grid.nj, grid.ni - 1), -1, dtype =
|
725
|
+
i_triangles = np.full((nk, grid.nj, grid.ni - 1), -1, dtype = t_dtype)
|
717
726
|
i_depths = np.full((nk, grid.nj, grid.ni - 1), np.nan)
|
718
727
|
i_offsets = np.full((nk, grid.nj, grid.ni - 1), np.nan)
|
719
728
|
p_yz = np.delete(points, 0, 1)
|
720
729
|
|
721
|
-
i_hits = vec.points_in_triangles_aligned_optimised(grid.nj, nk, grid_dxyz[1], grid_dxyz[2], p_yz[triangles]
|
730
|
+
i_hits = vec.points_in_triangles_aligned_optimised(grid.nj, nk, grid_dxyz[1], grid_dxyz[2], p_yz[triangles],
|
731
|
+
n_batches)
|
722
732
|
|
733
|
+
del p_yz
|
723
734
|
axis = 0
|
724
735
|
index1 = 0
|
725
736
|
index2 = 1
|
@@ -741,7 +752,6 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
741
752
|
i_triangles,
|
742
753
|
)
|
743
754
|
del i_hits
|
744
|
-
del p_yz
|
745
755
|
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
746
756
|
# log.debug('expanding curtain faces')
|
747
757
|
i_faces = np.repeat(i_faces, grid.nk, axis = 0)
|
@@ -812,7 +822,7 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
812
822
|
related_uuid = surface.uuid)
|
813
823
|
assert (flange_bool_uuid is not None), f"No flange bool property found for surface: {surface.title}"
|
814
824
|
flange_bool = rqp.Property(surface.model, uuid = flange_bool_uuid)
|
815
|
-
flange_array = flange_bool.array_ref()
|
825
|
+
flange_array = flange_bool.array_ref(dtype = bool)
|
816
826
|
all_flange = np.take(flange_array, all_tris)
|
817
827
|
assert all_flange.shape == (gcs.count,)
|
818
828
|
|
@@ -836,7 +846,7 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
836
846
|
if progress_fn is not None:
|
837
847
|
progress_fn(1.0)
|
838
848
|
|
839
|
-
log.debug(f"finishing
|
849
|
+
log.debug(f"finishing find_faces_to_represent_surface_regular_dense_optimised for {name}")
|
840
850
|
|
841
851
|
# if returning properties, construct dictionary
|
842
852
|
if return_properties:
|
@@ -858,6 +868,536 @@ def find_faces_to_represent_surface_regular_optimised(
|
|
858
868
|
return gcs
|
859
869
|
|
860
870
|
|
871
|
+
def find_faces_to_represent_surface_regular_optimised(grid,
|
872
|
+
surface,
|
873
|
+
name,
|
874
|
+
title = None,
|
875
|
+
agitate = False,
|
876
|
+
random_agitation = False,
|
877
|
+
feature_type = "fault",
|
878
|
+
is_curtain = False,
|
879
|
+
progress_fn = None,
|
880
|
+
return_properties = None,
|
881
|
+
raw_bisector = False,
|
882
|
+
n_batches = 20,
|
883
|
+
packed_bisectors = False,
|
884
|
+
patch_indices = None):
|
885
|
+
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
886
|
+
|
887
|
+
argumants:
|
888
|
+
grid (RegularGrid): the grid for which to create a grid connection set representation of the surface;
|
889
|
+
must be aligned, ie. I with +x, J with +y, K with +z and local origin of (0.0, 0.0, 0.0)
|
890
|
+
surface (Surface): the surface to be intersected with the grid
|
891
|
+
name (str): the feature name to use in the grid connection set
|
892
|
+
title (str, optional): the citation title to use for the grid connection set; defaults to name
|
893
|
+
agitate (bool, default False): if True, the points of the surface are perturbed by a small
|
894
|
+
offset, which can help if the surface has been built from a regular mesh with a periodic resonance
|
895
|
+
with the grid
|
896
|
+
random_agitation (bool, default False): if True, the agitation is by a small random distance; if False,
|
897
|
+
a constant positive shift of 5.0e-6 is applied to x, y & z values; ignored if agitate is False
|
898
|
+
feature_type (str, default 'fault'): 'fault', 'horizon' or 'geobody boundary'
|
899
|
+
is_curtain (bool, default False): if True, only the top layer of the grid is processed and the bisector
|
900
|
+
property, if requested, is generated with indexable element columns
|
901
|
+
progress_fn (f(x: float), optional): a callback function to be called at intervals by this function;
|
902
|
+
the argument will progress from 0.0 to 1.0 in unspecified and uneven increments
|
903
|
+
return_properties (List[str]): if present, a list of property arrays to calculate and
|
904
|
+
return as a dictionary; recognised values in the list are 'triangle', 'depth', 'offset',
|
905
|
+
'flange bool', 'grid bisector', or 'grid shadow';
|
906
|
+
triangle is an index into the surface triangles of the triangle detected for the gcs face; depth is
|
907
|
+
the z value of the intersection point of the inter-cell centre vector with a triangle in the surface;
|
908
|
+
offset is a measure of the distance between the centre of the cell face and the intersection point;
|
909
|
+
grid bisector is a grid cell boolean property holding True for the set of cells on one
|
910
|
+
side of the surface, deemed to be shallower;
|
911
|
+
grid shadow is a grid cell int8 property holding 0: cell neither above nor below a K face of the
|
912
|
+
gridded surface, 1 cell is above K face(s), 2 cell is below K face(s), 3 cell is between K faces;
|
913
|
+
the returned dictionary has the passed strings as keys and numpy arrays as values
|
914
|
+
raw_bisector (bool, default False): if True and grid bisector is requested then it is left in a raw
|
915
|
+
form without assessing which side is shallower (True values indicate same side as origin cell)
|
916
|
+
n_batches (int, default 20): the number of batches of triangles to use at the low level (numba multi
|
917
|
+
threading allows some parallelism between the batches)
|
918
|
+
packed_bisectors (bool, default False): if True and return properties include 'grid bisector' then
|
919
|
+
non curtain bisectors are returned in packed form
|
920
|
+
patch_indices (numpy int array, optional): if present, an array over grid cells indicating which
|
921
|
+
patch of surface is applicable in terms of a bisector, for each cell
|
922
|
+
|
923
|
+
returns:
|
924
|
+
gcs or (gcs, gcs_props)
|
925
|
+
where gcs is a new GridConnectionSet with a single feature, not yet written to hdf5 nor xml created;
|
926
|
+
gcs_props is a dictionary mapping from requested return_properties string to numpy array (or tuple
|
927
|
+
of numpy array and curtain bool in the case of grid bisector)
|
928
|
+
|
929
|
+
notes:
|
930
|
+
this function is designed for aligned regular grids only;
|
931
|
+
this function can handle the surface and grid being in different coordinate reference systems, as
|
932
|
+
long as the implicit parent crs is shared;
|
933
|
+
no trimming of the surface is carried out here: for computational efficiency, it is recommended
|
934
|
+
to trim first;
|
935
|
+
organisational objects for the feature are created if needed;
|
936
|
+
if the offset return property is requested, the implicit units will be the z units of the grid's crs;
|
937
|
+
if patch_indices is present and grid bisectors are being returned, a composite bisector array is returned
|
938
|
+
with elements set from individual bisectors for each patch of surface
|
939
|
+
"""
|
940
|
+
|
941
|
+
assert isinstance(grid, grr.RegularGrid)
|
942
|
+
assert grid.is_aligned
|
943
|
+
return_triangles = False
|
944
|
+
return_depths = False
|
945
|
+
return_offsets = False
|
946
|
+
return_bisector = False
|
947
|
+
return_shadow = False
|
948
|
+
return_flange_bool = False
|
949
|
+
if return_properties:
|
950
|
+
assert all([
|
951
|
+
p in [
|
952
|
+
"triangle",
|
953
|
+
"depth",
|
954
|
+
"offset",
|
955
|
+
"grid bisector",
|
956
|
+
"grid shadow",
|
957
|
+
"flange bool",
|
958
|
+
] for p in return_properties
|
959
|
+
])
|
960
|
+
return_triangles = "triangle" in return_properties
|
961
|
+
return_depths = "depth" in return_properties
|
962
|
+
return_offsets = "offset" in return_properties
|
963
|
+
return_bisector = "grid bisector" in return_properties
|
964
|
+
return_shadow = "grid shadow" in return_properties
|
965
|
+
return_flange_bool = "flange bool" in return_properties
|
966
|
+
if return_flange_bool:
|
967
|
+
return_triangles = True
|
968
|
+
patchwork = return_bisector and patch_indices is not None
|
969
|
+
if patchwork:
|
970
|
+
return_triangles = True # triangle numbers are used to infer patch index
|
971
|
+
assert patch_indices.shape == tuple(grid.extent_kji)
|
972
|
+
if title is None:
|
973
|
+
title = name
|
974
|
+
|
975
|
+
if progress_fn is not None:
|
976
|
+
progress_fn(0.0)
|
977
|
+
|
978
|
+
log.debug(f"intersecting surface {surface.title} with regular grid {grid.title}")
|
979
|
+
# log.debug(f'grid extent kji: {grid.extent_kji}')
|
980
|
+
|
981
|
+
triangles, points = surface.triangles_and_points(copy = True)
|
982
|
+
surface.decache_triangles_and_points()
|
983
|
+
|
984
|
+
t_dtype = np.int32 if len(triangles) < 2_147_483_648 else np.int64
|
985
|
+
|
986
|
+
assert (triangles is not None and points is not None), f"surface {surface.title} is empty"
|
987
|
+
if agitate:
|
988
|
+
if random_agitation:
|
989
|
+
points += 1.0e-5 * (np.random.random(points.shape) - 0.5)
|
990
|
+
else:
|
991
|
+
points += 5.0e-6
|
992
|
+
# log.debug(f'surface: {surface.title}; p0: {points[0]}; crs uuid: {surface.crs_uuid}')
|
993
|
+
# log.debug(f'surface min xyz: {np.min(points, axis = 0)}')
|
994
|
+
# log.debug(f'surface max xyz: {np.max(points, axis = 0)}')
|
995
|
+
if not bu.matching_uuids(grid.crs_uuid, surface.crs_uuid):
|
996
|
+
log.debug("converting from surface crs to grid crs")
|
997
|
+
s_crs = rqc.Crs(surface.model, uuid = surface.crs_uuid)
|
998
|
+
s_crs.convert_array_to(grid.crs, points)
|
999
|
+
surface.crs_uuid = grid.crs.uuid
|
1000
|
+
# log.debug(f'surface: {surface.title}; p0: {points[0]}; crs uuid: {surface.crs_uuid}')
|
1001
|
+
# log.debug(f'surface min xyz: {np.min(points, axis = 0)}')
|
1002
|
+
# log.debug(f'surface max xyz: {np.max(points, axis = 0)}')
|
1003
|
+
|
1004
|
+
# convert surface points to work with unit cube grid cells
|
1005
|
+
dx = grid.block_dxyz_dkji[2, 0]
|
1006
|
+
dy = grid.block_dxyz_dkji[1, 1]
|
1007
|
+
dz = grid.block_dxyz_dkji[0, 2]
|
1008
|
+
points[:, 0] /= dx
|
1009
|
+
points[:, 1] /= dy
|
1010
|
+
points[:, 2] /= dz
|
1011
|
+
points[:] -= 0.5
|
1012
|
+
p = points[triangles]
|
1013
|
+
|
1014
|
+
nk = 1 if is_curtain else grid.nk
|
1015
|
+
# K direction (xy projection)
|
1016
|
+
k_faces_kji0 = None
|
1017
|
+
k_triangles = None
|
1018
|
+
k_depths = None
|
1019
|
+
k_offsets = None
|
1020
|
+
k_props = None
|
1021
|
+
if nk > 1:
|
1022
|
+
# log.debug("searching for k faces")
|
1023
|
+
|
1024
|
+
k_hits, k_depths = vec.points_in_triangles_aligned_unified(grid.ni, grid.nj, 0, 1, 2, p, n_batches)
|
1025
|
+
|
1026
|
+
k_faces = np.floor(k_depths)
|
1027
|
+
mask = np.logical_and(k_faces >= 0, k_faces < nk - 1)
|
1028
|
+
|
1029
|
+
if np.any(mask):
|
1030
|
+
k_hits = k_hits[mask, :]
|
1031
|
+
k_faces = k_faces[mask]
|
1032
|
+
k_depths = k_depths[mask]
|
1033
|
+
k_triangles = k_hits[:, 0]
|
1034
|
+
k_faces_kji0 = np.empty((len(k_faces), 3), dtype = np.int32)
|
1035
|
+
k_faces_kji0[:, 0] = k_faces
|
1036
|
+
k_faces_kji0[:, 1] = k_hits[:, 1]
|
1037
|
+
k_faces_kji0[:, 2] = k_hits[:, 2]
|
1038
|
+
if return_offsets:
|
1039
|
+
k_offsets = (k_depths - k_faces.astype(np.float64) - 0.5) * dz
|
1040
|
+
if return_depths:
|
1041
|
+
k_depths[:] += 0.5
|
1042
|
+
k_depths[:] *= dz
|
1043
|
+
k_props = []
|
1044
|
+
if return_triangles:
|
1045
|
+
k_props.append(k_triangles)
|
1046
|
+
if return_depths:
|
1047
|
+
k_props.append(k_depths)
|
1048
|
+
if return_offsets:
|
1049
|
+
k_props.append(k_offsets)
|
1050
|
+
log.debug(f"k face count: {len(k_faces_kji0)}")
|
1051
|
+
|
1052
|
+
del k_hits
|
1053
|
+
del k_faces
|
1054
|
+
|
1055
|
+
if progress_fn is not None:
|
1056
|
+
progress_fn(0.3)
|
1057
|
+
|
1058
|
+
# J direction (xz projection)
|
1059
|
+
j_faces_kji0 = None
|
1060
|
+
j_triangles = None
|
1061
|
+
j_depths = None
|
1062
|
+
j_offsets = None
|
1063
|
+
j_props = None
|
1064
|
+
if grid.nj > 1:
|
1065
|
+
# log.debug("searching for J faces")
|
1066
|
+
|
1067
|
+
j_hits, j_depths = vec.points_in_triangles_aligned_unified(grid.ni, nk, 0, 2, 1, p, n_batches)
|
1068
|
+
|
1069
|
+
j_faces = np.floor(j_depths)
|
1070
|
+
mask = np.logical_and(j_faces >= 0, j_faces < grid.nj - 1)
|
1071
|
+
|
1072
|
+
if np.any(mask):
|
1073
|
+
j_hits = j_hits[mask, :]
|
1074
|
+
j_faces = j_faces[mask]
|
1075
|
+
j_depths = j_depths[mask]
|
1076
|
+
j_triangles = j_hits[:, 0]
|
1077
|
+
j_faces_kji0 = np.empty((len(j_faces), 3), dtype = np.int32)
|
1078
|
+
j_faces_kji0[:, 0] = j_hits[:, 1]
|
1079
|
+
j_faces_kji0[:, 1] = j_faces
|
1080
|
+
j_faces_kji0[:, 2] = j_hits[:, 2]
|
1081
|
+
if return_offsets:
|
1082
|
+
j_offsets = (j_depths - j_faces.astype(np.float64) - 0.5) * dy
|
1083
|
+
if return_depths:
|
1084
|
+
j_depths[:] += 0.5
|
1085
|
+
j_depths[:] *= dy
|
1086
|
+
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
1087
|
+
j_faces = np.repeat(np.expand_dims(j_faces_kji0, axis = 0), grid.nk, axis = 0)
|
1088
|
+
j_faces[:, :, 0] = np.expand_dims(np.arange(grid.nk, dtype = np.int32), axis = -1)
|
1089
|
+
j_faces_kji0 = j_faces.reshape((-1, 3))
|
1090
|
+
j_triangles = np.repeat(j_triangles, grid.nk, axis = 0)
|
1091
|
+
if return_offsets:
|
1092
|
+
j_offsets = np.repeat(j_offsets, grid.nk, axis = 0)
|
1093
|
+
if return_depths:
|
1094
|
+
j_depths = np.repeat(j_depths, grid.nk, axis = 0)
|
1095
|
+
j_props = []
|
1096
|
+
if return_triangles:
|
1097
|
+
j_props.append(j_triangles)
|
1098
|
+
if return_depths:
|
1099
|
+
j_props.append(j_depths)
|
1100
|
+
if return_offsets:
|
1101
|
+
j_props.append(j_offsets)
|
1102
|
+
log.debug(f"j face count: {len(j_faces_kji0)}")
|
1103
|
+
|
1104
|
+
del j_hits
|
1105
|
+
del j_faces
|
1106
|
+
|
1107
|
+
if progress_fn is not None:
|
1108
|
+
progress_fn(0.6)
|
1109
|
+
|
1110
|
+
# I direction (yz projection)
|
1111
|
+
i_faces_kji0 = None
|
1112
|
+
i_triangles = None
|
1113
|
+
i_depths = None
|
1114
|
+
i_offsets = None
|
1115
|
+
i_props = None
|
1116
|
+
if grid.ni > 1:
|
1117
|
+
# log.debug("searching for I faces")
|
1118
|
+
|
1119
|
+
i_hits, i_depths = vec.points_in_triangles_aligned_unified(grid.nj, nk, 1, 2, 0, p, n_batches)
|
1120
|
+
|
1121
|
+
i_faces = np.floor(i_depths)
|
1122
|
+
mask = np.logical_and(i_faces >= 0, i_faces < grid.ni - 1)
|
1123
|
+
|
1124
|
+
if np.any(mask):
|
1125
|
+
i_hits = i_hits[mask, :]
|
1126
|
+
i_faces = i_faces[mask]
|
1127
|
+
i_depths = i_depths[mask]
|
1128
|
+
i_triangles = i_hits[:, 0]
|
1129
|
+
i_faces_kji0 = np.empty((len(i_faces), 3), dtype = np.int32)
|
1130
|
+
i_faces_kji0[:, 0] = i_hits[:, 1]
|
1131
|
+
i_faces_kji0[:, 1] = i_hits[:, 2]
|
1132
|
+
i_faces_kji0[:, 2] = i_faces
|
1133
|
+
if return_offsets:
|
1134
|
+
i_offsets = (i_depths - i_faces.astype(np.float64) - 0.5) * dx
|
1135
|
+
if return_depths:
|
1136
|
+
i_depths[:] += 0.5
|
1137
|
+
i_depths[:] *= dx
|
1138
|
+
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
1139
|
+
i_faces = np.repeat(np.expand_dims(i_faces_kji0, axis = 0), grid.nk, axis = 0)
|
1140
|
+
i_faces[:, :, 0] = np.expand_dims(np.arange(grid.nk, dtype = np.int32), axis = -1)
|
1141
|
+
i_faces_kji0 = i_faces.reshape((-1, 3))
|
1142
|
+
i_triangles = np.repeat(i_triangles, grid.nk, axis = 0)
|
1143
|
+
if return_offsets:
|
1144
|
+
i_offsets = np.repeat(i_offsets, grid.nk, axis = 0)
|
1145
|
+
if return_depths:
|
1146
|
+
i_depths = np.repeat(i_depths, grid.nk, axis = 0)
|
1147
|
+
i_props = []
|
1148
|
+
if return_triangles:
|
1149
|
+
i_props.append(i_triangles)
|
1150
|
+
if return_depths:
|
1151
|
+
i_props.append(i_depths)
|
1152
|
+
if return_offsets:
|
1153
|
+
i_props.append(i_offsets)
|
1154
|
+
log.debug(f"i face count: {len(i_faces_kji0)}")
|
1155
|
+
|
1156
|
+
del i_hits
|
1157
|
+
del i_faces
|
1158
|
+
|
1159
|
+
if progress_fn is not None:
|
1160
|
+
progress_fn(0.9)
|
1161
|
+
|
1162
|
+
if ((k_faces_kji0 is None or len(k_faces_kji0) == 0) and (j_faces_kji0 is None or len(j_faces_kji0) == 0) and
|
1163
|
+
(i_faces_kji0 is None or len(i_faces_kji0) == 0)):
|
1164
|
+
log.error(f'did not find any faces to represent {name}: surface does not intersect grid?')
|
1165
|
+
if return_properties:
|
1166
|
+
return (None, {})
|
1167
|
+
else:
|
1168
|
+
return None
|
1169
|
+
|
1170
|
+
# note: following is a grid cells property, not a gcs property
|
1171
|
+
bisector = None
|
1172
|
+
if return_bisector:
|
1173
|
+
if is_curtain and not patchwork:
|
1174
|
+
log.debug(f'preparing columns bisector for: {surface.title}')
|
1175
|
+
if j_faces_kji0 is None:
|
1176
|
+
j_faces_ji0 = np.empty((0, 2), dtype = np.int32)
|
1177
|
+
else:
|
1178
|
+
j_faces_ji0 = j_faces_kji0[:, 1:]
|
1179
|
+
if i_faces_kji0 is None:
|
1180
|
+
i_faces_ji0 = np.empty((0, 2), dtype = np.int32)
|
1181
|
+
else:
|
1182
|
+
i_faces_ji0 = i_faces_kji0[:, 1:]
|
1183
|
+
bisector = column_bisector_from_face_indices((grid.nj, grid.ni), j_faces_ji0, i_faces_ji0)
|
1184
|
+
# log.debug('finished preparing columns bisector')
|
1185
|
+
elif patchwork:
|
1186
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1187
|
+
all_tris = None
|
1188
|
+
assert return_triangles
|
1189
|
+
# log.debug('preparing triangles array')
|
1190
|
+
k_triangles = np.empty((0,), dtype = np.int32) if k_props is None else k_props[0]
|
1191
|
+
j_triangles = np.empty((0,), dtype = np.int32) if j_props is None else j_props[0]
|
1192
|
+
i_triangles = np.empty((0,), dtype = np.int32) if i_props is None else i_props[0]
|
1193
|
+
all_tris = np.concatenate((k_triangles, j_triangles, i_triangles), axis = 0)
|
1194
|
+
# log.debug(f'gcs count: {gcs.count}; all triangles shape: {all_tris.shape}')
|
1195
|
+
n_patches = surface.number_of_patches()
|
1196
|
+
log.info(f'preparing composite cells bisector for surface: {surface.title}; number of patches: {n_patches}')
|
1197
|
+
nkf = 0 if k_faces_kji0 is None else len(k_faces_kji0)
|
1198
|
+
njf = 0 if j_faces_kji0 is None else len(j_faces_kji0)
|
1199
|
+
nif = 0 if i_faces_kji0 is None else len(i_faces_kji0)
|
1200
|
+
# fetch patch indices for triangle hits
|
1201
|
+
assert all_tris is not None and len(all_tris) == nkf + njf + nif
|
1202
|
+
patch_indices_k = surface.patch_indices_for_triangle_indices(all_tris[:nkf])
|
1203
|
+
patch_indices_j = surface.patch_indices_for_triangle_indices(all_tris[nkf:nkf + njf])
|
1204
|
+
patch_indices_i = surface.patch_indices_for_triangle_indices(all_tris[nkf + njf:])
|
1205
|
+
# add extra dimension to bisector array (at axis 0) for patches
|
1206
|
+
pb_shape = tuple([n_patches] + list(grid.extent_kji))
|
1207
|
+
if packed_bisectors:
|
1208
|
+
bisector = np.invert(np.zeros(_shape_packed(grid.extent_kji), dtype = np.uint8), dtype = np.uint8)
|
1209
|
+
else:
|
1210
|
+
bisector = np.ones(tuple(grid.extent_kji), dtype = np.bool_)
|
1211
|
+
# populate composite bisector
|
1212
|
+
for patch in range(n_patches):
|
1213
|
+
log.debug(f'processing patch {patch} of surface: {surface.title}')
|
1214
|
+
mask = (patch_indices == patch)
|
1215
|
+
mask_count = np.count_nonzero(mask)
|
1216
|
+
if mask_count == 0:
|
1217
|
+
log.warning(f'patch {patch} of surface {surface.title} is not applicable to any cells in grid')
|
1218
|
+
continue
|
1219
|
+
patch_box, box_count = get_box(mask)
|
1220
|
+
assert box_count == mask_count
|
1221
|
+
assert np.all(patch_box[1] > patch_box[0])
|
1222
|
+
patch_box = expanded_box(patch_box, tuple(grid.extent_kji))
|
1223
|
+
patch_box[0, 0] = 0
|
1224
|
+
patch_box[1, 0] = grid.extent_kji[0]
|
1225
|
+
packed_box = shrunk_box_for_packing(patch_box)
|
1226
|
+
patch_k_faces_kji0 = None
|
1227
|
+
if k_faces_kji0 is not None:
|
1228
|
+
patch_k_faces_kji0 = k_faces_kji0[(patch_indices_k == patch).astype(bool)]
|
1229
|
+
patch_j_faces_kji0 = None
|
1230
|
+
if j_faces_kji0 is not None:
|
1231
|
+
patch_j_faces_kji0 = j_faces_kji0[(patch_indices_j == patch).astype(bool)]
|
1232
|
+
patch_i_faces_kji0 = None
|
1233
|
+
if i_faces_kji0 is not None:
|
1234
|
+
patch_i_faces_kji0 = i_faces_kji0[(patch_indices_i == patch).astype(bool)]
|
1235
|
+
if packed_bisectors:
|
1236
|
+
mask = np.packbits(mask, axis = -1)
|
1237
|
+
patch_bisector, is_curtain = \
|
1238
|
+
packed_bisector_from_face_indices(tuple(grid.extent_kji),
|
1239
|
+
patch_k_faces_kji0,
|
1240
|
+
patch_j_faces_kji0,
|
1241
|
+
patch_i_faces_kji0,
|
1242
|
+
raw_bisector,
|
1243
|
+
patch_box)
|
1244
|
+
# bisector[:] = np.bitwise_or(np.bitwise_and(mask, patch_bisector),
|
1245
|
+
# np.bitwise_and(np.invert(mask, dtype = np.uint8), bisector))
|
1246
|
+
_set_packed_where_mask(bisector, mask, patch_bisector, packed_box)
|
1247
|
+
else:
|
1248
|
+
patch_bisector, is_curtain = \
|
1249
|
+
bisector_from_face_indices(tuple(grid.extent_kji),
|
1250
|
+
patch_k_faces_kji0,
|
1251
|
+
patch_j_faces_kji0,
|
1252
|
+
patch_i_faces_kji0,
|
1253
|
+
raw_bisector,
|
1254
|
+
patch_box)
|
1255
|
+
bisector[mask] = patch_bisector[mask]
|
1256
|
+
if is_curtain:
|
1257
|
+
# TODO: downgrade following to debug once downstream functionality tested
|
1258
|
+
log.warning(f'ignoring curtain nature of bisector for patch {patch} of surface: {surface.title}')
|
1259
|
+
is_curtain = False
|
1260
|
+
else:
|
1261
|
+
log.info(f'preparing singlular cells bisector for surface: {surface.title}') # could downgrade to debug
|
1262
|
+
if ((k_faces_kji0 is None or len(k_faces_kji0) == 0) and
|
1263
|
+
(j_faces_kji0 is None or len(j_faces_kji0) == 0) and (i_faces_kji0 is None or len(i_faces_kji0) == 0)):
|
1264
|
+
bisector = np.ones((grid.nj, grid.ni), dtype = bool)
|
1265
|
+
is_curtain = True
|
1266
|
+
elif packed_bisectors:
|
1267
|
+
bisector, is_curtain = packed_bisector_from_face_indices(tuple(grid.extent_kji), k_faces_kji0,
|
1268
|
+
j_faces_kji0, i_faces_kji0, raw_bisector, None)
|
1269
|
+
if is_curtain:
|
1270
|
+
bisector = np.unpackbits(bisector[0], axis = -1,
|
1271
|
+
count = grid.ni).astype(bool) # reduce to a columns property
|
1272
|
+
else:
|
1273
|
+
bisector, is_curtain = bisector_from_face_indices(tuple(grid.extent_kji), k_faces_kji0, j_faces_kji0,
|
1274
|
+
i_faces_kji0, raw_bisector, None)
|
1275
|
+
if is_curtain:
|
1276
|
+
bisector = bisector[0] # reduce to a columns property
|
1277
|
+
|
1278
|
+
# if using patchwork, filter all faces (and properties) to those within (or on boundary of) volume corresponding to patch
|
1279
|
+
if patchwork:
|
1280
|
+
if k_faces_kji0 is not None:
|
1281
|
+
selection = filter_faces(k_faces_kji0, patch_indices_k, patch_indices, 0)
|
1282
|
+
if np.any(selection):
|
1283
|
+
k_faces_kji0 = k_faces_kji0[selection]
|
1284
|
+
if k_props is not None:
|
1285
|
+
k_props = [prop[selection] for prop in k_props]
|
1286
|
+
else:
|
1287
|
+
k_faces_kji0 = None
|
1288
|
+
k_props = None
|
1289
|
+
if j_faces_kji0 is not None:
|
1290
|
+
selection = filter_faces(j_faces_kji0, patch_indices_j, patch_indices, 1)
|
1291
|
+
if np.any(selection):
|
1292
|
+
j_faces_kji0 = j_faces_kji0[selection]
|
1293
|
+
if j_props is not None:
|
1294
|
+
j_props = [prop[selection] for prop in j_props]
|
1295
|
+
else:
|
1296
|
+
j_faces_kji0 = None
|
1297
|
+
j_props = None
|
1298
|
+
if i_faces_kji0 is not None:
|
1299
|
+
selection = filter_faces(i_faces_kji0, patch_indices_i, patch_indices, 2)
|
1300
|
+
if np.any(selection):
|
1301
|
+
i_faces_kji0 = i_faces_kji0[selection]
|
1302
|
+
if i_props is not None:
|
1303
|
+
i_props = [prop[selection] for prop in i_props]
|
1304
|
+
else:
|
1305
|
+
i_faces_kji0 = None
|
1306
|
+
i_props = None
|
1307
|
+
|
1308
|
+
log.debug("converting face sets into grid connection set")
|
1309
|
+
# NB: kji0 arrays in internal face protocol: used as cell_kji0 with polarity of 1
|
1310
|
+
# property lists have elements replaced with sorted and filtered equivalents
|
1311
|
+
gcs = rqf.GridConnectionSet.from_faces_indices(grid = grid,
|
1312
|
+
k_faces_kji0 = k_faces_kji0,
|
1313
|
+
j_faces_kji0 = j_faces_kji0,
|
1314
|
+
i_faces_kji0 = i_faces_kji0,
|
1315
|
+
remove_duplicates = not patchwork,
|
1316
|
+
k_properties = k_props,
|
1317
|
+
j_properties = j_props,
|
1318
|
+
i_properties = i_props,
|
1319
|
+
feature_name = name,
|
1320
|
+
feature_type = feature_type,
|
1321
|
+
create_organizing_objects_where_needed = True,
|
1322
|
+
title = title)
|
1323
|
+
# log.debug('finished coversion to gcs')
|
1324
|
+
|
1325
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1326
|
+
all_tris = None
|
1327
|
+
if return_triangles:
|
1328
|
+
# log.debug('preparing triangles array')
|
1329
|
+
k_triangles = np.empty((0,), dtype = np.int32) if k_props is None else k_props.pop(0)
|
1330
|
+
j_triangles = np.empty((0,), dtype = np.int32) if j_props is None else j_props.pop(0)
|
1331
|
+
i_triangles = np.empty((0,), dtype = np.int32) if i_props is None else i_props.pop(0)
|
1332
|
+
all_tris = np.concatenate((k_triangles, j_triangles, i_triangles), axis = 0)
|
1333
|
+
# log.debug(f'gcs count: {gcs.count}; all triangles shape: {all_tris.shape}')
|
1334
|
+
assert all_tris.shape == (gcs.count,)
|
1335
|
+
|
1336
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1337
|
+
all_depths = None
|
1338
|
+
if return_depths:
|
1339
|
+
# log.debug('preparing depths array')
|
1340
|
+
k_depths = np.empty((0,), dtype = np.float64) if k_props is None else k_props.pop(0)
|
1341
|
+
j_depths = np.empty((0,), dtype = np.float64) if j_props is None else j_props.pop(0)
|
1342
|
+
i_depths = np.empty((0,), dtype = np.float64) if i_props is None else i_props.pop(0)
|
1343
|
+
all_depths = np.concatenate((k_depths, j_depths, i_depths), axis = 0)
|
1344
|
+
# log.debug(f'gcs count: {gcs.count}; all depths shape: {all_depths.shape}')
|
1345
|
+
assert all_depths.shape == (gcs.count,)
|
1346
|
+
|
1347
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1348
|
+
all_offsets = None
|
1349
|
+
if return_offsets:
|
1350
|
+
# log.debug('preparing offsets array')
|
1351
|
+
k_offsets = np.empty((0,), dtype = np.float64) if k_props is None else k_props[0]
|
1352
|
+
j_offsets = np.empty((0,), dtype = np.float64) if j_props is None else j_props[0]
|
1353
|
+
i_offsets = np.empty((0,), dtype = np.float64) if i_props is None else i_props[0]
|
1354
|
+
all_offsets = _all_offsets(grid.crs, k_offsets, j_offsets, i_offsets)
|
1355
|
+
# log.debug(f'gcs count: {gcs.count}; all offsets shape: {all_offsets.shape}')
|
1356
|
+
assert all_offsets.shape == (gcs.count,)
|
1357
|
+
|
1358
|
+
all_flange = None
|
1359
|
+
if return_flange_bool:
|
1360
|
+
# log.debug('preparing flange array')
|
1361
|
+
flange_bool_uuid = surface.model.uuid(title = "flange bool",
|
1362
|
+
obj_type = "DiscreteProperty",
|
1363
|
+
related_uuid = surface.uuid)
|
1364
|
+
assert (flange_bool_uuid is not None), f"No flange bool property found for surface: {surface.title}"
|
1365
|
+
flange_bool = rqp.Property(surface.model, uuid = flange_bool_uuid)
|
1366
|
+
flange_array = flange_bool.array_ref(dtype = bool)
|
1367
|
+
all_flange = np.take(flange_array, all_tris)
|
1368
|
+
assert all_flange.shape == (gcs.count,)
|
1369
|
+
|
1370
|
+
# note: following is a grid cells property, not a gcs property
|
1371
|
+
shadow = None
|
1372
|
+
if return_shadow:
|
1373
|
+
log.debug("preparing cells shadow")
|
1374
|
+
shadow = shadow_from_face_indices(tuple(grid.extent_kji), k_faces_kji0)
|
1375
|
+
|
1376
|
+
if progress_fn is not None:
|
1377
|
+
progress_fn(1.0)
|
1378
|
+
|
1379
|
+
log.debug(f"finishing find_faces_to_represent_surface_regular_optimised for {name}")
|
1380
|
+
|
1381
|
+
# if returning properties, construct dictionary
|
1382
|
+
if return_properties:
|
1383
|
+
props_dict = {}
|
1384
|
+
if 'triangle' in return_properties:
|
1385
|
+
props_dict["triangle"] = all_tris
|
1386
|
+
if return_depths:
|
1387
|
+
props_dict["depth"] = all_depths
|
1388
|
+
if return_offsets:
|
1389
|
+
props_dict["offset"] = all_offsets
|
1390
|
+
if return_bisector:
|
1391
|
+
props_dict["grid bisector"] = (bisector, is_curtain)
|
1392
|
+
if return_shadow:
|
1393
|
+
props_dict["grid shadow"] = shadow
|
1394
|
+
if return_flange_bool:
|
1395
|
+
props_dict["flange bool"] = all_flange
|
1396
|
+
return (gcs, props_dict)
|
1397
|
+
|
1398
|
+
return gcs
|
1399
|
+
|
1400
|
+
|
861
1401
|
def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_type = "fault", progress_fn = None):
|
862
1402
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
863
1403
|
|
@@ -885,19 +1425,7 @@ def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_
|
|
885
1425
|
mode = "regular_optimised"
|
886
1426
|
else:
|
887
1427
|
mode = "staffa"
|
888
|
-
if mode == "
|
889
|
-
return find_faces_to_represent_surface_staffa(grid,
|
890
|
-
surface,
|
891
|
-
name,
|
892
|
-
feature_type = feature_type,
|
893
|
-
progress_fn = progress_fn)
|
894
|
-
elif mode == "regular":
|
895
|
-
return find_faces_to_represent_surface_regular(grid,
|
896
|
-
surface,
|
897
|
-
name,
|
898
|
-
feature_type = feature_type,
|
899
|
-
progress_fn = progress_fn)
|
900
|
-
elif mode == "regular_optimised":
|
1428
|
+
if mode == "regular_optimised":
|
901
1429
|
return find_faces_to_represent_surface_regular_optimised(grid,
|
902
1430
|
surface,
|
903
1431
|
name,
|
@@ -911,161 +1439,331 @@ def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_
|
|
911
1439
|
name,
|
912
1440
|
feature_type = feature_type,
|
913
1441
|
progress_fn = progress_fn)
|
1442
|
+
elif mode == "staffa":
|
1443
|
+
return find_faces_to_represent_surface_staffa(grid,
|
1444
|
+
surface,
|
1445
|
+
name,
|
1446
|
+
feature_type = feature_type,
|
1447
|
+
progress_fn = progress_fn)
|
1448
|
+
elif mode == "regular_dense":
|
1449
|
+
return find_faces_to_represent_surface_regular_dense_optimised(grid,
|
1450
|
+
surface,
|
1451
|
+
name,
|
1452
|
+
feature_type = feature_type,
|
1453
|
+
progress_fn = progress_fn)
|
1454
|
+
elif mode == "regular":
|
1455
|
+
return find_faces_to_represent_surface_regular(grid,
|
1456
|
+
surface,
|
1457
|
+
name,
|
1458
|
+
feature_type = feature_type,
|
1459
|
+
progress_fn = progress_fn)
|
914
1460
|
log.critical("unrecognised mode: " + str(mode))
|
915
1461
|
return None
|
916
1462
|
|
917
1463
|
|
918
1464
|
def bisector_from_faces( # type: ignore
|
919
|
-
|
920
|
-
|
921
|
-
j_faces: np.ndarray,
|
922
|
-
i_faces: np.ndarray,
|
923
|
-
raw_bisector: bool,
|
924
|
-
) -> Tuple[np.ndarray, bool]:
|
1465
|
+
grid_extent_kji: Tuple[int, int, int], k_faces: Union[np.ndarray, None], j_faces: Union[np.ndarray, None],
|
1466
|
+
i_faces: Union[np.ndarray, None], raw_bisector: bool) -> Tuple[np.ndarray, bool]:
|
925
1467
|
"""Creates a boolean array denoting the bisection of the grid by the face sets.
|
926
1468
|
|
927
1469
|
arguments:
|
928
|
-
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
929
|
-
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
930
|
-
j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
931
|
-
i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1470
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1471
|
+
- k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1472
|
+
- j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1473
|
+
- i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1474
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
932
1475
|
|
933
1476
|
returns:
|
934
1477
|
Tuple containing:
|
1478
|
+
- array (np.ndarray): boolean bisectors array where values are True for cells on the side
|
1479
|
+
of the surface that has a lower mean k index on average and False for cells on the other side.
|
1480
|
+
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False.
|
1481
|
+
|
1482
|
+
notes:
|
1483
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1484
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1485
|
+
assigned to either the True or False part
|
1486
|
+
- this function is DEPRECATED, use newer indices based approach instead: bisector_from_face_indices()
|
1487
|
+
"""
|
1488
|
+
warnings.warn('DEPRECATED: grid_surface.bisector_from_faces() function; use bisector_from_face_indices() instead')
|
1489
|
+
assert len(grid_extent_kji) == 3
|
1490
|
+
|
1491
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid)
|
1492
|
+
box = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
1493
|
+
box_shape = box[1, :] - box[0, :]
|
1494
|
+
|
1495
|
+
# set up the bisector array for the bounding box
|
1496
|
+
box_array = np.zeros(box_shape, dtype = np.bool_)
|
1497
|
+
|
1498
|
+
# seed the bisector box array at (0, 0, 0)
|
1499
|
+
box_array[0, 0, 0] = True
|
1500
|
+
|
1501
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1502
|
+
if k_faces is None:
|
1503
|
+
open_k = np.ones((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = bool)
|
1504
|
+
else:
|
1505
|
+
k_faces = k_faces[box[0, 0]:box[1, 0] - 1, box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]]
|
1506
|
+
open_k = np.logical_not(k_faces)
|
1507
|
+
if j_faces is None:
|
1508
|
+
open_j = np.ones((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = bool)
|
1509
|
+
else:
|
1510
|
+
j_faces = j_faces[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1] - 1, box[0, 2]:box[1, 2]]
|
1511
|
+
open_j = np.logical_not(j_faces)
|
1512
|
+
if i_faces is None:
|
1513
|
+
open_i = np.ones((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = bool)
|
1514
|
+
else:
|
1515
|
+
i_faces = i_faces[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2] - 1]
|
1516
|
+
open_i = np.logical_not(i_faces)
|
1517
|
+
|
1518
|
+
# populate bisector array for box
|
1519
|
+
_fill_bisector(box_array, open_k, open_j, open_i)
|
1520
|
+
|
1521
|
+
# set up the full bisectors array and assigning the bounding box values
|
1522
|
+
array = np.zeros(grid_extent_kji, dtype = np.bool_)
|
1523
|
+
array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1524
|
+
|
1525
|
+
# set bisector values outside of the bounding box
|
1526
|
+
_set_bisector_outside_box(array, box, box_array)
|
1527
|
+
|
1528
|
+
# check all array elements are not the same
|
1529
|
+
true_count = np.count_nonzero(array)
|
1530
|
+
cell_count = array.size
|
1531
|
+
if 0 < true_count < cell_count:
|
1532
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1533
|
+
is_curtain = _shallow_or_curtain(array, true_count, raw_bisector)
|
1534
|
+
else:
|
1535
|
+
assert raw_bisector, 'face set for surface is leaky or empty (surface does not intersect grid)'
|
1536
|
+
log.error('face set for surface is leaky or empty (surface does not intersect grid)')
|
1537
|
+
is_curtain = False
|
1538
|
+
|
1539
|
+
return array, is_curtain
|
1540
|
+
|
1541
|
+
|
1542
|
+
# yapf: disable
|
1543
|
+
def bisector_from_face_indices( # type: ignore
|
1544
|
+
grid_extent_kji: Tuple[int, int, int],
|
1545
|
+
k_faces_kji0: Union[np.ndarray, None],
|
1546
|
+
j_faces_kji0: Union[np.ndarray, None],
|
1547
|
+
i_faces_kji0: Union[np.ndarray, None],
|
1548
|
+
raw_bisector: bool,
|
1549
|
+
p_box: Union[np.ndarray, None]) -> Tuple[np.ndarray, bool]:
|
1550
|
+
# yapf: enable
|
1551
|
+
"""Creates a boolean array denoting the bisection of the grid by the face sets.
|
935
1552
|
|
1553
|
+
arguments:
|
1554
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1555
|
+
- k_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the k dimension
|
1556
|
+
- j_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the j dimension
|
1557
|
+
- i_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the i dimension
|
1558
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
1559
|
+
- p_box (np.ndarray): a python protocol box to limit the bisector evaluation over
|
1560
|
+
|
1561
|
+
returns:
|
1562
|
+
Tuple containing:
|
936
1563
|
- array (np.ndarray): boolean bisectors array where values are True for cells on the side
|
937
|
-
|
1564
|
+
of the surface that has a lower mean k index on average and False for cells on the other side.
|
938
1565
|
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False.
|
939
1566
|
|
940
1567
|
notes:
|
941
|
-
|
942
|
-
|
943
|
-
|
1568
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1569
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1570
|
+
assigned to either the True or False part
|
944
1571
|
"""
|
945
1572
|
assert len(grid_extent_kji) == 3
|
946
1573
|
|
947
|
-
#
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
)
|
1574
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid)
|
1575
|
+
face_box = get_boundary_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, grid_extent_kji)
|
1576
|
+
box = np.empty((2, 3), dtype = np.int32)
|
1577
|
+
if p_box is None:
|
1578
|
+
box[:] = face_box
|
1579
|
+
else:
|
1580
|
+
box[:] = box_intersection(p_box, face_box)
|
1581
|
+
if np.all(box == 0):
|
1582
|
+
box[:] = face_box
|
1583
|
+
# set k_faces as bool arrays covering box
|
1584
|
+
k_faces, j_faces, i_faces = _box_face_arrays_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, box)
|
959
1585
|
|
960
|
-
|
961
|
-
boundary_values = tuple(boundary.values())
|
962
|
-
bounding_array, first_k, first_j, first_i = _seed_array((0, 0, 0), k_faces, j_faces, i_faces, boundary_values,
|
963
|
-
bounding_array)
|
964
|
-
points = set()
|
965
|
-
for dimension, first_true in enumerate([first_k, first_j, first_i]):
|
966
|
-
for dimension_value in range(1, first_true):
|
967
|
-
point = [0, 0, 0]
|
968
|
-
point[dimension] = dimension_value
|
969
|
-
point = tuple(point) # type: ignore
|
970
|
-
bounding_array, first_k_sub, first_j_sub, first_i_sub = _seed_array(point, k_faces, j_faces, i_faces,
|
971
|
-
boundary_values, bounding_array)
|
972
|
-
for sub_dimension, first_true_sub in enumerate([first_k_sub, first_j_sub, first_i_sub]):
|
973
|
-
if dimension != sub_dimension:
|
974
|
-
for sub_dimension_value in range(1, first_true_sub):
|
975
|
-
point = [0, 0, 0]
|
976
|
-
point[dimension] = dimension_value
|
977
|
-
point[sub_dimension] = sub_dimension_value
|
978
|
-
point = tuple(point) # type: ignore
|
979
|
-
if point not in points:
|
980
|
-
points.add(point)
|
981
|
-
bounding_array, _, _, _ = _seed_array(
|
982
|
-
point,
|
983
|
-
k_faces,
|
984
|
-
j_faces,
|
985
|
-
i_faces,
|
986
|
-
boundary_values,
|
987
|
-
bounding_array,
|
988
|
-
)
|
989
|
-
|
990
|
-
# Setting up the array for the changing values.
|
991
|
-
changing_array = np.zeros_like(bounding_array, dtype = np.bool_)
|
992
|
-
|
993
|
-
# Repeatedly spreading True values to neighbouring cells that are not the other side of a face.
|
994
|
-
open_k = np.logical_not(k_faces)[boundary["k_min"]:boundary["k_max"], boundary["j_min"]:boundary["j_max"] + 1,
|
995
|
-
boundary["i_min"]:boundary["i_max"] + 1,]
|
996
|
-
open_j = np.logical_not(j_faces)[boundary["k_min"]:boundary["k_max"] + 1, boundary["j_min"]:boundary["j_max"],
|
997
|
-
boundary["i_min"]:boundary["i_max"] + 1,]
|
998
|
-
open_i = np.logical_not(i_faces)[boundary["k_min"]:boundary["k_max"] + 1, boundary["j_min"]:boundary["j_max"] + 1,
|
999
|
-
boundary["i_min"]:boundary["i_max"],]
|
1000
|
-
while True:
|
1001
|
-
changing_array[:] = False
|
1002
|
-
|
1003
|
-
# k faces
|
1004
|
-
changing_array[1:, :, :] = np.logical_and(bounding_array[:-1, :, :], open_k)
|
1005
|
-
changing_array[:-1, :, :] = np.logical_or(changing_array[:-1, :, :],
|
1006
|
-
np.logical_and(bounding_array[1:, :, :], open_k))
|
1586
|
+
box_shape = box[1, :] - box[0, :]
|
1007
1587
|
|
1008
|
-
|
1009
|
-
|
1010
|
-
np.logical_and(bounding_array[:, :-1, :], open_j))
|
1011
|
-
changing_array[:, :-1, :] = np.logical_or(changing_array[:, :-1, :],
|
1012
|
-
np.logical_and(bounding_array[:, 1:, :], open_j))
|
1588
|
+
# set up the bisector array for the bounding box
|
1589
|
+
box_array = np.zeros(box_shape, dtype = np.bool_)
|
1013
1590
|
|
1014
|
-
|
1015
|
-
|
1016
|
-
np.logical_and(bounding_array[:, :, :-1], open_i))
|
1017
|
-
changing_array[:, :, :-1] = np.logical_or(changing_array[:, :, :-1],
|
1018
|
-
np.logical_and(bounding_array[:, :, 1:], open_i))
|
1591
|
+
# seed the bisector box array at (0, 0, 0)
|
1592
|
+
box_array[0, 0, 0] = True
|
1019
1593
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1594
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1595
|
+
if k_faces is None:
|
1596
|
+
open_k = np.ones((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = bool)
|
1597
|
+
else:
|
1598
|
+
open_k = np.logical_not(k_faces)
|
1599
|
+
if j_faces is None:
|
1600
|
+
open_j = np.ones((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = bool)
|
1601
|
+
else:
|
1602
|
+
open_j = np.logical_not(j_faces)
|
1603
|
+
if i_faces is None:
|
1604
|
+
open_i = np.ones((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = bool)
|
1605
|
+
else:
|
1606
|
+
open_i = np.logical_not(i_faces)
|
1607
|
+
|
1608
|
+
# populate bisector array for box
|
1609
|
+
_fill_bisector(box_array, open_k, open_j, open_i)
|
1024
1610
|
|
1025
|
-
#
|
1611
|
+
# set up the full bisectors array and assigning the bounding box values
|
1026
1612
|
array = np.zeros(grid_extent_kji, dtype = np.bool_)
|
1027
|
-
array[
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
if boundary["k_min"] != 0:
|
1034
|
-
array[:boundary["k_min"], :, :] = True
|
1035
|
-
if boundary["j_max"] != grid_extent_kji[1] - 1 and np.any(bounding_array[:, -1, :]):
|
1036
|
-
array[:, boundary["j_max"] + 1:, :] = True
|
1037
|
-
if boundary["j_min"] != 0:
|
1038
|
-
array[:, :boundary["j_min"], :] = True
|
1039
|
-
if boundary["i_max"] != grid_extent_kji[2] - 1 and np.any(bounding_array[:, :, -1]):
|
1040
|
-
array[:, :, boundary["i_max"] + 1:] = True
|
1041
|
-
if boundary["i_min"] != 0:
|
1042
|
-
array[:, :, :boundary["i_min"]] = True
|
1043
|
-
|
1044
|
-
# Check all array elements are not the same.
|
1613
|
+
array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1614
|
+
|
1615
|
+
# set bisector values outside of the bounding box
|
1616
|
+
_set_bisector_outside_box(array, box, box_array)
|
1617
|
+
|
1618
|
+
# check all array elements are not the same
|
1045
1619
|
true_count = np.count_nonzero(array)
|
1046
1620
|
cell_count = array.size
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
for k in range(grid_extent_kji[0]):
|
1055
|
-
array_layer_count = np.count_nonzero(array[k])
|
1056
|
-
array_k_sum += (k + 1) * array_layer_count
|
1057
|
-
array_opposite_k_sum += (k + 1) * (layer_cell_count - array_layer_count)
|
1058
|
-
array_mean_k = float(array_k_sum) / float(true_count)
|
1059
|
-
array_opposite_mean_k = float(array_opposite_k_sum) / float(cell_count - true_count)
|
1060
|
-
if array_mean_k > array_opposite_mean_k and not raw_bisector:
|
1061
|
-
array[:] = np.logical_not(array)
|
1062
|
-
if abs(array_mean_k - array_opposite_mean_k) <= 0.001:
|
1063
|
-
# log.warning('unable to determine which side of surface is shallower')
|
1064
|
-
is_curtain = True
|
1621
|
+
if 0 < true_count < cell_count:
|
1622
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1623
|
+
is_curtain = _shallow_or_curtain(array, true_count, raw_bisector)
|
1624
|
+
else:
|
1625
|
+
assert raw_bisector, 'face set for surface is leaky or empty (surface does not intersect grid)'
|
1626
|
+
log.error('face set for surface is leaky or empty (surface does not intersect grid)')
|
1627
|
+
is_curtain = False
|
1065
1628
|
|
1066
1629
|
return array, is_curtain
|
1067
1630
|
|
1068
1631
|
|
1632
|
+
# yapf: disable
|
1633
|
+
def packed_bisector_from_face_indices( # type: ignore
|
1634
|
+
grid_extent_kji: Tuple[int, int, int],
|
1635
|
+
k_faces_kji0: Union[np.ndarray, None],
|
1636
|
+
j_faces_kji0: Union[np.ndarray, None],
|
1637
|
+
i_faces_kji0: Union[np.ndarray, None],
|
1638
|
+
raw_bisector: bool,
|
1639
|
+
p_box: Union[np.ndarray, None]) -> Tuple[np.ndarray, bool]:
|
1640
|
+
# yapf: enable
|
1641
|
+
"""Creates a uint8 (packed bool) array denoting the bisection of the grid by the face sets.
|
1642
|
+
|
1643
|
+
arguments:
|
1644
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1645
|
+
- k_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the k dimension
|
1646
|
+
- j_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the j dimension
|
1647
|
+
- i_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the i dimension
|
1648
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
1649
|
+
- p_box (np.ndarray): a python protocol unshrunken box to limit the bisector evaluation over
|
1650
|
+
|
1651
|
+
returns:
|
1652
|
+
Tuple containing:
|
1653
|
+
- array (np.uint8 array): packed boolean bisector array where values are 1 for cells on the side
|
1654
|
+
of the surface that has a lower mean k index on average and 0 for cells on the other side
|
1655
|
+
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False
|
1656
|
+
|
1657
|
+
notes:
|
1658
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1659
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1660
|
+
assigned to either the True or False part
|
1661
|
+
- the returned array is packed in the I axis; use np.unpackbits() to unpack
|
1662
|
+
"""
|
1663
|
+
assert len(grid_extent_kji) == 3
|
1664
|
+
|
1665
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid), and shrink the I axis
|
1666
|
+
face_box = get_packed_boundary_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, grid_extent_kji)
|
1667
|
+
box = np.empty((2, 3), dtype = np.int32)
|
1668
|
+
if p_box is None:
|
1669
|
+
box[:] = face_box
|
1670
|
+
else:
|
1671
|
+
box[:] = box_intersection(shrunk_box_for_packing(p_box), face_box)
|
1672
|
+
if np.all(box == 0):
|
1673
|
+
box[:] = face_box
|
1674
|
+
|
1675
|
+
# set k_faces, j_faces & i_faces as uint8 packed bool arrays covering box
|
1676
|
+
k_faces, j_faces, i_faces = _packed_box_face_arrays_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, box)
|
1677
|
+
|
1678
|
+
box_shape = box[1, :] - box[0, :]
|
1679
|
+
|
1680
|
+
# set up the bisector array for the bounding box
|
1681
|
+
box_array = np.zeros(box_shape, dtype = np.uint8)
|
1682
|
+
|
1683
|
+
# seed the bisector box array at (0, 0, 0)
|
1684
|
+
box_array[0, 0, 0] = 0x80 # first bit only set
|
1685
|
+
|
1686
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1687
|
+
if k_faces is None:
|
1688
|
+
open_k = np.invert(np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.uint8), dtype = np.uint8)
|
1689
|
+
else:
|
1690
|
+
open_k = np.invert(k_faces, dtype = np.uint8)
|
1691
|
+
if j_faces is None:
|
1692
|
+
open_j = np.invert(np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.uint8), dtype = np.uint8)
|
1693
|
+
else:
|
1694
|
+
open_j = np.invert(j_faces, dtype = np.uint8)
|
1695
|
+
if i_faces is None:
|
1696
|
+
open_i = np.invert(np.zeros(tuple(box_shape), dtype = np.uint8), dtype = np.uint8)
|
1697
|
+
else:
|
1698
|
+
open_i = np.invert(i_faces, dtype = np.uint8)
|
1699
|
+
|
1700
|
+
# close off faces in padding bits, if within box
|
1701
|
+
if box[1, 2] * 8 > grid_extent_kji[2]:
|
1702
|
+
tail = grid_extent_kji[2] % 8 # number of valid bits in padded byte
|
1703
|
+
assert tail
|
1704
|
+
m = np.uint8((255 << (8 - tail)) & 255)
|
1705
|
+
open_k[:, :, -1] &= m
|
1706
|
+
open_j[:, :, -1] &= m
|
1707
|
+
m = np.uint8((m << 1) & 255)
|
1708
|
+
open_i[:, :, -1] &= m
|
1709
|
+
|
1710
|
+
# populate bisector array for box
|
1711
|
+
_fill_packed_bisector(box_array, open_k, open_j, open_i)
|
1712
|
+
|
1713
|
+
del open_i, open_j, open_k
|
1714
|
+
|
1715
|
+
# set up the full bisectors array and assigning the bounding box values
|
1716
|
+
p_array = np.zeros(_shape_packed(grid_extent_kji), dtype = np.uint8)
|
1717
|
+
p_array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1718
|
+
|
1719
|
+
# set bisector values outside of the bounding box
|
1720
|
+
_set_packed_bisector_outside_box(p_array, box, box_array, grid_extent_kji[2] % 8)
|
1721
|
+
|
1722
|
+
# check all array elements are not the same
|
1723
|
+
if hasattr(np, 'bitwise_count'):
|
1724
|
+
true_count = np.sum(np.bitwise_count(p_array))
|
1725
|
+
else:
|
1726
|
+
true_count = _bitwise_count_njit(p_array) # note: will usually include some padding bits, so not so true!
|
1727
|
+
cell_count = np.prod(grid_extent_kji)
|
1728
|
+
if 0 < true_count < cell_count:
|
1729
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1730
|
+
# TODO: switch to _packed_shallow_or_curtain() when numba supports np.bitwise_count()
|
1731
|
+
is_curtain = _packed_shallow_or_curtain_temp_bitwise_count(p_array, true_count, raw_bisector)
|
1732
|
+
else:
|
1733
|
+
assert raw_bisector, 'face set for surface is leaky or empty (surface does not intersect grid)'
|
1734
|
+
log.error('face set for surface is leaky or empty (surface does not intersect grid)')
|
1735
|
+
is_curtain = False
|
1736
|
+
|
1737
|
+
return p_array, is_curtain
|
1738
|
+
|
1739
|
+
|
1740
|
+
def column_bisector_from_face_indices(grid_extent_ji: Tuple[int, int], j_faces_ji0: np.ndarray,
|
1741
|
+
i_faces_ji0: np.ndarray) -> np.ndarray:
|
1742
|
+
"""Returns a numpy bool array denoting the bisection of the top layer of the grid by the curtain face sets.
|
1743
|
+
|
1744
|
+
arguments:
|
1745
|
+
- grid_extent_ji (pair of int): the shape of a layer of the grid
|
1746
|
+
- j_faces_ji0, i_faces_ji0 (2D numpy int arrays of shape (N, 2)): indices of faces within a layer
|
1747
|
+
|
1748
|
+
returns:
|
1749
|
+
numpy bool array of shape grid_extent_ji, set True for cells on one side of the face sets;
|
1750
|
+
set False for cells on othe side
|
1751
|
+
|
1752
|
+
notes:
|
1753
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1754
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1755
|
+
assigned to the False part
|
1756
|
+
- the resulting array is suitable for use as a grid property with indexable element of columns
|
1757
|
+
- the array is set True for the side of the curtain that contains cell [0, 0]
|
1758
|
+
"""
|
1759
|
+
assert len(grid_extent_ji) == 2
|
1760
|
+
j_faces = np.zeros((grid_extent_ji[0] - 1, grid_extent_ji[1]), dtype = np.bool_)
|
1761
|
+
i_faces = np.zeros((grid_extent_ji[0], grid_extent_ji[1] - 1), dtype = np.bool_)
|
1762
|
+
j_faces[j_faces_ji0[:, 0], j_faces_ji0[:, 1]] = True
|
1763
|
+
i_faces[i_faces_ji0[:, 0], i_faces_ji0[:, 1]] = True
|
1764
|
+
return column_bisector_from_faces(grid_extent_ji, j_faces, i_faces)
|
1765
|
+
|
1766
|
+
|
1069
1767
|
def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndarray, i_faces: np.ndarray) -> np.ndarray:
|
1070
1768
|
"""Returns a numpy bool array denoting the bisection of the top layer of the grid by the curtain face sets.
|
1071
1769
|
|
@@ -1116,6 +1814,42 @@ def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndar
|
|
1116
1814
|
return a
|
1117
1815
|
|
1118
1816
|
|
1817
|
+
def shadow_from_face_indices(extent_kji, kji0):
|
1818
|
+
"""Returns a numpy int8 array indicating whether cells are above, below or between K faces.
|
1819
|
+
|
1820
|
+
arguments:
|
1821
|
+
extent_kji (triple int): the shape of the grid
|
1822
|
+
kji0 (numpy int array of shape (N, 3)): indices where a K face is present
|
1823
|
+
|
1824
|
+
returns:
|
1825
|
+
numpy int8 array of shape extent_kji; values are: 0 neither above nor below a K face;
|
1826
|
+
1: above any K faces in the column; 2 below any K faces in the column;
|
1827
|
+
3: between K faces (one or more above and one or more below)
|
1828
|
+
"""
|
1829
|
+
assert len(extent_kji) == 3
|
1830
|
+
limit = extent_kji[0] - 1 # maximum number of iterations needed to spead shadow
|
1831
|
+
shadow = np.zeros(extent_kji, dtype = np.int8)
|
1832
|
+
shadow[kji0[:, 0], kji0[:, 1], kji0[:, 2]] = 1
|
1833
|
+
shadow[kji0[:, 0] + 1, kji0[:, 1], kji0[:, 2]] += 2
|
1834
|
+
for _ in range(limit):
|
1835
|
+
c = np.logical_and(shadow[:-1] == 0, shadow[1:] == 1)
|
1836
|
+
if np.count_nonzero(c) == 0:
|
1837
|
+
break
|
1838
|
+
shadow[:-1][c] = 1
|
1839
|
+
for _ in range(limit):
|
1840
|
+
c = np.logical_and(shadow[1:] == 0, shadow[:-1] == 2)
|
1841
|
+
if np.count_nonzero(c) == 0:
|
1842
|
+
break
|
1843
|
+
shadow[1:][c] = 2
|
1844
|
+
for _ in range(limit):
|
1845
|
+
c = np.logical_and(shadow[:-1] >= 2, shadow[1:] == 1)
|
1846
|
+
if np.count_nonzero(c) == 0:
|
1847
|
+
break
|
1848
|
+
shadow[:-1][c] = 3
|
1849
|
+
shadow[1:][c] = 3
|
1850
|
+
return shadow
|
1851
|
+
|
1852
|
+
|
1119
1853
|
def shadow_from_faces(extent_kji, k_faces):
|
1120
1854
|
"""Returns a numpy int8 array indicating whether cells are above, below or between K faces.
|
1121
1855
|
|
@@ -1137,28 +1871,28 @@ def shadow_from_faces(extent_kji, k_faces):
|
|
1137
1871
|
c = np.logical_and(shadow[:-1] == 0, shadow[1:] == 1)
|
1138
1872
|
if np.count_nonzero(c) == 0:
|
1139
1873
|
break
|
1140
|
-
shadow[:-1] =
|
1874
|
+
shadow[:-1][c] = 1
|
1141
1875
|
for _ in range(limit):
|
1142
1876
|
c = np.logical_and(shadow[1:] == 0, shadow[:-1] == 2)
|
1143
1877
|
if np.count_nonzero(c) == 0:
|
1144
1878
|
break
|
1145
|
-
shadow[1:] =
|
1879
|
+
shadow[1:][c] = 2
|
1146
1880
|
for _ in range(limit):
|
1147
1881
|
c = np.logical_and(shadow[:-1] >= 2, shadow[1:] == 1)
|
1148
1882
|
if np.count_nonzero(c) == 0:
|
1149
1883
|
break
|
1150
|
-
shadow[:-1] =
|
1151
|
-
shadow[1:] =
|
1884
|
+
shadow[:-1][c] = 3
|
1885
|
+
shadow[1:][c] = 3
|
1152
1886
|
return shadow
|
1153
1887
|
|
1154
1888
|
|
1155
1889
|
def get_boundary( # type: ignore
|
1156
|
-
k_faces: np.ndarray,
|
1157
|
-
j_faces: np.ndarray,
|
1158
|
-
i_faces: np.ndarray,
|
1890
|
+
k_faces: Union[np.ndarray, None],
|
1891
|
+
j_faces: Union[np.ndarray, None],
|
1892
|
+
i_faces: Union[np.ndarray, None],
|
1159
1893
|
grid_extent_kji: Tuple[int, int, int],
|
1160
|
-
) ->
|
1161
|
-
"""Cretaes a
|
1894
|
+
) -> np.ndarray:
|
1895
|
+
"""Cretaes a box of the indices that bound the surface (where the faces are True).
|
1162
1896
|
|
1163
1897
|
arguments:
|
1164
1898
|
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
@@ -1167,26 +1901,22 @@ def get_boundary( # type: ignore
|
|
1167
1901
|
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1168
1902
|
|
1169
1903
|
returns:
|
1170
|
-
|
1904
|
+
int array of shape (2, 3): bounding box in python protocol (max values have been incremented)
|
1171
1905
|
|
1172
1906
|
note:
|
1173
1907
|
input faces arrays are for internal grid faces (ie. extent reduced by 1 in axis of faces);
|
1174
1908
|
a buffer slice is included where the surface does not reach the edge of the grid
|
1175
1909
|
"""
|
1176
1910
|
|
1177
|
-
boundary =
|
1178
|
-
"k_min": None,
|
1179
|
-
"k_max": None,
|
1180
|
-
"j_min": None,
|
1181
|
-
"j_max": None,
|
1182
|
-
"i_min": None,
|
1183
|
-
"i_max": None,
|
1184
|
-
}
|
1911
|
+
boundary = np.zeros((2, 3), dtype = np.int32)
|
1185
1912
|
|
1186
1913
|
starting = True
|
1187
1914
|
|
1188
1915
|
for f_i, faces in enumerate([k_faces, j_faces, i_faces]):
|
1189
1916
|
|
1917
|
+
if faces is None:
|
1918
|
+
continue
|
1919
|
+
|
1190
1920
|
# NB. k, j & i for rest of loop refer to indices of faces, regardless of which face set is being processed
|
1191
1921
|
|
1192
1922
|
where_k, where_j, where_i = _where_true(faces)
|
@@ -1210,44 +1940,90 @@ def get_boundary( # type: ignore
|
|
1210
1940
|
else:
|
1211
1941
|
if min_k > 0:
|
1212
1942
|
min_k -= 1
|
1213
|
-
if max_k <
|
1943
|
+
if max_k < grid_extent_kji[0] - 1:
|
1214
1944
|
max_k += 1
|
1215
1945
|
if f_i == 1:
|
1216
1946
|
max_j += 1
|
1217
1947
|
else:
|
1218
1948
|
if min_j > 0:
|
1219
1949
|
min_j -= 1
|
1220
|
-
if max_j <
|
1950
|
+
if max_j < grid_extent_kji[1] - 1:
|
1221
1951
|
max_j += 1
|
1222
1952
|
if f_i == 2:
|
1223
1953
|
max_i += 1
|
1224
1954
|
else:
|
1225
1955
|
if min_i > 0:
|
1226
1956
|
min_i -= 1
|
1227
|
-
if max_i <
|
1957
|
+
if max_i < grid_extent_kji[2] - 1:
|
1228
1958
|
max_i += 1
|
1229
1959
|
|
1230
1960
|
if starting:
|
1231
|
-
boundary[
|
1232
|
-
boundary[
|
1233
|
-
boundary[
|
1234
|
-
boundary[
|
1235
|
-
boundary[
|
1236
|
-
boundary[
|
1961
|
+
boundary[0, 0] = min_k
|
1962
|
+
boundary[1, 0] = max_k
|
1963
|
+
boundary[0, 1] = min_j
|
1964
|
+
boundary[1, 1] = max_j
|
1965
|
+
boundary[0, 2] = min_i
|
1966
|
+
boundary[1, 2] = max_i
|
1237
1967
|
starting = False
|
1238
1968
|
else:
|
1239
|
-
if min_k < boundary[
|
1240
|
-
boundary[
|
1241
|
-
if max_k > boundary[
|
1242
|
-
boundary[
|
1243
|
-
if min_j < boundary[
|
1244
|
-
boundary[
|
1245
|
-
if max_j > boundary[
|
1246
|
-
boundary[
|
1247
|
-
if min_i < boundary[
|
1248
|
-
boundary[
|
1249
|
-
if max_i > boundary[
|
1250
|
-
boundary[
|
1969
|
+
if min_k < boundary[0, 0]:
|
1970
|
+
boundary[0, 0] = min_k
|
1971
|
+
if max_k > boundary[1, 0]:
|
1972
|
+
boundary[1, 0] = max_k
|
1973
|
+
if min_j < boundary[0, 1]:
|
1974
|
+
boundary[0, 1] = min_j
|
1975
|
+
if max_j > boundary[1, 1]:
|
1976
|
+
boundary[1, 1] = max_j
|
1977
|
+
if min_i < boundary[0, 2]:
|
1978
|
+
boundary[0, 2] = min_i
|
1979
|
+
if max_i > boundary[1, 2]:
|
1980
|
+
boundary[1, 2] = max_i
|
1981
|
+
|
1982
|
+
boundary[1, :] += 1 # increment max values to give python protocol box
|
1983
|
+
|
1984
|
+
return boundary # type: ignore
|
1985
|
+
|
1986
|
+
|
1987
|
+
def get_boundary_dict( # type: ignore
|
1988
|
+
k_faces: Union[np.ndarray, None],
|
1989
|
+
j_faces: Union[np.ndarray, None],
|
1990
|
+
i_faces: Union[np.ndarray, None],
|
1991
|
+
grid_extent_kji: Tuple[int, int, int],
|
1992
|
+
) -> Dict[str, int]:
|
1993
|
+
"""Cretaes a dictionary of the indices that bound the surface (where the faces are True).
|
1994
|
+
|
1995
|
+
arguments:
|
1996
|
+
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1997
|
+
j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1998
|
+
i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1999
|
+
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
2000
|
+
|
2001
|
+
returns:
|
2002
|
+
boundary (Dict[str, int]): a dictionary of the indices that bound the surface
|
2003
|
+
|
2004
|
+
note:
|
2005
|
+
input faces arrays are for internal grid faces (ie. extent reduced by 1 in axis of faces);
|
2006
|
+
a buffer slice is included where the surface does not reach the edge of the grid;
|
2007
|
+
max values are not increment, ie. need to be incremented to be used as an upper end of a python range
|
2008
|
+
"""
|
2009
|
+
|
2010
|
+
boundary = {
|
2011
|
+
"k_min": None,
|
2012
|
+
"k_max": None,
|
2013
|
+
"j_min": None,
|
2014
|
+
"j_max": None,
|
2015
|
+
"i_min": None,
|
2016
|
+
"i_max": None,
|
2017
|
+
}
|
2018
|
+
|
2019
|
+
box = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
2020
|
+
|
2021
|
+
boundary["k_min"] = box[0, 0]
|
2022
|
+
boundary["k_max"] = box[1, 0] - 1
|
2023
|
+
boundary["j_min"] = box[0, 1]
|
2024
|
+
boundary["j_max"] = box[1, 1] - 1
|
2025
|
+
boundary["i_min"] = box[0, 2]
|
2026
|
+
boundary["i_max"] = box[1, 2] - 1
|
1251
2027
|
|
1252
2028
|
return boundary # type: ignore
|
1253
2029
|
|
@@ -1259,7 +2035,7 @@ def _where_true(data: np.ndarray):
|
|
1259
2035
|
|
1260
2036
|
|
1261
2037
|
@njit # pragma: no cover
|
1262
|
-
def _first_true(array: np.ndarray) ->
|
2038
|
+
def _first_true(array: np.ndarray) -> int: # type: ignore
|
1263
2039
|
"""Returns the index + 1 of the first True value in the array."""
|
1264
2040
|
for idx, val in np.ndenumerate(array):
|
1265
2041
|
if val:
|
@@ -1267,6 +2043,25 @@ def _first_true(array: np.ndarray) -> Optional[int]: # type: ignore
|
|
1267
2043
|
return array.size
|
1268
2044
|
|
1269
2045
|
|
2046
|
+
@njit('(uint8[:,:,:], uint8[:,:,:], uint8[:,:,:], int32[:,:])', parallel = True) # pragma: no cover
|
2047
|
+
def _set_packed_where_mask(a: np.ndarray, mask: np.ndarray, v: np.ndarray, box: np.ndarray): # type: ignore
|
2048
|
+
"""Update 3D packed boolean array a from packed boolean array v where packed boolean array mask is set."""
|
2049
|
+
assert a.ndim == 3 and a.shape == mask.shape and a.shape == v.shape and box.shape == (2, 3)
|
2050
|
+
sk: int = box[0, 0]
|
2051
|
+
sj: int = box[0, 1]
|
2052
|
+
si: int = box[0, 2]
|
2053
|
+
ek: int = box[1, 0]
|
2054
|
+
ej: int = box[1, 1]
|
2055
|
+
ei: int = box[1, 2]
|
2056
|
+
for k in prange(sk, ek):
|
2057
|
+
for j in range(sj, ej):
|
2058
|
+
for i in range(si, ei):
|
2059
|
+
m: np.uint8 = mask[k, j, i]
|
2060
|
+
if m != 0:
|
2061
|
+
not_m: np.uint8 = ~m
|
2062
|
+
a[k, j, i] = (m & v[k, j, i]) | (not_m & a[k, j, i])
|
2063
|
+
|
2064
|
+
|
1270
2065
|
@njit # pragma: no cover
|
1271
2066
|
def intersect_numba(
|
1272
2067
|
axis: int,
|
@@ -1350,6 +2145,7 @@ def intersect_numba(
|
|
1350
2145
|
face_idx[index2] = d2
|
1351
2146
|
face_idx[2 - axis] = face
|
1352
2147
|
|
2148
|
+
# dangerous: relies on indivisible read-modify-write of memory word containing multiple faces elements
|
1353
2149
|
faces[face_idx[0], face_idx[1], face_idx[2]] = True
|
1354
2150
|
|
1355
2151
|
if return_depths:
|
@@ -1362,62 +2158,426 @@ def intersect_numba(
|
|
1362
2158
|
return faces, offsets, triangle_per_face
|
1363
2159
|
|
1364
2160
|
|
2161
|
+
def _all_offsets(crs, k_offsets_list, j_offsets_list, i_offsets_list):
|
2162
|
+
if crs.xy_units == crs.z_units:
|
2163
|
+
return np.concatenate((k_offsets_list, j_offsets_list, i_offsets_list), axis = 0)
|
2164
|
+
ji_offsets = np.concatenate((j_offsets_list, i_offsets_list), axis = 0)
|
2165
|
+
wam.convert_lengths(ji_offsets, crs.xy_units, crs.z_units)
|
2166
|
+
return np.concatenate((k_offsets_list, ji_offsets), axis = 0)
|
2167
|
+
|
2168
|
+
|
1365
2169
|
@njit # pragma: no cover
|
1366
|
-
def
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
2170
|
+
def _fill_bisector(bisect: np.ndarray, open_k: np.ndarray, open_j: np.ndarray, open_i: np.ndarray):
|
2171
|
+
nk: int = bisect.shape[0]
|
2172
|
+
nj: int = bisect.shape[1]
|
2173
|
+
ni: int = bisect.shape[2]
|
2174
|
+
going: bool = True
|
2175
|
+
while going:
|
2176
|
+
going = False
|
2177
|
+
for k in range(nk):
|
2178
|
+
for j in range(nj):
|
2179
|
+
for i in range(ni):
|
2180
|
+
if bisect[k, j, i]:
|
2181
|
+
continue
|
2182
|
+
if ((k and bisect[k - 1, j, i] and open_k[k - 1, j, i]) or
|
2183
|
+
(j and bisect[k, j - 1, i] and open_j[k, j - 1, i]) or
|
2184
|
+
(i and bisect[k, j, i - 1] and open_i[k, j, i - 1]) or
|
2185
|
+
(k < nk - 1 and bisect[k + 1, j, i] and open_k[k, j, i]) or
|
2186
|
+
(j < nj - 1 and bisect[k, j + 1, i] and open_j[k, j, i]) or
|
2187
|
+
(i < ni - 1 and bisect[k, j, i + 1] and open_i[k, j, i])):
|
2188
|
+
bisect[k, j, i] = True
|
2189
|
+
going = True
|
1375
2190
|
|
1376
|
-
arguments:
|
1377
|
-
point (Tuple[int, int, int]): coordinates of the initial seed point.
|
1378
|
-
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension.
|
1379
|
-
j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension.
|
1380
|
-
i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension.
|
1381
|
-
boundary (Tuple[int, int, int, int, int, int]): the boundaries of the surface given in the order
|
1382
|
-
(minimum k, maximum k, minimum j, maximum j, minimum i, maximum i).
|
1383
|
-
array (np.ndarray): boolean array that will be seeded.
|
1384
2191
|
|
1385
|
-
|
1386
|
-
|
2192
|
+
@njit # pragma: no cover
|
2193
|
+
def _fill_packed_bisector(bisect: np.ndarray, open_k: np.ndarray, open_j: np.ndarray, open_i: np.ndarray):
|
2194
|
+
nk: int = bisect.shape[0]
|
2195
|
+
nj: int = bisect.shape[1]
|
2196
|
+
ni: int = bisect.shape[2]
|
2197
|
+
going: bool = True
|
2198
|
+
m: np.uint8 = np.uint8(0)
|
2199
|
+
om: np.uint8 = np.uint8(0)
|
2200
|
+
oi: np.uint8 = np.uint8(0)
|
2201
|
+
while going:
|
2202
|
+
going = False
|
2203
|
+
for k in range(nk):
|
2204
|
+
for j in range(nj):
|
2205
|
+
for i in range(ni):
|
2206
|
+
m = np.uint8(bisect[k, j, i]) # 8 bools packed into a uint8
|
2207
|
+
if bisect[k, j, i] == np.uint8(0xFF): # all 8 values already set
|
2208
|
+
continue
|
2209
|
+
om = m # copy to check for changes later
|
2210
|
+
if k:
|
2211
|
+
m |= (bisect[k - 1, j, i] & open_k[k - 1, j, i])
|
2212
|
+
if k < nk - 1:
|
2213
|
+
m |= (bisect[k + 1, j, i] & open_k[k, j, i])
|
2214
|
+
if j:
|
2215
|
+
m |= (bisect[k, j - 1, i] & open_j[k, j - 1, i])
|
2216
|
+
if j < nj - 1:
|
2217
|
+
m |= (bisect[k, j + 1, i] & open_j[k, j, i])
|
2218
|
+
oi = np.uint8(open_i[k, j, i]) # type: ignore
|
2219
|
+
m |= (m >> 1) & (oi >> 1) # type: ignore
|
2220
|
+
m |= (m << 1) & oi # type: ignore
|
2221
|
+
# handle rollover bits for I
|
2222
|
+
if i and (bisect[k, j, i - 1] & open_i[k, j, i - 1] & np.uint8(0x01)):
|
2223
|
+
m |= np.uint8(0x80)
|
2224
|
+
if (i < ni - 1) and (oi & 1) and (bisect[k, j, i + 1] & 0x80):
|
2225
|
+
m |= np.uint8(0x01)
|
2226
|
+
if m != om:
|
2227
|
+
bisect[k, j, i] = m
|
2228
|
+
going = True
|
1387
2229
|
|
1388
|
-
- array (np.ndarray): boolean array that has been seeded.
|
1389
|
-
- first_k (int): the index of the first k face in the k direction from the seed point or the
|
1390
|
-
array size in the k direction if there are no k faces.
|
1391
|
-
- first_j (int): the index of the first j face in the j direction from the seed point or the
|
1392
|
-
array size in the j direction if there are no j faces.
|
1393
|
-
- first_i (int): the index of the first i face in the i direction from the seed point or the
|
1394
|
-
array size in the i direction if there are no i faces.
|
1395
|
-
"""
|
1396
|
-
k = point[0]
|
1397
|
-
j = point[1]
|
1398
|
-
i = point[2]
|
1399
2230
|
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
2231
|
+
@njit # pragma: no cover
|
2232
|
+
def _shallow_or_curtain(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
2233
|
+
# negate the bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
2234
|
+
assert a.ndim == 3
|
2235
|
+
layer_cell_count: int = a.shape[1] * a.shape[2]
|
2236
|
+
k_sum: int = 0
|
2237
|
+
opposite_k_sum: int = 0
|
2238
|
+
is_curtain: bool = False
|
2239
|
+
layer_count: int = 0
|
2240
|
+
for k in range(a.shape[0]):
|
2241
|
+
layer_count = np.count_nonzero(a[k])
|
2242
|
+
k_sum += (k + 1) * layer_count
|
2243
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
2244
|
+
mean_k: float = float(k_sum) / float(true_count)
|
2245
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(a.size - true_count)
|
2246
|
+
if mean_k > opposite_mean_k and not raw:
|
2247
|
+
a[:] = np.logical_not(a)
|
2248
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
2249
|
+
# log.warning('unable to determine which side of surface is shallower')
|
2250
|
+
is_curtain = True
|
2251
|
+
return is_curtain
|
1404
2252
|
|
1405
|
-
first_j = 0
|
1406
|
-
if j == 0:
|
1407
|
-
first_j = _first_true(j_faces[boundary[0] + k, boundary[2]:boundary[3], boundary[4] + i])
|
1408
|
-
array[k, :first_j, i] = True
|
1409
2253
|
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
2254
|
+
@njit # pragma: no cover
|
2255
|
+
def _packed_shallow_or_curtain(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
2256
|
+
# negate the packed bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
2257
|
+
assert a.ndim == 3
|
2258
|
+
layer_cell_count: int = 8 * a.shape[1] * a.shape[2] # note: includes padding bits
|
2259
|
+
k_sum: int = 0
|
2260
|
+
opposite_k_sum: int = 0
|
2261
|
+
is_curtain: bool = False
|
2262
|
+
layer_count: int = 0
|
2263
|
+
for k in range(a.shape[0]):
|
2264
|
+
# np.bitwise_count() not yet supported by numba
|
2265
|
+
layer_count = np.sum(np.bitwise_count(a[k]), dtype = np.int64) # type: ignore
|
2266
|
+
k_sum += (k + 1) * layer_count
|
2267
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
2268
|
+
mean_k: float = float(k_sum) / float(true_count)
|
2269
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(8 * a.size - true_count)
|
2270
|
+
if mean_k > opposite_mean_k and not raw:
|
2271
|
+
a[:] = np.invert(a)
|
2272
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
2273
|
+
# log.warning('unable to determine which side of surface is shallower')
|
2274
|
+
is_curtain = True
|
2275
|
+
return is_curtain
|
1414
2276
|
|
1415
|
-
return array, first_k, first_j, first_i
|
1416
2277
|
|
2278
|
+
@njit # pragma: no cover
|
2279
|
+
def _packed_shallow_or_curtain_temp_bitwise_count(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
2280
|
+
# negate the packed bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
2281
|
+
assert a.ndim == 3
|
2282
|
+
# note: following 'cell count' includes padding bits
|
2283
|
+
layer_cell_count: np.int64 = 8 * a.shape[1] * a.shape[2] # type: ignore
|
2284
|
+
k_sum: np.int64 = 0 # type: ignore
|
2285
|
+
opposite_k_sum: np.int64 = 0 # type: ignore
|
2286
|
+
is_curtain: bool = False
|
2287
|
+
layer_count: np.int64 = 0 # type: ignore
|
2288
|
+
for k in range(a.shape[0]):
|
2289
|
+
layer_count = _bitwise_count_njit(a[k, :, :])
|
2290
|
+
k_sum += (k + 1) * layer_count
|
2291
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
2292
|
+
mean_k: float = float(k_sum) / float(true_count)
|
2293
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(8 * a.size - true_count)
|
2294
|
+
if mean_k > opposite_mean_k and not raw:
|
2295
|
+
a[:] = np.invert(a)
|
2296
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
2297
|
+
# log.warning('unable to determine which side of surface is shallower')
|
2298
|
+
is_curtain = True
|
2299
|
+
return is_curtain
|
2300
|
+
|
2301
|
+
|
2302
|
+
def _set_bisector_outside_box(a: np.ndarray, box: np.ndarray, box_array: np.ndarray): # type: ignore
|
2303
|
+
# set values outside of the bounding box
|
2304
|
+
if box[1, 0] < a.shape[0] and np.any(box_array[-1, :, :]):
|
2305
|
+
a[box[1, 0]:, :, :] = True
|
2306
|
+
if box[0, 0] != 0:
|
2307
|
+
a[:box[0, 0], :, :] = True
|
2308
|
+
if box[1, 1] < a.shape[1] and np.any(box_array[:, -1, :]):
|
2309
|
+
a[:, box[1, 1]:, :] = True
|
2310
|
+
if box[0, 1] != 0:
|
2311
|
+
a[:, :box[0, 1], :] = True
|
2312
|
+
if box[1, 2] < a.shape[2] and np.any(box_array[:, :, -1]):
|
2313
|
+
a[:, :, box[1, 2]:] = True
|
2314
|
+
if box[0, 2] != 0:
|
2315
|
+
a[:, :, :box[0, 2]] = True
|
2316
|
+
|
2317
|
+
|
2318
|
+
def _set_packed_bisector_outside_box(a: np.ndarray, box: np.ndarray, box_array: np.ndarray, tail: int):
|
2319
|
+
# set values outside of the bounding box, working with packed arrays
|
2320
|
+
if box[1, 0] < a.shape[0] and np.any(box_array[-1, :, :]):
|
2321
|
+
a[box[1, 0]:, :, :] = 255
|
2322
|
+
if box[0, 0] != 0:
|
2323
|
+
a[:box[0, 0], :, :] = 255
|
2324
|
+
if box[1, 1] < a.shape[1] and np.any(box_array[:, -1, :]):
|
2325
|
+
a[:, box[1, 1]:, :] = 255
|
2326
|
+
if box[0, 1] != 0:
|
2327
|
+
a[:, :box[0, 1], :] = 255
|
2328
|
+
if box[1, 2] < a.shape[2] and np.any(np.bitwise_and(box_array[:, :, -1], 1)):
|
2329
|
+
a[:, :, box[1, 2]:] = 255
|
2330
|
+
if box[0, 2] != 0:
|
2331
|
+
a[:, :, :box[0, 2]] = 255
|
2332
|
+
if tail:
|
2333
|
+
m = np.uint8((255 << (8 - tail)) & 255)
|
2334
|
+
a[:, :, -1] &= m
|
2335
|
+
|
2336
|
+
|
2337
|
+
def _box_face_arrays_from_indices( # type: ignore
|
2338
|
+
k_faces_kji0: Union[np.ndarray, None], j_faces_kji0: Union[np.ndarray, None],
|
2339
|
+
i_faces_kji0: Union[np.ndarray, None], box: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
2340
|
+
box_shape = box[1, :] - box[0, :]
|
2341
|
+
k_a = np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.bool_)
|
2342
|
+
j_a = np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.bool_)
|
2343
|
+
i_a = np.zeros((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = np.bool_)
|
2344
|
+
ko = box[0, 0]
|
2345
|
+
jo = box[0, 1]
|
2346
|
+
io = box[0, 2]
|
2347
|
+
kr = box[1, 0] - ko
|
2348
|
+
jr = box[1, 1] - jo
|
2349
|
+
ir = box[1, 2] - io
|
2350
|
+
if k_faces_kji0 is not None:
|
2351
|
+
_set_face_array(k_a, k_faces_kji0, ko, jo, io, kr - 1, jr, ir)
|
2352
|
+
if j_faces_kji0 is not None:
|
2353
|
+
_set_face_array(j_a, j_faces_kji0, ko, jo, io, kr, jr - 1, ir)
|
2354
|
+
if i_faces_kji0 is not None:
|
2355
|
+
_set_face_array(i_a, i_faces_kji0, ko, jo, io, kr, jr, ir - 1)
|
2356
|
+
return k_a, j_a, i_a
|
2357
|
+
|
2358
|
+
|
2359
|
+
def _packed_box_face_arrays_from_indices( # type: ignore
|
2360
|
+
k_faces_kji0: Union[np.ndarray, None], j_faces_kji0: Union[np.ndarray, None],
|
2361
|
+
i_faces_kji0: Union[np.ndarray, None], box: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
2362
|
+
box_shape = box[1, :] - box[0, :] # note: I axis already shrunken
|
2363
|
+
k_a = np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.uint8)
|
2364
|
+
j_a = np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.uint8)
|
2365
|
+
i_a = np.zeros(tuple(box_shape), dtype = np.uint8)
|
2366
|
+
ko = box[0, 0]
|
2367
|
+
jo = box[0, 1]
|
2368
|
+
io = box[0, 2] * 8
|
2369
|
+
kr = box[1, 0] - ko
|
2370
|
+
jr = box[1, 1] - jo
|
2371
|
+
ir = box[1, 2] * 8 - io
|
2372
|
+
if k_faces_kji0 is not None:
|
2373
|
+
_set_packed_face_array(k_a, k_faces_kji0, ko, jo, io, kr - 1, jr, ir)
|
2374
|
+
if j_faces_kji0 is not None:
|
2375
|
+
_set_packed_face_array(j_a, j_faces_kji0, ko, jo, io, kr, jr - 1, ir)
|
2376
|
+
if i_faces_kji0 is not None:
|
2377
|
+
_set_packed_face_array(i_a, i_faces_kji0, ko, jo, io, kr, jr, ir)
|
2378
|
+
return k_a, j_a, i_a
|
1417
2379
|
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
2380
|
+
|
2381
|
+
@njit # pragma: no cover
|
2382
|
+
def _set_face_array(a: np.ndarray, indices: np.ndarray, ko: int, jo: int, io: int, kr: int, jr: int, ir: int) -> None:
|
2383
|
+
k: int = 0
|
2384
|
+
j: int = 0
|
2385
|
+
i: int = 0
|
2386
|
+
for ind in range(len(indices)):
|
2387
|
+
k = indices[ind, 0] - ko
|
2388
|
+
if k < 0 or k >= kr:
|
2389
|
+
continue
|
2390
|
+
j = indices[ind, 1] - jo
|
2391
|
+
if j < 0 or j >= jr:
|
2392
|
+
continue
|
2393
|
+
i = indices[ind, 2] - io
|
2394
|
+
if i < 0 or i >= ir:
|
2395
|
+
continue
|
2396
|
+
a[k, j, i] = True
|
2397
|
+
|
2398
|
+
|
2399
|
+
@njit # pragma: no cover
|
2400
|
+
def _set_packed_face_array(a: np.ndarray, indices: np.ndarray, ko: int, jo: int, io: int, kr: int, jr: int,
|
2401
|
+
ir: int) -> None:
|
2402
|
+
k: int = 0
|
2403
|
+
j: int = 0
|
2404
|
+
i: int = 0
|
2405
|
+
for ind in range(len(indices)):
|
2406
|
+
k = indices[ind, 0] - ko
|
2407
|
+
if k < 0 or k >= kr:
|
2408
|
+
continue
|
2409
|
+
j = indices[ind, 1] - jo
|
2410
|
+
if j < 0 or j >= jr:
|
2411
|
+
continue
|
2412
|
+
i = indices[ind, 2] - io
|
2413
|
+
if i < 0 or i >= ir:
|
2414
|
+
continue
|
2415
|
+
ii, ib = divmod(i, 8)
|
2416
|
+
a[k, j, ii] |= (1 << (7 - ib))
|
2417
|
+
|
2418
|
+
|
2419
|
+
# yapf: disable
|
2420
|
+
def get_boundary_from_indices( # type: ignore
|
2421
|
+
k_faces_kji0: Union[np.ndarray, None],
|
2422
|
+
j_faces_kji0: Union[np.ndarray, None],
|
2423
|
+
i_faces_kji0: Union[np.ndarray, None],
|
2424
|
+
grid_extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
2425
|
+
# yapf: enable
|
2426
|
+
"""Return python protocol box containing indices"""
|
2427
|
+
k_min_kji0 = None if ((k_faces_kji0 is None) or (k_faces_kji0.size == 0)) else np.min(k_faces_kji0, axis = 0)
|
2428
|
+
k_max_kji0 = None if ((k_faces_kji0 is None) or (k_faces_kji0.size == 0)) else np.max(k_faces_kji0, axis = 0)
|
2429
|
+
j_min_kji0 = None if ((j_faces_kji0 is None) or (j_faces_kji0.size == 0)) else np.min(j_faces_kji0, axis = 0)
|
2430
|
+
j_max_kji0 = None if ((j_faces_kji0 is None) or (j_faces_kji0.size == 0)) else np.max(j_faces_kji0, axis = 0)
|
2431
|
+
i_min_kji0 = None if ((i_faces_kji0 is None) or (i_faces_kji0.size == 0)) else np.min(i_faces_kji0, axis = 0)
|
2432
|
+
i_max_kji0 = None if ((i_faces_kji0 is None) or (i_faces_kji0.size == 0)) else np.max(i_faces_kji0, axis = 0)
|
2433
|
+
box = np.empty((2, 3), dtype = np.int32)
|
2434
|
+
box[0, :] = grid_extent_kji
|
2435
|
+
box[1, :] = -1
|
2436
|
+
if k_min_kji0 is not None:
|
2437
|
+
box[0, 0] = k_min_kji0[0]
|
2438
|
+
box[0, 1] = k_min_kji0[1]
|
2439
|
+
box[0, 2] = k_min_kji0[2]
|
2440
|
+
box[1, 0] = k_max_kji0[0] # type: ignore
|
2441
|
+
box[1, 1] = k_max_kji0[1] # type: ignore
|
2442
|
+
box[1, 2] = k_max_kji0[2] # type: ignore
|
2443
|
+
if j_min_kji0 is not None:
|
2444
|
+
box[0, 0] = min(box[0, 0], j_min_kji0[0])
|
2445
|
+
box[0, 1] = min(box[0, 1], j_min_kji0[1])
|
2446
|
+
box[0, 2] = min(box[0, 2], j_min_kji0[2])
|
2447
|
+
box[1, 0] = max(box[1, 0], j_max_kji0[0]) # type: ignore
|
2448
|
+
box[1, 1] = max(box[1, 1], j_max_kji0[1]) # type: ignore
|
2449
|
+
box[1, 2] = max(box[1, 2], j_max_kji0[2]) # type: ignore
|
2450
|
+
if i_min_kji0 is not None:
|
2451
|
+
box[0, 0] = min(box[0, 0], i_min_kji0[0])
|
2452
|
+
box[0, 1] = min(box[0, 1], i_min_kji0[1])
|
2453
|
+
box[0, 2] = min(box[0, 2], i_min_kji0[2])
|
2454
|
+
box[1, 0] = max(box[1, 0], i_max_kji0[0]) # type: ignore
|
2455
|
+
box[1, 1] = max(box[1, 1], i_max_kji0[1]) # type: ignore
|
2456
|
+
box[1, 2] = max(box[1, 2], i_max_kji0[2]) # type: ignore
|
2457
|
+
assert np.all(box[1] >= box[0]), 'attempt to find bounding box when all faces None'
|
2458
|
+
# include buffer layer where box does not reach edge of grid
|
2459
|
+
box[1, :] += 1 # switch to python protocol
|
2460
|
+
return expanded_box(box, grid_extent_kji)
|
2461
|
+
|
2462
|
+
|
2463
|
+
def expanded_box(box: np.ndarray, extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
2464
|
+
"""Return a python protocol box expanded by a single slice on all six faces, where extent alloas."""
|
2465
|
+
# include buffer layer where box does not reach edge of grid
|
2466
|
+
np_extent_kji = np.array(extent_kji, dtype = np.int32)
|
2467
|
+
e_box = np.zeros((2, 3), dtype = np.int32)
|
2468
|
+
e_box[0, :] = np.maximum(box[0, :] - 1, 0)
|
2469
|
+
e_box[1, :] = np.minimum(box[1, :] + 1, extent_kji)
|
2470
|
+
assert np.all(e_box[0] >= 0)
|
2471
|
+
assert np.all(e_box[1] > e_box[0])
|
2472
|
+
assert np.all(e_box[1] <= np_extent_kji)
|
2473
|
+
return e_box
|
2474
|
+
|
2475
|
+
|
2476
|
+
def get_packed_boundary_from_indices( # type: ignore
|
2477
|
+
k_faces_kji0: Union[np.ndarray, None], j_faces_kji0: Union[np.ndarray, None],
|
2478
|
+
i_faces_kji0: Union[np.ndarray, None], grid_extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
2479
|
+
"""Return python protocol box containing indices, with I axis packed"""
|
2480
|
+
box = get_boundary_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, grid_extent_kji)
|
2481
|
+
return shrunk_box_for_packing(box)
|
2482
|
+
|
2483
|
+
|
2484
|
+
def shrunk_box_for_packing(box: np.ndarray) -> np.ndarray:
|
2485
|
+
"""Return box with I dimension shrunk for bit packing equivalent."""
|
2486
|
+
shrunk_box = box.copy()
|
2487
|
+
shrunk_box[0, 2] /= 8
|
2488
|
+
shrunk_box[1, 2] = ((box[1, 2] - 1) // 8) + 1
|
2489
|
+
return shrunk_box
|
2490
|
+
|
2491
|
+
|
2492
|
+
def _shape_packed(unpacked_shape):
|
2493
|
+
"""Return the equivalent packed shape for a given unpacked shape, as a tuple."""
|
2494
|
+
shrunken = ((unpacked_shape[-1] - 1) // 8) + 1
|
2495
|
+
if len(unpacked_shape) == 1:
|
2496
|
+
return (shrunken,)
|
2497
|
+
head = list(unpacked_shape[:-1])
|
2498
|
+
head.append(shrunken)
|
2499
|
+
return tuple(head)
|
2500
|
+
|
2501
|
+
|
2502
|
+
@njit # pragma: no cover
|
2503
|
+
def _bitwise_count_njit(a: np.ndarray) -> np.int64:
|
2504
|
+
"""Deprecated: only needed till numpy versions < 2.0.0 are dropped."""
|
2505
|
+
c: np.int64 = 0 # type: ignore
|
2506
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x01))
|
2507
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x02))
|
2508
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x04))
|
2509
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x08))
|
2510
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x10))
|
2511
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x20))
|
2512
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x40))
|
2513
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x80))
|
2514
|
+
return c
|
2515
|
+
|
2516
|
+
|
2517
|
+
@njit # pragma: no cover
|
2518
|
+
def box_intersection(box_a: np.ndarray, box_b: np.ndarray) -> np.ndarray:
|
2519
|
+
"""Return a box which is the intersection of two boxes, python protocol; all zeros if no intersection."""
|
2520
|
+
box = np.zeros((2, 3), dtype = np.int32)
|
2521
|
+
box[0] = np.maximum(box_a[0], box_b[0])
|
2522
|
+
box[1] = np.minimum(box_a[1], box_b[1])
|
2523
|
+
if np.any(box[1] <= box[0]):
|
2524
|
+
box[:] = 0
|
2525
|
+
return box
|
2526
|
+
|
2527
|
+
|
2528
|
+
@njit # pragma: no cover
|
2529
|
+
def get_box(mask: np.ndarray) -> Tuple[np.ndarray, int]:
|
2530
|
+
"""Returns a python protocol box enclosing True elements of 3D boolean mask, and count which is zero if all False."""
|
2531
|
+
box = np.full((2, 3), -1, dtype = np.int32)
|
2532
|
+
count = 0
|
2533
|
+
for k in range(mask.shape[0]):
|
2534
|
+
for j in range(mask.shape[1]):
|
2535
|
+
for i in range(mask.shape[2]):
|
2536
|
+
if mask[k, j, i]:
|
2537
|
+
if count == 0:
|
2538
|
+
box[0, 0] = k
|
2539
|
+
box[0, 1] = j
|
2540
|
+
box[0, 2] = i
|
2541
|
+
box[1, 0] = k + 1
|
2542
|
+
box[1, 1] = j + 1
|
2543
|
+
box[1, 2] = i + 1
|
2544
|
+
else:
|
2545
|
+
if k < box[0, 0]:
|
2546
|
+
box[0, 0] = k
|
2547
|
+
elif k >= box[1, 0]:
|
2548
|
+
box[1, 0] = k + 1
|
2549
|
+
if j < box[0, 1]:
|
2550
|
+
box[0, 1] = j
|
2551
|
+
elif j >= box[1, 1]:
|
2552
|
+
box[1, 1] = j + 1
|
2553
|
+
if i < box[0, 2]:
|
2554
|
+
box[0, 2] = i
|
2555
|
+
elif i >= box[1, 2]:
|
2556
|
+
box[1, 2] = i + 1
|
2557
|
+
count += 1
|
2558
|
+
return box, count
|
2559
|
+
|
2560
|
+
|
2561
|
+
@njit # pragma: no cover
|
2562
|
+
def filter_faces(faces_kji0: np.ndarray, face_patches: np.ndarray, cell_patches: np.ndarray, axis: int) -> np.ndarray:
|
2563
|
+
"""Return 1D boolean selection array indicating subset of faces that are applicable to cells with matching patch."""
|
2564
|
+
n: int = len(faces_kji0)
|
2565
|
+
assert len(face_patches) == n
|
2566
|
+
selection = np.zeros(n, dtype = np.bool_)
|
2567
|
+
for f in range(n):
|
2568
|
+
k: int = faces_kji0[f, 0]
|
2569
|
+
j: int = faces_kji0[f, 1]
|
2570
|
+
i: int = faces_kji0[f, 2]
|
2571
|
+
if face_patches[f] == cell_patches[k, j, i]:
|
2572
|
+
selection[f] = True
|
2573
|
+
else:
|
2574
|
+
if axis == 0:
|
2575
|
+
if face_patches[f] == cell_patches[k + 1, j, i]:
|
2576
|
+
selection[f] = True
|
2577
|
+
elif axis == 1:
|
2578
|
+
if face_patches[f] == cell_patches[k, j + 1, i]:
|
2579
|
+
selection[f] = True
|
2580
|
+
else:
|
2581
|
+
if face_patches[f] == cell_patches[k, j, i + 1]:
|
2582
|
+
selection[f] = True
|
2583
|
+
return selection
|