pub mod hyperedge;
pub mod sheaf;
pub mod sparse_tda;
pub mod topology;
pub use hyperedge::{Hyperedge, HyperedgeIndex};
pub use sheaf::{SheafInconsistency, SheafStructure};
pub use sparse_tda::{
PersistenceBar, PersistenceDiagram as SparsePersistenceDiagram, SparseRipsComplex,
};
pub use topology::{PersistenceDiagram, SimplicialComplex};
use dashmap::DashMap;
use exo_core::{
EntityId, Error, HyperedgeId, HyperedgeResult, Relation, SectionId, SheafConsistencyResult,
TopologicalQuery,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HypergraphConfig {
pub enable_sheaf: bool,
pub max_dimension: usize,
pub epsilon: f32,
}
impl Default for HypergraphConfig {
fn default() -> Self {
Self {
enable_sheaf: false,
max_dimension: 3,
epsilon: 1e-6,
}
}
}
pub struct HypergraphSubstrate {
#[allow(dead_code)]
config: HypergraphConfig,
entities: Arc<DashMap<EntityId, EntityRecord>>,
hyperedges: HyperedgeIndex,
topology: SimplicialComplex,
sheaf: Option<SheafStructure>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct EntityRecord {
id: EntityId,
metadata: serde_json::Value,
}
impl HypergraphSubstrate {
pub fn new(config: HypergraphConfig) -> Self {
let sheaf = if config.enable_sheaf {
Some(SheafStructure::new())
} else {
None
};
Self {
config,
entities: Arc::new(DashMap::new()),
hyperedges: HyperedgeIndex::new(),
topology: SimplicialComplex::new(),
sheaf,
}
}
pub fn add_entity(&self, id: EntityId, metadata: serde_json::Value) {
self.entities.insert(id, EntityRecord { id, metadata });
}
pub fn contains_entity(&self, id: &EntityId) -> bool {
self.entities.contains_key(id)
}
pub fn create_hyperedge(
&mut self,
entities: &[EntityId],
relation: &Relation,
) -> Result<HyperedgeId, Error> {
for entity in entities {
if !self.contains_entity(entity) {
return Err(Error::NotFound(format!("Entity not found: {}", entity)));
}
}
let hyperedge_id = self.hyperedges.insert(entities, relation);
self.topology.add_simplex(entities);
if let Some(ref mut sheaf) = self.sheaf {
sheaf.update_sections(hyperedge_id, entities)?;
}
Ok(hyperedge_id)
}
pub fn hyperedges_for_entity(&self, entity: &EntityId) -> Vec<HyperedgeId> {
self.hyperedges.get_by_entity(entity)
}
pub fn get_hyperedge(&self, id: &HyperedgeId) -> Option<Hyperedge> {
self.hyperedges.get(id)
}
pub fn persistent_homology(
&self,
dimension: usize,
epsilon_range: (f32, f32),
) -> PersistenceDiagram {
self.topology.persistent_homology(dimension, epsilon_range)
}
pub fn betti_numbers(&self, max_dim: usize) -> Vec<usize> {
(0..=max_dim)
.map(|d| self.topology.betti_number(d))
.collect()
}
pub fn check_sheaf_consistency(&self, sections: &[SectionId]) -> SheafConsistencyResult {
match &self.sheaf {
Some(sheaf) => sheaf.check_consistency(sections),
None => SheafConsistencyResult::NotConfigured,
}
}
pub fn query(&self, query: &TopologicalQuery) -> Result<HyperedgeResult, Error> {
match query {
TopologicalQuery::PersistentHomology {
dimension,
epsilon_range,
} => {
let diagram = self.persistent_homology(*dimension, *epsilon_range);
Ok(HyperedgeResult::PersistenceDiagram(diagram.pairs))
}
TopologicalQuery::BettiNumbers { max_dimension } => {
let betti = self.betti_numbers(*max_dimension);
Ok(HyperedgeResult::BettiNumbers(betti))
}
TopologicalQuery::SheafConsistency { local_sections } => {
let result = self.check_sheaf_consistency(local_sections);
Ok(HyperedgeResult::SheafConsistency(result))
}
}
}
pub fn stats(&self) -> HypergraphStats {
HypergraphStats {
num_entities: self.entities.len(),
num_hyperedges: self.hyperedges.len(),
max_hyperedge_size: self.hyperedges.max_size(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HypergraphStats {
pub num_entities: usize,
pub num_hyperedges: usize,
pub max_hyperedge_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use exo_core::RelationType;
#[test]
fn test_create_hyperedge() {
let config = HypergraphConfig::default();
let mut hg = HypergraphSubstrate::new(config);
let e1 = EntityId::new();
let e2 = EntityId::new();
let e3 = EntityId::new();
hg.add_entity(e1, serde_json::json!({}));
hg.add_entity(e2, serde_json::json!({}));
hg.add_entity(e3, serde_json::json!({}));
let relation = Relation {
relation_type: RelationType::new("test"),
properties: serde_json::json!({}),
};
let he_id = hg.create_hyperedge(&[e1, e2, e3], &relation).unwrap();
assert!(hg.get_hyperedge(&he_id).is_some());
assert_eq!(hg.hyperedges_for_entity(&e1).len(), 1);
}
#[test]
fn test_betti_numbers() {
let config = HypergraphConfig::default();
let hg = HypergraphSubstrate::new(config);
let betti = hg.betti_numbers(2);
assert_eq!(betti, vec![0, 0, 0]);
}
}