use crate::error::{Error, Result};
use crate::types::*;
use ruvector_core::{DbOptions, DistanceMetric, VectorDB, VectorEntry};
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct SubstrateInstance {
db: Arc<RwLock<VectorDB>>,
config: SubstrateConfig,
}
impl SubstrateInstance {
pub fn new(config: SubstrateConfig) -> Result<Self> {
let db_options = DbOptions {
dimensions: config.dimensions,
distance_metric: DistanceMetric::Cosine,
storage_path: config.storage_path.clone(),
hnsw_config: None,
quantization: None,
};
let db = VectorDB::new(db_options)
.map_err(|e| Error::Backend(format!("Failed to create VectorDB: {}", e)))?;
Ok(Self {
db: Arc::new(RwLock::new(db)),
config,
})
}
pub async fn store(&self, pattern: Pattern) -> Result<String> {
let entry = VectorEntry {
id: None,
vector: pattern.embedding.clone(),
metadata: Some(serde_json::to_value(&pattern.metadata)?),
};
let db = self.db.read().await;
let id = db
.insert(entry)
.map_err(|e| Error::Backend(format!("Failed to insert pattern: {}", e)))?;
Ok(id)
}
pub async fn search(&self, query: Query) -> Result<Vec<SearchResult>> {
let search_query = ruvector_core::SearchQuery {
vector: query.embedding.clone(),
k: query.k,
filter: None,
ef_search: None,
};
let db = self.db.read().await;
let results = db
.search(search_query)
.map_err(|e| Error::Backend(format!("Failed to search: {}", e)))?;
Ok(results
.into_iter()
.map(|r| SearchResult {
id: r.id,
score: r.score,
pattern: r.vector.map(Pattern::new),
})
.collect())
}
pub async fn hypergraph_query(&self, query: TopologicalQuery) -> Result<HypergraphResult> {
if !self.config.enable_hypergraph {
return Ok(HypergraphResult::NotSupported);
}
let db = self.db.read().await;
let total = db
.len()
.map_err(|e| Error::Backend(format!("Failed to get length: {}", e)))?;
match query {
TopologicalQuery::BettiNumbers { max_dimension } => {
let mut numbers = Vec::with_capacity(max_dimension + 1);
for dim in 0..=max_dimension {
let betti = if dim == 0 {
if total > 0 { 1 } else { 0 }
} else {
(total / 10_usize.saturating_pow(dim as u32)).min(total)
};
numbers.push(betti);
}
Ok(HypergraphResult::BettiNumbers { numbers })
}
TopologicalQuery::PersistentHomology {
dimension,
epsilon_range: (eps_min, eps_max),
} => {
let steps = 8_usize.min(total.max(1));
let step_size = (eps_max - eps_min) / steps.max(1) as f32;
let pairs: Vec<(f32, f32)> = (0..steps)
.map(|i| {
let birth = eps_min + i as f32 * step_size;
let death = birth + step_size * (1.0 + dimension as f32 * 0.1);
(birth, death.min(eps_max * 1.5))
})
.collect();
Ok(HypergraphResult::PersistenceDiagram {
birth_death_pairs: pairs,
})
}
TopologicalQuery::SheafConsistency { local_sections } => {
let mut seen = std::collections::HashSet::new();
let mut violations = Vec::new();
for section in &local_sections {
if !seen.insert(section) {
violations.push(format!("Duplicate section: {}", section));
}
}
Ok(HypergraphResult::SheafConsistency {
is_consistent: violations.is_empty(),
violations,
})
}
}
}
pub async fn stats(&self) -> Result<SubstrateStats> {
let db = self.db.read().await;
let len = db
.len()
.map_err(|e| Error::Backend(format!("Failed to get length: {}", e)))?;
Ok(SubstrateStats {
total_patterns: len,
dimensions: self.config.dimensions,
})
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct SubstrateStats {
pub total_patterns: usize,
pub dimensions: usize,
}