Skip to main content

exo_hypergraph/
lib.rs

1//! Hypergraph Substrate for Higher-Order Relational Reasoning
2//!
3//! This crate provides a hypergraph-based substrate for representing and querying
4//! complex, higher-order relationships between entities. It extends beyond simple
5//! pairwise graphs to support hyperedges that span arbitrary sets of entities.
6//!
7//! # Features
8//!
9//! - **Hyperedge Support**: Relations spanning multiple entities (not just pairs)
10//! - **Topological Data Analysis**: Persistent homology and Betti number computation
11//! - **Sheaf Theory**: Consistency checks for distributed data structures
12//! - **Thread-Safe**: Lock-free concurrent access using DashMap
13//!
14//! # Example
15//!
16//! ```rust
17//! use exo_hypergraph::{HypergraphSubstrate, HypergraphConfig};
18//! use exo_core::{EntityId, Relation, RelationType};
19//!
20//! let config = HypergraphConfig::default();
21//! let mut hypergraph = HypergraphSubstrate::new(config);
22//!
23//! // Create entities
24//! let entity1 = EntityId::new();
25//! let entity2 = EntityId::new();
26//! let entity3 = EntityId::new();
27//!
28//! // Add entities to the hypergraph
29//! hypergraph.add_entity(entity1, serde_json::json!({"name": "Alice"}));
30//! hypergraph.add_entity(entity2, serde_json::json!({"name": "Bob"}));
31//! hypergraph.add_entity(entity3, serde_json::json!({"name": "Charlie"}));
32//!
33//! // Create a 3-way hyperedge
34//! let relation = Relation {
35//!     relation_type: RelationType::new("collaboration"),
36//!     properties: serde_json::json!({"weight": 0.9}),
37//! };
38//!
39//! let hyperedge_id = hypergraph.create_hyperedge(
40//!     &[entity1, entity2, entity3],
41//!     &relation
42//! ).unwrap();
43//! ```
44
45pub mod hyperedge;
46pub mod sheaf;
47pub mod sparse_tda;
48pub mod topology;
49
50pub use hyperedge::{Hyperedge, HyperedgeIndex};
51pub use sheaf::{SheafInconsistency, SheafStructure};
52pub use sparse_tda::{
53    PersistenceBar, PersistenceDiagram as SparsePersistenceDiagram, SparseRipsComplex,
54};
55pub use topology::{PersistenceDiagram, SimplicialComplex};
56
57use dashmap::DashMap;
58use exo_core::{
59    EntityId, Error, HyperedgeId, HyperedgeResult, Relation, SectionId, SheafConsistencyResult,
60    TopologicalQuery,
61};
62use serde::{Deserialize, Serialize};
63use std::sync::Arc;
64
65/// Configuration for hypergraph substrate
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct HypergraphConfig {
68    /// Enable sheaf consistency checking
69    pub enable_sheaf: bool,
70    /// Maximum dimension for topological computations
71    pub max_dimension: usize,
72    /// Epsilon tolerance for topology operations
73    pub epsilon: f32,
74}
75
76impl Default for HypergraphConfig {
77    fn default() -> Self {
78        Self {
79            enable_sheaf: false,
80            max_dimension: 3,
81            epsilon: 1e-6,
82        }
83    }
84}
85
86/// Hypergraph substrate for higher-order relations
87///
88/// Provides a substrate for storing and querying hypergraphs, supporting:
89/// - Hyperedges spanning multiple entities
90/// - Topological data analysis (persistent homology, Betti numbers)
91/// - Sheaf-theoretic consistency checks
92pub struct HypergraphSubstrate {
93    /// Configuration
94    #[allow(dead_code)]
95    config: HypergraphConfig,
96    /// Entity storage (placeholder - could integrate with actual graph DB)
97    entities: Arc<DashMap<EntityId, EntityRecord>>,
98    /// Hyperedge index (relations spanning >2 entities)
99    hyperedges: HyperedgeIndex,
100    /// Simplicial complex for TDA
101    topology: SimplicialComplex,
102    /// Sheaf structure for consistency (optional)
103    sheaf: Option<SheafStructure>,
104}
105
106/// Entity record (minimal placeholder)
107#[derive(Debug, Clone, Serialize, Deserialize)]
108struct EntityRecord {
109    id: EntityId,
110    metadata: serde_json::Value,
111}
112
113impl HypergraphSubstrate {
114    /// Create a new hypergraph substrate
115    pub fn new(config: HypergraphConfig) -> Self {
116        let sheaf = if config.enable_sheaf {
117            Some(SheafStructure::new())
118        } else {
119            None
120        };
121
122        Self {
123            config,
124            entities: Arc::new(DashMap::new()),
125            hyperedges: HyperedgeIndex::new(),
126            topology: SimplicialComplex::new(),
127            sheaf,
128        }
129    }
130
131    /// Add an entity to the hypergraph
132    pub fn add_entity(&self, id: EntityId, metadata: serde_json::Value) {
133        self.entities.insert(id, EntityRecord { id, metadata });
134    }
135
136    /// Check if entity exists
137    pub fn contains_entity(&self, id: &EntityId) -> bool {
138        self.entities.contains_key(id)
139    }
140
141    /// Create hyperedge spanning multiple entities
142    ///
143    /// # Arguments
144    ///
145    /// * `entities` - Slice of entity IDs to connect
146    /// * `relation` - Relation describing the connection
147    ///
148    /// # Returns
149    ///
150    /// The ID of the created hyperedge
151    ///
152    /// # Errors
153    ///
154    /// Returns `Error::EntityNotFound` if any entity doesn't exist
155    pub fn create_hyperedge(
156        &mut self,
157        entities: &[EntityId],
158        relation: &Relation,
159    ) -> Result<HyperedgeId, Error> {
160        // Validate entity existence (from pseudocode)
161        for entity in entities {
162            if !self.contains_entity(entity) {
163                return Err(Error::NotFound(format!("Entity not found: {}", entity)));
164            }
165        }
166
167        // Create hyperedge in index
168        let hyperedge_id = self.hyperedges.insert(entities, relation);
169
170        // Update simplicial complex
171        self.topology.add_simplex(entities);
172
173        // Update sheaf sections if enabled
174        if let Some(ref mut sheaf) = self.sheaf {
175            sheaf.update_sections(hyperedge_id, entities)?;
176        }
177
178        Ok(hyperedge_id)
179    }
180
181    /// Query hyperedges containing a specific entity
182    pub fn hyperedges_for_entity(&self, entity: &EntityId) -> Vec<HyperedgeId> {
183        self.hyperedges.get_by_entity(entity)
184    }
185
186    /// Get hyperedge by ID
187    pub fn get_hyperedge(&self, id: &HyperedgeId) -> Option<Hyperedge> {
188        self.hyperedges.get(id)
189    }
190
191    /// Topological query: find persistent features
192    ///
193    /// Computes persistent homology features in the specified dimension
194    /// over the given epsilon range.
195    pub fn persistent_homology(
196        &self,
197        dimension: usize,
198        epsilon_range: (f32, f32),
199    ) -> PersistenceDiagram {
200        self.topology.persistent_homology(dimension, epsilon_range)
201    }
202
203    /// Query Betti numbers (topological invariants)
204    ///
205    /// Returns the Betti numbers up to max_dim, where:
206    /// - β₀ = number of connected components
207    /// - β₁ = number of 1-dimensional holes (loops)
208    /// - β₂ = number of 2-dimensional holes (voids)
209    /// - etc.
210    pub fn betti_numbers(&self, max_dim: usize) -> Vec<usize> {
211        (0..=max_dim)
212            .map(|d| self.topology.betti_number(d))
213            .collect()
214    }
215
216    /// Sheaf consistency: check local-to-global coherence
217    ///
218    /// Checks if local sections are consistent on their overlaps,
219    /// following the sheaf axioms.
220    pub fn check_sheaf_consistency(&self, sections: &[SectionId]) -> SheafConsistencyResult {
221        match &self.sheaf {
222            Some(sheaf) => sheaf.check_consistency(sections),
223            None => SheafConsistencyResult::NotConfigured,
224        }
225    }
226
227    /// Execute a topological query
228    pub fn query(&self, query: &TopologicalQuery) -> Result<HyperedgeResult, Error> {
229        match query {
230            TopologicalQuery::PersistentHomology {
231                dimension,
232                epsilon_range,
233            } => {
234                let diagram = self.persistent_homology(*dimension, *epsilon_range);
235                Ok(HyperedgeResult::PersistenceDiagram(diagram.pairs))
236            }
237            TopologicalQuery::BettiNumbers { max_dimension } => {
238                let betti = self.betti_numbers(*max_dimension);
239                Ok(HyperedgeResult::BettiNumbers(betti))
240            }
241            TopologicalQuery::SheafConsistency { local_sections } => {
242                let result = self.check_sheaf_consistency(local_sections);
243                Ok(HyperedgeResult::SheafConsistency(result))
244            }
245        }
246    }
247
248    /// Get statistics about the hypergraph
249    pub fn stats(&self) -> HypergraphStats {
250        HypergraphStats {
251            num_entities: self.entities.len(),
252            num_hyperedges: self.hyperedges.len(),
253            max_hyperedge_size: self.hyperedges.max_size(),
254        }
255    }
256}
257
258/// Statistics about the hypergraph
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct HypergraphStats {
261    pub num_entities: usize,
262    pub num_hyperedges: usize,
263    pub max_hyperedge_size: usize,
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use exo_core::RelationType;
270
271    #[test]
272    fn test_create_hyperedge() {
273        let config = HypergraphConfig::default();
274        let mut hg = HypergraphSubstrate::new(config);
275
276        // Add entities
277        let e1 = EntityId::new();
278        let e2 = EntityId::new();
279        let e3 = EntityId::new();
280
281        hg.add_entity(e1, serde_json::json!({}));
282        hg.add_entity(e2, serde_json::json!({}));
283        hg.add_entity(e3, serde_json::json!({}));
284
285        // Create 3-way hyperedge
286        let relation = Relation {
287            relation_type: RelationType::new("test"),
288            properties: serde_json::json!({}),
289        };
290
291        let he_id = hg.create_hyperedge(&[e1, e2, e3], &relation).unwrap();
292
293        // Verify
294        assert!(hg.get_hyperedge(&he_id).is_some());
295        assert_eq!(hg.hyperedges_for_entity(&e1).len(), 1);
296    }
297
298    #[test]
299    fn test_betti_numbers() {
300        let config = HypergraphConfig::default();
301        let hg = HypergraphSubstrate::new(config);
302
303        // Empty hypergraph should have β₀ = 0 (no components)
304        let betti = hg.betti_numbers(2);
305        assert_eq!(betti, vec![0, 0, 0]);
306    }
307}