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