Skip to main content

dyntri_core/triangulation/
checks.rs

1use crate::triangulation::boundary_map::get_face;
2use crate::triangulation::simplices::{
3    CausalSimplex, CausalSimplexKind, canonical_oriented_simplex, canonical_simplex,
4};
5use crate::triangulation::{
6    CausalTriangulation, CausalTriangulation2D, CausalTriangulation3D, CausalTriangulation4D,
7};
8use crate::triangulation::{Triangulation, Triangulation2D, Triangulation3D, Triangulation4D};
9
10#[derive(Debug, thiserror::Error)]
11#[error("Neighbour {to} not correctly connected back to {from}.")]
12pub struct SimplexConnectivityError {
13    from: usize,
14    to: usize,
15}
16
17/// Checks to check the validity of the [`Triangulation`] structure
18impl<const N: usize> Triangulation<N> {
19    /// Check if the [`Triangulation`] is properly biconnective, i.e. that each simplex
20    /// neighbour points back to the 4-simplex at the correct index.
21    pub fn check_biconnectivity(&self) -> Result<(), SimplexConnectivityError> {
22        for (label, simplex) in self.simplices.iter().enumerate() {
23            for (nbr_label, backindex) in simplex.get_neighbours_backindices() {
24                if self.get_neighbour(nbr_label, backindex) != label {
25                    return Err(SimplexConnectivityError {
26                        from: label,
27                        to: nbr_label,
28                    });
29                }
30            }
31        }
32        Ok(())
33    }
34}
35
36/// Checks to check the validity of the [`CausalTriangulation`] structure
37impl<K, const N: usize> CausalTriangulation<K, N> {
38    /// Check if the [`CausalTriangulation`] is properly biconnective, i.e. that each simplex
39    /// neighbour points back to the 4-simplex at the correct index.
40    pub fn check_biconnectivity(&self) -> Result<(), SimplexConnectivityError> {
41        for (label, simplex) in self.simplices.iter().enumerate() {
42            for (nbr_label, backindex) in simplex.get_neighbours_backindices() {
43                if self.get_neighbour(nbr_label, backindex) != label {
44                    return Err(SimplexConnectivityError {
45                        from: label,
46                        to: nbr_label,
47                    });
48                }
49            }
50        }
51        Ok(())
52    }
53}
54
55#[derive(Debug, thiserror::Error)]
56pub enum SimplexCheckError {
57    #[error("Vertex count mismatch: expected {expected}, found {found}")]
58    VertexCountMismatch { expected: usize, found: usize },
59
60    #[error("Face count mismatch: expected {expected}, found {found} (2*N_(D-1) = (D+1)*N_(D))")]
61    FaceCountMismatch { expected: usize, found: usize },
62
63    #[error("Dehn-Sommerville relation violated")]
64    DehnSommervilleViolation,
65}
66
67impl Triangulation2D {
68    /// Checks that the number of simplices are all correct
69    pub fn check_simplex_counts(&self) -> Result<(), SimplexCheckError> {
70        let n0 = self.count_vertices();
71        let n1 = self.count_n1();
72
73        if n0 != self.num_vertices() {
74            return Err(SimplexCheckError::VertexCountMismatch {
75                expected: n0,
76                found: self.num_vertices(),
77            });
78        }
79        if n1 != self.num_faces() {
80            return Err(SimplexCheckError::FaceCountMismatch {
81                expected: n1,
82                found: self.num_faces(),
83            });
84        }
85
86        Ok(())
87    }
88}
89
90impl Triangulation3D {
91    /// Checks that the number of simplices are all correct
92    pub fn check_simplex_counts(&self) -> Result<(), SimplexCheckError> {
93        let n0 = self.count_vertices();
94        let n1 = self.count_n1();
95        let n2 = self.count_n2();
96
97        if n0 != self.num_vertices() {
98            return Err(SimplexCheckError::VertexCountMismatch {
99                expected: n0,
100                found: self.num_vertices(),
101            });
102        }
103        if n2 != self.num_faces() {
104            return Err(SimplexCheckError::FaceCountMismatch {
105                expected: n2,
106                found: self.num_faces(),
107            });
108        }
109        if 2 * n0 + n2 != 2 * n1 {
110            return Err(SimplexCheckError::DehnSommervilleViolation);
111        }
112
113        Ok(())
114    }
115}
116
117impl Triangulation4D {
118    /// Checks that the number of simplices are all correct
119    pub fn check_simplex_counts(&self) -> Result<(), SimplexCheckError> {
120        let n0 = self.count_vertices();
121        let n1 = self.count_n1();
122        let n2 = self.count_n2();
123        let n3 = self.count_n3();
124
125        if n0 != self.num_vertices() {
126            return Err(SimplexCheckError::VertexCountMismatch {
127                expected: n0,
128                found: self.num_vertices(),
129            });
130        }
131        if n3 != self.num_faces() {
132            return Err(SimplexCheckError::FaceCountMismatch {
133                expected: n3,
134                found: self.num_faces(),
135            });
136        }
137        if 2 * n1 + 2 * n3 != 3 * n2 {
138            return Err(SimplexCheckError::DehnSommervilleViolation);
139        }
140
141        Ok(())
142    }
143}
144
145impl CausalTriangulation2D {
146    /// Checks that the number of simplices are all correct
147    pub fn check_simplex_counts(&self) -> Result<(), SimplexCheckError> {
148        let n0 = self.count_vertices();
149        let n1 = self.count_n1();
150
151        if n0 != self.num_vertices() {
152            return Err(SimplexCheckError::VertexCountMismatch {
153                expected: n0,
154                found: self.num_vertices(),
155            });
156        }
157        if n1 != self.num_faces() {
158            return Err(SimplexCheckError::FaceCountMismatch {
159                expected: n1,
160                found: self.num_faces(),
161            });
162        }
163
164        Ok(())
165    }
166}
167
168impl CausalTriangulation3D {
169    /// Checks that the number of simplices are all correct
170    pub fn check_simplex_counts(&self) -> Result<(), SimplexCheckError> {
171        let n0 = self.count_vertices();
172        let n1 = self.count_n1();
173        let n2 = self.count_n2();
174
175        if n0 != self.num_vertices() {
176            return Err(SimplexCheckError::VertexCountMismatch {
177                expected: n0,
178                found: self.num_vertices(),
179            });
180        }
181        if n2 != self.num_faces() {
182            return Err(SimplexCheckError::FaceCountMismatch {
183                expected: n2,
184                found: self.num_faces(),
185            });
186        }
187        if 2 * n0 + n2 != 2 * n1 {
188            return Err(SimplexCheckError::DehnSommervilleViolation);
189        }
190
191        Ok(())
192    }
193}
194
195impl CausalTriangulation4D {
196    /// Checks that the number of simplices are all correct
197    pub fn check_simplex_counts(&self) -> Result<(), SimplexCheckError> {
198        let n0 = self.count_vertices();
199        let n1 = self.count_n1();
200        let n2 = self.count_n2();
201        let n3 = self.count_n3();
202
203        if n0 != self.num_vertices() {
204            return Err(SimplexCheckError::VertexCountMismatch {
205                expected: n0,
206                found: self.num_vertices(),
207            });
208        }
209        if n3 != self.num_faces() {
210            return Err(SimplexCheckError::FaceCountMismatch {
211                expected: n3,
212                found: self.num_faces(),
213            });
214        }
215        if 2 * n1 + 2 * n3 != 3 * n2 {
216            return Err(SimplexCheckError::DehnSommervilleViolation);
217        }
218
219        Ok(())
220    }
221}
222
223#[derive(Debug, thiserror::Error)]
224pub enum VertexConsistencyError<const N: usize> {
225    #[error("Trianglation has incorrectly matched vertices: {face:?} {nbr_face:?}")]
226    VertexMatching {
227        face: [usize; N],
228        nbr_face: [usize; N],
229    },
230    #[error("Trianglation has incorrectly oriented vertices: {face:?} {nbr_face:?}")]
231    Orientation {
232        face: [usize; N],
233        nbr_face: [usize; N],
234    },
235}
236
237macro_rules! impl_vertex_consistency_check {
238    ($triangulation:ty, $dim:expr) => {
239        impl $triangulation {
240            /// Checks whether the vertices shared between neighbouring simplices are the same
241            pub fn check_vertex_consistency(&self) -> Result<(), VertexConsistencyError<$dim>> {
242                for simplex in self.simplices.iter() {
243                    for (index, (nbr_label, backindex)) in
244                        simplex.get_neighbours_backindices().into_iter().enumerate()
245                    {
246                        let vertices = simplex.vertices;
247                        let nbr_vertices = self.get_simplex_vertices(nbr_label);
248
249                        let face = get_face(vertices, index);
250                        let nbr_face = get_face(nbr_vertices, backindex);
251                        let face_canon = canonical_simplex(face);
252                        let nbr_face_canon = canonical_simplex(nbr_face);
253
254                        if face_canon != nbr_face_canon {
255                            return Err(VertexConsistencyError::VertexMatching { face, nbr_face });
256                        }
257                    }
258                }
259
260                Ok(())
261            }
262
263            /// Checks whether the vertices shared between neighbouring simplices are the same
264            /// and they are consistently ordered.
265            pub fn check_vertex_consistency_ordered(
266                &self,
267            ) -> Result<(), VertexConsistencyError<$dim>> {
268                for simplex in self.simplices.iter() {
269                    for (index, (nbr_label, backindex)) in
270                        simplex.get_neighbours_backindices().into_iter().enumerate()
271                    {
272                        let vertices = simplex.vertices;
273                        let nbr_vertices = self.get_simplex_vertices(nbr_label);
274
275                        let face = get_face(vertices, index);
276                        let nbr_face = get_face(nbr_vertices, backindex);
277                        let (mut parity, face_canon) = canonical_oriented_simplex(face);
278                        let (mut nbr_parity, nbr_face_canon) = canonical_oriented_simplex(nbr_face);
279                        parity = !(parity ^ index.is_multiple_of(2));
280                        nbr_parity = !(nbr_parity ^ backindex.is_multiple_of(2));
281                        if face_canon != nbr_face_canon {
282                            return Err(VertexConsistencyError::VertexMatching { face, nbr_face });
283                        }
284                        // Check parity is opposite
285                        if nbr_parity == parity {
286                            return Err(VertexConsistencyError::Orientation { face, nbr_face });
287                        }
288                    }
289                }
290
291                Ok(())
292            }
293        }
294    };
295}
296
297impl_vertex_consistency_check!(Triangulation2D, 2);
298impl_vertex_consistency_check!(Triangulation3D, 3);
299impl_vertex_consistency_check!(Triangulation4D, 4);
300
301impl_vertex_consistency_check!(CausalTriangulation2D, 2);
302impl_vertex_consistency_check!(CausalTriangulation3D, 3);
303impl_vertex_consistency_check!(CausalTriangulation4D, 4);
304
305//=============================
306// Checks of absolute ordering
307//=============================
308
309#[derive(Debug, Clone, thiserror::Error)]
310#[error(
311    "Simplex ({label}: {simplex:?}) has incorrect absolute order of neighbour \
312     ({label_nbr}: {simplex_nbr:?}) or the incorrect SimplexKind"
313)]
314pub struct NeighbourAbsoluteOrderingError<K, const N: usize> {
315    label: usize,
316    simplex: CausalSimplex<K, N>,
317    label_nbr: usize,
318    simplex_nbr: CausalSimplex<K, N>,
319}
320
321#[derive(Debug, Clone, thiserror::Error)]
322pub enum SimplexKindError<K> {
323    #[error("Simplex vertex have time {tfound} which is inconsistent with {tsimplex}")]
324    IncorrectTime { tsimplex: u16, tfound: u16 },
325    #[error("Simplex kind {kind:?} inconsistent with found signature {signature:?}")]
326    InconsistentKind { kind: K, signature: (u8, u8) },
327}
328
329impl<K: Copy + CausalSimplexKind, const N: usize> CausalTriangulation<K, N> {
330    /// Checks if the triangulation obeys the absolute ordering of simplex neighbours such that
331    /// the first (index 0) neighbour is the time-like neighbour and time values are correctly set.
332    ///
333    /// See also notes on fields of [`CausalSimplex`](crate::triangulation::simplices::CausalSimplex).
334    pub fn check_neighbour_absolute_ordering(
335        &self,
336    ) -> Result<(), NeighbourAbsoluteOrderingError<K, N>> {
337        for label in 0..self.num_simplices() {
338            let t = self.get_simplex_time(label);
339            // Compare to neighbour 0
340            let label_nbr = self.get_neighbour(label, 0);
341            let tnbr = self.get_simplex_time(label_nbr);
342
343            match self.get_simplex_kind(label).orientation() {
344                super::simplices::CausalOrientation::Past => {
345                    if (tnbr + 1) % self.num_timeslices() != t {
346                        return Err(NeighbourAbsoluteOrderingError {
347                            label,
348                            simplex: *self.get_simplex(label),
349                            label_nbr,
350                            simplex_nbr: *self.get_simplex(label_nbr),
351                        });
352                    }
353                }
354                super::simplices::CausalOrientation::Space => {
355                    if t != tnbr {
356                        return Err(NeighbourAbsoluteOrderingError {
357                            label,
358                            simplex: *self.get_simplex(label),
359                            label_nbr,
360                            simplex_nbr: *self.get_simplex(label_nbr),
361                        });
362                    }
363                }
364                super::simplices::CausalOrientation::Future => {
365                    if tnbr != (t + 1) % self.num_timeslices() {
366                        return Err(NeighbourAbsoluteOrderingError {
367                            label,
368                            simplex: *self.get_simplex(label),
369                            label_nbr,
370                            simplex_nbr: *self.get_simplex(label_nbr),
371                        });
372                    }
373                }
374            }
375        }
376        Ok(())
377    }
378
379    /// Returns whether the triangulation obeys the absolute ordering of triangle neighbours
380    /// such that the first (index 0) neighbour is the time-like neighbour.
381    ///
382    /// See also notes on fields of [`CausalTriangle`](super::simplices::CausalTriangle).
383    pub fn check_simplex_kinds(&self) -> Result<(), SimplexKindError<K>> {
384        for label in 0..self.num_simplices() {
385            // Get lower vertex times
386            let t_lower = self.get_simplex_time(label);
387            // Get upper vertex times
388            let t_upper = (t_lower + 1) % self.num_timeslices();
389
390            let mut lower_count = 0u8;
391            let mut upper_count = 0u8;
392            for vtime in self
393                .get_simplex_vertices(label)
394                .map(|vlabel| self.get_vertex_time(vlabel))
395            {
396                if vtime == t_lower {
397                    lower_count += 1
398                } else if vtime == t_upper {
399                    upper_count += 1
400                } else {
401                    return Err(SimplexKindError::IncorrectTime {
402                        tsimplex: t_lower,
403                        tfound: vtime,
404                    });
405                }
406            }
407
408            if self.get_simplex_kind(label).signature() != (lower_count, upper_count) {
409                return Err(SimplexKindError::InconsistentKind {
410                    kind: self.get_simplex_kind(label),
411                    signature: (lower_count, upper_count),
412                });
413            }
414        }
415        Ok(())
416    }
417}