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.

Files changed (129) hide show
  1. LoopStructural/__init__.py +52 -0
  2. LoopStructural/datasets/__init__.py +23 -0
  3. LoopStructural/datasets/_base.py +301 -0
  4. LoopStructural/datasets/_example_models.py +10 -0
  5. LoopStructural/datasets/data/claudius.csv +21049 -0
  6. LoopStructural/datasets/data/claudiusbb.txt +2 -0
  7. LoopStructural/datasets/data/duplex.csv +126 -0
  8. LoopStructural/datasets/data/duplexbb.txt +2 -0
  9. LoopStructural/datasets/data/fault_trace/fault_trace.cpg +1 -0
  10. LoopStructural/datasets/data/fault_trace/fault_trace.dbf +0 -0
  11. LoopStructural/datasets/data/fault_trace/fault_trace.prj +1 -0
  12. LoopStructural/datasets/data/fault_trace/fault_trace.shp +0 -0
  13. LoopStructural/datasets/data/fault_trace/fault_trace.shx +0 -0
  14. LoopStructural/datasets/data/geological_map_data/bbox.csv +2 -0
  15. LoopStructural/datasets/data/geological_map_data/contacts.csv +657 -0
  16. LoopStructural/datasets/data/geological_map_data/fault_displacement.csv +7 -0
  17. LoopStructural/datasets/data/geological_map_data/fault_edges.txt +2 -0
  18. LoopStructural/datasets/data/geological_map_data/fault_locations.csv +79 -0
  19. LoopStructural/datasets/data/geological_map_data/fault_orientations.csv +19 -0
  20. LoopStructural/datasets/data/geological_map_data/stratigraphic_order.csv +13 -0
  21. LoopStructural/datasets/data/geological_map_data/stratigraphic_orientations.csv +207 -0
  22. LoopStructural/datasets/data/geological_map_data/stratigraphic_thickness.csv +13 -0
  23. LoopStructural/datasets/data/intrusion.csv +1017 -0
  24. LoopStructural/datasets/data/intrusionbb.txt +2 -0
  25. LoopStructural/datasets/data/onefoldbb.txt +2 -0
  26. LoopStructural/datasets/data/onefolddata.csv +2226 -0
  27. LoopStructural/datasets/data/refolded_bb.txt +2 -0
  28. LoopStructural/datasets/data/refolded_fold.csv +205 -0
  29. LoopStructural/datasets/data/tabular_intrusion.csv +23 -0
  30. LoopStructural/datatypes/__init__.py +4 -0
  31. LoopStructural/datatypes/_bounding_box.py +422 -0
  32. LoopStructural/datatypes/_point.py +166 -0
  33. LoopStructural/datatypes/_structured_grid.py +94 -0
  34. LoopStructural/datatypes/_surface.py +184 -0
  35. LoopStructural/export/exporters.py +554 -0
  36. LoopStructural/export/file_formats.py +15 -0
  37. LoopStructural/export/geoh5.py +100 -0
  38. LoopStructural/export/gocad.py +126 -0
  39. LoopStructural/export/omf_wrapper.py +88 -0
  40. LoopStructural/interpolators/__init__.py +105 -0
  41. LoopStructural/interpolators/_api.py +143 -0
  42. LoopStructural/interpolators/_builders.py +149 -0
  43. LoopStructural/interpolators/_cython/__init__.py +0 -0
  44. LoopStructural/interpolators/_discrete_fold_interpolator.py +183 -0
  45. LoopStructural/interpolators/_discrete_interpolator.py +692 -0
  46. LoopStructural/interpolators/_finite_difference_interpolator.py +470 -0
  47. LoopStructural/interpolators/_geological_interpolator.py +380 -0
  48. LoopStructural/interpolators/_interpolator_factory.py +89 -0
  49. LoopStructural/interpolators/_non_linear_discrete_interpolator.py +0 -0
  50. LoopStructural/interpolators/_operator.py +38 -0
  51. LoopStructural/interpolators/_p1interpolator.py +228 -0
  52. LoopStructural/interpolators/_p2interpolator.py +277 -0
  53. LoopStructural/interpolators/_surfe_wrapper.py +174 -0
  54. LoopStructural/interpolators/supports/_2d_base_unstructured.py +340 -0
  55. LoopStructural/interpolators/supports/_2d_p1_unstructured.py +68 -0
  56. LoopStructural/interpolators/supports/_2d_p2_unstructured.py +288 -0
  57. LoopStructural/interpolators/supports/_2d_structured_grid.py +462 -0
  58. LoopStructural/interpolators/supports/_2d_structured_tetra.py +0 -0
  59. LoopStructural/interpolators/supports/_3d_base_structured.py +467 -0
  60. LoopStructural/interpolators/supports/_3d_p2_tetra.py +331 -0
  61. LoopStructural/interpolators/supports/_3d_structured_grid.py +470 -0
  62. LoopStructural/interpolators/supports/_3d_structured_tetra.py +746 -0
  63. LoopStructural/interpolators/supports/_3d_unstructured_tetra.py +637 -0
  64. LoopStructural/interpolators/supports/__init__.py +55 -0
  65. LoopStructural/interpolators/supports/_aabb.py +77 -0
  66. LoopStructural/interpolators/supports/_base_support.py +114 -0
  67. LoopStructural/interpolators/supports/_face_table.py +70 -0
  68. LoopStructural/interpolators/supports/_support_factory.py +32 -0
  69. LoopStructural/modelling/__init__.py +29 -0
  70. LoopStructural/modelling/core/__init__.py +0 -0
  71. LoopStructural/modelling/core/geological_model.py +1867 -0
  72. LoopStructural/modelling/features/__init__.py +32 -0
  73. LoopStructural/modelling/features/_analytical_feature.py +79 -0
  74. LoopStructural/modelling/features/_base_geological_feature.py +364 -0
  75. LoopStructural/modelling/features/_cross_product_geological_feature.py +100 -0
  76. LoopStructural/modelling/features/_geological_feature.py +288 -0
  77. LoopStructural/modelling/features/_lambda_geological_feature.py +93 -0
  78. LoopStructural/modelling/features/_region.py +18 -0
  79. LoopStructural/modelling/features/_structural_frame.py +186 -0
  80. LoopStructural/modelling/features/_unconformity_feature.py +83 -0
  81. LoopStructural/modelling/features/builders/__init__.py +5 -0
  82. LoopStructural/modelling/features/builders/_base_builder.py +111 -0
  83. LoopStructural/modelling/features/builders/_fault_builder.py +590 -0
  84. LoopStructural/modelling/features/builders/_folded_feature_builder.py +129 -0
  85. LoopStructural/modelling/features/builders/_geological_feature_builder.py +543 -0
  86. LoopStructural/modelling/features/builders/_structural_frame_builder.py +237 -0
  87. LoopStructural/modelling/features/fault/__init__.py +3 -0
  88. LoopStructural/modelling/features/fault/_fault_function.py +444 -0
  89. LoopStructural/modelling/features/fault/_fault_function_feature.py +82 -0
  90. LoopStructural/modelling/features/fault/_fault_segment.py +505 -0
  91. LoopStructural/modelling/features/fold/__init__.py +9 -0
  92. LoopStructural/modelling/features/fold/_fold.py +167 -0
  93. LoopStructural/modelling/features/fold/_fold_rotation_angle.py +149 -0
  94. LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py +67 -0
  95. LoopStructural/modelling/features/fold/_foldframe.py +194 -0
  96. LoopStructural/modelling/features/fold/_svariogram.py +188 -0
  97. LoopStructural/modelling/input/__init__.py +2 -0
  98. LoopStructural/modelling/input/fault_network.py +80 -0
  99. LoopStructural/modelling/input/map2loop_processor.py +165 -0
  100. LoopStructural/modelling/input/process_data.py +650 -0
  101. LoopStructural/modelling/input/project_file.py +84 -0
  102. LoopStructural/modelling/intrusions/__init__.py +25 -0
  103. LoopStructural/modelling/intrusions/geom_conceptual_models.py +142 -0
  104. LoopStructural/modelling/intrusions/geometric_scaling_functions.py +123 -0
  105. LoopStructural/modelling/intrusions/intrusion_builder.py +672 -0
  106. LoopStructural/modelling/intrusions/intrusion_feature.py +410 -0
  107. LoopStructural/modelling/intrusions/intrusion_frame_builder.py +971 -0
  108. LoopStructural/modelling/intrusions/intrusion_support_functions.py +460 -0
  109. LoopStructural/utils/__init__.py +38 -0
  110. LoopStructural/utils/_surface.py +143 -0
  111. LoopStructural/utils/_transformation.py +76 -0
  112. LoopStructural/utils/config.py +18 -0
  113. LoopStructural/utils/dtm_creator.py +17 -0
  114. LoopStructural/utils/exceptions.py +31 -0
  115. LoopStructural/utils/helper.py +292 -0
  116. LoopStructural/utils/json_encoder.py +18 -0
  117. LoopStructural/utils/linalg.py +8 -0
  118. LoopStructural/utils/logging.py +79 -0
  119. LoopStructural/utils/maths.py +245 -0
  120. LoopStructural/utils/regions.py +103 -0
  121. LoopStructural/utils/typing.py +7 -0
  122. LoopStructural/utils/utils.py +68 -0
  123. LoopStructural/version.py +1 -0
  124. LoopStructural/visualisation/__init__.py +11 -0
  125. LoopStructural-1.6.1.dist-info/LICENSE +21 -0
  126. LoopStructural-1.6.1.dist-info/METADATA +81 -0
  127. LoopStructural-1.6.1.dist-info/RECORD +129 -0
  128. LoopStructural-1.6.1.dist-info/WHEEL +5 -0
  129. 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
+ ]