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}