1pub 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#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct HypergraphConfig {
68 pub enable_sheaf: bool,
70 pub max_dimension: usize,
72 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
86pub struct HypergraphSubstrate {
93 #[allow(dead_code)]
95 config: HypergraphConfig,
96 entities: Arc<DashMap<EntityId, EntityRecord>>,
98 hyperedges: HyperedgeIndex,
100 topology: SimplicialComplex,
102 sheaf: Option<SheafStructure>,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108struct EntityRecord {
109 id: EntityId,
110 metadata: serde_json::Value,
111}
112
113impl HypergraphSubstrate {
114 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 pub fn add_entity(&self, id: EntityId, metadata: serde_json::Value) {
133 self.entities.insert(id, EntityRecord { id, metadata });
134 }
135
136 pub fn contains_entity(&self, id: &EntityId) -> bool {
138 self.entities.contains_key(id)
139 }
140
141 pub fn create_hyperedge(
156 &mut self,
157 entities: &[EntityId],
158 relation: &Relation,
159 ) -> Result<HyperedgeId, Error> {
160 for entity in entities {
162 if !self.contains_entity(entity) {
163 return Err(Error::NotFound(format!("Entity not found: {}", entity)));
164 }
165 }
166
167 let hyperedge_id = self.hyperedges.insert(entities, relation);
169
170 self.topology.add_simplex(entities);
172
173 if let Some(ref mut sheaf) = self.sheaf {
175 sheaf.update_sections(hyperedge_id, entities)?;
176 }
177
178 Ok(hyperedge_id)
179 }
180
181 pub fn hyperedges_for_entity(&self, entity: &EntityId) -> Vec<HyperedgeId> {
183 self.hyperedges.get_by_entity(entity)
184 }
185
186 pub fn get_hyperedge(&self, id: &HyperedgeId) -> Option<Hyperedge> {
188 self.hyperedges.get(id)
189 }
190
191 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 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 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 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 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#[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 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 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 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 let betti = hg.betti_numbers(2);
305 assert_eq!(betti, vec![0, 0, 0]);
306 }
307}