exo_backend_classical/
lib.rs1#![warn(missing_docs)]
8
9pub mod graph;
10pub mod vector;
11
12use exo_core::{
13 Error as ExoError, Filter, ManifoldDelta, Pattern, Result as ExoResult,
14 SearchResult, SubstrateBackend,
15};
16use parking_lot::RwLock;
17use std::sync::Arc;
18use vector::VectorIndexWrapper;
19
20pub use graph::GraphWrapper;
21
22#[derive(Debug, Clone)]
24pub struct ClassicalConfig {
25 pub dimensions: usize,
27 pub distance_metric: ruvector_core::DistanceMetric,
29}
30
31impl Default for ClassicalConfig {
32 fn default() -> Self {
33 Self {
34 dimensions: 768,
35 distance_metric: ruvector_core::DistanceMetric::Cosine,
36 }
37 }
38}
39
40pub struct ClassicalBackend {
46 vector_index: Arc<RwLock<VectorIndexWrapper>>,
48 graph_db: Arc<RwLock<GraphWrapper>>,
50 config: ClassicalConfig,
52}
53
54impl ClassicalBackend {
55 pub fn new(config: ClassicalConfig) -> ExoResult<Self> {
57 let vector_index = VectorIndexWrapper::new(config.dimensions, config.distance_metric)
58 .map_err(|e| ExoError::Backend(format!("Failed to create vector index: {}", e)))?;
59
60 let graph_db = GraphWrapper::new();
61
62 Ok(Self {
63 vector_index: Arc::new(RwLock::new(vector_index)),
64 graph_db: Arc::new(RwLock::new(graph_db)),
65 config,
66 })
67 }
68
69 pub fn with_dimensions(dimensions: usize) -> ExoResult<Self> {
71 let mut config = ClassicalConfig::default();
72 config.dimensions = dimensions;
73 Self::new(config)
74 }
75
76 pub fn graph_db(&self) -> Arc<RwLock<GraphWrapper>> {
78 Arc::clone(&self.graph_db)
79 }
80}
81
82impl SubstrateBackend for ClassicalBackend {
83 fn similarity_search(
84 &self,
85 query: &[f32],
86 k: usize,
87 filter: Option<&Filter>,
88 ) -> ExoResult<Vec<SearchResult>> {
89 if query.len() != self.config.dimensions {
91 return Err(ExoError::InvalidDimension {
92 expected: self.config.dimensions,
93 got: query.len(),
94 });
95 }
96
97 let index = self.vector_index.read();
99 index.search(query, k, filter)
100 }
101
102 fn manifold_deform(&self, pattern: &Pattern, _learning_rate: f32) -> ExoResult<ManifoldDelta> {
103 if pattern.embedding.len() != self.config.dimensions {
105 return Err(ExoError::InvalidDimension {
106 expected: self.config.dimensions,
107 got: pattern.embedding.len(),
108 });
109 }
110
111 let mut index = self.vector_index.write();
113 let id = index.insert(pattern)?;
114
115 Ok(ManifoldDelta::DiscreteInsert { id })
116 }
117
118 fn dimension(&self) -> usize {
119 self.config.dimensions
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use exo_core::{Metadata, PatternId, SubstrateTime};
127
128 #[test]
129 fn test_classical_backend_creation() {
130 let backend = ClassicalBackend::with_dimensions(128).unwrap();
131 assert_eq!(backend.dimension(), 128);
132 }
133
134 #[test]
135 fn test_insert_and_search() {
136 let backend = ClassicalBackend::with_dimensions(3).unwrap();
137
138 let pattern = Pattern {
140 id: PatternId::new(),
141 embedding: vec![1.0, 2.0, 3.0],
142 metadata: Metadata::default(),
143 timestamp: SubstrateTime::now(),
144 antecedents: vec![],
145 salience: 1.0,
146 };
147
148 let result = backend.manifold_deform(&pattern, 0.0);
150 assert!(result.is_ok());
151
152 let query = vec![1.1, 2.1, 3.1];
154 let results = backend.similarity_search(&query, 1, None);
155 assert!(results.is_ok());
156 let results = results.unwrap();
157 assert_eq!(results.len(), 1);
158 }
159}