LoopStructural 1.6.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.
Potentially problematic release.
This version of LoopStructural might be problematic. Click here for more details.
- LoopStructural/__init__.py +52 -0
- LoopStructural/datasets/__init__.py +23 -0
- LoopStructural/datasets/_base.py +301 -0
- LoopStructural/datasets/_example_models.py +10 -0
- LoopStructural/datasets/data/claudius.csv +21049 -0
- LoopStructural/datasets/data/claudiusbb.txt +2 -0
- LoopStructural/datasets/data/duplex.csv +126 -0
- LoopStructural/datasets/data/duplexbb.txt +2 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
- LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
- LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
- LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
- LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
- LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
- LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
- LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
- LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
- LoopStructural/datasets/data/intrusion.csv +1017 -0
- LoopStructural/datasets/data/intrusionbb.txt +2 -0
- LoopStructural/datasets/data/onefoldbb.txt +2 -0
- LoopStructural/datasets/data/onefolddata.csv +2226 -0
- LoopStructural/datasets/data/refolded_bb.txt +2 -0
- LoopStructural/datasets/data/refolded_fold.csv +205 -0
- LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
- LoopStructural/datatypes/__init__.py +4 -0
- LoopStructural/datatypes/_bounding_box.py +422 -0
- LoopStructural/datatypes/_point.py +166 -0
- LoopStructural/datatypes/_structured_grid.py +94 -0
- LoopStructural/datatypes/_surface.py +184 -0
- LoopStructural/export/exporters.py +554 -0
- LoopStructural/export/file_formats.py +15 -0
- LoopStructural/export/geoh5.py +100 -0
- LoopStructural/export/gocad.py +126 -0
- LoopStructural/export/omf_wrapper.py +88 -0
- LoopStructural/interpolators/__init__.py +105 -0
- LoopStructural/interpolators/_api.py +143 -0
- LoopStructural/interpolators/_builders.py +149 -0
- LoopStructural/interpolators/_cython/__init__.py +0 -0
- LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
- LoopStructural/interpolators/_discrete_interpolator.py +692 -0
- LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
- LoopStructural/interpolators/_geological_interpolator.py +380 -0
- LoopStructural/interpolators/_interpolator_factory.py +89 -0
- LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
- LoopStructural/interpolators/_operator.py +38 -0
- LoopStructural/interpolators/_p1interpolator.py +228 -0
- LoopStructural/interpolators/_p2interpolator.py +277 -0
- LoopStructural/interpolators/_surfe_wrapper.py +174 -0
- LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
- LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
- LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
- LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
- LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
- LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
- LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
- LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
- LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
- LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
- LoopStructural/interpolators/supports/__init__.py +55 -0
- LoopStructural/interpolators/supports/_aabb.py +77 -0
- LoopStructural/interpolators/supports/_base_support.py +114 -0
- LoopStructural/interpolators/supports/_face_table.py +70 -0
- LoopStructural/interpolators/supports/_support_factory.py +32 -0
- LoopStructural/modelling/__init__.py +29 -0
- LoopStructural/modelling/core/__init__.py +0 -0
- LoopStructural/modelling/core/geological_model.py +1867 -0
- LoopStructural/modelling/features/__init__.py +32 -0
- LoopStructural/modelling/features/_analytical_feature.py +79 -0
- LoopStructural/modelling/features/_base_geological_feature.py +364 -0
- LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
- LoopStructural/modelling/features/_geological_feature.py +288 -0
- LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
- LoopStructural/modelling/features/_region.py +18 -0
- LoopStructural/modelling/features/_structural_frame.py +186 -0
- LoopStructural/modelling/features/_unconformity_feature.py +83 -0
- LoopStructural/modelling/features/builders/__init__.py +5 -0
- LoopStructural/modelling/features/builders/_base_builder.py +111 -0
- LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
- LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
- LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
- LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
- LoopStructural/modelling/features/fault/__init__.py +3 -0
- LoopStructural/modelling/features/fault/_fault_function.py +444 -0
- LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
- LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
- LoopStructural/modelling/features/fold/__init__.py +9 -0
- LoopStructural/modelling/features/fold/_fold.py +167 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
- LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
- LoopStructural/modelling/features/fold/_foldframe.py +194 -0
- LoopStructural/modelling/features/fold/_svariogram.py +188 -0
- LoopStructural/modelling/input/__init__.py +2 -0
- LoopStructural/modelling/input/fault_network.py +80 -0
- LoopStructural/modelling/input/map2loop_processor.py +165 -0
- LoopStructural/modelling/input/process_data.py +650 -0
- LoopStructural/modelling/input/project_file.py +84 -0
- LoopStructural/modelling/intrusions/__init__.py +25 -0
- LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
- LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
- LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
- LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
- LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
- LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
- LoopStructural/utils/__init__.py +38 -0
- LoopStructural/utils/_surface.py +143 -0
- LoopStructural/utils/_transformation.py +76 -0
- LoopStructural/utils/config.py +18 -0
- LoopStructural/utils/dtm_creator.py +17 -0
- LoopStructural/utils/exceptions.py +31 -0
- LoopStructural/utils/helper.py +292 -0
- LoopStructural/utils/json_encoder.py +18 -0
- LoopStructural/utils/linalg.py +8 -0
- LoopStructural/utils/logging.py +79 -0
- LoopStructural/utils/maths.py +245 -0
- LoopStructural/utils/regions.py +103 -0
- LoopStructural/utils/typing.py +7 -0
- LoopStructural/utils/utils.py +68 -0
- LoopStructural/version.py +1 -0
- LoopStructural/visualisation/__init__.py +11 -0
- LoopStructural-1.6.1.dist-info/LICENSE +21 -0
- LoopStructural-1.6.1.dist-info/METADATA +81 -0
- LoopStructural-1.6.1.dist-info/RECORD +129 -0
- LoopStructural-1.6.1.dist-info/WHEEL +5 -0
- LoopStructural-1.6.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tetmesh based on cartesian grid for piecewise linear interpolation
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from ast import Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.sparse import csr_matrix, coo_matrix, tril
|
|
10
|
+
|
|
11
|
+
from . import StructuredGrid
|
|
12
|
+
from LoopStructural.utils import getLogger
|
|
13
|
+
from . import SupportType
|
|
14
|
+
from ._base_support import BaseSupport
|
|
15
|
+
|
|
16
|
+
logger = getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UnStructuredTetMesh(BaseSupport):
|
|
20
|
+
""" """
|
|
21
|
+
|
|
22
|
+
dimension = 3
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
nodes: np.ndarray,
|
|
27
|
+
elements: np.ndarray,
|
|
28
|
+
neighbours: np.ndarray,
|
|
29
|
+
aabb_nsteps=None,
|
|
30
|
+
):
|
|
31
|
+
"""An unstructured mesh defined by nodes, elements and neighbours
|
|
32
|
+
An axis aligned bounding box (AABB) is used to speed up finding
|
|
33
|
+
which tetra a point is in.
|
|
34
|
+
The aabb grid is calculated so that there are approximately 10 tetra per
|
|
35
|
+
element.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
nodes : array or array like
|
|
40
|
+
container of vertex locations
|
|
41
|
+
elements : array or array like, dtype cast to long
|
|
42
|
+
container of tetra indicies
|
|
43
|
+
neighbours : array or array like, dtype cast to long
|
|
44
|
+
array containing element neighbours
|
|
45
|
+
aabb_nsteps : list, optional
|
|
46
|
+
force nsteps for aabb, by default None
|
|
47
|
+
"""
|
|
48
|
+
self.type = SupportType.UnStructuredTetMesh
|
|
49
|
+
self._nodes = np.array(nodes)
|
|
50
|
+
if self._nodes.shape[1] != 3:
|
|
51
|
+
raise ValueError("Nodes must be 3D")
|
|
52
|
+
self.neighbours = np.array(neighbours, dtype=np.int64)
|
|
53
|
+
if self.neighbours.shape[1] != 4:
|
|
54
|
+
raise ValueError("Neighbours array is too big")
|
|
55
|
+
self._elements = np.array(elements, dtype=np.int64)
|
|
56
|
+
if self.elements.shape[0] != self.neighbours.shape[0]:
|
|
57
|
+
raise ValueError("Number of elements and neighbours do not match")
|
|
58
|
+
self._barycentre = np.sum(self.nodes[self.elements[:, :4]][:, :, :], axis=1) / 4.0
|
|
59
|
+
self.minimum = np.min(self.nodes, axis=0)
|
|
60
|
+
self.maximum = np.max(self.nodes, axis=0)
|
|
61
|
+
length = self.maximum - self.minimum
|
|
62
|
+
self.minimum -= length * 0.1
|
|
63
|
+
self.maximum += length * 0.1
|
|
64
|
+
if aabb_nsteps is None:
|
|
65
|
+
box_vol = np.prod(self.maximum - self.minimum)
|
|
66
|
+
element_volume = box_vol / (len(self.elements) / 20)
|
|
67
|
+
# calculate the step vector of a regular cube
|
|
68
|
+
step_vector = np.zeros(3)
|
|
69
|
+
step_vector[:] = element_volume ** (1.0 / 3.0)
|
|
70
|
+
# number of steps is the length of the box / step vector
|
|
71
|
+
aabb_nsteps = np.ceil((self.maximum - self.minimum) / step_vector).astype(int)
|
|
72
|
+
# make sure there is at least one cell in every dimension
|
|
73
|
+
aabb_nsteps[aabb_nsteps < 2] = 2
|
|
74
|
+
aabb_nsteps = np.array(aabb_nsteps, dtype=int)
|
|
75
|
+
step_vector = (self.maximum - self.minimum) / (aabb_nsteps - 1)
|
|
76
|
+
self.aabb_grid = StructuredGrid(self.minimum, nsteps=aabb_nsteps, step_vector=step_vector)
|
|
77
|
+
# make a big table to store which tetra are in which element.
|
|
78
|
+
# if this takes up too much memory it could be simplified by using sparse matrices or dict but
|
|
79
|
+
# at the expense of speed
|
|
80
|
+
self.aabb_table = csr_matrix((self.aabb_grid.n_elements, len(self.elements)), dtype=bool)
|
|
81
|
+
self.shared_element_relationships = np.zeros(
|
|
82
|
+
(self.neighbours[self.neighbours >= 0].flatten().shape[0], 2), dtype=int
|
|
83
|
+
)
|
|
84
|
+
self.shared_elements = np.zeros(
|
|
85
|
+
(self.neighbours[self.neighbours >= 0].flatten().shape[0], 3), dtype=int
|
|
86
|
+
)
|
|
87
|
+
self._init_face_table()
|
|
88
|
+
self._initialise_aabb()
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def nodes(self):
|
|
92
|
+
return self._nodes
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def elements(self):
|
|
96
|
+
return self._elements
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def barycentre(self):
|
|
100
|
+
return self._barycentre
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def n_nodes(self):
|
|
104
|
+
return self.nodes.shape[0]
|
|
105
|
+
|
|
106
|
+
def onGeometryChange(self):
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
def _init_face_table(self):
|
|
110
|
+
"""
|
|
111
|
+
Fill table containing elements that share a face, and another
|
|
112
|
+
table that contains the nodes for a face.
|
|
113
|
+
"""
|
|
114
|
+
# need to identify the shared nodes for pairs of elements
|
|
115
|
+
# we do this by creating a sparse matrix that has N rows (number of elements)
|
|
116
|
+
# and M columns (number of nodes).
|
|
117
|
+
# We then fill the location where a node is in an element with true
|
|
118
|
+
# Then we create a table for the pairs of elements in the mesh
|
|
119
|
+
# we have the neighbour relationships, which are the 4 neighbours for each element
|
|
120
|
+
# create a new table that shows the element index repeated four times
|
|
121
|
+
# flatten both of these arrays so we effectively have a table with pairs of neighbours
|
|
122
|
+
# disgard the negative neighbours because these are border neighbours
|
|
123
|
+
rows = np.tile(np.arange(self.n_elements)[:, None], (1, 4))
|
|
124
|
+
elements = self.get_elements()
|
|
125
|
+
neighbours = self.get_neighbours()
|
|
126
|
+
# add array of bool to the location where there are elements for each node
|
|
127
|
+
|
|
128
|
+
# use this to determine shared faces
|
|
129
|
+
|
|
130
|
+
element_nodes = coo_matrix(
|
|
131
|
+
(np.ones(elements.shape[0] * 4), (rows.ravel(), elements[:, :4].ravel())),
|
|
132
|
+
shape=(self.n_elements, self.n_nodes),
|
|
133
|
+
dtype=bool,
|
|
134
|
+
).tocsr()
|
|
135
|
+
n1 = np.tile(np.arange(neighbours.shape[0], dtype=int)[:, None], (1, 4))
|
|
136
|
+
n1 = n1.flatten()
|
|
137
|
+
n2 = neighbours.flatten()
|
|
138
|
+
n1 = n1[n2 >= 0]
|
|
139
|
+
n2 = n2[n2 >= 0]
|
|
140
|
+
el_rel = np.zeros((self.neighbours.flatten().shape[0], 2), dtype=int)
|
|
141
|
+
el_rel[:] = -1
|
|
142
|
+
el_rel[np.arange(n1.shape[0]), 0] = n1
|
|
143
|
+
el_rel[np.arange(n1.shape[0]), 1] = n2
|
|
144
|
+
el_rel = el_rel[el_rel[:, 0] >= 0, :]
|
|
145
|
+
|
|
146
|
+
# el_rel2 = np.zeros((self.neighbours.flatten().shape[0], 2), dtype=int)
|
|
147
|
+
self.shared_element_relationships[:] = -1
|
|
148
|
+
el_pairs = coo_matrix((np.ones(el_rel.shape[0]), (el_rel[:, 0], el_rel[:, 1]))).tocsr()
|
|
149
|
+
i, j = tril(el_pairs).nonzero()
|
|
150
|
+
self.shared_element_relationships[: len(i), 0] = i
|
|
151
|
+
self.shared_element_relationships[: len(i), 1] = j
|
|
152
|
+
|
|
153
|
+
self.shared_element_relationships = self.shared_element_relationships[
|
|
154
|
+
self.shared_element_relationships[:, 0] >= 0, :
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
faces = element_nodes[self.shared_element_relationships[:, 0], :].multiply(
|
|
158
|
+
element_nodes[self.shared_element_relationships[:, 1], :]
|
|
159
|
+
)
|
|
160
|
+
shared_faces = faces[np.array(np.sum(faces, axis=1) == 3).flatten(), :]
|
|
161
|
+
row, col = shared_faces.nonzero()
|
|
162
|
+
row = row[row.argsort()]
|
|
163
|
+
col = col[row.argsort()]
|
|
164
|
+
shared_face_index = np.zeros((shared_faces.shape[0], 3), dtype=int)
|
|
165
|
+
shared_face_index[:] = -1
|
|
166
|
+
shared_face_index[row.reshape(-1, 3)[:, 0], :] = col.reshape(-1, 3)
|
|
167
|
+
|
|
168
|
+
self.shared_elements[np.arange(self.shared_element_relationships.shape[0]), :] = (
|
|
169
|
+
shared_face_index
|
|
170
|
+
)
|
|
171
|
+
# resize
|
|
172
|
+
self.shared_elements = self.shared_elements[: len(self.shared_element_relationships), :]
|
|
173
|
+
# flag = np.zeros(self.elements.shape[0])
|
|
174
|
+
# face_index = 0
|
|
175
|
+
# for i, t in enumerate(self.elements):
|
|
176
|
+
# flag[i] = True
|
|
177
|
+
# for n in self.neighbours[i]:
|
|
178
|
+
# if n < 0:
|
|
179
|
+
# continue
|
|
180
|
+
# if flag[n]:
|
|
181
|
+
# continue
|
|
182
|
+
# face_node_index = 0
|
|
183
|
+
# self.shared_element_relationships[face_index, 0] = i
|
|
184
|
+
# self.shared_element_relationships[face_index, 1] = n
|
|
185
|
+
# for v in t:
|
|
186
|
+
# if v in self.elements[n, :4]:
|
|
187
|
+
# self.shared_elements[face_index, face_node_index] = v
|
|
188
|
+
# face_node_index += 1
|
|
189
|
+
|
|
190
|
+
# face_index += 1
|
|
191
|
+
# self.shared_elements = self.shared_elements[:face_index, :]
|
|
192
|
+
# self.shared_element_relationships = self.shared_element_relationships[
|
|
193
|
+
# :face_index, :
|
|
194
|
+
# ]
|
|
195
|
+
|
|
196
|
+
def _initialise_aabb(self):
|
|
197
|
+
"""assigns the tetras to the grid cells where the bounding box
|
|
198
|
+
of the tetra element overlaps the grid cell.
|
|
199
|
+
It could be changed to use the separating axis theorem, however this would require
|
|
200
|
+
significantly more calculations. (12 more I think).. #TODO test timing
|
|
201
|
+
"""
|
|
202
|
+
# calculate the bounding box for all tetraherdon in the mesh
|
|
203
|
+
# find the min/max extents for xyz
|
|
204
|
+
# tetra_bb = np.zeros((self.elements.shape[0], 19, 3))
|
|
205
|
+
minx = np.min(self.nodes[self.elements[:, :4], 0], axis=1)
|
|
206
|
+
maxx = np.max(self.nodes[self.elements[:, :4], 0], axis=1)
|
|
207
|
+
miny = np.min(self.nodes[self.elements[:, :4], 1], axis=1)
|
|
208
|
+
maxy = np.max(self.nodes[self.elements[:, :4], 1], axis=1)
|
|
209
|
+
minz = np.min(self.nodes[self.elements[:, :4], 2], axis=1)
|
|
210
|
+
maxz = np.max(self.nodes[self.elements[:, :4], 2], axis=1)
|
|
211
|
+
cell_indexes = self.aabb_grid.global_index_to_cell_index(
|
|
212
|
+
np.arange(self.aabb_grid.n_elements)
|
|
213
|
+
)
|
|
214
|
+
corners = self.aabb_grid.cell_corner_indexes(cell_indexes)
|
|
215
|
+
positions = self.aabb_grid.node_indexes_to_position(corners)
|
|
216
|
+
## Because we known the node orders just select min/max from each
|
|
217
|
+
# coordinate. Use these to check whether the tetra is in the cell
|
|
218
|
+
x_boundary = positions[:, [0, 1], 0]
|
|
219
|
+
y_boundary = positions[:, [0, 2], 1]
|
|
220
|
+
z_boundary = positions[:, [0, 6], 2]
|
|
221
|
+
a = np.logical_and(
|
|
222
|
+
minx[None, :] > x_boundary[:, None, 0],
|
|
223
|
+
minx[None, :] < x_boundary[:, None, 1],
|
|
224
|
+
) # min point between cell
|
|
225
|
+
b = np.logical_and(
|
|
226
|
+
maxx[None, :] < x_boundary[:, None, 1],
|
|
227
|
+
maxx[None, :] > x_boundary[:, None, 0],
|
|
228
|
+
) # max point between cell
|
|
229
|
+
c = np.logical_and(
|
|
230
|
+
minx[None, :] < x_boundary[:, None, 0],
|
|
231
|
+
maxx[None, :] > x_boundary[:, None, 0],
|
|
232
|
+
) # min point < than cell & max point > cell
|
|
233
|
+
|
|
234
|
+
x_logic = np.logical_or(np.logical_or(a, b), c)
|
|
235
|
+
|
|
236
|
+
a = np.logical_and(
|
|
237
|
+
miny[None, :] > y_boundary[:, None, 0],
|
|
238
|
+
miny[None, :] < y_boundary[:, None, 1],
|
|
239
|
+
) # min point between cell
|
|
240
|
+
b = np.logical_and(
|
|
241
|
+
maxy[None, :] < y_boundary[:, None, 1],
|
|
242
|
+
maxy[None, :] > y_boundary[:, None, 0],
|
|
243
|
+
) # max point between cell
|
|
244
|
+
c = np.logical_and(
|
|
245
|
+
miny[None, :] < y_boundary[:, None, 0],
|
|
246
|
+
maxy[None, :] > y_boundary[:, None, 0],
|
|
247
|
+
) # min point < than cell & max point > cell
|
|
248
|
+
|
|
249
|
+
y_logic = np.logical_or(np.logical_or(a, b), c)
|
|
250
|
+
|
|
251
|
+
a = np.logical_and(
|
|
252
|
+
minz[None, :] > z_boundary[:, None, 0],
|
|
253
|
+
minz[None, :] < z_boundary[:, None, 1],
|
|
254
|
+
) # min point between cell
|
|
255
|
+
b = np.logical_and(
|
|
256
|
+
maxz[None, :] < z_boundary[:, None, 1],
|
|
257
|
+
maxz[None, :] > z_boundary[:, None, 0],
|
|
258
|
+
) # max point between cell
|
|
259
|
+
c = np.logical_and(
|
|
260
|
+
minz[None, :] < z_boundary[:, None, 0],
|
|
261
|
+
maxz[None, :] > z_boundary[:, None, 0],
|
|
262
|
+
) # min point < than cell & max point > cell
|
|
263
|
+
|
|
264
|
+
z_logic = np.logical_or(np.logical_or(a, b), c)
|
|
265
|
+
logic = np.logical_and(x_logic, y_logic)
|
|
266
|
+
logic = np.logical_and(logic, z_logic)
|
|
267
|
+
|
|
268
|
+
self.aabb_table = csr_matrix(logic)
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def ntetra(self):
|
|
272
|
+
return self.elements.shape[0]
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def n_elements(self):
|
|
276
|
+
return self.ntetra
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def n_cells(self):
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def shared_element_norm(self):
|
|
284
|
+
"""
|
|
285
|
+
Get the normal to all of the shared elements
|
|
286
|
+
"""
|
|
287
|
+
elements = self.shared_elements
|
|
288
|
+
v1 = self.nodes[elements[:, 1], :] - self.nodes[elements[:, 0], :]
|
|
289
|
+
v2 = self.nodes[elements[:, 2], :] - self.nodes[elements[:, 0], :]
|
|
290
|
+
return np.cross(v1, v2, axisa=1, axisb=1)
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def shared_element_size(self):
|
|
294
|
+
"""
|
|
295
|
+
Get the area of the share triangle
|
|
296
|
+
"""
|
|
297
|
+
norm = self.shared_element_norm
|
|
298
|
+
return 0.5 * np.linalg.norm(norm, axis=1)
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def element_size(self):
|
|
302
|
+
"""Calculate the volume of a tetrahedron using the 4 corners
|
|
303
|
+
volume = abs(det(A))/6 where A is the jacobian of the corners
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
_type_
|
|
308
|
+
_description_
|
|
309
|
+
"""
|
|
310
|
+
vecs = (
|
|
311
|
+
self.nodes[self.elements[:, :4], :][:, 1:, :]
|
|
312
|
+
- self.nodes[self.elements[:, :4], :][:, 0, None, :]
|
|
313
|
+
)
|
|
314
|
+
return np.abs(np.linalg.det(vecs)) / 6
|
|
315
|
+
|
|
316
|
+
def evaluate_shape_derivatives(self, locations, elements=None):
|
|
317
|
+
"""
|
|
318
|
+
Get the gradients of all tetras
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
elements
|
|
323
|
+
|
|
324
|
+
Returns
|
|
325
|
+
-------
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
inside = None
|
|
329
|
+
if elements is not None:
|
|
330
|
+
inside = np.zeros(self.n_elements, dtype=bool)
|
|
331
|
+
inside[elements] = True
|
|
332
|
+
if elements is None:
|
|
333
|
+
verts, c, elements, inside = self.get_element_for_location(locations)
|
|
334
|
+
# elements = np.arange(0, self.n_elements, dtype=int)
|
|
335
|
+
ps = self.nodes[self.elements, :]
|
|
336
|
+
m = np.array(
|
|
337
|
+
[
|
|
338
|
+
[
|
|
339
|
+
(ps[:, 1, 0] - ps[:, 0, 0]),
|
|
340
|
+
(ps[:, 1, 1] - ps[:, 0, 1]),
|
|
341
|
+
(ps[:, 1, 2] - ps[:, 0, 2]),
|
|
342
|
+
],
|
|
343
|
+
[
|
|
344
|
+
(ps[:, 2, 0] - ps[:, 0, 0]),
|
|
345
|
+
(ps[:, 2, 1] - ps[:, 0, 1]),
|
|
346
|
+
(ps[:, 2, 2] - ps[:, 0, 2]),
|
|
347
|
+
],
|
|
348
|
+
[
|
|
349
|
+
(ps[:, 3, 0] - ps[:, 0, 0]),
|
|
350
|
+
(ps[:, 3, 1] - ps[:, 0, 1]),
|
|
351
|
+
(ps[:, 3, 2] - ps[:, 0, 2]),
|
|
352
|
+
],
|
|
353
|
+
]
|
|
354
|
+
)
|
|
355
|
+
I = np.array([[-1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]])
|
|
356
|
+
m = np.swapaxes(m, 0, 2)
|
|
357
|
+
element_gradients = np.linalg.inv(m)
|
|
358
|
+
|
|
359
|
+
element_gradients = element_gradients.swapaxes(1, 2)
|
|
360
|
+
element_gradients = element_gradients @ I
|
|
361
|
+
|
|
362
|
+
return element_gradients[elements, :, :], elements, inside
|
|
363
|
+
|
|
364
|
+
def evaluate_shape(self, locations):
|
|
365
|
+
"""
|
|
366
|
+
Convenience function returning barycentric coords
|
|
367
|
+
|
|
368
|
+
"""
|
|
369
|
+
locations = np.array(locations)
|
|
370
|
+
verts, c, elements, inside = self.get_element_for_location(locations)
|
|
371
|
+
return c, elements, inside
|
|
372
|
+
|
|
373
|
+
def evaluate_value(self, pos, property_array):
|
|
374
|
+
"""
|
|
375
|
+
Evaluate value of interpolant
|
|
376
|
+
|
|
377
|
+
Parameters
|
|
378
|
+
----------
|
|
379
|
+
pos - numpy array
|
|
380
|
+
locations
|
|
381
|
+
prop - string
|
|
382
|
+
property name
|
|
383
|
+
|
|
384
|
+
Returns
|
|
385
|
+
-------
|
|
386
|
+
|
|
387
|
+
"""
|
|
388
|
+
values = np.zeros(pos.shape[0])
|
|
389
|
+
values[:] = np.nan
|
|
390
|
+
vertices, c, tetras, inside = self.get_element_for_location(pos)
|
|
391
|
+
values[inside] = np.sum(
|
|
392
|
+
c[inside, :] * property_array[self.elements[tetras[inside], :]], axis=1
|
|
393
|
+
)
|
|
394
|
+
return values
|
|
395
|
+
|
|
396
|
+
def evaluate_gradient(self, pos, property_array):
|
|
397
|
+
"""
|
|
398
|
+
Evaluate the gradient of an interpolant at the locations
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
pos - numpy array
|
|
403
|
+
locations
|
|
404
|
+
prop - string
|
|
405
|
+
property to evaluate
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
Returns
|
|
409
|
+
-------
|
|
410
|
+
|
|
411
|
+
"""
|
|
412
|
+
values = np.zeros(pos.shape)
|
|
413
|
+
values[:] = np.nan
|
|
414
|
+
(
|
|
415
|
+
vertices,
|
|
416
|
+
element_gradients,
|
|
417
|
+
tetras,
|
|
418
|
+
inside,
|
|
419
|
+
) = self.get_element_gradient_for_location(pos)
|
|
420
|
+
# grads = np.zeros(tetras.shape)
|
|
421
|
+
values[inside, :] = (
|
|
422
|
+
element_gradients[inside, :, :] * property_array[self.elements[tetras][inside, None, :]]
|
|
423
|
+
).sum(2)
|
|
424
|
+
# length = np.sum(values[inside, :], axis=1)
|
|
425
|
+
# values[inside,:] /= length[:,None]
|
|
426
|
+
return values
|
|
427
|
+
|
|
428
|
+
def inside(self, pos):
|
|
429
|
+
if pos.shape[1] > 3:
|
|
430
|
+
logger.warning(f"Converting {pos.shape[1]} to 3d using first 3 columns")
|
|
431
|
+
pos = pos[:, :3]
|
|
432
|
+
|
|
433
|
+
inside = np.ones(pos.shape[0]).astype(bool)
|
|
434
|
+
for i in range(3):
|
|
435
|
+
inside *= pos[:, i] > self.origin[None, i]
|
|
436
|
+
inside *= (
|
|
437
|
+
pos[:, i]
|
|
438
|
+
< self.origin[None, i] + self.step_vector[None, i] * self.nsteps_cells[None, i]
|
|
439
|
+
)
|
|
440
|
+
return inside
|
|
441
|
+
|
|
442
|
+
def get_elements(self):
|
|
443
|
+
return self.elements
|
|
444
|
+
|
|
445
|
+
def get_element_for_location(self, points: np.ndarray) -> Tuple:
|
|
446
|
+
"""
|
|
447
|
+
Determine the tetrahedron from a numpy array of points
|
|
448
|
+
|
|
449
|
+
Parameters
|
|
450
|
+
----------
|
|
451
|
+
pos : np.array
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
Returns
|
|
456
|
+
-------
|
|
457
|
+
|
|
458
|
+
"""
|
|
459
|
+
verts = np.zeros((points.shape[0], 4, 3))
|
|
460
|
+
bc = np.zeros((points.shape[0], 4))
|
|
461
|
+
tetras = np.zeros(points.shape[0], dtype="int64")
|
|
462
|
+
inside = np.zeros(points.shape[0], dtype=bool)
|
|
463
|
+
npts = 0
|
|
464
|
+
npts_step = int(1e4)
|
|
465
|
+
# break into blocks of 10k points
|
|
466
|
+
while npts < points.shape[0]:
|
|
467
|
+
cell_index, inside = self.aabb_grid.position_to_cell_index(
|
|
468
|
+
points[: npts + npts_step, :]
|
|
469
|
+
)
|
|
470
|
+
global_index = (
|
|
471
|
+
cell_index[:, 0]
|
|
472
|
+
+ self.aabb_grid.nsteps_cells[None, 0] * cell_index[:, 1]
|
|
473
|
+
+ self.aabb_grid.nsteps_cells[None, 0]
|
|
474
|
+
* self.aabb_grid.nsteps_cells[None, 1]
|
|
475
|
+
* cell_index[:, 2]
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
tetra_indices = self.aabb_table[global_index[inside], :].tocoo()
|
|
479
|
+
# tetra_indices[:] = -1
|
|
480
|
+
row = tetra_indices.row
|
|
481
|
+
col = tetra_indices.col
|
|
482
|
+
# using returned indexes calculate barycentric coords to determine which tetra the points are in
|
|
483
|
+
vertices = self.nodes[self.elements[col, :4]]
|
|
484
|
+
pos = points[row, :]
|
|
485
|
+
vap = pos[:, :] - vertices[:, 0, :]
|
|
486
|
+
vbp = pos[:, :] - vertices[:, 1, :]
|
|
487
|
+
# # vcp = p - points[:, 2, :]
|
|
488
|
+
# # vdp = p - points[:, 3, :]
|
|
489
|
+
vab = vertices[:, 1, :] - vertices[:, 0, :]
|
|
490
|
+
vac = vertices[:, 2, :] - vertices[:, 0, :]
|
|
491
|
+
vad = vertices[:, 3, :] - vertices[:, 0, :]
|
|
492
|
+
vbc = vertices[:, 2, :] - vertices[:, 1, :]
|
|
493
|
+
vbd = vertices[:, 3, :] - vertices[:, 1, :]
|
|
494
|
+
|
|
495
|
+
va = np.einsum("ij, ij->i", vbp, np.cross(vbd, vbc, axisa=1, axisb=1)) / 6.0
|
|
496
|
+
vb = np.einsum("ij, ij->i", vap, np.cross(vac, vad, axisa=1, axisb=1)) / 6.0
|
|
497
|
+
vc = np.einsum("ij, ij->i", vap, np.cross(vad, vab, axisa=1, axisb=1)) / 6.0
|
|
498
|
+
vd = np.einsum("ij, ij->i", vap, np.cross(vab, vac, axisa=1, axisb=1)) / 6.0
|
|
499
|
+
v = np.einsum("ij, ij->i", vab, np.cross(vac, vad, axisa=1, axisb=1)) / 6.0
|
|
500
|
+
c = np.zeros((va.shape[0], 4))
|
|
501
|
+
c[:, 0] = va / v
|
|
502
|
+
c[:, 1] = vb / v
|
|
503
|
+
c[:, 2] = vc / v
|
|
504
|
+
c[:, 3] = vd / v
|
|
505
|
+
# inside = np.ones(c.shape[0],dtype=bool)
|
|
506
|
+
mask = np.all(c >= 0, axis=1)
|
|
507
|
+
|
|
508
|
+
verts[: npts + npts_step, :, :][row[mask], :, :] = vertices[mask, :, :]
|
|
509
|
+
bc[: npts + npts_step, :][row[mask], :] = c[mask, :]
|
|
510
|
+
tetras[: npts + npts_step][row[mask]] = col[mask]
|
|
511
|
+
inside[: npts + npts_step][row[mask]] = True
|
|
512
|
+
npts += npts_step
|
|
513
|
+
tetra_return = np.zeros((points.shape[0])).astype(int)
|
|
514
|
+
tetra_return[:] = -1
|
|
515
|
+
|
|
516
|
+
tetra_return[inside] = tetras[inside]
|
|
517
|
+
return verts, bc, tetra_return, inside
|
|
518
|
+
|
|
519
|
+
def get_element_gradients(self, elements=None):
|
|
520
|
+
"""
|
|
521
|
+
Get the gradients of all tetras
|
|
522
|
+
|
|
523
|
+
Parameters
|
|
524
|
+
----------
|
|
525
|
+
elements
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
|
|
530
|
+
"""
|
|
531
|
+
# points = np.zeros((5, 4, self.n_cells, 3))
|
|
532
|
+
# points[:, :, even_mask, :] = nodes[:, even_mask, :][self.tetra_mask_even, :, :]
|
|
533
|
+
# points[:, :, ~even_mask, :] = nodes[:, ~even_mask, :][self.tetra_mask, :, :]
|
|
534
|
+
|
|
535
|
+
# # changing order to points, tetra, nodes, coord
|
|
536
|
+
# points = points.swapaxes(0, 2)
|
|
537
|
+
# points = points.swapaxes(1, 2)
|
|
538
|
+
if elements is None:
|
|
539
|
+
elements = np.arange(0, self.n_elements, dtype=int)
|
|
540
|
+
ps = self.nodes[
|
|
541
|
+
self.elements, :
|
|
542
|
+
] # points.reshape(points.shape[0] * points.shape[1], points.shape[2], points.shape[3])
|
|
543
|
+
# vertices = self.nodes[self.elements[col,:]]
|
|
544
|
+
m = np.array(
|
|
545
|
+
[
|
|
546
|
+
[
|
|
547
|
+
(ps[:, 1, 0] - ps[:, 0, 0]),
|
|
548
|
+
(ps[:, 1, 1] - ps[:, 0, 1]),
|
|
549
|
+
(ps[:, 1, 2] - ps[:, 0, 2]),
|
|
550
|
+
],
|
|
551
|
+
[
|
|
552
|
+
(ps[:, 2, 0] - ps[:, 0, 0]),
|
|
553
|
+
(ps[:, 2, 1] - ps[:, 0, 1]),
|
|
554
|
+
(ps[:, 2, 2] - ps[:, 0, 2]),
|
|
555
|
+
],
|
|
556
|
+
[
|
|
557
|
+
(ps[:, 3, 0] - ps[:, 0, 0]),
|
|
558
|
+
(ps[:, 3, 1] - ps[:, 0, 1]),
|
|
559
|
+
(ps[:, 3, 2] - ps[:, 0, 2]),
|
|
560
|
+
],
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
I = np.array([[-1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]])
|
|
564
|
+
m = np.swapaxes(m, 0, 2)
|
|
565
|
+
element_gradients = np.linalg.inv(m)
|
|
566
|
+
|
|
567
|
+
element_gradients = element_gradients.swapaxes(1, 2)
|
|
568
|
+
element_gradients = element_gradients @ I
|
|
569
|
+
|
|
570
|
+
return element_gradients[elements, :, :]
|
|
571
|
+
|
|
572
|
+
def get_element_gradient_for_location(self, pos):
|
|
573
|
+
"""
|
|
574
|
+
Get the gradient of the tetra for a location
|
|
575
|
+
|
|
576
|
+
Parameters
|
|
577
|
+
----------
|
|
578
|
+
pos
|
|
579
|
+
|
|
580
|
+
Returns
|
|
581
|
+
-------
|
|
582
|
+
|
|
583
|
+
"""
|
|
584
|
+
vertices, bc, tetras, inside = self.get_element_for_location(pos)
|
|
585
|
+
ps = vertices
|
|
586
|
+
m = np.array(
|
|
587
|
+
[
|
|
588
|
+
[
|
|
589
|
+
(ps[:, 1, 0] - ps[:, 0, 0]),
|
|
590
|
+
(ps[:, 1, 1] - ps[:, 0, 1]),
|
|
591
|
+
(ps[:, 1, 2] - ps[:, 0, 2]),
|
|
592
|
+
],
|
|
593
|
+
[
|
|
594
|
+
(ps[:, 2, 0] - ps[:, 0, 0]),
|
|
595
|
+
(ps[:, 2, 1] - ps[:, 0, 1]),
|
|
596
|
+
(ps[:, 2, 2] - ps[:, 0, 2]),
|
|
597
|
+
],
|
|
598
|
+
[
|
|
599
|
+
(ps[:, 3, 0] - ps[:, 0, 0]),
|
|
600
|
+
(ps[:, 3, 1] - ps[:, 0, 1]),
|
|
601
|
+
(ps[:, 3, 2] - ps[:, 0, 2]),
|
|
602
|
+
],
|
|
603
|
+
]
|
|
604
|
+
)
|
|
605
|
+
I = np.array([[-1.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 1.0, 0.0], [-1.0, 0.0, 0.0, 1.0]])
|
|
606
|
+
m = np.swapaxes(m, 0, 2)
|
|
607
|
+
element_gradients = np.linalg.inv(m)
|
|
608
|
+
|
|
609
|
+
element_gradients = element_gradients.swapaxes(1, 2)
|
|
610
|
+
element_gradients = element_gradients @ I
|
|
611
|
+
return vertices, element_gradients, tetras, inside
|
|
612
|
+
|
|
613
|
+
def get_neighbours(self):
|
|
614
|
+
"""
|
|
615
|
+
This function goes through all of the elements in the mesh and assembles a numpy array
|
|
616
|
+
with the neighbours for each element
|
|
617
|
+
|
|
618
|
+
Returns
|
|
619
|
+
-------
|
|
620
|
+
|
|
621
|
+
"""
|
|
622
|
+
return self.neighbours
|
|
623
|
+
|
|
624
|
+
def vtk(self):
|
|
625
|
+
try:
|
|
626
|
+
import pyvista as pv
|
|
627
|
+
except ImportError:
|
|
628
|
+
raise ImportError("pyvista is required for vtk support")
|
|
629
|
+
|
|
630
|
+
from pyvista import CellType
|
|
631
|
+
|
|
632
|
+
celltype = np.full(self.elements.shape[0], CellType.TETRA, dtype=np.uint8)
|
|
633
|
+
elements = np.hstack(
|
|
634
|
+
[np.zeros(self.elements.shape[0], dtype=int)[:, None] + 4, self.elements]
|
|
635
|
+
)
|
|
636
|
+
elements = elements.flatten()
|
|
637
|
+
return pv.UnstructuredGrid(elements, celltype, self.nodes)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SupportType(IntEnum):
|
|
5
|
+
"""
|
|
6
|
+
Enum for the different interpolator types
|
|
7
|
+
|
|
8
|
+
1-9 should cover interpolators with supports
|
|
9
|
+
9+ are data supported
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
StructuredGrid2D = 0
|
|
13
|
+
StructuredGrid = 1
|
|
14
|
+
UnStructuredTetMesh = 2
|
|
15
|
+
P1Unstructured2d = 3
|
|
16
|
+
P2Unstructured2d = 4
|
|
17
|
+
BaseUnstructured2d = 5
|
|
18
|
+
BaseStructured = 6
|
|
19
|
+
TetMesh = 10
|
|
20
|
+
P2UnstructuredTetMesh = 11
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
from ._2d_base_unstructured import BaseUnstructured2d
|
|
24
|
+
from ._2d_p1_unstructured import P1Unstructured2d
|
|
25
|
+
from ._2d_p2_unstructured import P2Unstructured2d
|
|
26
|
+
from ._2d_structured_grid import StructuredGrid2D
|
|
27
|
+
from ._3d_structured_grid import StructuredGrid
|
|
28
|
+
from ._3d_unstructured_tetra import UnStructuredTetMesh
|
|
29
|
+
from ._3d_structured_tetra import TetMesh
|
|
30
|
+
from ._3d_p2_tetra import P2UnstructuredTetMesh
|
|
31
|
+
|
|
32
|
+
support_map = {
|
|
33
|
+
SupportType.StructuredGrid2D: StructuredGrid2D,
|
|
34
|
+
SupportType.StructuredGrid: StructuredGrid,
|
|
35
|
+
SupportType.UnStructuredTetMesh: UnStructuredTetMesh,
|
|
36
|
+
SupportType.P1Unstructured2d: P1Unstructured2d,
|
|
37
|
+
SupportType.P2Unstructured2d: P2Unstructured2d,
|
|
38
|
+
SupportType.TetMesh: TetMesh,
|
|
39
|
+
SupportType.P2UnstructuredTetMesh: P2UnstructuredTetMesh,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
from ._support_factory import SupportFactory
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"BaseUnstructured2d",
|
|
46
|
+
"P1Unstructured2d",
|
|
47
|
+
"P2Unstructured2d",
|
|
48
|
+
"StructuredGrid2D",
|
|
49
|
+
"StructuredGrid",
|
|
50
|
+
"UnStructuredTetMesh",
|
|
51
|
+
"TetMesh",
|
|
52
|
+
"P2UnstructuredTetMesh",
|
|
53
|
+
"support_map",
|
|
54
|
+
"SupportType",
|
|
55
|
+
]
|