Skip to main content

conspire/geometry/mesh/quality/metrics/
mod.rs

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}