conspire/geometry/mesh/quality/metrics/
mod.rs1mod hexahedron;
2mod quadrilateral;
3mod tetrahedron;
4mod triangle;
5
6use crate::{
7 geometry::{
8 Coordinate, Coordinates,
9 mesh::{Connectivity, Mesh},
10 },
11 math::{Scalar, Tensor, TensorRank2},
12};
13use std::array::from_fn;
14
15pub trait Verdict {
16 fn maximum_edge_ratios(&self) -> Vec<Vec<Scalar>>;
17 fn maximum_skews(&self) -> Vec<Vec<Scalar>>;
18 fn minimum_jacobians(&self) -> Vec<Vec<Scalar>>;
19 fn minimum_scaled_jacobians(&self) -> Vec<Vec<Scalar>>;
20 fn volumes(&self) -> Vec<Vec<Scalar>>;
21}
22
23impl<const D: usize> Verdict for Mesh<D> {
24 fn maximum_edge_ratios(&self) -> Vec<Vec<Scalar>> {
25 let coordinates = self.coordinates();
26 self.iter()
27 .map(|block| match block {
28 Connectivity::Triangular(elements) => elements
29 .iter()
30 .map(|element| triangle::maximum_edge_ratio(element, coordinates))
31 .collect(),
32 Connectivity::Quadrilateral(elements) => elements
33 .iter()
34 .map(|element| quadrilateral::maximum_edge_ratio(element, coordinates))
35 .collect(),
36 Connectivity::Tetrahedral(elements) => elements
37 .iter()
38 .map(|element| tetrahedron::maximum_edge_ratio(element, coordinates))
39 .collect(),
40 Connectivity::Hexahedral(elements) => elements
41 .iter()
42 .map(|element| hexahedron::maximum_edge_ratio(element, coordinates))
43 .collect(),
44 _ => todo!(),
45 })
46 .collect()
47 }
48 fn minimum_jacobians(&self) -> Vec<Vec<Scalar>> {
49 let coordinates = self.coordinates();
50 self.iter()
51 .map(|block| match block {
52 Connectivity::Triangular(elements) => elements
53 .iter()
54 .map(|element| triangle::minimum_jacobian(element, coordinates))
55 .collect(),
56 Connectivity::Quadrilateral(elements) => elements
57 .iter()
58 .map(|element| quadrilateral::minimum_jacobian(element, coordinates))
59 .collect(),
60 Connectivity::Tetrahedral(elements) => elements
61 .iter()
62 .map(|element| tetrahedron::minimum_jacobian(element, coordinates))
63 .collect(),
64 Connectivity::Hexahedral(elements) => elements
65 .iter()
66 .map(|element| hexahedron::minimum_jacobian(element, coordinates))
67 .collect(),
68 _ => todo!(),
69 })
70 .collect()
71 }
72 fn minimum_scaled_jacobians(&self) -> Vec<Vec<Scalar>> {
73 let coordinates = self.coordinates();
74 self.iter()
75 .map(|block| match block {
76 Connectivity::Triangular(elements) => elements
77 .iter()
78 .map(|element| triangle::minimum_scaled_jacobian(element, coordinates))
79 .collect(),
80 Connectivity::Quadrilateral(elements) => elements
81 .iter()
82 .map(|element| quadrilateral::minimum_scaled_jacobian(element, coordinates))
83 .collect(),
84 Connectivity::Tetrahedral(elements) => elements
85 .iter()
86 .map(|element| tetrahedron::minimum_scaled_jacobian(element, coordinates))
87 .collect(),
88 Connectivity::Hexahedral(elements) => elements
89 .iter()
90 .map(|element| hexahedron::minimum_scaled_jacobian(element, coordinates))
91 .collect(),
92 _ => todo!(),
93 })
94 .collect()
95 }
96 fn maximum_skews(&self) -> Vec<Vec<Scalar>> {
97 let coordinates = self.coordinates();
98 self.iter()
99 .map(|block| match block {
100 Connectivity::Triangular(elements) => elements
101 .iter()
102 .map(|element| triangle::maximum_skew(element, coordinates))
103 .collect(),
104 Connectivity::Quadrilateral(elements) => elements
105 .iter()
106 .map(|element| quadrilateral::maximum_skew(element, coordinates))
107 .collect(),
108 Connectivity::Tetrahedral(elements) => elements
109 .iter()
110 .map(|element| tetrahedron::maximum_skew(element, coordinates))
111 .collect(),
112 Connectivity::Hexahedral(elements) => elements
113 .iter()
114 .map(|element| hexahedron::maximum_skew(element, coordinates))
115 .collect(),
116 _ => todo!(),
117 })
118 .collect()
119 }
120 fn volumes(&self) -> Vec<Vec<Scalar>> {
121 let coordinates = self.coordinates();
122 self.iter()
123 .map(|block| match block {
124 Connectivity::Triangular(elements) => elements
125 .iter()
126 .map(|element| triangle::volume(element, coordinates))
127 .collect(),
128 Connectivity::Quadrilateral(elements) => elements
129 .iter()
130 .map(|element| quadrilateral::volume(element, coordinates))
131 .collect(),
132 Connectivity::Tetrahedral(elements) => elements
133 .iter()
134 .map(|element| tetrahedron::volume(element, coordinates))
135 .collect(),
136 Connectivity::Hexahedral(elements) => elements
137 .iter()
138 .map(|element| hexahedron::volume(element, coordinates))
139 .collect(),
140 _ => todo!(),
141 })
142 .collect()
143 }
144}
145
146const EQUIANGLE: Scalar = std::f64::consts::FRAC_PI_3;
147
148fn cross<const D: usize>(a: &Coordinate<D>, b: &Coordinate<D>) -> [Scalar; 3] {
149 let az = if D > 2 { a[2] } else { 0.0 };
150 let bz = if D > 2 { b[2] } else { 0.0 };
151 [
152 a[1] * bz - az * b[1],
153 az * b[0] - a[0] * bz,
154 a[0] * b[1] - a[1] * b[0],
155 ]
156}
157
158fn triple_product<const D: usize>(
159 a: &Coordinate<D>,
160 b: &Coordinate<D>,
161 c: &Coordinate<D>,
162) -> Scalar {
163 let bc = cross(b, c);
164 a[0] * bc[0] + a[1] * bc[1] + a[2] * bc[2]
165}
166
167fn cross_norm<const D: usize>(a: &Coordinate<D>, b: &Coordinate<D>) -> Scalar {
168 let n = cross(a, b);
169 (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt()
170}
171
172fn triangle_area<const D: usize>(triangle: &[usize; 3], coordinates: &Coordinates<D>) -> Scalar {
173 let a = &coordinates[triangle[1]] - &coordinates[triangle[0]];
174 let b = &coordinates[triangle[2]] - &coordinates[triangle[0]];
175 0.5 * cross_norm(&a, &b)
176}
177
178fn tet_volume<const D: usize>(tetrahedron: &[usize; 4], coordinates: &Coordinates<D>) -> Scalar {
179 let a = &coordinates[tetrahedron[1]] - &coordinates[tetrahedron[0]];
180 let b = &coordinates[tetrahedron[2]] - &coordinates[tetrahedron[0]];
181 let c = &coordinates[tetrahedron[3]] - &coordinates[tetrahedron[0]];
182 triple_product(&a, &b, &c) / 6.0
183}
184
185fn triangle_skew<const D: usize>(
186 a: &Coordinate<D>,
187 b: &Coordinate<D>,
188 c: &Coordinate<D>,
189) -> Scalar {
190 let l0 = (c - b).normalized();
191 let l1 = (a - c).normalized();
192 let l2 = (b - a).normalized();
193 let minimum_angle = [
194 (-(&l0 * &l1)).acos(),
195 (-(&l1 * &l2)).acos(),
196 (-(&l2 * &l0)).acos(),
197 ]
198 .into_iter()
199 .fold(Scalar::INFINITY, Scalar::min);
200 (EQUIANGLE - minimum_angle) / EQUIANGLE
201}
202
203fn maximum_edge_ratio<const D: usize, const E: usize>(
204 edges: &[[usize; 2]; E],
205 element: &[usize],
206 coordinates: &Coordinates<D>,
207) -> Scalar {
208 let mut shortest = Scalar::INFINITY;
209 let mut longest: Scalar = 0.0;
210 for [a, b] in edges {
211 let length = (&coordinates[element[*b]] - &coordinates[element[*a]]).norm();
212 shortest = shortest.min(length);
213 longest = longest.max(length);
214 }
215 if shortest > 0.0 {
216 longest / shortest
217 } else {
218 Scalar::INFINITY
219 }
220}
221
222fn min_jacobian<const D: usize, const K: usize, const C: usize>(
223 table: &[[usize; K]; C],
224 element: &[usize],
225 coordinates: &Coordinates<D>,
226) -> Scalar {
227 corners(table, element, coordinates)
228 .into_iter()
229 .map(|(measure, _)| measure)
230 .fold(Scalar::INFINITY, Scalar::min)
231}
232
233fn min_scaled_jacobian<const D: usize, const K: usize, const C: usize>(
234 table: &[[usize; K]; C],
235 element: &[usize],
236 coordinates: &Coordinates<D>,
237 scale: Scalar,
238) -> Scalar {
239 corners(table, element, coordinates)
240 .into_iter()
241 .map(|(measure, normalizer)| {
242 if normalizer > 0.0 {
243 (scale * measure / normalizer).clamp(-1.0, 1.0)
244 } else {
245 0.0
246 }
247 })
248 .fold(Scalar::INFINITY, Scalar::min)
249}
250
251fn corners<const D: usize, const K: usize, const C: usize>(
252 table: &[[usize; K]; C],
253 element: &[usize],
254 coordinates: &Coordinates<D>,
255) -> [(Scalar, Scalar); C] {
256 from_fn(|corner| {
257 let origin = &coordinates[element[corner]];
258 let edges: [Coordinate<D>; K] =
259 from_fn(|edge| &coordinates[element[table[corner][edge]]] - origin);
260 let normalizer: Scalar = edges.iter().map(|edge| edge.norm()).product();
261 (corner_measure(&edges), normalizer)
262 })
263}
264
265fn corner_measure<const D: usize, const K: usize>(edges: &[Coordinate<D>; K]) -> Scalar {
266 if K == D {
267 let matrix: [[Scalar; K]; K] = from_fn(|row| from_fn(|column| edges[row][column]));
268 TensorRank2::<K, 0, 0>::from(matrix).determinant()
269 } else {
270 let gram: [[Scalar; K]; K] = from_fn(|i| from_fn(|j| &edges[i] * &edges[j]));
271 TensorRank2::<K, 0, 0>::from(gram)
272 .determinant()
273 .max(0.0)
274 .sqrt()
275 }
276}
277
278#[derive(Clone, Copy)]
279pub(super) enum Kind {
280 Triangle,
281 Quadrilateral,
282 Tetrahedron,
283 Hexahedron,
284}
285
286impl Kind {
287 pub(super) fn of(connectivity: &Connectivity) -> Option<Self> {
288 match connectivity {
289 Connectivity::Triangular(_) => Some(Self::Triangle),
290 Connectivity::Quadrilateral(_) => Some(Self::Quadrilateral),
291 Connectivity::Tetrahedral(_) => Some(Self::Tetrahedron),
292 Connectivity::Hexahedral(_) => Some(Self::Hexahedron),
293 _ => None,
294 }
295 }
296}
297
298pub(super) fn minimum_jacobian<const D: usize>(
299 kind: Kind,
300 element: &[usize],
301 coordinates: &Coordinates<D>,
302) -> Scalar {
303 match kind {
304 Kind::Triangle => triangle::minimum_jacobian(element, coordinates),
305 Kind::Quadrilateral => quadrilateral::minimum_jacobian(element, coordinates),
306 Kind::Tetrahedron => tetrahedron::minimum_jacobian(element, coordinates),
307 Kind::Hexahedron => hexahedron::minimum_jacobian(element, coordinates),
308 }
309}
310
311pub(super) fn minimum_scaled_jacobian<const D: usize>(
312 kind: Kind,
313 element: &[usize],
314 coordinates: &Coordinates<D>,
315) -> Scalar {
316 match kind {
317 Kind::Triangle => triangle::minimum_scaled_jacobian(element, coordinates),
318 Kind::Quadrilateral => quadrilateral::minimum_scaled_jacobian(element, coordinates),
319 Kind::Tetrahedron => tetrahedron::minimum_scaled_jacobian(element, coordinates),
320 Kind::Hexahedron => hexahedron::minimum_scaled_jacobian(element, coordinates),
321 }
322}