resqpy 4.17.0__py3-none-any.whl → 4.17.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- resqpy/__init__.py +1 -1
- resqpy/fault/_grid_connection_set.py +150 -16
- resqpy/grid_surface/__init__.py +4 -0
- resqpy/grid_surface/_find_faces.py +727 -209
- resqpy/olio/vector_utilities.py +175 -1
- resqpy/surface/_surface.py +25 -14
- resqpy/surface/_triangulated_patch.py +46 -38
- {resqpy-4.17.0.dist-info → resqpy-4.17.1.dist-info}/METADATA +1 -1
- {resqpy-4.17.0.dist-info → resqpy-4.17.1.dist-info}/RECORD +11 -11
- {resqpy-4.17.0.dist-info → resqpy-4.17.1.dist-info}/LICENSE +0 -0
- {resqpy-4.17.0.dist-info → resqpy-4.17.1.dist-info}/WHEEL +0 -0
@@ -495,18 +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
|
-
|
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):
|
510
510
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
511
511
|
|
512
512
|
argumants:
|
@@ -553,7 +553,10 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
553
553
|
no trimming of the surface is carried out here: for computational efficiency, it is recommended
|
554
554
|
to trim first;
|
555
555
|
organisational objects for the feature are created if needed;
|
556
|
-
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()
|
557
560
|
"""
|
558
561
|
|
559
562
|
assert isinstance(grid, grr.RegularGrid)
|
@@ -602,6 +605,7 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
602
605
|
t_dtype = np.int32 if len(triangles) < 2_000_000_000 else np.int64
|
603
606
|
assert (triangles is not None and points is not None), f"surface {surface.title} is empty"
|
604
607
|
if agitate:
|
608
|
+
points = points.copy()
|
605
609
|
if random_agitation:
|
606
610
|
points += 1.0e-5 * (np.random.random(points.shape) - 0.5)
|
607
611
|
else:
|
@@ -828,7 +832,8 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
828
832
|
# log.debug('finished preparing columns bisector')
|
829
833
|
else:
|
830
834
|
log.debug("preparing cells bisector")
|
831
|
-
bisector, is_curtain = bisector_from_faces(tuple(grid.extent_kji), k_faces, j_faces, i_faces, raw_bisector
|
835
|
+
bisector, is_curtain = bisector_from_faces(tuple(grid.extent_kji), k_faces, j_faces, i_faces, raw_bisector,
|
836
|
+
False)
|
832
837
|
if is_curtain:
|
833
838
|
bisector = bisector[0] # reduce to a columns property
|
834
839
|
|
@@ -862,6 +867,386 @@ def find_faces_to_represent_surface_regular_optimised(grid,
|
|
862
867
|
return gcs
|
863
868
|
|
864
869
|
|
870
|
+
def find_faces_to_represent_surface_regular_optimised(grid,
|
871
|
+
surface,
|
872
|
+
name,
|
873
|
+
title = None,
|
874
|
+
agitate = False,
|
875
|
+
random_agitation = False,
|
876
|
+
feature_type = "fault",
|
877
|
+
is_curtain = False,
|
878
|
+
progress_fn = None,
|
879
|
+
return_properties = None,
|
880
|
+
raw_bisector = False,
|
881
|
+
n_batches = 20):
|
882
|
+
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
883
|
+
|
884
|
+
argumants:
|
885
|
+
grid (RegularGrid): the grid for which to create a grid connection set representation of the surface;
|
886
|
+
must be aligned, ie. I with +x, J with +y, K with +z and local origin of (0.0, 0.0, 0.0)
|
887
|
+
surface (Surface): the surface to be intersected with the grid
|
888
|
+
name (str): the feature name to use in the grid connection set
|
889
|
+
title (str, optional): the citation title to use for the grid connection set; defaults to name
|
890
|
+
agitate (bool, default False): if True, the points of the surface are perturbed by a small
|
891
|
+
offset, which can help if the surface has been built from a regular mesh with a periodic resonance
|
892
|
+
with the grid
|
893
|
+
random_agitation (bool, default False): if True, the agitation is by a small random distance; if False,
|
894
|
+
a constant positive shift of 5.0e-6 is applied to x, y & z values; ignored if agitate is False
|
895
|
+
feature_type (str, default 'fault'): 'fault', 'horizon' or 'geobody boundary'
|
896
|
+
is_curtain (bool, default False): if True, only the top layer of the grid is processed and the bisector
|
897
|
+
property, if requested, is generated with indexable element columns
|
898
|
+
progress_fn (f(x: float), optional): a callback function to be called at intervals by this function;
|
899
|
+
the argument will progress from 0.0 to 1.0 in unspecified and uneven increments
|
900
|
+
return_properties (List[str]): if present, a list of property arrays to calculate and
|
901
|
+
return as a dictionary; recognised values in the list are 'triangle', 'depth', 'offset',
|
902
|
+
'flange bool', 'grid bisector', or 'grid shadow';
|
903
|
+
triangle is an index into the surface triangles of the triangle detected for the gcs face; depth is
|
904
|
+
the z value of the intersection point of the inter-cell centre vector with a triangle in the surface;
|
905
|
+
offset is a measure of the distance between the centre of the cell face and the intersection point;
|
906
|
+
grid bisector is a grid cell boolean property holding True for the set of cells on one
|
907
|
+
side of the surface, deemed to be shallower;
|
908
|
+
grid shadow is a grid cell int8 property holding 0: cell neither above nor below a K face of the
|
909
|
+
gridded surface, 1 cell is above K face(s), 2 cell is below K face(s), 3 cell is between K faces;
|
910
|
+
the returned dictionary has the passed strings as keys and numpy arrays as values
|
911
|
+
raw_bisector (bool, default False): if True and grid bisector is requested then it is left in a raw
|
912
|
+
form without assessing which side is shallower (True values indicate same side as origin cell)
|
913
|
+
n_batches (int, default 20): the number of batches of triangles to use at the low level (numba multi
|
914
|
+
threading allows some parallelism between the batches)
|
915
|
+
|
916
|
+
returns:
|
917
|
+
gcs or (gcs, gcs_props)
|
918
|
+
where gcs is a new GridConnectionSet with a single feature, not yet written to hdf5 nor xml created;
|
919
|
+
gcs_props is a dictionary mapping from requested return_properties string to numpy array
|
920
|
+
|
921
|
+
notes:
|
922
|
+
this function is designed for aligned regular grids only;
|
923
|
+
this function can handle the surface and grid being in different coordinate reference systems, as
|
924
|
+
long as the implicit parent crs is shared;
|
925
|
+
no trimming of the surface is carried out here: for computational efficiency, it is recommended
|
926
|
+
to trim first;
|
927
|
+
organisational objects for the feature are created if needed;
|
928
|
+
if the offset return property is requested, the implicit units will be the z units of the grid's crs
|
929
|
+
"""
|
930
|
+
|
931
|
+
assert isinstance(grid, grr.RegularGrid)
|
932
|
+
assert grid.is_aligned
|
933
|
+
return_triangles = False
|
934
|
+
return_depths = False
|
935
|
+
return_offsets = False
|
936
|
+
return_bisector = False
|
937
|
+
return_shadow = False
|
938
|
+
return_flange_bool = False
|
939
|
+
if return_properties:
|
940
|
+
assert all([
|
941
|
+
p in [
|
942
|
+
"triangle",
|
943
|
+
"depth",
|
944
|
+
"offset",
|
945
|
+
"grid bisector",
|
946
|
+
"grid shadow",
|
947
|
+
"flange bool",
|
948
|
+
] for p in return_properties
|
949
|
+
])
|
950
|
+
return_triangles = "triangle" in return_properties
|
951
|
+
return_depths = "depth" in return_properties
|
952
|
+
return_offsets = "offset" in return_properties
|
953
|
+
return_bisector = "grid bisector" in return_properties
|
954
|
+
return_shadow = "grid shadow" in return_properties
|
955
|
+
return_flange_bool = "flange bool" in return_properties
|
956
|
+
if return_flange_bool:
|
957
|
+
return_triangles = True
|
958
|
+
|
959
|
+
if title is None:
|
960
|
+
title = name
|
961
|
+
|
962
|
+
if progress_fn is not None:
|
963
|
+
progress_fn(0.0)
|
964
|
+
|
965
|
+
log.debug(f"intersecting surface {surface.title} with regular grid {grid.title}")
|
966
|
+
# log.debug(f'grid extent kji: {grid.extent_kji}')
|
967
|
+
|
968
|
+
triangles, points = surface.triangles_and_points(copy = True)
|
969
|
+
surface.decache_triangles_and_points()
|
970
|
+
|
971
|
+
t_dtype = np.int32 if len(triangles) < 2_147_483_648 else np.int64
|
972
|
+
|
973
|
+
assert (triangles is not None and points is not None), f"surface {surface.title} is empty"
|
974
|
+
if agitate:
|
975
|
+
if random_agitation:
|
976
|
+
points += 1.0e-5 * (np.random.random(points.shape) - 0.5)
|
977
|
+
else:
|
978
|
+
points += 5.0e-6
|
979
|
+
# log.debug(f'surface: {surface.title}; p0: {points[0]}; crs uuid: {surface.crs_uuid}')
|
980
|
+
# log.debug(f'surface min xyz: {np.min(points, axis = 0)}')
|
981
|
+
# log.debug(f'surface max xyz: {np.max(points, axis = 0)}')
|
982
|
+
if not bu.matching_uuids(grid.crs_uuid, surface.crs_uuid):
|
983
|
+
log.debug("converting from surface crs to grid crs")
|
984
|
+
s_crs = rqc.Crs(surface.model, uuid = surface.crs_uuid)
|
985
|
+
s_crs.convert_array_to(grid.crs, points)
|
986
|
+
surface.crs_uuid = grid.crs.uuid
|
987
|
+
# log.debug(f'surface: {surface.title}; p0: {points[0]}; crs uuid: {surface.crs_uuid}')
|
988
|
+
# log.debug(f'surface min xyz: {np.min(points, axis = 0)}')
|
989
|
+
# log.debug(f'surface max xyz: {np.max(points, axis = 0)}')
|
990
|
+
|
991
|
+
# convert surface points to work with unit cube grid cells
|
992
|
+
dx = grid.block_dxyz_dkji[2, 0]
|
993
|
+
dy = grid.block_dxyz_dkji[1, 1]
|
994
|
+
dz = grid.block_dxyz_dkji[0, 2]
|
995
|
+
points[:, 0] /= dx
|
996
|
+
points[:, 1] /= dy
|
997
|
+
points[:, 2] /= dz
|
998
|
+
points[:] -= 0.5
|
999
|
+
p = points[triangles]
|
1000
|
+
|
1001
|
+
nk = 1 if is_curtain else grid.nk
|
1002
|
+
# K direction (xy projection)
|
1003
|
+
k_faces_kji0 = None
|
1004
|
+
k_triangles = None
|
1005
|
+
k_depths = None
|
1006
|
+
k_offsets = None
|
1007
|
+
k_props = None
|
1008
|
+
if nk > 1:
|
1009
|
+
# log.debug("searching for k faces")
|
1010
|
+
|
1011
|
+
k_hits, k_depths = vec.points_in_triangles_aligned_unified(grid.ni, grid.nj, 0, 1, 2, p, n_batches)
|
1012
|
+
|
1013
|
+
k_faces = np.floor(k_depths)
|
1014
|
+
mask = np.logical_and(k_faces >= 0, k_faces < nk - 1)
|
1015
|
+
|
1016
|
+
if np.any(mask):
|
1017
|
+
k_hits = k_hits[mask, :]
|
1018
|
+
k_faces = k_faces[mask]
|
1019
|
+
k_depths = k_depths[mask]
|
1020
|
+
k_triangles = k_hits[:, 0]
|
1021
|
+
k_faces_kji0 = np.empty((len(k_faces), 3), dtype = np.int32)
|
1022
|
+
k_faces_kji0[:, 0] = k_faces
|
1023
|
+
k_faces_kji0[:, 1] = k_hits[:, 1]
|
1024
|
+
k_faces_kji0[:, 2] = k_hits[:, 2]
|
1025
|
+
if return_offsets:
|
1026
|
+
k_offsets = (k_depths - k_faces.astype(np.float64) - 0.5) * dz
|
1027
|
+
if return_depths:
|
1028
|
+
k_depths[:] += 0.5
|
1029
|
+
k_depths[:] *= dz
|
1030
|
+
k_props = []
|
1031
|
+
if return_triangles:
|
1032
|
+
k_props.append(k_triangles)
|
1033
|
+
if return_depths:
|
1034
|
+
k_props.append(k_depths)
|
1035
|
+
if return_offsets:
|
1036
|
+
k_props.append(k_offsets)
|
1037
|
+
log.debug(f"k face count: {len(k_faces_kji0)}")
|
1038
|
+
|
1039
|
+
del k_hits
|
1040
|
+
del k_faces
|
1041
|
+
|
1042
|
+
if progress_fn is not None:
|
1043
|
+
progress_fn(0.3)
|
1044
|
+
|
1045
|
+
# J direction (xz projection)
|
1046
|
+
j_faces_kji0 = None
|
1047
|
+
j_triangles = None
|
1048
|
+
j_depths = None
|
1049
|
+
j_offsets = None
|
1050
|
+
j_props = None
|
1051
|
+
if grid.nj > 1:
|
1052
|
+
# log.debug("searching for J faces")
|
1053
|
+
|
1054
|
+
j_hits, j_depths = vec.points_in_triangles_aligned_unified(grid.ni, grid.nk, 0, 2, 1, p, n_batches)
|
1055
|
+
|
1056
|
+
j_faces = np.floor(j_depths)
|
1057
|
+
mask = np.logical_and(j_faces >= 0, j_faces < grid.nj - 1)
|
1058
|
+
|
1059
|
+
if np.any(mask):
|
1060
|
+
j_hits = j_hits[mask, :]
|
1061
|
+
j_faces = j_faces[mask]
|
1062
|
+
j_depths = j_depths[mask]
|
1063
|
+
j_triangles = j_hits[:, 0]
|
1064
|
+
j_faces_kji0 = np.empty((len(j_faces), 3), dtype = np.int32)
|
1065
|
+
j_faces_kji0[:, 0] = j_hits[:, 1]
|
1066
|
+
j_faces_kji0[:, 1] = j_faces
|
1067
|
+
j_faces_kji0[:, 2] = j_hits[:, 2]
|
1068
|
+
if return_offsets:
|
1069
|
+
j_offsets = (j_depths - j_faces.astype(np.float64) - 0.5) * dy
|
1070
|
+
if return_depths:
|
1071
|
+
j_depths[:] += 0.5
|
1072
|
+
j_depths[:] *= dy
|
1073
|
+
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
1074
|
+
j_faces = np.repeat(np.expand_dims(j_faces_kji0, axis = 0), grid.nk, axis = 0)
|
1075
|
+
j_faces[:, :, 0] = np.expand_dims(np.arange(grid.nk, dtype = np.int32), axis = -1)
|
1076
|
+
j_faces_kji0 = j_faces.reshape((-1, 3))
|
1077
|
+
j_triangles = np.repeat(j_triangles, grid.nk, axis = 0)
|
1078
|
+
if return_offsets:
|
1079
|
+
j_offsets = np.repeat(j_offsets, grid.nk, axis = 0)
|
1080
|
+
if return_depths:
|
1081
|
+
j_depths = np.repeat(j_depths, grid.nk, axis = 0)
|
1082
|
+
j_props = []
|
1083
|
+
if return_triangles:
|
1084
|
+
j_props.append(j_triangles)
|
1085
|
+
if return_depths:
|
1086
|
+
j_props.append(j_depths)
|
1087
|
+
if return_offsets:
|
1088
|
+
j_props.append(j_offsets)
|
1089
|
+
log.debug(f"j face count: {len(j_faces_kji0)}")
|
1090
|
+
|
1091
|
+
del j_hits
|
1092
|
+
del j_faces
|
1093
|
+
|
1094
|
+
if progress_fn is not None:
|
1095
|
+
progress_fn(0.6)
|
1096
|
+
|
1097
|
+
# I direction (yz projection)
|
1098
|
+
i_faces_kji0 = None
|
1099
|
+
i_triangles = None
|
1100
|
+
i_depths = None
|
1101
|
+
i_offsets = None
|
1102
|
+
i_props = None
|
1103
|
+
if grid.ni > 1:
|
1104
|
+
# log.debug("searching for I faces")
|
1105
|
+
|
1106
|
+
i_hits, i_depths = vec.points_in_triangles_aligned_unified(grid.nj, grid.nk, 1, 2, 0, p, n_batches)
|
1107
|
+
|
1108
|
+
i_faces = np.floor(i_depths)
|
1109
|
+
mask = np.logical_and(i_faces >= 0, i_faces < grid.ni - 1)
|
1110
|
+
|
1111
|
+
if np.any(mask):
|
1112
|
+
i_hits = i_hits[mask, :]
|
1113
|
+
i_faces = i_faces[mask]
|
1114
|
+
i_depths = i_depths[mask]
|
1115
|
+
i_triangles = i_hits[:, 0]
|
1116
|
+
i_faces_kji0 = np.empty((len(i_faces), 3), dtype = np.int32)
|
1117
|
+
i_faces_kji0[:, 0] = i_hits[:, 1]
|
1118
|
+
i_faces_kji0[:, 1] = i_hits[:, 2]
|
1119
|
+
i_faces_kji0[:, 2] = i_faces
|
1120
|
+
if return_offsets:
|
1121
|
+
i_offsets = (i_depths - i_faces.astype(np.float64) - 0.5) * dx
|
1122
|
+
if return_depths:
|
1123
|
+
i_depths[:] += 0.5
|
1124
|
+
i_depths[:] *= dx
|
1125
|
+
if is_curtain and grid.nk > 1: # expand arrays to all layers
|
1126
|
+
i_faces = np.repeat(np.expand_dims(i_faces_kji0, axis = 0), grid.nk, axis = 0)
|
1127
|
+
i_faces[:, :, 0] = np.expand_dims(np.arange(grid.nk, dtype = np.int32), axis = -1)
|
1128
|
+
i_faces_kji0 = i_faces.reshape((-1, 3))
|
1129
|
+
i_triangles = np.repeat(i_triangles, grid.nk, axis = 0)
|
1130
|
+
if return_offsets:
|
1131
|
+
i_offsets = np.repeat(i_offsets, grid.nk, axis = 0)
|
1132
|
+
if return_depths:
|
1133
|
+
i_depths = np.repeat(i_depths, grid.nk, axis = 0)
|
1134
|
+
i_props = []
|
1135
|
+
if return_triangles:
|
1136
|
+
i_props.append(i_triangles)
|
1137
|
+
if return_depths:
|
1138
|
+
i_props.append(i_depths)
|
1139
|
+
if return_offsets:
|
1140
|
+
i_props.append(i_offsets)
|
1141
|
+
log.debug(f"i face count: {len(i_faces_kji0)}")
|
1142
|
+
|
1143
|
+
del i_hits
|
1144
|
+
del i_faces
|
1145
|
+
|
1146
|
+
if progress_fn is not None:
|
1147
|
+
progress_fn(0.9)
|
1148
|
+
|
1149
|
+
log.debug("converting face sets into grid connection set")
|
1150
|
+
# NB: kji0 arrays in internal face protocol: used as cell_kji0 with polarity of 1
|
1151
|
+
# property lists have elements replaced with sorted and filtered equivalents
|
1152
|
+
gcs = rqf.GridConnectionSet.from_faces_indices(grid = grid,
|
1153
|
+
k_faces_kji0 = k_faces_kji0,
|
1154
|
+
j_faces_kji0 = j_faces_kji0,
|
1155
|
+
i_faces_kji0 = i_faces_kji0,
|
1156
|
+
remove_duplicates = True,
|
1157
|
+
k_properties = k_props,
|
1158
|
+
j_properties = j_props,
|
1159
|
+
i_properties = i_props,
|
1160
|
+
feature_name = name,
|
1161
|
+
feature_type = feature_type,
|
1162
|
+
create_organizing_objects_where_needed = True,
|
1163
|
+
title = title)
|
1164
|
+
# log.debug('finished coversion to gcs')
|
1165
|
+
|
1166
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1167
|
+
if return_triangles:
|
1168
|
+
# log.debug('preparing triangles array')
|
1169
|
+
k_triangles = np.emptry((0,), dtype = np.int32) if k_props is None else k_props.pop(0)
|
1170
|
+
j_triangles = np.emptry((0,), dtype = np.int32) if j_props is None else j_props.pop(0)
|
1171
|
+
i_triangles = np.emptry((0,), dtype = np.int32) if i_props is None else i_props.pop(0)
|
1172
|
+
all_tris = np.concatenate((k_triangles, j_triangles, i_triangles), axis = 0)
|
1173
|
+
# log.debug(f'gcs count: {gcs.count}; all triangles shape: {all_tris.shape}')
|
1174
|
+
assert all_tris.shape == (gcs.count,)
|
1175
|
+
|
1176
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1177
|
+
if return_depths:
|
1178
|
+
# log.debug('preparing depths array')
|
1179
|
+
k_depths = np.emptry((0,), dtype = np.float64) if k_props is None else k_props.pop(0)
|
1180
|
+
j_depths = np.emptry((0,), dtype = np.float64) if j_props is None else j_props.pop(0)
|
1181
|
+
i_depths = np.emptry((0,), dtype = np.float64) if i_props is None else i_props.pop(0)
|
1182
|
+
all_depths = np.concatenate((k_depths, j_depths, i_depths), axis = 0)
|
1183
|
+
# log.debug(f'gcs count: {gcs.count}; all depths shape: {all_depths.shape}')
|
1184
|
+
assert all_depths.shape == (gcs.count,)
|
1185
|
+
|
1186
|
+
# NB. following assumes faces have been added to gcs in a particular order!
|
1187
|
+
if return_offsets:
|
1188
|
+
# log.debug('preparing offsets array')
|
1189
|
+
k_offsets = np.emptry((0,), dtype = np.float64) if k_props is None else k_props[0]
|
1190
|
+
j_offsets = np.emptry((0,), dtype = np.float64) if j_props is None else j_props[0]
|
1191
|
+
i_offsets = np.emptry((0,), dtype = np.float64) if i_props is None else i_props[0]
|
1192
|
+
all_offsets = _all_offsets(grid.crs, k_offsets, j_offsets, i_offsets)
|
1193
|
+
# log.debug(f'gcs count: {gcs.count}; all offsets shape: {all_offsets.shape}')
|
1194
|
+
assert all_offsets.shape == (gcs.count,)
|
1195
|
+
|
1196
|
+
if return_flange_bool:
|
1197
|
+
# log.debug('preparing flange array')
|
1198
|
+
flange_bool_uuid = surface.model.uuid(title = "flange bool",
|
1199
|
+
obj_type = "DiscreteProperty",
|
1200
|
+
related_uuid = surface.uuid)
|
1201
|
+
assert (flange_bool_uuid is not None), f"No flange bool property found for surface: {surface.title}"
|
1202
|
+
flange_bool = rqp.Property(surface.model, uuid = flange_bool_uuid)
|
1203
|
+
flange_array = flange_bool.array_ref(dtype = bool)
|
1204
|
+
all_flange = np.take(flange_array, all_tris)
|
1205
|
+
assert all_flange.shape == (gcs.count,)
|
1206
|
+
|
1207
|
+
# note: following is a grid cells property, not a gcs property
|
1208
|
+
if return_bisector:
|
1209
|
+
if is_curtain:
|
1210
|
+
log.debug("preparing columns bisector")
|
1211
|
+
bisector = column_bisector_from_face_indices((grid.nj, grid.ni), j_faces_kji0[:, 1:], i_faces_kji0[:, 1:])
|
1212
|
+
# log.debug('finished preparing columns bisector')
|
1213
|
+
else:
|
1214
|
+
log.debug("preparing cells bisector")
|
1215
|
+
bisector, is_curtain = bisector_from_faces(tuple(grid.extent_kji), k_faces_kji0, j_faces_kji0, i_faces_kji0,
|
1216
|
+
raw_bisector, True)
|
1217
|
+
if is_curtain:
|
1218
|
+
bisector = bisector[0] # reduce to a columns property
|
1219
|
+
|
1220
|
+
# note: following is a grid cells property, not a gcs property
|
1221
|
+
if return_shadow:
|
1222
|
+
log.debug("preparing cells shadow")
|
1223
|
+
shadow = shadow_from_face_indices(tuple(grid.extent_kji), k_faces_kji0)
|
1224
|
+
|
1225
|
+
if progress_fn is not None:
|
1226
|
+
progress_fn(1.0)
|
1227
|
+
|
1228
|
+
log.debug(f"finishing find_faces_to_represent_surface_regular_optimised for {name}")
|
1229
|
+
|
1230
|
+
# if returning properties, construct dictionary
|
1231
|
+
if return_properties:
|
1232
|
+
props_dict = {}
|
1233
|
+
if return_triangles:
|
1234
|
+
props_dict["triangle"] = all_tris
|
1235
|
+
if return_depths:
|
1236
|
+
props_dict["depth"] = all_depths
|
1237
|
+
if return_offsets:
|
1238
|
+
props_dict["offset"] = all_offsets
|
1239
|
+
if return_bisector:
|
1240
|
+
props_dict["grid bisector"] = (bisector, is_curtain)
|
1241
|
+
if return_shadow:
|
1242
|
+
props_dict["grid shadow"] = shadow
|
1243
|
+
if return_flange_bool:
|
1244
|
+
props_dict["flange bool"] = all_flange
|
1245
|
+
return (gcs, props_dict)
|
1246
|
+
|
1247
|
+
return gcs
|
1248
|
+
|
1249
|
+
|
865
1250
|
def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_type = "fault", progress_fn = None):
|
866
1251
|
"""Returns a grid connection set containing those cell faces which are deemed to represent the surface.
|
867
1252
|
|
@@ -889,19 +1274,7 @@ def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_
|
|
889
1274
|
mode = "regular_optimised"
|
890
1275
|
else:
|
891
1276
|
mode = "staffa"
|
892
|
-
if mode == "
|
893
|
-
return find_faces_to_represent_surface_staffa(grid,
|
894
|
-
surface,
|
895
|
-
name,
|
896
|
-
feature_type = feature_type,
|
897
|
-
progress_fn = progress_fn)
|
898
|
-
elif mode == "regular":
|
899
|
-
return find_faces_to_represent_surface_regular(grid,
|
900
|
-
surface,
|
901
|
-
name,
|
902
|
-
feature_type = feature_type,
|
903
|
-
progress_fn = progress_fn)
|
904
|
-
elif mode == "regular_optimised":
|
1277
|
+
if mode == "regular_optimised":
|
905
1278
|
return find_faces_to_represent_surface_regular_optimised(grid,
|
906
1279
|
surface,
|
907
1280
|
name,
|
@@ -915,177 +1288,129 @@ def find_faces_to_represent_surface(grid, surface, name, mode = "auto", feature_
|
|
915
1288
|
name,
|
916
1289
|
feature_type = feature_type,
|
917
1290
|
progress_fn = progress_fn)
|
1291
|
+
elif mode == "staffa":
|
1292
|
+
return find_faces_to_represent_surface_staffa(grid,
|
1293
|
+
surface,
|
1294
|
+
name,
|
1295
|
+
feature_type = feature_type,
|
1296
|
+
progress_fn = progress_fn)
|
1297
|
+
elif mode == "regular_dense":
|
1298
|
+
return find_faces_to_represent_surface_regular_dense_optimised(grid,
|
1299
|
+
surface,
|
1300
|
+
name,
|
1301
|
+
feature_type = feature_type,
|
1302
|
+
progress_fn = progress_fn)
|
1303
|
+
elif mode == "regular":
|
1304
|
+
return find_faces_to_represent_surface_regular(grid,
|
1305
|
+
surface,
|
1306
|
+
name,
|
1307
|
+
feature_type = feature_type,
|
1308
|
+
progress_fn = progress_fn)
|
918
1309
|
log.critical("unrecognised mode: " + str(mode))
|
919
1310
|
return None
|
920
1311
|
|
921
1312
|
|
922
1313
|
def bisector_from_faces( # type: ignore
|
923
|
-
|
924
|
-
|
925
|
-
j_faces: np.ndarray,
|
926
|
-
i_faces: np.ndarray,
|
927
|
-
raw_bisector: bool,
|
928
|
-
) -> Tuple[np.ndarray, bool]:
|
1314
|
+
grid_extent_kji: Tuple[int, int, int], k_faces: np.ndarray, j_faces: np.ndarray, i_faces: np.ndarray,
|
1315
|
+
raw_bisector: bool, using_indices: bool) -> Tuple[np.ndarray, bool]:
|
929
1316
|
"""Creates a boolean array denoting the bisection of the grid by the face sets.
|
930
1317
|
|
931
1318
|
arguments:
|
932
|
-
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
933
|
-
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
934
|
-
j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
935
|
-
i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1319
|
+
- grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1320
|
+
- k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1321
|
+
- j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1322
|
+
- i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1323
|
+
- raw_bisector (bool): if True, the bisector is returned without determining which side is shallower
|
1324
|
+
- using_indices (bool): if True, k_faces etc. are list-like arrays of kji indices; if False, they
|
1325
|
+
are full boolean arrays covering the internal faces
|
936
1326
|
|
937
1327
|
returns:
|
938
1328
|
Tuple containing:
|
939
|
-
|
940
1329
|
- array (np.ndarray): boolean bisectors array where values are True for cells on the side
|
941
|
-
|
1330
|
+
of the surface that has a lower mean k index on average and False for cells on the other side.
|
942
1331
|
- is_curtain (bool): True if the surface is a curtain (vertical), otherwise False.
|
943
1332
|
|
944
1333
|
notes:
|
945
|
-
|
946
|
-
|
947
|
-
|
1334
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1335
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1336
|
+
assigned to either the True or False part
|
1337
|
+
- a value of False for using_indices is DEPRECATED, pending proving of newer indices based approach
|
948
1338
|
"""
|
949
1339
|
assert len(grid_extent_kji) == 3
|
950
1340
|
|
951
|
-
#
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
)
|
1341
|
+
# find the surface boundary (includes a buffer slice where surface does not reach edge of grid)
|
1342
|
+
if using_indices:
|
1343
|
+
box = get_boundary_from_indices(k_faces, j_faces, i_faces, grid_extent_kji)
|
1344
|
+
# set k_faces as bool arrays covering box
|
1345
|
+
k_faces, j_faces, i_faces = _box_face_arrays_from_indices(k_faces, j_faces, i_faces, box)
|
1346
|
+
else:
|
1347
|
+
box = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
1348
|
+
# switch k_faces etc. to box coverage
|
1349
|
+
k_faces = k_faces[box[0, 0]:box[1, 0] - 1, box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]]
|
1350
|
+
j_faces = j_faces[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1] - 1, box[0, 2]:box[1, 2]]
|
1351
|
+
i_faces = i_faces[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2] - 1]
|
963
1352
|
|
964
|
-
|
965
|
-
boundary_values = tuple(boundary.values())
|
966
|
-
bounding_array, first_k, first_j, first_i = _seed_array((0, 0, 0), k_faces, j_faces, i_faces, boundary_values,
|
967
|
-
bounding_array)
|
968
|
-
points = set()
|
969
|
-
for dimension, first_true in enumerate([first_k, first_j, first_i]):
|
970
|
-
for dimension_value in range(1, first_true):
|
971
|
-
point = [0, 0, 0]
|
972
|
-
point[dimension] = dimension_value
|
973
|
-
point = tuple(point) # type: ignore
|
974
|
-
bounding_array, first_k_sub, first_j_sub, first_i_sub = _seed_array(point, k_faces, j_faces, i_faces,
|
975
|
-
boundary_values, bounding_array)
|
976
|
-
for sub_dimension, first_true_sub in enumerate([first_k_sub, first_j_sub, first_i_sub]):
|
977
|
-
if dimension != sub_dimension:
|
978
|
-
for sub_dimension_value in range(1, first_true_sub):
|
979
|
-
point = [0, 0, 0]
|
980
|
-
point[dimension] = dimension_value
|
981
|
-
point[sub_dimension] = sub_dimension_value
|
982
|
-
point = tuple(point) # type: ignore
|
983
|
-
if point not in points:
|
984
|
-
points.add(point)
|
985
|
-
bounding_array, _, _, _ = _seed_array(
|
986
|
-
point,
|
987
|
-
k_faces,
|
988
|
-
j_faces,
|
989
|
-
i_faces,
|
990
|
-
boundary_values,
|
991
|
-
bounding_array,
|
992
|
-
)
|
993
|
-
|
994
|
-
# Setting up the array for the changing values.
|
995
|
-
changing_array = np.zeros_like(bounding_array, dtype = np.bool_)
|
996
|
-
|
997
|
-
# Repeatedly spreading True values to neighbouring cells that are not the other side of a face.
|
998
|
-
# yapf: disable
|
999
|
-
open_k = np.logical_not(k_faces)[
|
1000
|
-
boundary["k_min"]:boundary["k_max"],
|
1001
|
-
boundary["j_min"]:boundary["j_max"] + 1,
|
1002
|
-
boundary["i_min"]:boundary["i_max"] + 1,
|
1003
|
-
]
|
1004
|
-
open_j = np.logical_not(j_faces)[
|
1005
|
-
boundary["k_min"]:boundary["k_max"] + 1,
|
1006
|
-
boundary["j_min"]:boundary["j_max"],
|
1007
|
-
boundary["i_min"]:boundary["i_max"] + 1,
|
1008
|
-
]
|
1009
|
-
open_i = np.logical_not(i_faces)[
|
1010
|
-
boundary["k_min"]:boundary["k_max"] + 1,
|
1011
|
-
boundary["j_min"]:boundary["j_max"] + 1,
|
1012
|
-
boundary["i_min"]:boundary["i_max"],
|
1013
|
-
]
|
1014
|
-
# yapf: enable
|
1015
|
-
while True:
|
1016
|
-
changing_array[:] = False
|
1017
|
-
|
1018
|
-
# k faces
|
1019
|
-
changing_array[1:, :, :] = np.logical_and(bounding_array[:-1, :, :], open_k)
|
1020
|
-
changing_array[:-1, :, :] = np.logical_or(changing_array[:-1, :, :],
|
1021
|
-
np.logical_and(bounding_array[1:, :, :], open_k))
|
1353
|
+
box_shape = box[1, :] - box[0, :]
|
1022
1354
|
|
1023
|
-
|
1024
|
-
|
1025
|
-
np.logical_and(bounding_array[:, :-1, :], open_j))
|
1026
|
-
changing_array[:, :-1, :] = np.logical_or(changing_array[:, :-1, :],
|
1027
|
-
np.logical_and(bounding_array[:, 1:, :], open_j))
|
1355
|
+
# set up the bisector array for the bounding box
|
1356
|
+
box_array = np.zeros(box_shape, dtype = np.bool_)
|
1028
1357
|
|
1029
|
-
|
1030
|
-
|
1031
|
-
np.logical_and(bounding_array[:, :, :-1], open_i))
|
1032
|
-
changing_array[:, :, :-1] = np.logical_or(changing_array[:, :, :-1],
|
1033
|
-
np.logical_and(bounding_array[:, :, 1:], open_i))
|
1358
|
+
# seed the bisector box array at (0, 0, 0)
|
1359
|
+
box_array[0, 0, 0] = True
|
1034
1360
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1361
|
+
# prepare to spread True values to neighbouring cells that are not the other side of a face
|
1362
|
+
open_k = np.logical_not(k_faces)
|
1363
|
+
open_j = np.logical_not(j_faces)
|
1364
|
+
open_i = np.logical_not(i_faces)
|
1039
1365
|
|
1040
|
-
#
|
1366
|
+
# populate bisector array for box
|
1367
|
+
_fill_bisector(box_array, open_k, open_j, open_i)
|
1368
|
+
|
1369
|
+
# set up the full bisectors array and assigning the bounding box values
|
1041
1370
|
array = np.zeros(grid_extent_kji, dtype = np.bool_)
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
# yapf: enable
|
1049
|
-
|
1050
|
-
# Setting values outside of the bounding box.
|
1051
|
-
if boundary["k_max"] != grid_extent_kji[0] - 1 and np.any(bounding_array[-1, :, :]):
|
1052
|
-
array[boundary["k_max"] + 1:, :, :] = True
|
1053
|
-
if boundary["k_min"] != 0:
|
1054
|
-
array[:boundary["k_min"], :, :] = True
|
1055
|
-
if boundary["j_max"] != grid_extent_kji[1] - 1 and np.any(bounding_array[:, -1, :]):
|
1056
|
-
array[:, boundary["j_max"] + 1:, :] = True
|
1057
|
-
if boundary["j_min"] != 0:
|
1058
|
-
array[:, :boundary["j_min"], :] = True
|
1059
|
-
if boundary["i_max"] != grid_extent_kji[2] - 1 and np.any(bounding_array[:, :, -1]):
|
1060
|
-
array[:, :, boundary["i_max"] + 1:] = True
|
1061
|
-
if boundary["i_min"] != 0:
|
1062
|
-
array[:, :, :boundary["i_min"]] = True
|
1063
|
-
|
1064
|
-
# Check all array elements are not the same.
|
1371
|
+
array[box[0, 0]:box[1, 0], box[0, 1]:box[1, 1], box[0, 2]:box[1, 2]] = box_array
|
1372
|
+
|
1373
|
+
# set bisector values outside of the bounding box
|
1374
|
+
_set_bisector_outside_box(array, box, box_array)
|
1375
|
+
|
1376
|
+
# check all array elements are not the same
|
1065
1377
|
true_count = np.count_nonzero(array)
|
1066
1378
|
cell_count = array.size
|
1067
|
-
assert (0 < true_count < cell_count), "
|
1068
|
-
|
1069
|
-
#
|
1070
|
-
|
1071
|
-
array_k_sum = 0
|
1072
|
-
array_opposite_k_sum = 0
|
1073
|
-
is_curtain = False
|
1074
|
-
for k in range(grid_extent_kji[0]):
|
1075
|
-
array_layer_count = np.count_nonzero(array[k])
|
1076
|
-
array_k_sum += (k + 1) * array_layer_count
|
1077
|
-
array_opposite_k_sum += (k + 1) * (layer_cell_count - array_layer_count)
|
1078
|
-
array_mean_k = float(array_k_sum) / float(true_count)
|
1079
|
-
array_opposite_mean_k = float(array_opposite_k_sum) / float(cell_count - true_count)
|
1080
|
-
if array_mean_k > array_opposite_mean_k and not raw_bisector:
|
1081
|
-
array[:] = np.logical_not(array)
|
1082
|
-
if abs(array_mean_k - array_opposite_mean_k) <= 0.001:
|
1083
|
-
# log.warning('unable to determine which side of surface is shallower')
|
1084
|
-
is_curtain = True
|
1379
|
+
assert (0 < true_count < cell_count), "face set for surface is leaky or empty (surface does not intersect grid)"
|
1380
|
+
|
1381
|
+
# negate the array if it minimises the mean k and determine if the surface is a curtain
|
1382
|
+
is_curtain = _shallow_or_curtain(array, true_count, raw_bisector)
|
1085
1383
|
|
1086
1384
|
return array, is_curtain
|
1087
1385
|
|
1088
1386
|
|
1387
|
+
def column_bisector_from_face_indices(grid_extent_ji: Tuple[int, int], j_faces_ji0: np.ndarray,
|
1388
|
+
i_faces_ji0: np.ndarray) -> np.ndarray:
|
1389
|
+
"""Returns a numpy bool array denoting the bisection of the top layer of the grid by the curtain face sets.
|
1390
|
+
|
1391
|
+
arguments:
|
1392
|
+
- grid_extent_ji (pair of int): the shape of a layer of the grid
|
1393
|
+
- j_faces_ji0, i_faces_ji0 (2D numpy int arrays of shape (N, 2)): indices of faces within a layer
|
1394
|
+
|
1395
|
+
returns:
|
1396
|
+
numpy bool array of shape grid_extent_ji, set True for cells on one side of the face sets;
|
1397
|
+
set False for cells on othe side
|
1398
|
+
|
1399
|
+
notes:
|
1400
|
+
- the face sets must form a single 'sealed' cut of the grid (eg. not waving in and out of the grid)
|
1401
|
+
- any 'boxed in' parts of the grid (completely enclosed by bisecting faces) will be consistently
|
1402
|
+
assigned to the False part
|
1403
|
+
- the resulting array is suitable for use as a grid property with indexable element of columns
|
1404
|
+
- the array is set True for the side of the curtain that contains cell [0, 0]
|
1405
|
+
"""
|
1406
|
+
assert len(grid_extent_ji) == 2
|
1407
|
+
j_faces = np.zeros((grid_extent_ji[0] - 1, grid_extent_ji[1]), dtype = np.bool_)
|
1408
|
+
i_faces = np.zeros((grid_extent_ji[0], grid_extent_ji[1] - 1), dtype = np.bool_)
|
1409
|
+
j_faces[j_faces_ji0[:, 0], j_faces_ji0[:, 1]] = True
|
1410
|
+
i_faces[i_faces_ji0[:, 0], i_faces_ji0[:, 1]] = True
|
1411
|
+
return column_bisector_from_faces(grid_extent_ji, j_faces, i_faces)
|
1412
|
+
|
1413
|
+
|
1089
1414
|
def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndarray, i_faces: np.ndarray) -> np.ndarray:
|
1090
1415
|
"""Returns a numpy bool array denoting the bisection of the top layer of the grid by the curtain face sets.
|
1091
1416
|
|
@@ -1136,6 +1461,42 @@ def column_bisector_from_faces(grid_extent_ji: Tuple[int, int], j_faces: np.ndar
|
|
1136
1461
|
return a
|
1137
1462
|
|
1138
1463
|
|
1464
|
+
def shadow_from_face_indices(extent_kji, kji0):
|
1465
|
+
"""Returns a numpy int8 array indicating whether cells are above, below or between K faces.
|
1466
|
+
|
1467
|
+
arguments:
|
1468
|
+
extent_kji (triple int): the shape of the grid
|
1469
|
+
kji0 (numpy int array of shape (N, 3)): indices where a K face is present
|
1470
|
+
|
1471
|
+
returns:
|
1472
|
+
numpy int8 array of shape extent_kji; values are: 0 neither above nor below a K face;
|
1473
|
+
1: above any K faces in the column; 2 below any K faces in the column;
|
1474
|
+
3: between K faces (one or more above and one or more below)
|
1475
|
+
"""
|
1476
|
+
assert len(extent_kji) == 3
|
1477
|
+
limit = extent_kji[0] - 1 # maximum number of iterations needed to spead shadow
|
1478
|
+
shadow = np.zeros(extent_kji, dtype = np.int8)
|
1479
|
+
shadow[kji0[:, 0], kji0[:, 1], kji0[:, 2]] = 1
|
1480
|
+
shadow[kji0[:, 0] + 1, kji0[:, 1], kji0[:, 2]] += 2
|
1481
|
+
for _ in range(limit):
|
1482
|
+
c = np.logical_and(shadow[:-1] == 0, shadow[1:] == 1)
|
1483
|
+
if np.count_nonzero(c) == 0:
|
1484
|
+
break
|
1485
|
+
shadow[:-1][c] = 1
|
1486
|
+
for _ in range(limit):
|
1487
|
+
c = np.logical_and(shadow[1:] == 0, shadow[:-1] == 2)
|
1488
|
+
if np.count_nonzero(c) == 0:
|
1489
|
+
break
|
1490
|
+
shadow[1:][c] = 2
|
1491
|
+
for _ in range(limit):
|
1492
|
+
c = np.logical_and(shadow[:-1] >= 2, shadow[1:] == 1)
|
1493
|
+
if np.count_nonzero(c) == 0:
|
1494
|
+
break
|
1495
|
+
shadow[:-1][c] = 3
|
1496
|
+
shadow[1:][c] = 3
|
1497
|
+
return shadow
|
1498
|
+
|
1499
|
+
|
1139
1500
|
def shadow_from_faces(extent_kji, k_faces):
|
1140
1501
|
"""Returns a numpy int8 array indicating whether cells are above, below or between K faces.
|
1141
1502
|
|
@@ -1177,8 +1538,8 @@ def get_boundary( # type: ignore
|
|
1177
1538
|
j_faces: np.ndarray,
|
1178
1539
|
i_faces: np.ndarray,
|
1179
1540
|
grid_extent_kji: Tuple[int, int, int],
|
1180
|
-
) ->
|
1181
|
-
"""Cretaes a
|
1541
|
+
) -> np.ndarray:
|
1542
|
+
"""Cretaes a box of the indices that bound the surface (where the faces are True).
|
1182
1543
|
|
1183
1544
|
arguments:
|
1184
1545
|
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
@@ -1187,21 +1548,14 @@ def get_boundary( # type: ignore
|
|
1187
1548
|
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1188
1549
|
|
1189
1550
|
returns:
|
1190
|
-
|
1551
|
+
int array of shape (2, 3): bounding box in python protocol (max values have been incremented)
|
1191
1552
|
|
1192
1553
|
note:
|
1193
1554
|
input faces arrays are for internal grid faces (ie. extent reduced by 1 in axis of faces);
|
1194
1555
|
a buffer slice is included where the surface does not reach the edge of the grid
|
1195
1556
|
"""
|
1196
1557
|
|
1197
|
-
boundary =
|
1198
|
-
"k_min": None,
|
1199
|
-
"k_max": None,
|
1200
|
-
"j_min": None,
|
1201
|
-
"j_max": None,
|
1202
|
-
"i_min": None,
|
1203
|
-
"i_max": None,
|
1204
|
-
}
|
1558
|
+
boundary = np.zeros((2, 3), dtype = np.int32)
|
1205
1559
|
|
1206
1560
|
starting = True
|
1207
1561
|
|
@@ -1248,26 +1602,72 @@ def get_boundary( # type: ignore
|
|
1248
1602
|
max_i += 1
|
1249
1603
|
|
1250
1604
|
if starting:
|
1251
|
-
boundary[
|
1252
|
-
boundary[
|
1253
|
-
boundary[
|
1254
|
-
boundary[
|
1255
|
-
boundary[
|
1256
|
-
boundary[
|
1605
|
+
boundary[0, 0] = min_k
|
1606
|
+
boundary[1, 0] = max_k
|
1607
|
+
boundary[0, 1] = min_j
|
1608
|
+
boundary[1, 1] = max_j
|
1609
|
+
boundary[0, 2] = min_i
|
1610
|
+
boundary[1, 2] = max_i
|
1257
1611
|
starting = False
|
1258
1612
|
else:
|
1259
|
-
if min_k < boundary[
|
1260
|
-
boundary[
|
1261
|
-
if max_k > boundary[
|
1262
|
-
boundary[
|
1263
|
-
if min_j < boundary[
|
1264
|
-
boundary[
|
1265
|
-
if max_j > boundary[
|
1266
|
-
boundary[
|
1267
|
-
if min_i < boundary[
|
1268
|
-
boundary[
|
1269
|
-
if max_i > boundary[
|
1270
|
-
boundary[
|
1613
|
+
if min_k < boundary[0, 0]:
|
1614
|
+
boundary[0, 0] = min_k
|
1615
|
+
if max_k > boundary[1, 0]:
|
1616
|
+
boundary[1, 0] = max_k
|
1617
|
+
if min_j < boundary[0, 1]:
|
1618
|
+
boundary[0, 1] = min_j
|
1619
|
+
if max_j > boundary[1, 1]:
|
1620
|
+
boundary[1, 1] = max_j
|
1621
|
+
if min_i < boundary[0, 2]:
|
1622
|
+
boundary[0, 2] = min_i
|
1623
|
+
if max_i > boundary[1, 2]:
|
1624
|
+
boundary[1, 2] = max_i
|
1625
|
+
|
1626
|
+
boundary[1, :] += 1 # increment max values to give python protocol box
|
1627
|
+
|
1628
|
+
return boundary # type: ignore
|
1629
|
+
|
1630
|
+
|
1631
|
+
def get_boundary_dict( # type: ignore
|
1632
|
+
k_faces: np.ndarray,
|
1633
|
+
j_faces: np.ndarray,
|
1634
|
+
i_faces: np.ndarray,
|
1635
|
+
grid_extent_kji: Tuple[int, int, int],
|
1636
|
+
) -> Dict[str, int]:
|
1637
|
+
"""Cretaes a dictionary of the indices that bound the surface (where the faces are True).
|
1638
|
+
|
1639
|
+
arguments:
|
1640
|
+
k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1641
|
+
j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1642
|
+
i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1643
|
+
grid_extent_kji (Tuple[int, int, int]): the shape of the grid
|
1644
|
+
|
1645
|
+
returns:
|
1646
|
+
boundary (Dict[str, int]): a dictionary of the indices that bound the surface
|
1647
|
+
|
1648
|
+
note:
|
1649
|
+
input faces arrays are for internal grid faces (ie. extent reduced by 1 in axis of faces);
|
1650
|
+
a buffer slice is included where the surface does not reach the edge of the grid;
|
1651
|
+
max values are not increment, ie. need to be incremented to be used as an upper end of a python range
|
1652
|
+
"""
|
1653
|
+
|
1654
|
+
boundary = {
|
1655
|
+
"k_min": None,
|
1656
|
+
"k_max": None,
|
1657
|
+
"j_min": None,
|
1658
|
+
"j_max": None,
|
1659
|
+
"i_min": None,
|
1660
|
+
"i_max": None,
|
1661
|
+
}
|
1662
|
+
|
1663
|
+
box = get_boundary(k_faces, j_faces, i_faces, grid_extent_kji)
|
1664
|
+
|
1665
|
+
boundary["k_min"] = box[0, 0]
|
1666
|
+
boundary["k_max"] = box[1, 0] - 1
|
1667
|
+
boundary["j_min"] = box[0, 1]
|
1668
|
+
boundary["j_max"] = box[1, 1] - 1
|
1669
|
+
boundary["i_min"] = box[0, 2]
|
1670
|
+
boundary["i_max"] = box[1, 2] - 1
|
1271
1671
|
|
1272
1672
|
return boundary # type: ignore
|
1273
1673
|
|
@@ -1279,7 +1679,7 @@ def _where_true(data: np.ndarray):
|
|
1279
1679
|
|
1280
1680
|
|
1281
1681
|
@njit # pragma: no cover
|
1282
|
-
def _first_true(array: np.ndarray) ->
|
1682
|
+
def _first_true(array: np.ndarray) -> int: # type: ignore
|
1283
1683
|
"""Returns the index + 1 of the first True value in the array."""
|
1284
1684
|
for idx, val in np.ndenumerate(array):
|
1285
1685
|
if val:
|
@@ -1370,6 +1770,7 @@ def intersect_numba(
|
|
1370
1770
|
face_idx[index2] = d2
|
1371
1771
|
face_idx[2 - axis] = face
|
1372
1772
|
|
1773
|
+
# dangerous: relies on indivisible read-modify-write of memory word containing multiple faces elements
|
1373
1774
|
faces[face_idx[0], face_idx[1], face_idx[2]] = True
|
1374
1775
|
|
1375
1776
|
if return_depths:
|
@@ -1388,19 +1789,19 @@ def _seed_array(
|
|
1388
1789
|
k_faces: np.ndarray,
|
1389
1790
|
j_faces: np.ndarray,
|
1390
1791
|
i_faces: np.ndarray,
|
1391
|
-
|
1792
|
+
box: np.ndarray,
|
1392
1793
|
array: np.ndarray,
|
1393
1794
|
) -> Tuple[np.ndarray, int, int, int]:
|
1394
1795
|
"""Sets values of the array True up until a face is hit in each direction.
|
1395
1796
|
|
1396
1797
|
arguments:
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
1402
|
-
|
1403
|
-
array (np.ndarray): boolean array that will be seeded
|
1798
|
+
|
1799
|
+
- point (Tuple[int, int, int]): coordinates of the initial seed point
|
1800
|
+
- k_faces (np.ndarray): a boolean array of which faces represent the surface in the k dimension
|
1801
|
+
- j_faces (np.ndarray): a boolean array of which faces represent the surface in the j dimension
|
1802
|
+
- i_faces (np.ndarray): a boolean array of which faces represent the surface in the i dimension
|
1803
|
+
- box (numpy int array of shape (2, 3)): the boundaries of the surface in python protocol
|
1804
|
+
- array (np.ndarray): boolean array that will be seeded
|
1404
1805
|
|
1405
1806
|
returns:
|
1406
1807
|
Tuple containing:
|
@@ -1412,6 +1813,10 @@ def _seed_array(
|
|
1412
1813
|
array size in the j direction if there are no j faces.
|
1413
1814
|
- first_i (int): the index of the first i face in the i direction from the seed point or the
|
1414
1815
|
array size in the i direction if there are no i faces.
|
1816
|
+
|
1817
|
+
note:
|
1818
|
+
|
1819
|
+
- this function is DEPRECATED as it is no longer in use
|
1415
1820
|
"""
|
1416
1821
|
k = point[0]
|
1417
1822
|
j = point[1]
|
@@ -1419,17 +1824,17 @@ def _seed_array(
|
|
1419
1824
|
|
1420
1825
|
first_k = 0
|
1421
1826
|
if k == 0:
|
1422
|
-
first_k = _first_true(k_faces[
|
1827
|
+
first_k = _first_true(k_faces[box[0, 0]:box[1, 0] - 1, box[0, 1] + j, box[0, 2] + i])
|
1423
1828
|
array[:first_k, j, i] = True
|
1424
1829
|
|
1425
1830
|
first_j = 0
|
1426
1831
|
if j == 0:
|
1427
|
-
first_j = _first_true(j_faces[
|
1832
|
+
first_j = _first_true(j_faces[box[0, 0] + k, box[0, 1]:box[1, 1] - 1, box[0, 2] + i])
|
1428
1833
|
array[k, :first_j, i] = True
|
1429
1834
|
|
1430
1835
|
first_i = 0
|
1431
1836
|
if i == 0:
|
1432
|
-
first_i = _first_true(i_faces[
|
1837
|
+
first_i = _first_true(i_faces[box[0, 0] + k, box[0, 1] + j, box[0, 2]:box[1, 2] - 1])
|
1433
1838
|
array[k, j, :first_i] = True
|
1434
1839
|
|
1435
1840
|
return array, first_k, first_j, first_i
|
@@ -1441,3 +1846,116 @@ def _all_offsets(crs, k_offsets_list, j_offsets_list, i_offsets_list):
|
|
1441
1846
|
ji_offsets = np.concatenate((j_offsets_list, i_offsets_list), axis = 0)
|
1442
1847
|
wam.convert_lengths(ji_offsets, crs.xy_units, crs.z_units)
|
1443
1848
|
return np.concatenate((k_offsets_list, ji_offsets), axis = 0)
|
1849
|
+
|
1850
|
+
|
1851
|
+
@njit # pragma: no cover
|
1852
|
+
def _fill_bisector(bisect: np.ndarray, open_k: np.ndarray, open_j: np.ndarray, open_i: np.ndarray):
|
1853
|
+
change = np.zeros(bisect.shape, dtype = np.bool_)
|
1854
|
+
nk: int = bisect.shape[0]
|
1855
|
+
nj: int = bisect.shape[1]
|
1856
|
+
ni: int = bisect.shape[2]
|
1857
|
+
going: bool = True
|
1858
|
+
while going:
|
1859
|
+
going = False
|
1860
|
+
change[:] = False
|
1861
|
+
for k in range(nk):
|
1862
|
+
for j in range(nj):
|
1863
|
+
for i in range(ni):
|
1864
|
+
if bisect[k, j, i]:
|
1865
|
+
continue
|
1866
|
+
if ((k and bisect[k - 1, j, i] and open_k[k - 1, j, i]) or
|
1867
|
+
(j and bisect[k, j - 1, i] and open_j[k, j - 1, i]) or
|
1868
|
+
(i and bisect[k, j, i - 1] and open_i[k, j, i - 1]) or
|
1869
|
+
(k < nk - 1 and bisect[k + 1, j, i] and open_k[k, j, i]) or
|
1870
|
+
(j < nj - 1 and bisect[k, j + 1, i] and open_j[k, j, i]) or
|
1871
|
+
(i < ni - 1 and bisect[k, j, i + 1] and open_i[k, j, i])):
|
1872
|
+
bisect[k, j, i] = True
|
1873
|
+
going = True
|
1874
|
+
continue
|
1875
|
+
|
1876
|
+
|
1877
|
+
@njit # pragma: no cover
|
1878
|
+
def _shallow_or_curtain(a: np.ndarray, true_count: int, raw: bool) -> bool:
|
1879
|
+
# negate the bool array if it minimises the mean k and determine if the bisector indicates a curtain
|
1880
|
+
assert a.ndim == 3
|
1881
|
+
layer_cell_count: int = a.shape[1] * a.shape[2]
|
1882
|
+
k_sum: int = 0
|
1883
|
+
opposite_k_sum: int = 0
|
1884
|
+
is_curtain: bool = False
|
1885
|
+
layer_count: int = 0
|
1886
|
+
for k in range(a.shape[0]):
|
1887
|
+
layer_count = np.count_nonzero(a[k])
|
1888
|
+
k_sum += (k + 1) * layer_count
|
1889
|
+
opposite_k_sum += (k + 1) * (layer_cell_count - layer_count)
|
1890
|
+
mean_k: float = float(k_sum) / float(true_count)
|
1891
|
+
opposite_mean_k: float = float(opposite_k_sum) / float(a.size - true_count)
|
1892
|
+
if mean_k > opposite_mean_k and not raw:
|
1893
|
+
a[:] = np.logical_not(a)
|
1894
|
+
if abs(mean_k - opposite_mean_k) <= 0.001:
|
1895
|
+
# log.warning('unable to determine which side of surface is shallower')
|
1896
|
+
is_curtain = True
|
1897
|
+
return is_curtain
|
1898
|
+
|
1899
|
+
|
1900
|
+
def _set_bisector_outside_box(a: np.ndarray, box: np.ndarray, box_array: np.ndarray):
|
1901
|
+
# set values outside of the bounding box
|
1902
|
+
if box[1, 0] < a.shape[0] and np.any(box_array[-1, :, :]):
|
1903
|
+
a[box[1, 0]:, :, :] = True
|
1904
|
+
if box[0, 0] != 0:
|
1905
|
+
a[:box[0, 0], :, :] = True
|
1906
|
+
if box[1, 1] < a.shape[1] and np.any(box_array[:, -1, :]):
|
1907
|
+
a[:, box[1, 1]:, :] = True
|
1908
|
+
if box[0, 1] != 0:
|
1909
|
+
a[:, :box[0, 1], :] = True
|
1910
|
+
if box[1, 2] < a.shape[2] and np.any(box_array[:, :, -1]):
|
1911
|
+
a[:, :, box[1, 2]:] = True
|
1912
|
+
if box[0, 2] != 0:
|
1913
|
+
a[:, :, :box[0, 2]] = True
|
1914
|
+
|
1915
|
+
|
1916
|
+
def _box_face_arrays_from_indices(k_faces: np.ndarray, j_faces: np.ndarray, i_faces: np.ndarray,
|
1917
|
+
box: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
1918
|
+
box_shape = box[1, :] - box[0, :]
|
1919
|
+
k_a = np.zeros((box_shape[0] - 1, box_shape[1], box_shape[2]), dtype = np.bool_)
|
1920
|
+
j_a = np.zeros((box_shape[0], box_shape[1] - 1, box_shape[2]), dtype = np.bool_)
|
1921
|
+
i_a = np.zeros((box_shape[0], box_shape[1], box_shape[2] - 1), dtype = np.bool_)
|
1922
|
+
ko = box[0, 0]
|
1923
|
+
jo = box[0, 1]
|
1924
|
+
io = box[0, 2]
|
1925
|
+
_set_face_array(k_a, k_faces, ko, jo, io)
|
1926
|
+
_set_face_array(j_a, j_faces, ko, jo, io)
|
1927
|
+
_set_face_array(i_a, i_faces, ko, jo, io)
|
1928
|
+
return k_a, j_a, i_a
|
1929
|
+
|
1930
|
+
|
1931
|
+
@njit # pragma: no cover
|
1932
|
+
def _set_face_array(a: np.ndarray, indices: np.ndarray, ko: int, jo: int, io: int):
|
1933
|
+
for ind in range(len(indices)):
|
1934
|
+
k = indices[ind, 0] - ko
|
1935
|
+
j = indices[ind, 1] - jo
|
1936
|
+
i = indices[ind, 2] - io
|
1937
|
+
a[k, j, i] = True
|
1938
|
+
|
1939
|
+
|
1940
|
+
def get_boundary_from_indices(k_faces: np.ndarray, j_faces: np.ndarray, i_faces: np.ndarray,
|
1941
|
+
grid_extent_kji: Tuple[int, int, int]) -> np.ndarray:
|
1942
|
+
"""Return python protocol box containing indices"""
|
1943
|
+
k_min_kji0 = np.min(k_faces, axis = 0)
|
1944
|
+
k_max_kji0 = np.max(k_faces, axis = 0)
|
1945
|
+
j_min_kji0 = np.min(j_faces, axis = 0)
|
1946
|
+
j_max_kji0 = np.max(j_faces, axis = 0)
|
1947
|
+
i_min_kji0 = np.min(i_faces, axis = 0)
|
1948
|
+
i_max_kji0 = np.max(i_faces, axis = 0)
|
1949
|
+
box = np.empty((2, 3), dtype = np.int32)
|
1950
|
+
box[0, 0] = min(k_min_kji0[0], j_min_kji0[0], i_min_kji0[0])
|
1951
|
+
box[0, 1] = min(k_min_kji0[1], j_min_kji0[1], i_min_kji0[1])
|
1952
|
+
box[0, 2] = min(k_min_kji0[2], j_min_kji0[2], i_min_kji0[2])
|
1953
|
+
box[1, 0] = max(k_max_kji0[0], j_max_kji0[0], i_max_kji0[0]) + 1
|
1954
|
+
box[1, 1] = max(k_max_kji0[1], j_max_kji0[1], i_max_kji0[1]) + 1
|
1955
|
+
box[1, 2] = max(k_max_kji0[2], j_max_kji0[2], i_max_kji0[2]) + 1
|
1956
|
+
box[0, :] = np.maximum(box[0, :] - 1, 0)
|
1957
|
+
# include buffer layer where box does not reach edge of grid
|
1958
|
+
extent_kji = np.array(grid_extent_kji, dtype = np.int32)
|
1959
|
+
assert np.all(box[1] <= grid_extent_kji)
|
1960
|
+
box[1, :] = np.minimum(box[1, :] + 1, extent_kji)
|
1961
|
+
return box
|