1#[cfg(test)]
2mod test;
3
4pub(crate) mod hexahedron;
5pub(crate) mod pyramid;
6mod quadrilateral;
7pub(crate) mod tetrahedron;
8mod triangle;
9pub(crate) mod wedge;
10
11use crate::{
12 geometry::{
13 Coordinate, Coordinates,
14 mesh::{Connectivity, Mesh},
15 },
16 math::{Quantity, Reference, Scalar, Tensor, TensorRank1List, TensorRank2},
17 units::{Area, Length, Volume},
18};
19use std::array::from_fn;
20
21const EQUIANGLE: Scalar = std::f64::consts::FRAC_PI_3;
22
23pub trait Verdict {
24 fn maximum_edge_ratios(&self) -> Vec<Vec<Scalar>>;
25 fn maximum_skews(&self) -> Vec<Vec<Scalar>>;
26 fn minimum_jacobians(&self) -> Vec<Vec<Scalar>>;
27 fn minimum_scaled_jacobians(&self) -> Vec<Vec<Scalar>>;
28 fn volumes(&self) -> Vec<Vec<Scalar>>;
29}
30
31impl<const D: usize> Verdict for Mesh<D> {
32 fn maximum_edge_ratios(&self) -> Vec<Vec<Scalar>> {
33 let coordinates = self.coordinates();
34 self.iter()
35 .map(|block| match block {
36 Connectivity::Triangular(elements) => elements
37 .iter()
38 .map(|element| triangle::maximum_edge_ratio(element, coordinates))
39 .collect(),
40 Connectivity::Quadrilateral(elements) => elements
41 .iter()
42 .map(|element| quadrilateral::maximum_edge_ratio(element, coordinates))
43 .collect(),
44 Connectivity::Tetrahedral(elements) => elements
45 .iter()
46 .map(|element| tetrahedron::maximum_edge_ratio(element, coordinates))
47 .collect(),
48 Connectivity::Hexahedral(elements) => elements
49 .iter()
50 .map(|element| hexahedron::maximum_edge_ratio(element, coordinates))
51 .collect(),
52 Connectivity::Pyramidal(elements) => elements
53 .iter()
54 .map(|element| pyramid::maximum_edge_ratio(element, coordinates))
55 .collect(),
56 Connectivity::Wedge(elements) => elements
57 .iter()
58 .map(|element| wedge::maximum_edge_ratio(element, coordinates))
59 .collect(),
60 Connectivity::Polygonal(_) | Connectivity::Polyhedral(_) => {
61 vec![Scalar::NAN; block.number_of_elements()]
62 }
63 })
64 .collect()
65 }
66 fn minimum_jacobians(&self) -> Vec<Vec<Scalar>> {
67 let coordinates = self.coordinates();
68 self.iter()
69 .map(|block| match block {
70 Connectivity::Triangular(elements) => elements
71 .iter()
72 .map(|element| triangle::minimum_jacobian(element, coordinates))
73 .collect(),
74 Connectivity::Quadrilateral(elements) => elements
75 .iter()
76 .map(|element| quadrilateral::minimum_jacobian(element, coordinates))
77 .collect(),
78 Connectivity::Tetrahedral(elements) => elements
79 .iter()
80 .map(|element| tetrahedron::minimum_jacobian(element, coordinates))
81 .collect(),
82 Connectivity::Hexahedral(elements) => elements
83 .iter()
84 .map(|element| hexahedron::minimum_jacobian(element, coordinates))
85 .collect(),
86 Connectivity::Pyramidal(elements) => elements
87 .iter()
88 .map(|element| pyramid::minimum_jacobian(element, coordinates))
89 .collect(),
90 Connectivity::Wedge(elements) => elements
91 .iter()
92 .map(|element| wedge::minimum_jacobian(element, coordinates))
93 .collect(),
94 Connectivity::Polygonal(_) | Connectivity::Polyhedral(_) => {
95 vec![Scalar::NAN; block.number_of_elements()]
96 }
97 })
98 .collect()
99 }
100 fn minimum_scaled_jacobians(&self) -> Vec<Vec<Scalar>> {
101 let coordinates = self.coordinates();
102 self.iter()
103 .map(|block| match block {
104 Connectivity::Triangular(elements) => elements
105 .iter()
106 .map(|element| triangle::minimum_scaled_jacobian(element, coordinates))
107 .collect(),
108 Connectivity::Quadrilateral(elements) => elements
109 .iter()
110 .map(|element| quadrilateral::minimum_scaled_jacobian(element, coordinates))
111 .collect(),
112 Connectivity::Tetrahedral(elements) => elements
113 .iter()
114 .map(|element| tetrahedron::minimum_scaled_jacobian(element, coordinates))
115 .collect(),
116 Connectivity::Hexahedral(elements) => elements
117 .iter()
118 .map(|element| hexahedron::minimum_scaled_jacobian(element, coordinates))
119 .collect(),
120 Connectivity::Pyramidal(elements) => elements
121 .iter()
122 .map(|element| pyramid::minimum_scaled_jacobian(element, coordinates))
123 .collect(),
124 Connectivity::Wedge(elements) => elements
125 .iter()
126 .map(|element| wedge::minimum_scaled_jacobian(element, coordinates))
127 .collect(),
128 Connectivity::Polygonal(_) | Connectivity::Polyhedral(_) => {
129 vec![Scalar::NAN; block.number_of_elements()]
130 }
131 })
132 .collect()
133 }
134 fn maximum_skews(&self) -> Vec<Vec<Scalar>> {
135 let coordinates = self.coordinates();
136 self.iter()
137 .map(|block| match block {
138 Connectivity::Triangular(elements) => elements
139 .iter()
140 .map(|element| triangle::maximum_skew(element, coordinates))
141 .collect(),
142 Connectivity::Quadrilateral(elements) => elements
143 .iter()
144 .map(|element| quadrilateral::maximum_skew(element, coordinates))
145 .collect(),
146 Connectivity::Tetrahedral(elements) => elements
147 .iter()
148 .map(|element| tetrahedron::maximum_skew(element, coordinates))
149 .collect(),
150 Connectivity::Hexahedral(elements) => elements
151 .iter()
152 .map(|element| hexahedron::maximum_skew(element, coordinates))
153 .collect(),
154 Connectivity::Pyramidal(elements) => elements
155 .iter()
156 .map(|element| pyramid::maximum_skew(element, coordinates))
157 .collect(),
158 Connectivity::Wedge(elements) => elements
159 .iter()
160 .map(|element| wedge::maximum_skew(element, coordinates))
161 .collect(),
162 Connectivity::Polygonal(_) | Connectivity::Polyhedral(_) => {
163 vec![Scalar::NAN; block.number_of_elements()]
164 }
165 })
166 .collect()
167 }
168 fn volumes(&self) -> Vec<Vec<Scalar>> {
169 let coordinates = self.coordinates();
170 self.iter()
171 .map(|block| match block {
172 Connectivity::Triangular(elements) => elements
173 .iter()
174 .map(|element| triangle::volume(element, coordinates).value())
175 .collect(),
176 Connectivity::Quadrilateral(elements) => elements
177 .iter()
178 .map(|element| quadrilateral::volume(element, coordinates).value())
179 .collect(),
180 Connectivity::Tetrahedral(elements) => elements
181 .iter()
182 .map(|element| tetrahedron::volume(element, coordinates).value())
183 .collect(),
184 Connectivity::Hexahedral(elements) => elements
185 .iter()
186 .map(|element| hexahedron::volume(element, coordinates).value())
187 .collect(),
188 Connectivity::Pyramidal(elements) => elements
189 .iter()
190 .map(|element| pyramid::volume(element, coordinates).value())
191 .collect(),
192 Connectivity::Wedge(elements) => elements
193 .iter()
194 .map(|element| wedge::volume(element, coordinates).value())
195 .collect(),
196 Connectivity::Polygonal(_) | Connectivity::Polyhedral(_) => {
197 vec![Scalar::NAN; block.number_of_elements()]
198 }
199 })
200 .collect()
201 }
202}
203
204fn cross<const D: usize>(a: &Coordinate<D>, b: &Coordinate<D>) -> [Quantity<Area>; 3] {
205 let zero = Quantity::<Length>::default();
206 let az = if D > 2 { a[2] } else { zero };
207 let bz = if D > 2 { b[2] } else { zero };
208 [
209 a[1] * bz - az * b[1],
210 az * b[0] - a[0] * bz,
211 a[0] * b[1] - a[1] * b[0],
212 ]
213}
214
215pub(crate) fn chi(epsilon: Scalar, determinant: Scalar) -> Scalar {
216 0.5 * (determinant + (epsilon * epsilon + determinant * determinant).sqrt())
217}
218
219pub(crate) fn regularized(edges: &TensorRank1List<3, Reference, 3>, epsilon: Scalar) -> Scalar {
220 edges.norm_squared().value().powf(1.5) / chi(epsilon, edges.scalar_triple_product())
221}
222
223fn triple_product<const D: usize>(
224 a: &Coordinate<D>,
225 b: &Coordinate<D>,
226 c: &Coordinate<D>,
227) -> Quantity<Volume> {
228 let bc = cross(b, c);
229 a[0] * bc[0] + a[1] * bc[1] + a[2] * bc[2]
230}
231
232fn cross_norm<const D: usize>(a: &Coordinate<D>, b: &Coordinate<D>) -> Quantity<Area> {
233 let n = cross(a, b);
234 Quantity::new((n[0].value().powi(2) + n[1].value().powi(2) + n[2].value().powi(2)).sqrt())
235}
236
237fn triangle_area<const D: usize>(
238 triangle: &[usize; 3],
239 coordinates: &Coordinates<D>,
240) -> Quantity<Area> {
241 let a = &coordinates[triangle[1]] - &coordinates[triangle[0]];
242 let b = &coordinates[triangle[2]] - &coordinates[triangle[0]];
243 cross_norm(&a, &b) * 0.5
244}
245
246fn tet_volume<const D: usize>(
247 tetrahedron: &[usize; 4],
248 coordinates: &Coordinates<D>,
249) -> Quantity<Volume> {
250 let a = &coordinates[tetrahedron[1]] - &coordinates[tetrahedron[0]];
251 let b = &coordinates[tetrahedron[2]] - &coordinates[tetrahedron[0]];
252 let c = &coordinates[tetrahedron[3]] - &coordinates[tetrahedron[0]];
253 triple_product(&a, &b, &c) / 6.0
254}
255
256fn triangle_skew<const D: usize>(
257 a: &Coordinate<D>,
258 b: &Coordinate<D>,
259 c: &Coordinate<D>,
260) -> Scalar {
261 let l0 = (c - b).normalized();
262 let l1 = (a - c).normalized();
263 let l2 = (b - a).normalized();
264 let minimum_angle = [
265 (-(&l0 * &l1).value()).acos(),
266 (-(&l1 * &l2).value()).acos(),
267 (-(&l2 * &l0).value()).acos(),
268 ]
269 .into_iter()
270 .fold(Scalar::INFINITY, Scalar::min);
271 (EQUIANGLE - minimum_angle) / EQUIANGLE
272}
273
274fn maximum_edge_ratio<const D: usize, const E: usize>(
275 edges: &[[usize; 2]; E],
276 element: &[usize],
277 coordinates: &Coordinates<D>,
278) -> Scalar {
279 let mut shortest = Quantity::new(Scalar::INFINITY);
280 let mut longest = Quantity::default();
281 for [a, b] in edges {
282 let length = (&coordinates[element[*b]] - &coordinates[element[*a]]).norm();
283 shortest = shortest.min(length);
284 longest = longest.max(length);
285 }
286 if shortest > Quantity::default() {
287 (longest / shortest).value()
288 } else {
289 Scalar::INFINITY
290 }
291}
292
293fn min_jacobian<const D: usize, const K: usize, const C: usize>(
294 table: &[(usize, [usize; K]); C],
295 element: &[usize],
296 coordinates: &Coordinates<D>,
297) -> Scalar {
298 corners(table, element, coordinates)
299 .into_iter()
300 .map(|(measure, _)| measure)
301 .fold(Scalar::INFINITY, Scalar::min)
302}
303
304fn min_scaled_jacobian<const D: usize, const K: usize, const C: usize>(
305 table: &[(usize, [usize; K]); C],
306 element: &[usize],
307 coordinates: &Coordinates<D>,
308 scale: Scalar,
309) -> Scalar {
310 corners(table, element, coordinates)
311 .into_iter()
312 .map(|(measure, normalizer)| {
313 if normalizer > 0.0 {
314 (scale * measure / normalizer).clamp(-1.0, 1.0)
315 } else {
316 0.0
317 }
318 })
319 .fold(Scalar::INFINITY, Scalar::min)
320}
321
322fn corners<const D: usize, const K: usize, const C: usize>(
323 table: &[(usize, [usize; K]); C],
324 element: &[usize],
325 coordinates: &Coordinates<D>,
326) -> [(Scalar, Scalar); C] {
327 from_fn(|corner| {
328 let (origin, neighbors) = &table[corner];
329 let origin = &coordinates[element[*origin]];
330 let edges: [Coordinate<D>; K] =
331 from_fn(|edge| &coordinates[element[neighbors[edge]]] - origin);
332 let normalizer: Scalar = edges.iter().map(|edge| edge.norm().value()).product();
333 (corner_measure(&edges), normalizer)
334 })
335}
336
337fn quad_skew<const D: usize>(
338 p0: &Coordinate<D>,
339 p1: &Coordinate<D>,
340 p2: &Coordinate<D>,
341 p3: &Coordinate<D>,
342) -> Scalar {
343 let x1 = (p1 - p0) + (p2 - p3);
344 let x2 = (p3 - p0) + (p2 - p1);
345 let (n1, n2) = (x1.norm(), x2.norm());
346 if n1 > Quantity::default() && n2 > Quantity::default() {
347 ((&x1 * &x2) / (n1 * n2)).abs().value()
348 } else {
349 0.0
350 }
351}
352
353fn corner_measure<const D: usize, const K: usize>(edges: &[Coordinate<D>; K]) -> Scalar {
354 if K == D {
355 let matrix = from_fn(|i| from_fn(|j| edges[i][j]));
356 TensorRank2::<K, Reference, Reference, _>::from(matrix).determinant()
357 } else {
358 let gram = from_fn(|i| from_fn(|j| &edges[i] * &edges[j]));
359 TensorRank2::<K, Reference, Reference, _>::from(gram)
360 .determinant()
361 .max(0.0)
362 .sqrt()
363 }
364}
365
366#[derive(Clone, Copy)]
367pub(crate) enum Kind {
368 Triangle,
369 Quadrilateral,
370 Tetrahedron,
371 Hexahedron,
372 Pyramid,
373 Wedge,
374}
375
376impl Kind {
377 pub(super) fn of(connectivity: &Connectivity) -> Option<Self> {
378 match connectivity {
379 Connectivity::Triangular(_) => Some(Self::Triangle),
380 Connectivity::Quadrilateral(_) => Some(Self::Quadrilateral),
381 Connectivity::Tetrahedral(_) => Some(Self::Tetrahedron),
382 Connectivity::Hexahedral(_) => Some(Self::Hexahedron),
383 Connectivity::Pyramidal(_) => Some(Self::Pyramid),
384 Connectivity::Wedge(_) => Some(Self::Wedge),
385 _ => None,
386 }
387 }
388}
389
390pub(super) fn minimum_jacobian<const D: usize>(
391 kind: Kind,
392 element: &[usize],
393 coordinates: &Coordinates<D>,
394) -> Scalar {
395 match kind {
396 Kind::Triangle => triangle::minimum_jacobian(element, coordinates),
397 Kind::Quadrilateral => quadrilateral::minimum_jacobian(element, coordinates),
398 Kind::Tetrahedron => tetrahedron::minimum_jacobian(element, coordinates),
399 Kind::Hexahedron => hexahedron::minimum_jacobian(element, coordinates),
400 Kind::Pyramid => pyramid::minimum_jacobian(element, coordinates),
401 Kind::Wedge => wedge::minimum_jacobian(element, coordinates),
402 }
403}
404
405pub(crate) fn minimum_scaled_jacobian<const D: usize>(
406 kind: Kind,
407 element: &[usize],
408 coordinates: &Coordinates<D>,
409) -> Scalar {
410 match kind {
411 Kind::Triangle => triangle::minimum_scaled_jacobian(element, coordinates),
412 Kind::Quadrilateral => quadrilateral::minimum_scaled_jacobian(element, coordinates),
413 Kind::Tetrahedron => tetrahedron::minimum_scaled_jacobian(element, coordinates),
414 Kind::Hexahedron => hexahedron::minimum_scaled_jacobian(element, coordinates),
415 Kind::Pyramid => pyramid::minimum_scaled_jacobian(element, coordinates),
416 Kind::Wedge => wedge::minimum_scaled_jacobian(element, coordinates),
417 }
418}