ruvector_math/homology/
simplex.rs

1//! Simplicial Complexes
2//!
3//! Basic building blocks for topological data analysis.
4
5use std::collections::{HashMap, HashSet};
6
7/// A simplex (k-simplex has k+1 vertices)
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9pub struct Simplex {
10    /// Sorted vertex indices
11    pub vertices: Vec<usize>,
12}
13
14impl Simplex {
15    /// Create simplex from vertices (will be sorted)
16    pub fn new(mut vertices: Vec<usize>) -> Self {
17        vertices.sort_unstable();
18        vertices.dedup();
19        Self { vertices }
20    }
21
22    /// Create 0-simplex (vertex)
23    pub fn vertex(v: usize) -> Self {
24        Self { vertices: vec![v] }
25    }
26
27    /// Create 1-simplex (edge)
28    pub fn edge(v0: usize, v1: usize) -> Self {
29        Self::new(vec![v0, v1])
30    }
31
32    /// Create 2-simplex (triangle)
33    pub fn triangle(v0: usize, v1: usize, v2: usize) -> Self {
34        Self::new(vec![v0, v1, v2])
35    }
36
37    /// Dimension of simplex (0 = vertex, 1 = edge, 2 = triangle, ...)
38    pub fn dim(&self) -> usize {
39        if self.vertices.is_empty() {
40            0
41        } else {
42            self.vertices.len() - 1
43        }
44    }
45
46    /// Is this a vertex (0-simplex)?
47    pub fn is_vertex(&self) -> bool {
48        self.vertices.len() == 1
49    }
50
51    /// Is this an edge (1-simplex)?
52    pub fn is_edge(&self) -> bool {
53        self.vertices.len() == 2
54    }
55
56    /// Get all faces (boundary simplices)
57    pub fn faces(&self) -> Vec<Simplex> {
58        if self.vertices.len() <= 1 {
59            return vec![];
60        }
61
62        (0..self.vertices.len())
63            .map(|i| {
64                let face_verts: Vec<usize> = self
65                    .vertices
66                    .iter()
67                    .enumerate()
68                    .filter(|&(j, _)| j != i)
69                    .map(|(_, &v)| v)
70                    .collect();
71                Simplex::new(face_verts)
72            })
73            .collect()
74    }
75
76    /// Check if this simplex is a face of another
77    pub fn is_face_of(&self, other: &Simplex) -> bool {
78        if self.vertices.len() >= other.vertices.len() {
79            return false;
80        }
81        self.vertices.iter().all(|v| other.vertices.contains(v))
82    }
83
84    /// Check if two simplices share a face
85    pub fn shares_face_with(&self, other: &Simplex) -> bool {
86        let intersection: Vec<usize> = self
87            .vertices
88            .iter()
89            .filter(|v| other.vertices.contains(v))
90            .copied()
91            .collect();
92        !intersection.is_empty()
93    }
94}
95
96/// Simplicial complex (collection of simplices)
97#[derive(Debug, Clone)]
98pub struct SimplicialComplex {
99    /// Simplices organized by dimension
100    simplices: Vec<HashSet<Simplex>>,
101    /// Maximum dimension
102    max_dim: usize,
103}
104
105impl SimplicialComplex {
106    /// Create empty complex
107    pub fn new() -> Self {
108        Self {
109            simplices: vec![HashSet::new()],
110            max_dim: 0,
111        }
112    }
113
114    /// Create from list of simplices (automatically adds faces)
115    pub fn from_simplices(simplices: Vec<Simplex>) -> Self {
116        let mut complex = Self::new();
117        for s in simplices {
118            complex.add(s);
119        }
120        complex
121    }
122
123    /// Add simplex and all its faces
124    pub fn add(&mut self, simplex: Simplex) {
125        let dim = simplex.dim();
126
127        // Ensure we have enough dimension levels
128        while self.simplices.len() <= dim {
129            self.simplices.push(HashSet::new());
130        }
131        self.max_dim = self.max_dim.max(dim);
132
133        // Add all faces recursively
134        self.add_with_faces(simplex);
135    }
136
137    fn add_with_faces(&mut self, simplex: Simplex) {
138        let dim = simplex.dim();
139
140        if self.simplices[dim].contains(&simplex) {
141            return; // Already present
142        }
143
144        // Add faces first
145        for face in simplex.faces() {
146            self.add_with_faces(face);
147        }
148
149        // Add this simplex
150        self.simplices[dim].insert(simplex);
151    }
152
153    /// Check if simplex is in complex
154    pub fn contains(&self, simplex: &Simplex) -> bool {
155        let dim = simplex.dim();
156        if dim >= self.simplices.len() {
157            return false;
158        }
159        self.simplices[dim].contains(simplex)
160    }
161
162    /// Get all simplices of dimension d
163    pub fn simplices_of_dim(&self, d: usize) -> impl Iterator<Item = &Simplex> {
164        self.simplices.get(d).into_iter().flat_map(|s| s.iter())
165    }
166
167    /// Get all simplices
168    pub fn all_simplices(&self) -> impl Iterator<Item = &Simplex> {
169        self.simplices.iter().flat_map(|s| s.iter())
170    }
171
172    /// Number of simplices of dimension d
173    pub fn count_dim(&self, d: usize) -> usize {
174        self.simplices.get(d).map(|s| s.len()).unwrap_or(0)
175    }
176
177    /// Total number of simplices
178    pub fn size(&self) -> usize {
179        self.simplices.iter().map(|s| s.len()).sum()
180    }
181
182    /// Maximum dimension
183    pub fn dimension(&self) -> usize {
184        self.max_dim
185    }
186
187    /// f-vector: (f_0, f_1, f_2, ...) = counts of each dimension
188    pub fn f_vector(&self) -> Vec<usize> {
189        self.simplices.iter().map(|s| s.len()).collect()
190    }
191
192    /// Euler characteristic via f-vector
193    pub fn euler_characteristic(&self) -> i64 {
194        self.simplices
195            .iter()
196            .enumerate()
197            .map(|(d, s)| {
198                let sign = if d % 2 == 0 { 1 } else { -1 };
199                sign * s.len() as i64
200            })
201            .sum()
202    }
203
204    /// Get vertex set
205    pub fn vertices(&self) -> HashSet<usize> {
206        self.simplices_of_dim(0)
207            .flat_map(|s| s.vertices.iter().copied())
208            .collect()
209    }
210
211    /// Get edges as pairs
212    pub fn edges(&self) -> Vec<(usize, usize)> {
213        self.simplices_of_dim(1)
214            .filter_map(|s| {
215                if s.vertices.len() == 2 {
216                    Some((s.vertices[0], s.vertices[1]))
217                } else {
218                    None
219                }
220            })
221            .collect()
222    }
223}
224
225impl Default for SimplicialComplex {
226    fn default() -> Self {
227        Self::new()
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn test_simplex_creation() {
237        let vertex = Simplex::vertex(0);
238        assert_eq!(vertex.dim(), 0);
239
240        let edge = Simplex::edge(0, 1);
241        assert_eq!(edge.dim(), 1);
242
243        let triangle = Simplex::triangle(0, 1, 2);
244        assert_eq!(triangle.dim(), 2);
245    }
246
247    #[test]
248    fn test_simplex_faces() {
249        let triangle = Simplex::triangle(0, 1, 2);
250        let faces = triangle.faces();
251
252        assert_eq!(faces.len(), 3);
253        assert!(faces.contains(&Simplex::edge(0, 1)));
254        assert!(faces.contains(&Simplex::edge(0, 2)));
255        assert!(faces.contains(&Simplex::edge(1, 2)));
256    }
257
258    #[test]
259    fn test_simplicial_complex() {
260        let mut complex = SimplicialComplex::new();
261        complex.add(Simplex::triangle(0, 1, 2));
262
263        // Should have 1 triangle, 3 edges, 3 vertices
264        assert_eq!(complex.count_dim(0), 3);
265        assert_eq!(complex.count_dim(1), 3);
266        assert_eq!(complex.count_dim(2), 1);
267
268        assert_eq!(complex.euler_characteristic(), 1); // 3 - 3 + 1 = 1
269    }
270
271    #[test]
272    fn test_f_vector() {
273        let complex = SimplicialComplex::from_simplices(vec![
274            Simplex::triangle(0, 1, 2),
275            Simplex::triangle(1, 2, 3),
276        ]);
277
278        let f = complex.f_vector();
279        assert_eq!(f[0], 4); // 4 vertices
280        assert_eq!(f[1], 5); // 5 edges (shared edge 1-2)
281        assert_eq!(f[2], 2); // 2 triangles
282    }
283
284    #[test]
285    fn test_is_face_of() {
286        let edge = Simplex::edge(0, 1);
287        let triangle = Simplex::triangle(0, 1, 2);
288
289        assert!(edge.is_face_of(&triangle));
290        assert!(!triangle.is_face_of(&edge));
291    }
292}