exo_hypergraph/
topology.rs

1//! Topological Data Analysis (TDA) structures
2//!
3//! Implements simplicial complexes, persistent homology computation,
4//! and Betti number calculations.
5
6use exo_core::{EntityId, Error};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9
10/// A simplex (generalization of triangle to arbitrary dimensions)
11#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct Simplex {
13    /// Vertices of the simplex
14    pub vertices: Vec<EntityId>,
15}
16
17impl Simplex {
18    /// Create a new simplex from vertices
19    pub fn new(mut vertices: Vec<EntityId>) -> Self {
20        vertices.sort_by_key(|v| v.0);
21        vertices.dedup();
22        Self { vertices }
23    }
24
25    /// Get the dimension of this simplex (0 for point, 1 for edge, 2 for triangle, etc.)
26    pub fn dimension(&self) -> usize {
27        self.vertices.len().saturating_sub(1)
28    }
29
30    /// Get all faces (sub-simplices) of this simplex
31    pub fn faces(&self) -> Vec<Simplex> {
32        if self.vertices.is_empty() {
33            return vec![];
34        }
35
36        let mut faces = Vec::new();
37
38        // Generate all non-empty subsets
39        for i in 0..self.vertices.len() {
40            let mut face_vertices = self.vertices.clone();
41            face_vertices.remove(i);
42            if !face_vertices.is_empty() {
43                faces.push(Simplex::new(face_vertices));
44            }
45        }
46
47        faces
48    }
49}
50
51/// Simplicial complex for topological data analysis
52///
53/// A simplicial complex is a collection of simplices (points, edges, triangles, etc.)
54/// that are "glued together" in a consistent way.
55#[derive(Clone, Debug, Serialize, Deserialize)]
56pub struct SimplicialComplex {
57    /// All simplices in the complex, organized by dimension
58    simplices: HashMap<usize, HashSet<Simplex>>,
59    /// Maximum dimension
60    max_dimension: usize,
61}
62
63impl SimplicialComplex {
64    /// Create a new empty simplicial complex
65    pub fn new() -> Self {
66        Self {
67            simplices: HashMap::new(),
68            max_dimension: 0,
69        }
70    }
71
72    /// Add a simplex and all its faces to the complex
73    pub fn add_simplex(&mut self, vertices: &[EntityId]) {
74        if vertices.is_empty() {
75            return;
76        }
77
78        let simplex = Simplex::new(vertices.to_vec());
79        let dim = simplex.dimension();
80
81        // Add the simplex itself
82        self.simplices
83            .entry(dim)
84            .or_insert_with(HashSet::new)
85            .insert(simplex.clone());
86
87        if dim > self.max_dimension {
88            self.max_dimension = dim;
89        }
90
91        // Add all faces recursively
92        for face in simplex.faces() {
93            self.add_simplex(&face.vertices);
94        }
95    }
96
97    /// Get all simplices of a given dimension
98    pub fn get_simplices(&self, dimension: usize) -> Vec<Simplex> {
99        self.simplices
100            .get(&dimension)
101            .map(|set| set.iter().cloned().collect())
102            .unwrap_or_default()
103    }
104
105    /// Get the number of simplices of a given dimension
106    pub fn count_simplices(&self, dimension: usize) -> usize {
107        self.simplices
108            .get(&dimension)
109            .map(|set| set.len())
110            .unwrap_or(0)
111    }
112
113    /// Compute Betti number for a given dimension
114    ///
115    /// Betti numbers are topological invariants:
116    /// - β₀ = number of connected components
117    /// - β₁ = number of 1-dimensional holes (loops)
118    /// - β₂ = number of 2-dimensional holes (voids)
119    ///
120    /// This is a simplified stub implementation.
121    pub fn betti_number(&self, dimension: usize) -> usize {
122        if dimension == 0 {
123            // β₀ = number of connected components
124            self.count_connected_components()
125        } else {
126            // For higher dimensions, return 0 (stub - full implementation requires
127            // boundary matrix computation and Smith normal form)
128            0
129        }
130    }
131
132    /// Count connected components (β₀)
133    fn count_connected_components(&self) -> usize {
134        let vertices = self.get_simplices(0);
135        if vertices.is_empty() {
136            return 0;
137        }
138
139        // Union-find to count components
140        let mut parent: HashMap<EntityId, EntityId> = HashMap::new();
141
142        // Initialize each vertex as its own component
143        for simplex in &vertices {
144            if let Some(v) = simplex.vertices.first() {
145                parent.insert(*v, *v);
146            }
147        }
148
149        // Process edges to merge components
150        let edges = self.get_simplices(1);
151        for edge in edges {
152            if edge.vertices.len() == 2 {
153                let v1 = edge.vertices[0];
154                let v2 = edge.vertices[1];
155                self.union(&mut parent, v1, v2);
156            }
157        }
158
159        // Count unique roots
160        let mut roots = HashSet::new();
161        for v in parent.keys() {
162            roots.insert(self.find(&parent, *v));
163        }
164
165        roots.len()
166    }
167
168    /// Union-find: find root
169    fn find(&self, parent: &HashMap<EntityId, EntityId>, mut x: EntityId) -> EntityId {
170        while parent.get(&x) != Some(&x) {
171            if let Some(&p) = parent.get(&x) {
172                x = p;
173            } else {
174                break;
175            }
176        }
177        x
178    }
179
180    /// Union-find: merge components
181    fn union(&self, parent: &mut HashMap<EntityId, EntityId>, x: EntityId, y: EntityId) {
182        let root_x = self.find(parent, x);
183        let root_y = self.find(parent, y);
184        if root_x != root_y {
185            parent.insert(root_x, root_y);
186        }
187    }
188
189    /// Build filtration (nested sequence of complexes) for persistent homology
190    ///
191    /// This is a stub - a full implementation would assign filtration values
192    /// to simplices based on some metric (e.g., edge weights, distances).
193    pub fn filtration(&self, _epsilon_range: (f32, f32)) -> Filtration {
194        Filtration {
195            complexes: vec![],
196            epsilon_values: vec![],
197        }
198    }
199
200    /// Compute persistent homology (stub implementation)
201    ///
202    /// Returns a persistence diagram showing birth and death of topological features.
203    /// This is a placeholder - full implementation requires:
204    /// - Building a filtration
205    /// - Constructing boundary matrices
206    /// - Column reduction algorithm
207    pub fn persistent_homology(
208        &self,
209        _dimension: usize,
210        _epsilon_range: (f32, f32),
211    ) -> PersistenceDiagram {
212        // Stub: return empty diagram
213        PersistenceDiagram { pairs: vec![] }
214    }
215}
216
217impl Default for SimplicialComplex {
218    fn default() -> Self {
219        Self::new()
220    }
221}
222
223/// Filtration: nested sequence of simplicial complexes
224///
225/// Used for persistent homology computation
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct Filtration {
228    /// Sequence of complexes
229    pub complexes: Vec<SimplicialComplex>,
230    /// Epsilon values at which complexes change
231    pub epsilon_values: Vec<f32>,
232}
233
234impl Filtration {
235    /// Get birth time of a simplex (stub)
236    pub fn birth_time(&self, _simplex_index: usize) -> f32 {
237        0.0
238    }
239}
240
241/// Persistence diagram showing birth and death of topological features
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct PersistenceDiagram {
244    /// Birth-death pairs (birth_time, death_time)
245    /// death_time = infinity (f32::INFINITY) for features that never die
246    pub pairs: Vec<(f32, f32)>,
247}
248
249impl PersistenceDiagram {
250    /// Get persistent features (those with significant lifetime)
251    pub fn significant_features(&self, min_persistence: f32) -> Vec<(f32, f32)> {
252        self.pairs
253            .iter()
254            .filter(|(birth, death)| {
255                if death.is_infinite() {
256                    true
257                } else {
258                    death - birth >= min_persistence
259                }
260            })
261            .copied()
262            .collect()
263    }
264}
265
266/// Column reduction for persistent homology (from pseudocode)
267///
268/// This is the standard algorithm from computational topology.
269/// Currently a stub - full implementation requires boundary matrix representation.
270#[allow(dead_code)]
271fn column_reduction(_matrix: &BoundaryMatrix) -> BoundaryMatrix {
272    // Stub implementation
273    BoundaryMatrix { columns: vec![] }
274}
275
276/// Boundary matrix for homology computation
277#[derive(Debug, Clone)]
278struct BoundaryMatrix {
279    columns: Vec<Vec<usize>>,
280}
281
282impl BoundaryMatrix {
283    #[allow(dead_code)]
284    fn low(&self, _col: usize) -> Option<usize> {
285        None
286    }
287
288    #[allow(dead_code)]
289    fn column(&self, _index: usize) -> Vec<usize> {
290        vec![]
291    }
292
293    #[allow(dead_code)]
294    fn num_cols(&self) -> usize {
295        self.columns.len()
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_simplex_dimension() {
305        let e1 = EntityId::new();
306        let e2 = EntityId::new();
307        let e3 = EntityId::new();
308
309        // 0-simplex (point)
310        let s0 = Simplex::new(vec![e1]);
311        assert_eq!(s0.dimension(), 0);
312
313        // 1-simplex (edge)
314        let s1 = Simplex::new(vec![e1, e2]);
315        assert_eq!(s1.dimension(), 1);
316
317        // 2-simplex (triangle)
318        let s2 = Simplex::new(vec![e1, e2, e3]);
319        assert_eq!(s2.dimension(), 2);
320    }
321
322    #[test]
323    fn test_simplex_faces() {
324        let e1 = EntityId::new();
325        let e2 = EntityId::new();
326        let e3 = EntityId::new();
327
328        // Triangle has 3 edges as faces
329        let triangle = Simplex::new(vec![e1, e2, e3]);
330        let faces = triangle.faces();
331        assert_eq!(faces.len(), 3);
332        assert!(faces.iter().all(|f| f.dimension() == 1));
333    }
334
335    #[test]
336    fn test_simplicial_complex() {
337        let mut complex = SimplicialComplex::new();
338
339        let e1 = EntityId::new();
340        let e2 = EntityId::new();
341        let e3 = EntityId::new();
342
343        // Add a triangle
344        complex.add_simplex(&[e1, e2, e3]);
345
346        // Should have 3 vertices, 3 edges, 1 triangle
347        assert_eq!(complex.count_simplices(0), 3);
348        assert_eq!(complex.count_simplices(1), 3);
349        assert_eq!(complex.count_simplices(2), 1);
350
351        // Connected, so β₀ = 1
352        assert_eq!(complex.betti_number(0), 1);
353    }
354
355    #[test]
356    fn test_betti_number_disconnected() {
357        let mut complex = SimplicialComplex::new();
358
359        let e1 = EntityId::new();
360        let e2 = EntityId::new();
361        let e3 = EntityId::new();
362        let e4 = EntityId::new();
363
364        // Add two separate edges (2 components)
365        complex.add_simplex(&[e1, e2]);
366        complex.add_simplex(&[e3, e4]);
367
368        // Two connected components
369        assert_eq!(complex.betti_number(0), 2);
370    }
371}