Skip to main content

exo_backend_classical/
lib.rs

1//! # EXO Backend Classical
2//!
3//! Classical substrate backend consuming ruvector crates.
4//! This provides a bridge between the EXO substrate abstractions and the
5//! high-performance ruvector vector database and graph database.
6
7#![warn(missing_docs)]
8
9pub mod dither_quantizer;
10pub mod domain_bridge;
11pub mod graph;
12pub mod thermo_layer;
13pub mod transfer_orchestrator;
14pub mod vector;
15
16use exo_core::{
17    Error as ExoError, Filter, ManifoldDelta, Pattern, Result as ExoResult, SearchResult,
18    SubstrateBackend,
19};
20use parking_lot::RwLock;
21use std::sync::Arc;
22use vector::VectorIndexWrapper;
23
24pub use graph::GraphWrapper;
25
26/// Configuration for the classical backend
27#[derive(Debug, Clone)]
28pub struct ClassicalConfig {
29    /// Vector dimensions
30    pub dimensions: usize,
31    /// Distance metric
32    pub distance_metric: ruvector_core::DistanceMetric,
33}
34
35impl Default for ClassicalConfig {
36    fn default() -> Self {
37        Self {
38            dimensions: 768,
39            distance_metric: ruvector_core::DistanceMetric::Cosine,
40        }
41    }
42}
43
44/// Classical substrate backend using ruvector
45///
46/// This backend wraps ruvector-core for vector operations and ruvector-graph
47/// for hypergraph operations, providing a classical (discrete) implementation
48/// of the substrate backend trait.
49pub struct ClassicalBackend {
50    /// Vector index wrapper
51    vector_index: Arc<RwLock<VectorIndexWrapper>>,
52    /// Graph database wrapper
53    graph_db: Arc<RwLock<GraphWrapper>>,
54    /// Configuration
55    config: ClassicalConfig,
56}
57
58impl ClassicalBackend {
59    /// Create a new classical backend with the given configuration
60    pub fn new(config: ClassicalConfig) -> ExoResult<Self> {
61        let vector_index = VectorIndexWrapper::new(config.dimensions, config.distance_metric)
62            .map_err(|e| ExoError::Backend(format!("Failed to create vector index: {}", e)))?;
63
64        let graph_db = GraphWrapper::new();
65
66        Ok(Self {
67            vector_index: Arc::new(RwLock::new(vector_index)),
68            graph_db: Arc::new(RwLock::new(graph_db)),
69            config,
70        })
71    }
72
73    /// Create with default configuration
74    pub fn with_dimensions(dimensions: usize) -> ExoResult<Self> {
75        let mut config = ClassicalConfig::default();
76        config.dimensions = dimensions;
77        Self::new(config)
78    }
79
80    /// Get access to the underlying graph database (for hyperedge operations)
81    pub fn graph_db(&self) -> Arc<RwLock<GraphWrapper>> {
82        Arc::clone(&self.graph_db)
83    }
84}
85
86impl SubstrateBackend for ClassicalBackend {
87    fn similarity_search(
88        &self,
89        query: &[f32],
90        k: usize,
91        filter: Option<&Filter>,
92    ) -> ExoResult<Vec<SearchResult>> {
93        // Validate dimensions
94        if query.len() != self.config.dimensions {
95            return Err(ExoError::InvalidDimension {
96                expected: self.config.dimensions,
97                got: query.len(),
98            });
99        }
100
101        // Delegate to vector index wrapper
102        let index = self.vector_index.read();
103        index.search(query, k, filter)
104    }
105
106    fn manifold_deform(&self, pattern: &Pattern, _learning_rate: f32) -> ExoResult<ManifoldDelta> {
107        // Validate dimensions
108        if pattern.embedding.len() != self.config.dimensions {
109            return Err(ExoError::InvalidDimension {
110                expected: self.config.dimensions,
111                got: pattern.embedding.len(),
112            });
113        }
114
115        // Classical backend: discrete insert (no continuous deformation)
116        let mut index = self.vector_index.write();
117        let id = index.insert(pattern)?;
118
119        Ok(ManifoldDelta::DiscreteInsert { id })
120    }
121
122    fn dimension(&self) -> usize {
123        self.config.dimensions
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use exo_core::{Metadata, PatternId, SubstrateTime};
131
132    #[test]
133    fn test_classical_backend_creation() {
134        let backend = ClassicalBackend::with_dimensions(128).unwrap();
135        assert_eq!(backend.dimension(), 128);
136    }
137
138    #[test]
139    fn test_insert_and_search() {
140        let backend = ClassicalBackend::with_dimensions(3).unwrap();
141
142        // Create a pattern
143        let pattern = Pattern {
144            id: PatternId::new(),
145            embedding: vec![1.0, 2.0, 3.0],
146            metadata: Metadata::default(),
147            timestamp: SubstrateTime::now(),
148            antecedents: vec![],
149            salience: 1.0,
150        };
151
152        // Insert pattern
153        let result = backend.manifold_deform(&pattern, 0.0);
154        assert!(result.is_ok());
155
156        // Search
157        let query = vec![1.1, 2.1, 3.1];
158        let results = backend.similarity_search(&query, 1, None);
159        assert!(results.is_ok());
160        let results = results.unwrap();
161        assert_eq!(results.len(), 1);
162    }
163}