exo_backend_classical/
graph.rs1use exo_core::{
4 EntityId, HyperedgeId, HyperedgeResult, Relation, SheafConsistencyResult,
5 TopologicalQuery,
6};
7use ruvector_graph::{GraphDB, Hyperedge, Node};
8use std::str::FromStr;
9
10use exo_core::{Error as ExoError, Result as ExoResult};
11
12#[cfg(test)]
13use exo_core::RelationType;
14
15pub struct GraphWrapper {
17 db: GraphDB,
19}
20
21impl GraphWrapper {
22 pub fn new() -> Self {
24 Self {
25 db: GraphDB::new(),
26 }
27 }
28
29 pub fn create_hyperedge(
31 &mut self,
32 entities: &[EntityId],
33 relation: &Relation,
34 ) -> ExoResult<HyperedgeId> {
35 for entity_id in entities {
37 let entity_id_str = entity_id.0.to_string();
38 if self.db.get_node(&entity_id_str).is_none() {
39 use ruvector_graph::types::{Label, Properties};
41 let node = Node::new(
42 entity_id_str,
43 vec![Label::new("Entity")],
44 Properties::new()
45 );
46 self.db.create_node(node).map_err(|e| {
47 ExoError::Backend(format!("Failed to create node: {}", e))
48 })?;
49 }
50 }
51
52 let entity_strs: Vec<String> = entities.iter().map(|e| e.0.to_string()).collect();
54
55 let mut hyperedge = Hyperedge::new(
56 entity_strs,
57 relation.relation_type.0.clone(),
58 );
59
60 if let Some(obj) = relation.properties.as_object() {
62 for (key, value) in obj {
63 if let Ok(prop_val) = serde_json::from_value(value.clone()) {
64 hyperedge.properties.insert(key.clone(), prop_val);
65 }
66 }
67 }
68
69 let hyperedge_id_str = hyperedge.id.clone();
70
71 self.db.create_hyperedge(hyperedge).map_err(|e| {
72 ExoError::Backend(format!("Failed to create hyperedge: {}", e))
73 })?;
74
75 let uuid = uuid::Uuid::from_str(&hyperedge_id_str)
77 .unwrap_or_else(|_| uuid::Uuid::new_v4());
78 Ok(HyperedgeId(uuid))
79 }
80
81 pub fn get_node(&self, id: &EntityId) -> Option<Node> {
83 self.db.get_node(&id.0.to_string())
84 }
85
86 pub fn get_hyperedge(&self, id: &HyperedgeId) -> Option<Hyperedge> {
88 self.db.get_hyperedge(&id.0.to_string())
89 }
90
91 pub fn query(&self, query: &TopologicalQuery) -> ExoResult<HyperedgeResult> {
93 match query {
94 TopologicalQuery::PersistentHomology {
95 dimension: _,
96 epsilon_range: _,
97 } => {
98 Ok(HyperedgeResult::NotSupported)
102 }
103 TopologicalQuery::BettiNumbers { max_dimension } => {
104 let betti_0 = self.approximate_connected_components();
111
112 let mut betti = vec![betti_0];
115 for _ in 1..=*max_dimension {
116 betti.push(0); }
118
119 Ok(HyperedgeResult::BettiNumbers(betti))
120 }
121 TopologicalQuery::SheafConsistency { local_sections: _ } => {
122 Ok(HyperedgeResult::SheafConsistency(
125 SheafConsistencyResult::Inconsistent(vec![
126 "Sheaf consistency not supported on classical backend".to_string()
127 ]),
128 ))
129 }
130 }
131 }
132
133 fn approximate_connected_components(&self) -> usize {
135 1
139 }
140
141 pub fn hyperedges_containing(&self, node_id: &EntityId) -> Vec<Hyperedge> {
143 self.db.get_hyperedges_by_node(&node_id.0.to_string())
145 }
146}
147
148impl Default for GraphWrapper {
149 fn default() -> Self {
150 Self::new()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use std::collections::HashMap;
158
159 #[test]
160 fn test_graph_creation() {
161 let graph = GraphWrapper::new();
162 assert!(graph.db.get_node("nonexistent").is_none());
164 }
165
166 #[test]
167 fn test_create_hyperedge() {
168 let mut graph = GraphWrapper::new();
169
170 let entities = vec![EntityId::new(), EntityId::new(), EntityId::new()];
171 let relation = Relation {
172 relation_type: RelationType::new("related_to"),
173 properties: serde_json::json!({}),
174 };
175
176 let result = graph.create_hyperedge(&entities, &relation);
177 assert!(result.is_ok());
178 }
179
180 #[test]
181 fn test_topological_query() {
182 let graph = GraphWrapper::new();
183
184 let query = TopologicalQuery::BettiNumbers { max_dimension: 2 };
185 let result = graph.query(&query);
186 assert!(result.is_ok());
187
188 if let Ok(HyperedgeResult::BettiNumbers(betti)) = result {
189 assert_eq!(betti.len(), 3); }
191 }
192}