resqpy 4.14.2__py3-none-any.whl → 5.1.5__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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 +2 -1
- 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 +1349 -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.5.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.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,497 @@ 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
|
+
log.debug("converting face sets into grid connection set")
|
1171
|
+
# NB: kji0 arrays in internal face protocol: used as cell_kji0 with polarity of 1
|
1172
|
+
# property lists have elements replaced with sorted and filtered equivalents
|
1173
|
+
gcs = rqf.GridConnectionSet.from_faces_indices(grid = grid,
|
1174
|
+
k_faces_kji0 = k_faces_kji0,
|
1175
|
+
j_faces_kji0 = j_faces_kji0,
|
1176
|
+
i_faces_kji0 = i_faces_kji0,
|
1177
|
+
remove_duplicates = not patchwork,
|
1178
|
+
k_properties = k_props,
|
1179
|
+
j_properties = j_props,
|
1180
|
+
i_properties = i_props,
|
1181
|
+
feature_name = name,
|
1182
|
+
feature_type = feature_type,
|
1183
|
+
create_organizing_objects_where_needed = True,
|
1184
|
+
title = title)
|
1185
|
+
# log.debug('finished coversion to gcs')
|
1186
|
+
|
1187
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1188
|
+
all_tris = None
|
1189
|
+
if return_triangles:
|
1190
|
+
# log.debug('preparing triangles array')
|
1191
|
+
k_triangles = np.empty((0,), dtype = np.int32) if k_props is None else k_props.pop(0)
|
1192
|
+
j_triangles = np.empty((0,), dtype = np.int32) if j_props is None else j_props.pop(0)
|
1193
|
+
i_triangles = np.empty((0,), dtype = np.int32) if i_props is None else i_props.pop(0)
|
1194
|
+
all_tris = np.concatenate((k_triangles, j_triangles, i_triangles), axis = 0)
|
1195
|
+
# log.debug(f'gcs count: {gcs.count}; all triangles shape: {all_tris.shape}')
|
1196
|
+
assert all_tris.shape == (gcs.count,)
|
1197
|
+
|
1198
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1199
|
+
all_depths = None
|
1200
|
+
if return_depths:
|
1201
|
+
# log.debug('preparing depths array')
|
1202
|
+
k_depths = np.empty((0,), dtype = np.float64) if k_props is None else k_props.pop(0)
|
1203
|
+
j_depths = np.empty((0,), dtype = np.float64) if j_props is None else j_props.pop(0)
|
1204
|
+
i_depths = np.empty((0,), dtype = np.float64) if i_props is None else i_props.pop(0)
|
1205
|
+
all_depths = np.concatenate((k_depths, j_depths, i_depths), axis = 0)
|
1206
|
+
# log.debug(f'gcs count: {gcs.count}; all depths shape: {all_depths.shape}')
|
1207
|
+
assert all_depths.shape == (gcs.count,)
|
1208
|
+
|
1209
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1210
|
+
all_offsets = None
|
1211
|
+
if return_offsets:
|
1212
|
+
# log.debug('preparing offsets array')
|
1213
|
+
k_offsets = np.empty((0,), dtype = np.float64) if k_props is None else k_props[0]
|
1214
|
+
j_offsets = np.empty((0,), dtype = np.float64) if j_props is None else j_props[0]
|
1215
|
+
i_offsets = np.empty((0,), dtype = np.float64) if i_props is None else i_props[0]
|
1216
|
+
all_offsets = _all_offsets(grid.crs, k_offsets, j_offsets, i_offsets)
|
1217
|
+
# log.debug(f'gcs count: {gcs.count}; all offsets shape: {all_offsets.shape}')
|
1218
|
+
assert all_offsets.shape == (gcs.count,)
|
1219
|
+
|
1220
|
+
all_flange = None
|
1221
|
+
if return_flange_bool:
|
1222
|
+
# log.debug('preparing flange array')
|
1223
|
+
flange_bool_uuid = surface.model.uuid(title = "flange bool",
|
1224
|
+
obj_type = "DiscreteProperty",
|
1225
|
+
related_uuid = surface.uuid)
|
1226
|
+
assert (flange_bool_uuid is not None), f"No flange bool property found for surface: {surface.title}"
|
1227
|
+
flange_bool = rqp.Property(surface.model, uuid = flange_bool_uuid)
|
1228
|
+
flange_array = flange_bool.array_ref(dtype = bool)
|
1229
|
+
all_flange = np.take(flange_array, all_tris)
|
1230
|
+
assert all_flange.shape == (gcs.count,)
|
1231
|
+
|
1232
|
+
# note: following is a grid cells property, not a gcs property
|
1233
|
+
bisector = None
|
1234
|
+
if return_bisector:
|
1235
|
+
if is_curtain and not patchwork:
|
1236
|
+
log.debug(f'preparing columns bisector for: {surface.title}')
|
1237
|
+
if j_faces_kji0 is None:
|
1238
|
+
j_faces_ji0 = np.empty((0, 2), dtype = np.int32)
|
1239
|
+
else:
|
1240
|
+
j_faces_ji0 = j_faces_kji0[:, 1:]
|
1241
|
+
if i_faces_kji0 is None:
|
1242
|
+
i_faces_ji0 = np.empty((0, 2), dtype = np.int32)
|
1243
|
+
else:
|
1244
|
+
i_faces_ji0 = i_faces_kji0[:, 1:]
|
1245
|
+
bisector = column_bisector_from_face_indices((grid.nj, grid.ni), j_faces_ji0, i_faces_ji0)
|
1246
|
+
# log.debug('finished preparing columns bisector')
|
1247
|
+
elif patchwork:
|
1248
|
+
n_patches = surface.number_of_patches()
|
1249
|
+
log.info(f'preparing composite cells bisector for surface: {surface.title}; number of patches: {n_patches}')
|
1250
|
+
nkf = 0 if k_faces_kji0 is None else len(k_faces_kji0)
|
1251
|
+
njf = 0 if j_faces_kji0 is None else len(j_faces_kji0)
|
1252
|
+
nif = 0 if i_faces_kji0 is None else len(i_faces_kji0)
|
1253
|
+
# fetch patch indices for triangle hits
|
1254
|
+
assert all_tris is not None and len(all_tris) == nkf + njf + nif
|
1255
|
+
patch_indices_k = surface.patch_indices_for_triangle_indices(all_tris[:nkf])
|
1256
|
+
patch_indices_j = surface.patch_indices_for_triangle_indices(all_tris[nkf:nkf + njf])
|
1257
|
+
patch_indices_i = surface.patch_indices_for_triangle_indices(all_tris[nkf + njf:])
|
1258
|
+
# add extra dimension to bisector array (at axis 0) for patches
|
1259
|
+
pb_shape = tuple([n_patches] + list(grid.extent_kji))
|
1260
|
+
if packed_bisectors:
|
1261
|
+
bisector = np.invert(np.zeros(_shape_packed(grid.extent_kji), dtype = np.uint8), dtype = np.uint8)
|
1262
|
+
else:
|
1263
|
+
bisector = np.ones(tuple(grid.extent_kji), dtype = np.bool_)
|
1264
|
+
# populate composite bisector
|
1265
|
+
for patch in range(n_patches):
|
1266
|
+
log.debug(f'processing patch {patch} of surface: {surface.title}')
|
1267
|
+
mask = (patch_indices == patch)
|
1268
|
+
mask_count = np.count_nonzero(mask)
|
1269
|
+
if mask_count == 0:
|
1270
|
+
log.warning(f'patch {patch} of surface {surface.title} is not applicable to any cells in grid')
|
1271
|
+
continue
|
1272
|
+
patch_box, box_count = get_box(mask)
|
1273
|
+
assert box_count == mask_count
|
1274
|
+
assert np.all(patch_box[1] > patch_box[0])
|
1275
|
+
patch_box = expanded_box(patch_box, tuple(grid.extent_kji))
|
1276
|
+
patch_box[0, 0] = 0
|
1277
|
+
patch_box[1, 0] = grid.extent_kji[0]
|
1278
|
+
packed_box = shrunk_box_for_packing(patch_box)
|
1279
|
+
patch_k_faces_kji0 = None
|
1280
|
+
if k_faces_kji0 is not None:
|
1281
|
+
patch_k_faces_kji0 = k_faces_kji0[(patch_indices_k == patch).astype(bool)]
|
1282
|
+
patch_j_faces_kji0 = None
|
1283
|
+
if j_faces_kji0 is not None:
|
1284
|
+
patch_j_faces_kji0 = j_faces_kji0[(patch_indices_j == patch).astype(bool)]
|
1285
|
+
patch_i_faces_kji0 = None
|
1286
|
+
if i_faces_kji0 is not None:
|
1287
|
+
patch_i_faces_kji0 = i_faces_kji0[(patch_indices_i == patch).astype(bool)]
|
1288
|
+
if packed_bisectors:
|
1289
|
+
mask = np.packbits(mask, axis = -1)
|
1290
|
+
patch_bisector, is_curtain = \
|
1291
|
+
packed_bisector_from_face_indices(tuple(grid.extent_kji),
|
1292
|
+
patch_k_faces_kji0,
|
1293
|
+
patch_j_faces_kji0,
|
1294
|
+
patch_i_faces_kji0,
|
1295
|
+
raw_bisector,
|
1296
|
+
patch_box)
|
1297
|
+
# bisector[:] = np.bitwise_or(np.bitwise_and(mask, patch_bisector),
|
1298
|
+
# np.bitwise_and(np.invert(mask, dtype = np.uint8), bisector))
|
1299
|
+
_set_packed_where_mask(bisector, mask, patch_bisector, packed_box)
|
1300
|
+
else:
|
1301
|
+
patch_bisector, is_curtain = \
|
1302
|
+
bisector_from_face_indices(tuple(grid.extent_kji),
|
1303
|
+
patch_k_faces_kji0,
|
1304
|
+
patch_j_faces_kji0,
|
1305
|
+
patch_i_faces_kji0,
|
1306
|
+
raw_bisector,
|
1307
|
+
patch_box)
|
1308
|
+
bisector[mask] = patch_bisector[mask]
|
1309
|
+
if is_curtain:
|
1310
|
+
# TODO: downgrade following to debug once downstream functionality tested
|
1311
|
+
log.warning(f'ignoring curtain nature of bisector for patch {patch} of surface: {surface.title}')
|
1312
|
+
is_curtain = False
|
1313
|
+
else:
|
1314
|
+
log.info(f'preparing singlular cells bisector for surface: {surface.title}') # could downgrade to debug
|
1315
|
+
if ((k_faces_kji0 is None or len(k_faces_kji0) == 0) and
|
1316
|
+
(j_faces_kji0 is None or len(j_faces_kji0) == 0) and (i_faces_kji0 is None or len(i_faces_kji0) == 0)):
|
1317
|
+
bisector = np.ones((grid.nj, grid.ni), dtype = bool)
|
1318
|
+
is_curtain = True
|
1319
|
+
elif packed_bisectors:
|
1320
|
+
bisector, is_curtain = packed_bisector_from_face_indices(tuple(grid.extent_kji), k_faces_kji0,
|
1321
|
+
j_faces_kji0, i_faces_kji0, raw_bisector, None)
|
1322
|
+
if is_curtain:
|
1323
|
+
bisector = np.unpackbits(bisector[0], axis = -1,
|
1324
|
+
count = grid.ni).astype(bool) # reduce to a columns property
|
1325
|
+
else:
|
1326
|
+
bisector, is_curtain = bisector_from_face_indices(tuple(grid.extent_kji), k_faces_kji0, j_faces_kji0,
|
1327
|
+
i_faces_kji0, raw_bisector, None)
|
1328
|
+
if is_curtain:
|
1329
|
+
bisector = bisector[0] # reduce to a columns property
|
1330
|
+
|
1331
|
+
# note: following is a grid cells property, not a gcs property
|
1332
|
+
shadow = None
|
1333
|
+
if return_shadow:
|
1334
|
+
log.debug("preparing cells shadow")
|
1335
|
+
shadow = shadow_from_face_indices(tuple(grid.extent_kji), k_faces_kji0)
|
1336
|
+
|
1337
|
+
if progress_fn is not None:
|
1338
|
+
progress_fn(1.0)
|
1339
|
+
|
1340
|
+
log.debug(f"finishing find_faces_to_represent_surface_regular_optimised for {name}")
|
1341
|
+
|
1342
|
+
# if returning properties, construct dictionary
|
1343
|
+
if return_properties:
|
1344
|
+
props_dict = {}
|
1345
|
+
if 'triangle' in return_properties:
|
1346
|
+
props_dict["triangle"] = all_tris
|
1347
|
+
if return_depths:
|
1348
|
+
props_dict["depth"] = all_depths
|
1349
|
+
if return_offsets:
|
1350
|
+
props_dict["offset"] = all_offsets
|
1351
|
+
if return_bisector:
|
1352
|
+
props_dict["grid bisector"] = (bisector, is_curtain)
|
1353
|
+
if return_shadow:
|
1354
|
+
props_dict["grid shadow"] = shadow
|
1355
|
+
if return_flange_bool:
|
1356
|
+
props_dict["flange bool"] = all_flange
|
1357
|
+
return (gcs, props_dict)
|
1358
|
+
|
1359
|
+
return gcs
|
1360
|
+
|
1361
|
+
|
861
1362
|
def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_type = "fault", progress_fn = None):
|
862
1363
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
863
1364
|
|
@@ -885,19 +1386,7 @@ def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_
|
|
885
1386
|
mode = "regular_optimised"
|
886
1387
|
else:
|
887
1388
|
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":
|
1389
|
+
if mode == "regular_optimised":
|
901
1390
|
return find_faces_to_represent_surface_regular_optimised(grid,
|
902
1391
|
surface,
|
903
1392
|
name,
|
@@ -911,161 +1400,331 @@ def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_
|
|
911
1400
|
name,
|
912
1401
|
feature_type = feature_type,
|
913
1402
|
progress_fn = progress_fn)
|
1403
|
+
elif mode == "staffa":
|
1404
|
+
return find_faces_to_represent_surface_staffa(grid,
|
1405
|
+
surface,
|
1406
|
+
name,
|
1407
|
+
feature_type = feature_type,
|
1408
|
+
progress_fn = progress_fn)
|
1409
|
+
elif mode == "regular_dense":
|
1410
|
+
return find_faces_to_represent_surface_regular_dense_optimised(grid,
|
1411
|
+
surface,
|
1412
|
+
name,
|
1413
|
+
feature_type = feature_type,
|
1414
|
+
progress_fn = progress_fn)
|
1415
|
+
elif mode == "regular":
|
1416
|
+
return find_faces_to_represent_surface_regular(grid,
|
1417
|
+
surface,
|
1418
|
+
name,
|
1419
|
+
feature_type = feature_type,
|
1420
|
+
progress_fn = progress_fn)
|
914
1421
|
log.critical("unrecognised mode: " + str(mode))
|
915
1422
|
return None
|
916
1423
|
|
917
1424
|
|
918
1425
|
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]:
|
1426
|
+
grid_extent_kji: Tuple[int, int, int], k_faces: Union[np.ndarray, None], j_faces: Union[np.ndarray, None],
|
1427
|
+
i_faces: Union[np.ndarray, None], raw_bisector: bool) -> Tuple[np.ndarray, bool]:
|
925
1428
|
"""Creates a boolean array denoting the bisection of the grid by the face sets.
|
926
1429
|
|
927
1430
|
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
|
1431
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1432
|
+
- k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1433
|
+
- j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1434
|
+
- i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1435
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
932
1436
|
|
933
1437
|
returns:
|
934
1438
|
Tuple containing:
|
1439
|
+
- array (np.ndarray): boolean bisectors array where values are True for cells on the side
|
1440
|
+
of the surface that has a lower mean k index on average and False for cells on the other side.
|
1441
|
+
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False.
|
1442
|
+
|
1443
|
+
notes:
|
1444
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1445
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1446
|
+
assigned to either the True or False part
|
1447
|
+
- this function is DEPRECATED, use newer indices based approach instead: bisector_from_face_indices()
|
1448
|
+
"""
|
1449
|
+
warnings.warn('DEPRECATED: grid_surface.bisector_from_faces() function; use bisector_from_face_indices() instead')
|
1450
|
+
assert len(grid_extent_kji) == 3
|
935
1451
|
|
1452
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid)
|
1453
|
+
box = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
1454
|
+
box_shape = box[1, :] - box[0, :]
|
1455
|
+
|
1456
|
+
# set up the bisector array for the bounding box
|
1457
|
+
box_array = np.zeros(box_shape, dtype = np.bool_)
|
1458
|
+
|
1459
|
+
# seed the bisector box array at (0, 0, 0)
|
1460
|
+
box_array[0, 0, 0] = True
|
1461
|
+
|
1462
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1463
|
+
if k_faces is None:
|
1464
|
+
open_k = np.ones((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = bool)
|
1465
|
+
else:
|
1466
|
+
k_faces = k_faces[box[0, 0]:box[1, 0] - 1, box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]]
|
1467
|
+
open_k = np.logical_not(k_faces)
|
1468
|
+
if j_faces is None:
|
1469
|
+
open_j = np.ones((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = bool)
|
1470
|
+
else:
|
1471
|
+
j_faces = j_faces[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1] - 1, box[0, 2]:box[1, 2]]
|
1472
|
+
open_j = np.logical_not(j_faces)
|
1473
|
+
if i_faces is None:
|
1474
|
+
open_i = np.ones((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = bool)
|
1475
|
+
else:
|
1476
|
+
i_faces = i_faces[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2] - 1]
|
1477
|
+
open_i = np.logical_not(i_faces)
|
1478
|
+
|
1479
|
+
# populate bisector array for box
|
1480
|
+
_fill_bisector(box_array, open_k, open_j, open_i)
|
1481
|
+
|
1482
|
+
# set up the full bisectors array and assigning the bounding box values
|
1483
|
+
array = np.zeros(grid_extent_kji, dtype = np.bool_)
|
1484
|
+
array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1485
|
+
|
1486
|
+
# set bisector values outside of the bounding box
|
1487
|
+
_set_bisector_outside_box(array, box, box_array)
|
1488
|
+
|
1489
|
+
# check all array elements are not the same
|
1490
|
+
true_count = np.count_nonzero(array)
|
1491
|
+
cell_count = array.size
|
1492
|
+
if 0 < true_count < cell_count:
|
1493
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1494
|
+
is_curtain = _shallow_or_curtain(array, true_count, raw_bisector)
|
1495
|
+
else:
|
1496
|
+
assert raw_bisector, 'face set for surface is leaky or empty (surface does not intersect grid)'
|
1497
|
+
log.error('face set for surface is leaky or empty (surface does not intersect grid)')
|
1498
|
+
is_curtain = False
|
1499
|
+
|
1500
|
+
return array, is_curtain
|
1501
|
+
|
1502
|
+
|
1503
|
+
# yapf: disable
|
1504
|
+
def bisector_from_face_indices( # type: ignore
|
1505
|
+
grid_extent_kji: Tuple[int, int, int],
|
1506
|
+
k_faces_kji0: Union[np.ndarray, None],
|
1507
|
+
j_faces_kji0: Union[np.ndarray, None],
|
1508
|
+
i_faces_kji0: Union[np.ndarray, None],
|
1509
|
+
raw_bisector: bool,
|
1510
|
+
p_box: Union[np.ndarray, None]) -> Tuple[np.ndarray, bool]:
|
1511
|
+
# yapf: enable
|
1512
|
+
"""Creates a boolean array denoting the bisection of the grid by the face sets.
|
1513
|
+
|
1514
|
+
arguments:
|
1515
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1516
|
+
- k_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the k dimension
|
1517
|
+
- j_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the j dimension
|
1518
|
+
- i_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the i dimension
|
1519
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
1520
|
+
- p_box (np.ndarray): a python protocol box to limit the bisector evaluation over
|
1521
|
+
|
1522
|
+
returns:
|
1523
|
+
Tuple containing:
|
936
1524
|
- array (np.ndarray): boolean bisectors array where values are True for cells on the side
|
937
|
-
|
1525
|
+
of the surface that has a lower mean k index on average and False for cells on the other side.
|
938
1526
|
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False.
|
939
1527
|
|
940
1528
|
notes:
|
941
|
-
|
942
|
-
|
943
|
-
|
1529
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1530
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1531
|
+
assigned to either the True or False part
|
944
1532
|
"""
|
945
1533
|
assert len(grid_extent_kji) == 3
|
946
1534
|
|
947
|
-
#
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
)
|
1535
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid)
|
1536
|
+
face_box = get_boundary_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, grid_extent_kji)
|
1537
|
+
box = np.empty((2, 3), dtype = np.int32)
|
1538
|
+
if p_box is None:
|
1539
|
+
box[:] = face_box
|
1540
|
+
else:
|
1541
|
+
box[:] = box_intersection(p_box, face_box)
|
1542
|
+
if np.all(box == 0):
|
1543
|
+
box[:] = face_box
|
1544
|
+
# set k_faces as bool arrays covering box
|
1545
|
+
k_faces, j_faces, i_faces = _box_face_arrays_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, box)
|
959
1546
|
|
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))
|
1547
|
+
box_shape = box[1, :] - box[0, :]
|
1007
1548
|
|
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))
|
1549
|
+
# set up the bisector array for the bounding box
|
1550
|
+
box_array = np.zeros(box_shape, dtype = np.bool_)
|
1013
1551
|
|
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))
|
1552
|
+
# seed the bisector box array at (0, 0, 0)
|
1553
|
+
box_array[0, 0, 0] = True
|
1019
1554
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1555
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1556
|
+
if k_faces is None:
|
1557
|
+
open_k = np.ones((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = bool)
|
1558
|
+
else:
|
1559
|
+
open_k = np.logical_not(k_faces)
|
1560
|
+
if j_faces is None:
|
1561
|
+
open_j = np.ones((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = bool)
|
1562
|
+
else:
|
1563
|
+
open_j = np.logical_not(j_faces)
|
1564
|
+
if i_faces is None:
|
1565
|
+
open_i = np.ones((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = bool)
|
1566
|
+
else:
|
1567
|
+
open_i = np.logical_not(i_faces)
|
1024
1568
|
|
1025
|
-
#
|
1569
|
+
# populate bisector array for box
|
1570
|
+
_fill_bisector(box_array, open_k, open_j, open_i)
|
1571
|
+
|
1572
|
+
# set up the full bisectors array and assigning the bounding box values
|
1026
1573
|
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.
|
1574
|
+
array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1575
|
+
|
1576
|
+
# set bisector values outside of the bounding box
|
1577
|
+
_set_bisector_outside_box(array, box, box_array)
|
1578
|
+
|
1579
|
+
# check all array elements are not the same
|
1045
1580
|
true_count = np.count_nonzero(array)
|
1046
1581
|
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
|
1582
|
+
if 0 < true_count < cell_count:
|
1583
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1584
|
+
is_curtain = _shallow_or_curtain(array, true_count, raw_bisector)
|
1585
|
+
else:
|
1586
|
+
assert raw_bisector, 'face set for surface is leaky or empty (surface does not intersect grid)'
|
1587
|
+
log.error('face set for surface is leaky or empty (surface does not intersect grid)')
|
1588
|
+
is_curtain = False
|
1065
1589
|
|
1066
1590
|
return array, is_curtain
|
1067
1591
|
|
1068
1592
|
|
1593
|
+
# yapf: disable
|
1594
|
+
def packed_bisector_from_face_indices( # type: ignore
|
1595
|
+
grid_extent_kji: Tuple[int, int, int],
|
1596
|
+
k_faces_kji0: Union[np.ndarray, None],
|
1597
|
+
j_faces_kji0: Union[np.ndarray, None],
|
1598
|
+
i_faces_kji0: Union[np.ndarray, None],
|
1599
|
+
raw_bisector: bool,
|
1600
|
+
p_box: Union[np.ndarray, None]) -> Tuple[np.ndarray, bool]:
|
1601
|
+
# yapf: enable
|
1602
|
+
"""Creates a uint8 (packed bool) array denoting the bisection of the grid by the face sets.
|
1603
|
+
|
1604
|
+
arguments:
|
1605
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1606
|
+
- k_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the k dimension
|
1607
|
+
- j_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the j dimension
|
1608
|
+
- i_faces_kji0 (np.ndarray): an int array of indices of which faces represent the surface in the i dimension
|
1609
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
1610
|
+
- p_box (np.ndarray): a python protocol unshrunken box to limit the bisector evaluation over
|
1611
|
+
|
1612
|
+
returns:
|
1613
|
+
Tuple containing:
|
1614
|
+
- array (np.uint8 array): packed boolean bisector array where values are 1 for cells on the side
|
1615
|
+
of the surface that has a lower mean k index on average and 0 for cells on the other side
|
1616
|
+
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False
|
1617
|
+
|
1618
|
+
notes:
|
1619
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1620
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1621
|
+
assigned to either the True or False part
|
1622
|
+
- the returned array is packed in the I axis; use np.unpackbits() to unpack
|
1623
|
+
"""
|
1624
|
+
assert len(grid_extent_kji) == 3
|
1625
|
+
|
1626
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid), and shrink the I axis
|
1627
|
+
face_box = get_packed_boundary_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, grid_extent_kji)
|
1628
|
+
box = np.empty((2, 3), dtype = np.int32)
|
1629
|
+
if p_box is None:
|
1630
|
+
box[:] = face_box
|
1631
|
+
else:
|
1632
|
+
box[:] = box_intersection(shrunk_box_for_packing(p_box), face_box)
|
1633
|
+
if np.all(box == 0):
|
1634
|
+
box[:] = face_box
|
1635
|
+
|
1636
|
+
# set k_faces, j_faces & i_faces as uint8 packed bool arrays covering box
|
1637
|
+
k_faces, j_faces, i_faces = _packed_box_face_arrays_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, box)
|
1638
|
+
|
1639
|
+
box_shape = box[1, :] - box[0, :]
|
1640
|
+
|
1641
|
+
# set up the bisector array for the bounding box
|
1642
|
+
box_array = np.zeros(box_shape, dtype = np.uint8)
|
1643
|
+
|
1644
|
+
# seed the bisector box array at (0, 0, 0)
|
1645
|
+
box_array[0, 0, 0] = 0x80 # first bit only set
|
1646
|
+
|
1647
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1648
|
+
if k_faces is None:
|
1649
|
+
open_k = np.invert(np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.uint8), dtype = np.uint8)
|
1650
|
+
else:
|
1651
|
+
open_k = np.invert(k_faces, dtype = np.uint8)
|
1652
|
+
if j_faces is None:
|
1653
|
+
open_j = np.invert(np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.uint8), dtype = np.uint8)
|
1654
|
+
else:
|
1655
|
+
open_j = np.invert(j_faces, dtype = np.uint8)
|
1656
|
+
if i_faces is None:
|
1657
|
+
open_i = np.invert(np.zeros(tuple(box_shape), dtype = np.uint8), dtype = np.uint8)
|
1658
|
+
else:
|
1659
|
+
open_i = np.invert(i_faces, dtype = np.uint8)
|
1660
|
+
|
1661
|
+
# close off faces in padding bits, if within box
|
1662
|
+
if box[1, 2] * 8 > grid_extent_kji[2]:
|
1663
|
+
tail = grid_extent_kji[2] % 8 # number of valid bits in padded byte
|
1664
|
+
assert tail
|
1665
|
+
m = np.uint8((255 << (8 - tail)) & 255)
|
1666
|
+
open_k[:, :, -1] &= m
|
1667
|
+
open_j[:, :, -1] &= m
|
1668
|
+
m = np.uint8((m << 1) & 255)
|
1669
|
+
open_i[:, :, -1] &= m
|
1670
|
+
|
1671
|
+
# populate bisector array for box
|
1672
|
+
_fill_packed_bisector(box_array, open_k, open_j, open_i)
|
1673
|
+
|
1674
|
+
del open_i, open_j, open_k
|
1675
|
+
|
1676
|
+
# set up the full bisectors array and assigning the bounding box values
|
1677
|
+
p_array = np.zeros(_shape_packed(grid_extent_kji), dtype = np.uint8)
|
1678
|
+
p_array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1679
|
+
|
1680
|
+
# set bisector values outside of the bounding box
|
1681
|
+
_set_packed_bisector_outside_box(p_array, box, box_array, grid_extent_kji[2] % 8)
|
1682
|
+
|
1683
|
+
# check all array elements are not the same
|
1684
|
+
if hasattr(np, 'bitwise_count'):
|
1685
|
+
true_count = np.sum(np.bitwise_count(p_array))
|
1686
|
+
else:
|
1687
|
+
true_count = _bitwise_count_njit(p_array) # note: will usually include some padding bits, so not so true!
|
1688
|
+
cell_count = np.prod(grid_extent_kji)
|
1689
|
+
if 0 < true_count < cell_count:
|
1690
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1691
|
+
# TODO: switch to _packed_shallow_or_curtain() when numba supports np.bitwise_count()
|
1692
|
+
is_curtain = _packed_shallow_or_curtain_temp_bitwise_count(p_array, true_count, raw_bisector)
|
1693
|
+
else:
|
1694
|
+
assert raw_bisector, 'face set for surface is leaky or empty (surface does not intersect grid)'
|
1695
|
+
log.error('face set for surface is leaky or empty (surface does not intersect grid)')
|
1696
|
+
is_curtain = False
|
1697
|
+
|
1698
|
+
return p_array, is_curtain
|
1699
|
+
|
1700
|
+
|
1701
|
+
def column_bisector_from_face_indices(grid_extent_ji: Tuple[int, int], j_faces_ji0: np.ndarray,
|
1702
|
+
i_faces_ji0: np.ndarray) -> np.ndarray:
|
1703
|
+
"""Returns a numpy bool array denoting the bisection of the top layer of the grid by the curtain face sets.
|
1704
|
+
|
1705
|
+
arguments:
|
1706
|
+
- grid_extent_ji (pair of int): the shape of a layer of the grid
|
1707
|
+
- j_faces_ji0, i_faces_ji0 (2D numpy int arrays of shape (N, 2)): indices of faces within a layer
|
1708
|
+
|
1709
|
+
returns:
|
1710
|
+
numpy bool array of shape grid_extent_ji, set True for cells on one side of the face sets;
|
1711
|
+
set False for cells on othe side
|
1712
|
+
|
1713
|
+
notes:
|
1714
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1715
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1716
|
+
assigned to the False part
|
1717
|
+
- the resulting array is suitable for use as a grid property with indexable element of columns
|
1718
|
+
- the array is set True for the side of the curtain that contains cell [0, 0]
|
1719
|
+
"""
|
1720
|
+
assert len(grid_extent_ji) == 2
|
1721
|
+
j_faces = np.zeros((grid_extent_ji[0] - 1, grid_extent_ji[1]), dtype = np.bool_)
|
1722
|
+
i_faces = np.zeros((grid_extent_ji[0], grid_extent_ji[1] - 1), dtype = np.bool_)
|
1723
|
+
j_faces[j_faces_ji0[:, 0], j_faces_ji0[:, 1]] = True
|
1724
|
+
i_faces[i_faces_ji0[:, 0], i_faces_ji0[:, 1]] = True
|
1725
|
+
return column_bisector_from_faces(grid_extent_ji, j_faces, i_faces)
|
1726
|
+
|
1727
|
+
|
1069
1728
|
def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndarray, i_faces: np.ndarray) -> np.ndarray:
|
1070
1729
|
"""Returns a numpy bool array denoting the bisection of the top layer of the grid by the curtain face sets.
|
1071
1730
|
|
@@ -1116,6 +1775,42 @@ def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndar
|
|
1116
1775
|
return a
|
1117
1776
|
|
1118
1777
|
|
1778
|
+
def shadow_from_face_indices(extent_kji, kji0):
|
1779
|
+
"""Returns a numpy int8 array indicating whether cells are above, below or between K faces.
|
1780
|
+
|
1781
|
+
arguments:
|
1782
|
+
extent_kji (triple int): the shape of the grid
|
1783
|
+
kji0 (numpy int array of shape (N, 3)): indices where a K face is present
|
1784
|
+
|
1785
|
+
returns:
|
1786
|
+
numpy int8 array of shape extent_kji; values are: 0 neither above nor below a K face;
|
1787
|
+
1: above any K faces in the column; 2 below any K faces in the column;
|
1788
|
+
3: between K faces (one or more above and one or more below)
|
1789
|
+
"""
|
1790
|
+
assert len(extent_kji) == 3
|
1791
|
+
limit = extent_kji[0] - 1 # maximum number of iterations needed to spead shadow
|
1792
|
+
shadow = np.zeros(extent_kji, dtype = np.int8)
|
1793
|
+
shadow[kji0[:, 0], kji0[:, 1], kji0[:, 2]] = 1
|
1794
|
+
shadow[kji0[:, 0] + 1, kji0[:, 1], kji0[:, 2]] += 2
|
1795
|
+
for _ in range(limit):
|
1796
|
+
c = np.logical_and(shadow[:-1] == 0, shadow[1:] == 1)
|
1797
|
+
if np.count_nonzero(c) == 0:
|
1798
|
+
break
|
1799
|
+
shadow[:-1][c] = 1
|
1800
|
+
for _ in range(limit):
|
1801
|
+
c = np.logical_and(shadow[1:] == 0, shadow[:-1] == 2)
|
1802
|
+
if np.count_nonzero(c) == 0:
|
1803
|
+
break
|
1804
|
+
shadow[1:][c] = 2
|
1805
|
+
for _ in range(limit):
|
1806
|
+
c = np.logical_and(shadow[:-1] >= 2, shadow[1:] == 1)
|
1807
|
+
if np.count_nonzero(c) == 0:
|
1808
|
+
break
|
1809
|
+
shadow[:-1][c] = 3
|
1810
|
+
shadow[1:][c] = 3
|
1811
|
+
return shadow
|
1812
|
+
|
1813
|
+
|
1119
1814
|
def shadow_from_faces(extent_kji, k_faces):
|
1120
1815
|
"""Returns a numpy int8 array indicating whether cells are above, below or between K faces.
|
1121
1816
|
|
@@ -1137,28 +1832,28 @@ def shadow_from_faces(extent_kji, k_faces):
|
|
1137
1832
|
c = np.logical_and(shadow[:-1] == 0, shadow[1:] == 1)
|
1138
1833
|
if np.count_nonzero(c) == 0:
|
1139
1834
|
break
|
1140
|
-
shadow[:-1] =
|
1835
|
+
shadow[:-1][c] = 1
|
1141
1836
|
for _ in range(limit):
|
1142
1837
|
c = np.logical_and(shadow[1:] == 0, shadow[:-1] == 2)
|
1143
1838
|
if np.count_nonzero(c) == 0:
|
1144
1839
|
break
|
1145
|
-
shadow[1:] =
|
1840
|
+
shadow[1:][c] = 2
|
1146
1841
|
for _ in range(limit):
|
1147
1842
|
c = np.logical_and(shadow[:-1] >= 2, shadow[1:] == 1)
|
1148
1843
|
if np.count_nonzero(c) == 0:
|
1149
1844
|
break
|
1150
|
-
shadow[:-1] =
|
1151
|
-
shadow[1:] =
|
1845
|
+
shadow[:-1][c] = 3
|
1846
|
+
shadow[1:][c] = 3
|
1152
1847
|
return shadow
|
1153
1848
|
|
1154
1849
|
|
1155
1850
|
def get_boundary( # type: ignore
|
1156
|
-
k_faces: np.ndarray,
|
1157
|
-
j_faces: np.ndarray,
|
1158
|
-
i_faces: np.ndarray,
|
1851
|
+
k_faces: Union[np.ndarray, None],
|
1852
|
+
j_faces: Union[np.ndarray, None],
|
1853
|
+
i_faces: Union[np.ndarray, None],
|
1159
1854
|
grid_extent_kji: Tuple[int, int, int],
|
1160
|
-
) ->
|
1161
|
-
"""Cretaes a
|
1855
|
+
) -> np.ndarray:
|
1856
|
+
"""Cretaes a box of the indices that bound the surface (where the faces are True).
|
1162
1857
|
|
1163
1858
|
arguments:
|
1164
1859
|
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
@@ -1167,26 +1862,22 @@ def get_boundary( # type: ignore
|
|
1167
1862
|
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1168
1863
|
|
1169
1864
|
returns:
|
1170
|
-
|
1865
|
+
int array of shape (2, 3): bounding box in python protocol (max values have been incremented)
|
1171
1866
|
|
1172
1867
|
note:
|
1173
1868
|
input faces arrays are for internal grid faces (ie. extent reduced by 1 in axis of faces);
|
1174
1869
|
a buffer slice is included where the surface does not reach the edge of the grid
|
1175
1870
|
"""
|
1176
1871
|
|
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
|
-
}
|
1872
|
+
boundary = np.zeros((2, 3), dtype = np.int32)
|
1185
1873
|
|
1186
1874
|
starting = True
|
1187
1875
|
|
1188
1876
|
for f_i, faces in enumerate([k_faces, j_faces, i_faces]):
|
1189
1877
|
|
1878
|
+
if faces is None:
|
1879
|
+
continue
|
1880
|
+
|
1190
1881
|
# NB. k, j & i for rest of loop refer to indices of faces, regardless of which face set is being processed
|
1191
1882
|
|
1192
1883
|
where_k, where_j, where_i = _where_true(faces)
|
@@ -1210,44 +1901,90 @@ def get_boundary( # type: ignore
|
|
1210
1901
|
else:
|
1211
1902
|
if min_k > 0:
|
1212
1903
|
min_k -= 1
|
1213
|
-
if max_k <
|
1904
|
+
if max_k < grid_extent_kji[0] - 1:
|
1214
1905
|
max_k += 1
|
1215
1906
|
if f_i == 1:
|
1216
1907
|
max_j += 1
|
1217
1908
|
else:
|
1218
1909
|
if min_j > 0:
|
1219
1910
|
min_j -= 1
|
1220
|
-
if max_j <
|
1911
|
+
if max_j < grid_extent_kji[1] - 1:
|
1221
1912
|
max_j += 1
|
1222
1913
|
if f_i == 2:
|
1223
1914
|
max_i += 1
|
1224
1915
|
else:
|
1225
1916
|
if min_i > 0:
|
1226
1917
|
min_i -= 1
|
1227
|
-
if max_i <
|
1918
|
+
if max_i < grid_extent_kji[2] - 1:
|
1228
1919
|
max_i += 1
|
1229
1920
|
|
1230
1921
|
if starting:
|
1231
|
-
boundary[
|
1232
|
-
boundary[
|
1233
|
-
boundary[
|
1234
|
-
boundary[
|
1235
|
-
boundary[
|
1236
|
-
boundary[
|
1922
|
+
boundary[0, 0] = min_k
|
1923
|
+
boundary[1, 0] = max_k
|
1924
|
+
boundary[0, 1] = min_j
|
1925
|
+
boundary[1, 1] = max_j
|
1926
|
+
boundary[0, 2] = min_i
|
1927
|
+
boundary[1, 2] = max_i
|
1237
1928
|
starting = False
|
1238
1929
|
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[
|
1930
|
+
if min_k < boundary[0, 0]:
|
1931
|
+
boundary[0, 0] = min_k
|
1932
|
+
if max_k > boundary[1, 0]:
|
1933
|
+
boundary[1, 0] = max_k
|
1934
|
+
if min_j < boundary[0, 1]:
|
1935
|
+
boundary[0, 1] = min_j
|
1936
|
+
if max_j > boundary[1, 1]:
|
1937
|
+
boundary[1, 1] = max_j
|
1938
|
+
if min_i < boundary[0, 2]:
|
1939
|
+
boundary[0, 2] = min_i
|
1940
|
+
if max_i > boundary[1, 2]:
|
1941
|
+
boundary[1, 2] = max_i
|
1942
|
+
|
1943
|
+
boundary[1, :] += 1 # increment max values to give python protocol box
|
1944
|
+
|
1945
|
+
return boundary # type: ignore
|
1946
|
+
|
1947
|
+
|
1948
|
+
def get_boundary_dict( # type: ignore
|
1949
|
+
k_faces: Union[np.ndarray, None],
|
1950
|
+
j_faces: Union[np.ndarray, None],
|
1951
|
+
i_faces: Union[np.ndarray, None],
|
1952
|
+
grid_extent_kji: Tuple[int, int, int],
|
1953
|
+
) -> Dict[str, int]:
|
1954
|
+
"""Cretaes a dictionary of the indices that bound the surface (where the faces are True).
|
1955
|
+
|
1956
|
+
arguments:
|
1957
|
+
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1958
|
+
j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1959
|
+
i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1960
|
+
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1961
|
+
|
1962
|
+
returns:
|
1963
|
+
boundary (Dict[str, int]): a dictionary of the indices that bound the surface
|
1964
|
+
|
1965
|
+
note:
|
1966
|
+
input faces arrays are for internal grid faces (ie. extent reduced by 1 in axis of faces);
|
1967
|
+
a buffer slice is included where the surface does not reach the edge of the grid;
|
1968
|
+
max values are not increment, ie. need to be incremented to be used as an upper end of a python range
|
1969
|
+
"""
|
1970
|
+
|
1971
|
+
boundary = {
|
1972
|
+
"k_min": None,
|
1973
|
+
"k_max": None,
|
1974
|
+
"j_min": None,
|
1975
|
+
"j_max": None,
|
1976
|
+
"i_min": None,
|
1977
|
+
"i_max": None,
|
1978
|
+
}
|
1979
|
+
|
1980
|
+
box = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
1981
|
+
|
1982
|
+
boundary["k_min"] = box[0, 0]
|
1983
|
+
boundary["k_max"] = box[1, 0] - 1
|
1984
|
+
boundary["j_min"] = box[0, 1]
|
1985
|
+
boundary["j_max"] = box[1, 1] - 1
|
1986
|
+
boundary["i_min"] = box[0, 2]
|
1987
|
+
boundary["i_max"] = box[1, 2] - 1
|
1251
1988
|
|
1252
1989
|
return boundary # type: ignore
|
1253
1990
|
|
@@ -1259,7 +1996,7 @@ def _where_true(data: np.ndarray):
|
|
1259
1996
|
|
1260
1997
|
|
1261
1998
|
@njit # pragma: no cover
|
1262
|
-
def _first_true(array: np.ndarray) ->
|
1999
|
+
def _first_true(array: np.ndarray) -> int: # type: ignore
|
1263
2000
|
"""Returns the index + 1 of the first True value in the array."""
|
1264
2001
|
for idx, val in np.ndenumerate(array):
|
1265
2002
|
if val:
|
@@ -1267,6 +2004,25 @@ def _first_true(array: np.ndarray) -> Optional[int]: # type: ignore
|
|
1267
2004
|
return array.size
|
1268
2005
|
|
1269
2006
|
|
2007
|
+
@njit('(uint8[:,:,:], uint8[:,:,:], uint8[:,:,:], int32[:,:])', parallel = True) # pragma: no cover
|
2008
|
+
def _set_packed_where_mask(a: np.ndarray, mask: np.ndarray, v: np.ndarray, box: np.ndarray): # type: ignore
|
2009
|
+
"""Update 3D packed boolean array a from packed boolean array v where packed boolean array mask is set."""
|
2010
|
+
assert a.ndim == 3 and a.shape == mask.shape and a.shape == v.shape and box.shape == (2, 3)
|
2011
|
+
sk: int = box[0, 0]
|
2012
|
+
sj: int = box[0, 1]
|
2013
|
+
si: int = box[0, 2]
|
2014
|
+
ek: int = box[1, 0]
|
2015
|
+
ej: int = box[1, 1]
|
2016
|
+
ei: int = box[1, 2]
|
2017
|
+
for k in prange(sk, ek):
|
2018
|
+
for j in range(sj, ej):
|
2019
|
+
for i in range(si, ei):
|
2020
|
+
m: np.uint8 = mask[k, j, i]
|
2021
|
+
if m != 0:
|
2022
|
+
not_m: np.uint8 = ~m
|
2023
|
+
a[k, j, i] = (m & v[k, j, i]) | (not_m & a[k, j, i])
|
2024
|
+
|
2025
|
+
|
1270
2026
|
@njit # pragma: no cover
|
1271
2027
|
def intersect_numba(
|
1272
2028
|
axis: int,
|
@@ -1350,6 +2106,7 @@ def intersect_numba(
|
|
1350
2106
|
face_idx[index2] = d2
|
1351
2107
|
face_idx[2 - axis] = face
|
1352
2108
|
|
2109
|
+
# dangerous: relies on indivisible read-modify-write of memory word containing multiple faces elements
|
1353
2110
|
faces[face_idx[0], face_idx[1], face_idx[2]] = True
|
1354
2111
|
|
1355
2112
|
if return_depths:
|
@@ -1362,62 +2119,401 @@ def intersect_numba(
|
|
1362
2119
|
return faces, offsets, triangle_per_face
|
1363
2120
|
|
1364
2121
|
|
2122
|
+
def _all_offsets(crs, k_offsets_list, j_offsets_list, i_offsets_list):
|
2123
|
+
if crs.xy_units == crs.z_units:
|
2124
|
+
return np.concatenate((k_offsets_list, j_offsets_list, i_offsets_list), axis = 0)
|
2125
|
+
ji_offsets = np.concatenate((j_offsets_list, i_offsets_list), axis = 0)
|
2126
|
+
wam.convert_lengths(ji_offsets, crs.xy_units, crs.z_units)
|
2127
|
+
return np.concatenate((k_offsets_list, ji_offsets), axis = 0)
|
2128
|
+
|
2129
|
+
|
1365
2130
|
@njit # pragma: no cover
|
1366
|
-
def
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
2131
|
+
def _fill_bisector(bisect: np.ndarray, open_k: np.ndarray, open_j: np.ndarray, open_i: np.ndarray):
|
2132
|
+
nk: int = bisect.shape[0]
|
2133
|
+
nj: int = bisect.shape[1]
|
2134
|
+
ni: int = bisect.shape[2]
|
2135
|
+
going: bool = True
|
2136
|
+
while going:
|
2137
|
+
going = False
|
2138
|
+
for k in range(nk):
|
2139
|
+
for j in range(nj):
|
2140
|
+
for i in range(ni):
|
2141
|
+
if bisect[k, j, i]:
|
2142
|
+
continue
|
2143
|
+
if ((k and bisect[k - 1, j, i] and open_k[k - 1, j, i]) or
|
2144
|
+
(j and bisect[k, j - 1, i] and open_j[k, j - 1, i]) or
|
2145
|
+
(i and bisect[k, j, i - 1] and open_i[k, j, i - 1]) or
|
2146
|
+
(k < nk - 1 and bisect[k + 1, j, i] and open_k[k, j, i]) or
|
2147
|
+
(j < nj - 1 and bisect[k, j + 1, i] and open_j[k, j, i]) or
|
2148
|
+
(i < ni - 1 and bisect[k, j, i + 1] and open_i[k, j, i])):
|
2149
|
+
bisect[k, j, i] = True
|
2150
|
+
going = True
|
1375
2151
|
|
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
2152
|
|
1385
|
-
|
1386
|
-
|
2153
|
+
@njit # pragma: no cover
|
2154
|
+
def _fill_packed_bisector(bisect: np.ndarray, open_k: np.ndarray, open_j: np.ndarray, open_i: np.ndarray):
|
2155
|
+
nk: int = bisect.shape[0]
|
2156
|
+
nj: int = bisect.shape[1]
|
2157
|
+
ni: int = bisect.shape[2]
|
2158
|
+
going: bool = True
|
2159
|
+
m: np.uint8 = np.uint8(0)
|
2160
|
+
om: np.uint8 = np.uint8(0)
|
2161
|
+
oi: np.uint8 = np.uint8(0)
|
2162
|
+
while going:
|
2163
|
+
going = False
|
2164
|
+
for k in range(nk):
|
2165
|
+
for j in range(nj):
|
2166
|
+
for i in range(ni):
|
2167
|
+
m = np.uint8(bisect[k, j, i]) # 8 bools packed into a uint8
|
2168
|
+
if bisect[k, j, i] == np.uint8(0xFF): # all 8 values already set
|
2169
|
+
continue
|
2170
|
+
om = m # copy to check for changes later
|
2171
|
+
if k:
|
2172
|
+
m |= (bisect[k - 1, j, i] & open_k[k - 1, j, i])
|
2173
|
+
if k < nk - 1:
|
2174
|
+
m |= (bisect[k + 1, j, i] & open_k[k, j, i])
|
2175
|
+
if j:
|
2176
|
+
m |= (bisect[k, j - 1, i] & open_j[k, j - 1, i])
|
2177
|
+
if j < nj - 1:
|
2178
|
+
m |= (bisect[k, j + 1, i] & open_j[k, j, i])
|
2179
|
+
oi = np.uint8(open_i[k, j, i]) # type: ignore
|
2180
|
+
m |= (m >> 1) & (oi >> 1) # type: ignore
|
2181
|
+
m |= (m << 1) & oi # type: ignore
|
2182
|
+
# handle rollover bits for I
|
2183
|
+
if i and (bisect[k, j, i - 1] & open_i[k, j, i - 1] & np.uint8(0x01)):
|
2184
|
+
m |= np.uint8(0x80)
|
2185
|
+
if (i < ni - 1) and (oi & 1) and (bisect[k, j, i + 1] & 0x80):
|
2186
|
+
m |= np.uint8(0x01)
|
2187
|
+
if m != om:
|
2188
|
+
bisect[k, j, i] = m
|
2189
|
+
going = True
|
1387
2190
|
|
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
2191
|
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
|
2192
|
+
@njit # pragma: no cover
|
2193
|
+
def _shallow_or_curtain(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
2194
|
+
# negate the bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
2195
|
+
assert a.ndim == 3
|
2196
|
+
layer_cell_count: int = a.shape[1] * a.shape[2]
|
2197
|
+
k_sum: int = 0
|
2198
|
+
opposite_k_sum: int = 0
|
2199
|
+
is_curtain: bool = False
|
2200
|
+
layer_count: int = 0
|
2201
|
+
for k in range(a.shape[0]):
|
2202
|
+
layer_count = np.count_nonzero(a[k])
|
2203
|
+
k_sum += (k + 1) * layer_count
|
2204
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
2205
|
+
mean_k: float = float(k_sum) / float(true_count)
|
2206
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(a.size - true_count)
|
2207
|
+
if mean_k > opposite_mean_k and not raw:
|
2208
|
+
a[:] = np.logical_not(a)
|
2209
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
2210
|
+
# log.warning('unable to determine which side of surface is shallower')
|
2211
|
+
is_curtain = True
|
2212
|
+
return is_curtain
|
2213
|
+
|
1404
2214
|
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
2215
|
+
@njit # pragma: no cover
|
2216
|
+
def _packed_shallow_or_curtain(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
2217
|
+
# negate the packed bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
2218
|
+
assert a.ndim == 3
|
2219
|
+
layer_cell_count: int = 8 * a.shape[1] * a.shape[2] # note: includes padding bits
|
2220
|
+
k_sum: int = 0
|
2221
|
+
opposite_k_sum: int = 0
|
2222
|
+
is_curtain: bool = False
|
2223
|
+
layer_count: int = 0
|
2224
|
+
for k in range(a.shape[0]):
|
2225
|
+
# np.bitwise_count() not yet supported by numba
|
2226
|
+
layer_count = np.sum(np.bitwise_count(a[k]), dtype = np.int64) # type: ignore
|
2227
|
+
k_sum += (k + 1) * layer_count
|
2228
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
2229
|
+
mean_k: float = float(k_sum) / float(true_count)
|
2230
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(8 * a.size - true_count)
|
2231
|
+
if mean_k > opposite_mean_k and not raw:
|
2232
|
+
a[:] = np.invert(a)
|
2233
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
2234
|
+
# log.warning('unable to determine which side of surface is shallower')
|
2235
|
+
is_curtain = True
|
2236
|
+
return is_curtain
|
1409
2237
|
|
1410
|
-
first_i = 0
|
1411
|
-
if i == 0:
|
1412
|
-
first_i = _first_true(i_faces[boundary[0] + k, boundary[2] + j, boundary[4]:boundary[5]])
|
1413
|
-
array[k, j, :first_i] = True
|
1414
2238
|
|
1415
|
-
|
2239
|
+
@njit # pragma: no cover
|
2240
|
+
def _packed_shallow_or_curtain_temp_bitwise_count(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
2241
|
+
# negate the packed bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
2242
|
+
assert a.ndim == 3
|
2243
|
+
# note: following 'cell count' includes padding bits
|
2244
|
+
layer_cell_count: np.int64 = 8 * a.shape[1] * a.shape[2] # type: ignore
|
2245
|
+
k_sum: np.int64 = 0 # type: ignore
|
2246
|
+
opposite_k_sum: np.int64 = 0 # type: ignore
|
2247
|
+
is_curtain: bool = False
|
2248
|
+
layer_count: np.int64 = 0 # type: ignore
|
2249
|
+
for k in range(a.shape[0]):
|
2250
|
+
layer_count = _bitwise_count_njit(a[k, :, :])
|
2251
|
+
k_sum += (k + 1) * layer_count
|
2252
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
2253
|
+
mean_k: float = float(k_sum) / float(true_count)
|
2254
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(8 * a.size - true_count)
|
2255
|
+
if mean_k > opposite_mean_k and not raw:
|
2256
|
+
a[:] = np.invert(a)
|
2257
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
2258
|
+
# log.warning('unable to determine which side of surface is shallower')
|
2259
|
+
is_curtain = True
|
2260
|
+
return is_curtain
|
2261
|
+
|
2262
|
+
|
2263
|
+
def _set_bisector_outside_box(a: np.ndarray, box: np.ndarray, box_array: np.ndarray): # type: ignore
|
2264
|
+
# set values outside of the bounding box
|
2265
|
+
if box[1, 0] < a.shape[0] and np.any(box_array[-1, :, :]):
|
2266
|
+
a[box[1, 0]:, :, :] = True
|
2267
|
+
if box[0, 0] != 0:
|
2268
|
+
a[:box[0, 0], :, :] = True
|
2269
|
+
if box[1, 1] < a.shape[1] and np.any(box_array[:, -1, :]):
|
2270
|
+
a[:, box[1, 1]:, :] = True
|
2271
|
+
if box[0, 1] != 0:
|
2272
|
+
a[:, :box[0, 1], :] = True
|
2273
|
+
if box[1, 2] < a.shape[2] and np.any(box_array[:, :, -1]):
|
2274
|
+
a[:, :, box[1, 2]:] = True
|
2275
|
+
if box[0, 2] != 0:
|
2276
|
+
a[:, :, :box[0, 2]] = True
|
2277
|
+
|
2278
|
+
|
2279
|
+
def _set_packed_bisector_outside_box(a: np.ndarray, box: np.ndarray, box_array: np.ndarray, tail: int):
|
2280
|
+
# set values outside of the bounding box, working with packed arrays
|
2281
|
+
if box[1, 0] < a.shape[0] and np.any(box_array[-1, :, :]):
|
2282
|
+
a[box[1, 0]:, :, :] = 255
|
2283
|
+
if box[0, 0] != 0:
|
2284
|
+
a[:box[0, 0], :, :] = 255
|
2285
|
+
if box[1, 1] < a.shape[1] and np.any(box_array[:, -1, :]):
|
2286
|
+
a[:, box[1, 1]:, :] = 255
|
2287
|
+
if box[0, 1] != 0:
|
2288
|
+
a[:, :box[0, 1], :] = 255
|
2289
|
+
if box[1, 2] < a.shape[2] and np.any(np.bitwise_and(box_array[:, :, -1], 1)):
|
2290
|
+
a[:, :, box[1, 2]:] = 255
|
2291
|
+
if box[0, 2] != 0:
|
2292
|
+
a[:, :, :box[0, 2]] = 255
|
2293
|
+
if tail:
|
2294
|
+
m = np.uint8((255 << (8 - tail)) & 255)
|
2295
|
+
a[:, :, -1] &= m
|
2296
|
+
|
2297
|
+
|
2298
|
+
def _box_face_arrays_from_indices( # type: ignore
|
2299
|
+
k_faces_kji0: Union[np.ndarray, None], j_faces_kji0: Union[np.ndarray, None],
|
2300
|
+
i_faces_kji0: Union[np.ndarray, None], box: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
2301
|
+
box_shape = box[1, :] - box[0, :]
|
2302
|
+
k_a = np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.bool_)
|
2303
|
+
j_a = np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.bool_)
|
2304
|
+
i_a = np.zeros((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = np.bool_)
|
2305
|
+
ko = box[0, 0]
|
2306
|
+
jo = box[0, 1]
|
2307
|
+
io = box[0, 2]
|
2308
|
+
kr = box[1, 0] - ko
|
2309
|
+
jr = box[1, 1] - jo
|
2310
|
+
ir = box[1, 2] - io
|
2311
|
+
if k_faces_kji0 is not None:
|
2312
|
+
_set_face_array(k_a, k_faces_kji0, ko, jo, io, kr - 1, jr, ir)
|
2313
|
+
if j_faces_kji0 is not None:
|
2314
|
+
_set_face_array(j_a, j_faces_kji0, ko, jo, io, kr, jr - 1, ir)
|
2315
|
+
if i_faces_kji0 is not None:
|
2316
|
+
_set_face_array(i_a, i_faces_kji0, ko, jo, io, kr, jr, ir - 1)
|
2317
|
+
return k_a, j_a, i_a
|
2318
|
+
|
2319
|
+
|
2320
|
+
def _packed_box_face_arrays_from_indices( # type: ignore
|
2321
|
+
k_faces_kji0: Union[np.ndarray, None], j_faces_kji0: Union[np.ndarray, None],
|
2322
|
+
i_faces_kji0: Union[np.ndarray, None], box: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
2323
|
+
box_shape = box[1, :] - box[0, :] # note: I axis already shrunken
|
2324
|
+
k_a = np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.uint8)
|
2325
|
+
j_a = np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.uint8)
|
2326
|
+
i_a = np.zeros(tuple(box_shape), dtype = np.uint8)
|
2327
|
+
ko = box[0, 0]
|
2328
|
+
jo = box[0, 1]
|
2329
|
+
io = box[0, 2] * 8
|
2330
|
+
kr = box[1, 0] - ko
|
2331
|
+
jr = box[1, 1] - jo
|
2332
|
+
ir = box[1, 2] * 8 - io
|
2333
|
+
if k_faces_kji0 is not None:
|
2334
|
+
_set_packed_face_array(k_a, k_faces_kji0, ko, jo, io, kr - 1, jr, ir)
|
2335
|
+
if j_faces_kji0 is not None:
|
2336
|
+
_set_packed_face_array(j_a, j_faces_kji0, ko, jo, io, kr, jr - 1, ir)
|
2337
|
+
if i_faces_kji0 is not None:
|
2338
|
+
_set_packed_face_array(i_a, i_faces_kji0, ko, jo, io, kr, jr, ir)
|
2339
|
+
return k_a, j_a, i_a
|
1416
2340
|
|
1417
2341
|
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
2342
|
+
@njit # pragma: no cover
|
2343
|
+
def _set_face_array(a: np.ndarray, indices: np.ndarray, ko: int, jo: int, io: int, kr: int, jr: int, ir: int) -> None:
|
2344
|
+
k: int = 0
|
2345
|
+
j: int = 0
|
2346
|
+
i: int = 0
|
2347
|
+
for ind in range(len(indices)):
|
2348
|
+
k = indices[ind, 0] - ko
|
2349
|
+
if k < 0 or k >= kr:
|
2350
|
+
continue
|
2351
|
+
j = indices[ind, 1] - jo
|
2352
|
+
if j < 0 or j >= jr:
|
2353
|
+
continue
|
2354
|
+
i = indices[ind, 2] - io
|
2355
|
+
if i < 0 or i >= ir:
|
2356
|
+
continue
|
2357
|
+
a[k, j, i] = True
|
2358
|
+
|
2359
|
+
|
2360
|
+
@njit # pragma: no cover
|
2361
|
+
def _set_packed_face_array(a: np.ndarray, indices: np.ndarray, ko: int, jo: int, io: int, kr: int, jr: int,
|
2362
|
+
ir: int) -> None:
|
2363
|
+
k: int = 0
|
2364
|
+
j: int = 0
|
2365
|
+
i: int = 0
|
2366
|
+
for ind in range(len(indices)):
|
2367
|
+
k = indices[ind, 0] - ko
|
2368
|
+
if k < 0 or k >= kr:
|
2369
|
+
continue
|
2370
|
+
j = indices[ind, 1] - jo
|
2371
|
+
if j < 0 or j >= jr:
|
2372
|
+
continue
|
2373
|
+
i = indices[ind, 2] - io
|
2374
|
+
if i < 0 or i >= ir:
|
2375
|
+
continue
|
2376
|
+
ii, ib = divmod(i, 8)
|
2377
|
+
a[k, j, ii] |= (1 << (7 - ib))
|
2378
|
+
|
2379
|
+
|
2380
|
+
# yapf: disable
|
2381
|
+
def get_boundary_from_indices( # type: ignore
|
2382
|
+
k_faces_kji0: Union[np.ndarray, None],
|
2383
|
+
j_faces_kji0: Union[np.ndarray, None],
|
2384
|
+
i_faces_kji0: Union[np.ndarray, None],
|
2385
|
+
grid_extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
2386
|
+
# yapf: enable
|
2387
|
+
"""Return python protocol box containing indices"""
|
2388
|
+
k_min_kji0 = None if ((k_faces_kji0 is None) or (k_faces_kji0.size == 0)) else np.min(k_faces_kji0, axis = 0)
|
2389
|
+
k_max_kji0 = None if ((k_faces_kji0 is None) or (k_faces_kji0.size == 0)) else np.max(k_faces_kji0, axis = 0)
|
2390
|
+
j_min_kji0 = None if ((j_faces_kji0 is None) or (j_faces_kji0.size == 0)) else np.min(j_faces_kji0, axis = 0)
|
2391
|
+
j_max_kji0 = None if ((j_faces_kji0 is None) or (j_faces_kji0.size == 0)) else np.max(j_faces_kji0, axis = 0)
|
2392
|
+
i_min_kji0 = None if ((i_faces_kji0 is None) or (i_faces_kji0.size == 0)) else np.min(i_faces_kji0, axis = 0)
|
2393
|
+
i_max_kji0 = None if ((i_faces_kji0 is None) or (i_faces_kji0.size == 0)) else np.max(i_faces_kji0, axis = 0)
|
2394
|
+
box = np.empty((2, 3), dtype = np.int32)
|
2395
|
+
box[0, :] = grid_extent_kji
|
2396
|
+
box[1, :] = -1
|
2397
|
+
if k_min_kji0 is not None:
|
2398
|
+
box[0, 0] = k_min_kji0[0]
|
2399
|
+
box[0, 1] = k_min_kji0[1]
|
2400
|
+
box[0, 2] = k_min_kji0[2]
|
2401
|
+
box[1, 0] = k_max_kji0[0] # type: ignore
|
2402
|
+
box[1, 1] = k_max_kji0[1] # type: ignore
|
2403
|
+
box[1, 2] = k_max_kji0[2] # type: ignore
|
2404
|
+
if j_min_kji0 is not None:
|
2405
|
+
box[0, 0] = min(box[0, 0], j_min_kji0[0])
|
2406
|
+
box[0, 1] = min(box[0, 1], j_min_kji0[1])
|
2407
|
+
box[0, 2] = min(box[0, 2], j_min_kji0[2])
|
2408
|
+
box[1, 0] = max(box[1, 0], j_max_kji0[0]) # type: ignore
|
2409
|
+
box[1, 1] = max(box[1, 1], j_max_kji0[1]) # type: ignore
|
2410
|
+
box[1, 2] = max(box[1, 2], j_max_kji0[2]) # type: ignore
|
2411
|
+
if i_min_kji0 is not None:
|
2412
|
+
box[0, 0] = min(box[0, 0], i_min_kji0[0])
|
2413
|
+
box[0, 1] = min(box[0, 1], i_min_kji0[1])
|
2414
|
+
box[0, 2] = min(box[0, 2], i_min_kji0[2])
|
2415
|
+
box[1, 0] = max(box[1, 0], i_max_kji0[0]) # type: ignore
|
2416
|
+
box[1, 1] = max(box[1, 1], i_max_kji0[1]) # type: ignore
|
2417
|
+
box[1, 2] = max(box[1, 2], i_max_kji0[2]) # type: ignore
|
2418
|
+
assert np.all(box[1] >= box[0]), 'attempt to find bounding box when all faces None'
|
2419
|
+
# include buffer layer where box does not reach edge of grid
|
2420
|
+
box[1, :] += 1 # switch to python protocol
|
2421
|
+
return expanded_box(box, grid_extent_kji)
|
2422
|
+
|
2423
|
+
|
2424
|
+
def expanded_box(box: np.ndarray, extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
2425
|
+
"""Return a python protocol box expanded by a single slice on all six faces, where extent alloas."""
|
2426
|
+
# include buffer layer where box does not reach edge of grid
|
2427
|
+
np_extent_kji = np.array(extent_kji, dtype = np.int32)
|
2428
|
+
e_box = np.zeros((2, 3), dtype = np.int32)
|
2429
|
+
e_box[0, :] = np.maximum(box[0, :] - 1, 0)
|
2430
|
+
e_box[1, :] = np.minimum(box[1, :] + 1, extent_kji)
|
2431
|
+
assert np.all(e_box[0] >= 0)
|
2432
|
+
assert np.all(e_box[1] > e_box[0])
|
2433
|
+
assert np.all(e_box[1] <= np_extent_kji)
|
2434
|
+
return e_box
|
2435
|
+
|
2436
|
+
|
2437
|
+
def get_packed_boundary_from_indices( # type: ignore
|
2438
|
+
k_faces_kji0: Union[np.ndarray, None], j_faces_kji0: Union[np.ndarray, None],
|
2439
|
+
i_faces_kji0: Union[np.ndarray, None], grid_extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
2440
|
+
"""Return python protocol box containing indices, with I axis packed"""
|
2441
|
+
box = get_boundary_from_indices(k_faces_kji0, j_faces_kji0, i_faces_kji0, grid_extent_kji)
|
2442
|
+
return shrunk_box_for_packing(box)
|
2443
|
+
|
2444
|
+
|
2445
|
+
def shrunk_box_for_packing(box: np.ndarray) -> np.ndarray:
|
2446
|
+
"""Return box with I dimension shrunk for bit packing equivalent."""
|
2447
|
+
shrunk_box = box.copy()
|
2448
|
+
shrunk_box[0, 2] /= 8
|
2449
|
+
shrunk_box[1, 2] = ((box[1, 2] - 1) // 8) + 1
|
2450
|
+
return shrunk_box
|
2451
|
+
|
2452
|
+
|
2453
|
+
def _shape_packed(unpacked_shape):
|
2454
|
+
"""Return the equivalent packed shape for a given unpacked shape, as a tuple."""
|
2455
|
+
shrunken = ((unpacked_shape[-1] - 1) // 8) + 1
|
2456
|
+
if len(unpacked_shape) == 1:
|
2457
|
+
return (shrunken,)
|
2458
|
+
head = list(unpacked_shape[:-1])
|
2459
|
+
head.append(shrunken)
|
2460
|
+
return tuple(head)
|
2461
|
+
|
2462
|
+
|
2463
|
+
@njit # pragma: no cover
|
2464
|
+
def _bitwise_count_njit(a: np.ndarray) -> np.int64:
|
2465
|
+
"""Deprecated: only needed till numpy versions < 2.0.0 are dropped."""
|
2466
|
+
c: np.int64 = 0 # type: ignore
|
2467
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x01))
|
2468
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x02))
|
2469
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x04))
|
2470
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x08))
|
2471
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x10))
|
2472
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x20))
|
2473
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x40))
|
2474
|
+
c += np.count_nonzero(np.bitwise_and(a, 0x80))
|
2475
|
+
return c
|
2476
|
+
|
2477
|
+
|
2478
|
+
@njit
|
2479
|
+
def box_intersection(box_a: np.ndarray, box_b: np.ndarray) -> np.ndarray:
|
2480
|
+
"""Return a box which is the intersection of two boxes, python protocol; all zeros if no intersection."""
|
2481
|
+
box = np.zeros((2, 3), dtype = np.int32)
|
2482
|
+
box[0] = np.maximum(box_a[0], box_b[0])
|
2483
|
+
box[1] = np.minimum(box_a[1], box_b[1])
|
2484
|
+
if np.any(box[1] <= box[0]):
|
2485
|
+
box[:] = 0
|
2486
|
+
return box
|
2487
|
+
|
2488
|
+
|
2489
|
+
@njit
|
2490
|
+
def get_box(mask: np.ndarray) -> Tuple[np.ndarray, int]: # pragma: no cover
|
2491
|
+
"""Returns a python protocol box enclosing True elements of 3D boolean mask, and count which is zero if all False."""
|
2492
|
+
box = np.full((2, 3), -1, dtype = np.int32)
|
2493
|
+
count = 0
|
2494
|
+
for k in range(mask.shape[0]):
|
2495
|
+
for j in range(mask.shape[1]):
|
2496
|
+
for i in range(mask.shape[2]):
|
2497
|
+
if mask[k, j, i]:
|
2498
|
+
if count == 0:
|
2499
|
+
box[0, 0] = k
|
2500
|
+
box[0, 1] = j
|
2501
|
+
box[0, 2] = i
|
2502
|
+
box[1, 0] = k + 1
|
2503
|
+
box[1, 1] = j + 1
|
2504
|
+
box[1, 2] = i + 1
|
2505
|
+
else:
|
2506
|
+
if k < box[0, 0]:
|
2507
|
+
box[0, 0] = k
|
2508
|
+
elif k >= box[1, 0]:
|
2509
|
+
box[1, 0] = k + 1
|
2510
|
+
if j < box[0, 1]:
|
2511
|
+
box[0, 1] = j
|
2512
|
+
elif j >= box[1, 1]:
|
2513
|
+
box[1, 1] = j + 1
|
2514
|
+
if i < box[0, 2]:
|
2515
|
+
box[0, 2] = i
|
2516
|
+
elif i >= box[1, 2]:
|
2517
|
+
box[1, 2] = i + 1
|
2518
|
+
count += 1
|
2519
|
+
return box, count
|