use crate::hopfield::ModernHopfield;
use crate::plasticity::btsp::BTSPAssociativeMemory;
use crate::separate::DentateGyrus;
use crate::{NervousSystemError, Result};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct NervousConfig {
pub input_dim: usize,
pub hopfield_beta: f32,
pub hopfield_capacity: usize,
pub enable_pattern_separation: bool,
pub separation_output_dim: usize,
pub separation_k: usize,
pub enable_one_shot: bool,
pub seed: u64,
}
impl Default for NervousConfig {
fn default() -> Self {
Self {
input_dim: 128,
hopfield_beta: 3.0,
hopfield_capacity: 1000,
enable_pattern_separation: true,
separation_output_dim: 10000,
separation_k: 200, enable_one_shot: true,
seed: 42,
}
}
}
impl NervousConfig {
pub fn new(input_dim: usize) -> Self {
Self {
input_dim,
separation_output_dim: input_dim * 78, separation_k: (input_dim * 78) / 50, ..Default::default()
}
}
pub fn with_hopfield(mut self, beta: f32, capacity: usize) -> Self {
self.hopfield_beta = beta;
self.hopfield_capacity = capacity;
self
}
pub fn with_pattern_separation(mut self, output_dim: usize, k: usize) -> Self {
self.enable_pattern_separation = true;
self.separation_output_dim = output_dim;
self.separation_k = k;
self
}
pub fn without_pattern_separation(mut self) -> Self {
self.enable_pattern_separation = false;
self
}
pub fn with_one_shot(mut self, enabled: bool) -> Self {
self.enable_one_shot = enabled;
self
}
}
#[derive(Debug, Clone)]
pub struct HybridSearchResult {
pub id: u64,
pub hnsw_distance: f32,
pub hopfield_similarity: f32,
pub combined_score: f32,
pub vector: Option<Vec<f32>>,
}
pub struct NervousVectorIndex {
config: NervousConfig,
hopfield: ModernHopfield,
pattern_encoder: Option<DentateGyrus>,
btsp_memory: Option<BTSPAssociativeMemory>,
vectors: HashMap<u64, Vec<f32>>,
next_id: u64,
metadata: HashMap<u64, String>,
}
impl NervousVectorIndex {
pub fn new(dimension: usize, config: NervousConfig) -> Self {
let hopfield = ModernHopfield::new(dimension, config.hopfield_beta);
let pattern_encoder = if config.enable_pattern_separation {
Some(DentateGyrus::new(
dimension,
config.separation_output_dim,
config.separation_k,
config.seed,
))
} else {
None
};
let btsp_memory = if config.enable_one_shot {
Some(BTSPAssociativeMemory::new(dimension, dimension))
} else {
None
};
Self {
config,
hopfield,
pattern_encoder,
btsp_memory,
vectors: HashMap::new(),
next_id: 0,
metadata: HashMap::new(),
}
}
pub fn insert(&mut self, vector: &[f32], metadata: Option<&str>) -> u64 {
let id = self.next_id;
self.next_id += 1;
self.vectors.insert(id, vector.to_vec());
if let Some(meta) = metadata {
self.metadata.insert(id, meta.to_string());
}
let _ = self.hopfield.store(vector.to_vec());
id
}
pub fn search_hybrid(&self, query: &[f32], k: usize) -> Vec<HybridSearchResult> {
let hopfield_result = self
.hopfield
.retrieve(query)
.unwrap_or_else(|_| vec![0.0; query.len()]);
let mut results: Vec<HybridSearchResult> = self
.vectors
.iter()
.map(|(id, vec)| {
let hopfield_sim = cosine_similarity(&hopfield_result, vec);
let hnsw_dist = euclidean_distance(query, vec);
let combined = 0.6 * hopfield_sim + 0.4 * (1.0 / (1.0 + hnsw_dist));
HybridSearchResult {
id: *id,
hnsw_distance: hnsw_dist,
hopfield_similarity: hopfield_sim,
combined_score: combined,
vector: Some(vec.clone()),
}
})
.collect();
results.sort_by(|a, b| {
b.combined_score
.partial_cmp(&a.combined_score)
.unwrap_or(std::cmp::Ordering::Equal)
});
results.into_iter().take(k).collect()
}
pub fn search_hopfield(&self, query: &[f32]) -> Option<Vec<f32>> {
self.hopfield.retrieve(query).ok()
}
pub fn search_hnsw(&self, query: &[f32], k: usize) -> Vec<(u64, f32)> {
let mut results: Vec<(u64, f32)> = self
.vectors
.iter()
.map(|(id, vec)| (*id, euclidean_distance(query, vec)))
.collect();
results.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
results.into_iter().take(k).collect()
}
pub fn learn_one_shot(&mut self, key: &[f32], value: &[f32]) {
if let Some(ref mut btsp) = self.btsp_memory {
let _ = btsp.store_one_shot(key, value);
}
}
pub fn retrieve_one_shot(&self, key: &[f32]) -> Option<Vec<f32>> {
self.btsp_memory
.as_ref()
.and_then(|btsp| btsp.retrieve(key).ok())
}
pub fn encode_pattern(&self, vector: &[f32]) -> Option<Vec<f32>> {
self.pattern_encoder
.as_ref()
.map(|encoder| encoder.encode_dense(vector))
}
pub fn config(&self) -> &NervousConfig {
&self.config
}
pub fn len(&self) -> usize {
self.vectors.len()
}
pub fn is_empty(&self) -> bool {
self.vectors.is_empty()
}
pub fn get_metadata(&self, id: u64) -> Option<&str> {
self.metadata.get(&id).map(|s| s.as_str())
}
pub fn get_vector(&self, id: u64) -> Option<&Vec<f32>> {
self.vectors.get(&id)
}
}
fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a == 0.0 || norm_b == 0.0 {
0.0
} else {
dot / (norm_a * norm_b)
}
}
fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f32>()
.sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nervous_vector_index_creation() {
let config = NervousConfig::new(128);
let index = NervousVectorIndex::new(128, config);
assert_eq!(index.len(), 0);
assert!(index.is_empty());
}
#[test]
fn test_insert_and_retrieve() {
let config = NervousConfig::new(128);
let mut index = NervousVectorIndex::new(128, config);
let vector = vec![0.5; 128];
let id = index.insert(&vector, Some("test"));
assert_eq!(index.len(), 1);
assert_eq!(index.get_metadata(id), Some("test"));
assert_eq!(index.get_vector(id), Some(&vector));
}
#[test]
fn test_hybrid_search() {
let config = NervousConfig::new(128);
let mut index = NervousVectorIndex::new(128, config);
let v1 = vec![1.0; 128];
let v2 = vec![0.5; 128];
let v3 = vec![0.0; 128];
index.insert(&v1, Some("v1"));
index.insert(&v2, Some("v2"));
index.insert(&v3, Some("v3"));
let query = vec![0.9; 128];
let results = index.search_hybrid(&query, 2);
assert_eq!(results.len(), 2);
assert!(results[0].combined_score >= results[1].combined_score);
}
#[test]
fn test_one_shot_learning() {
let config = NervousConfig::new(128).with_one_shot(true);
let mut index = NervousVectorIndex::new(128, config);
let key = vec![0.1; 128];
let value = vec![0.9; 128];
index.learn_one_shot(&key, &value);
let retrieved = index.retrieve_one_shot(&key);
assert!(retrieved.is_some());
let ret = retrieved.unwrap();
let error: f32 = ret
.iter()
.zip(value.iter())
.map(|(r, v)| (r - v).abs())
.sum::<f32>()
/ value.len() as f32;
assert!(error < 0.5, "One-shot learning error too high: {}", error);
}
#[test]
fn test_pattern_separation() {
let config = NervousConfig::new(128).with_pattern_separation(10000, 200);
let index = NervousVectorIndex::new(128, config);
let vector = vec![0.5; 128];
let encoded = index.encode_pattern(&vector);
assert!(encoded.is_some());
let enc = encoded.unwrap();
assert_eq!(enc.len(), 10000);
let nonzero_count = enc.iter().filter(|&&x| x != 0.0).count();
assert_eq!(nonzero_count, 200);
}
#[test]
fn test_hopfield_retrieval() {
let config = NervousConfig::new(64);
let mut index = NervousVectorIndex::new(64, config);
let pattern = vec![1.0; 64];
index.insert(&pattern, None);
let mut query = vec![0.9; 64];
query[0] = 0.1;
let retrieved = index.search_hopfield(&query);
assert!(retrieved.is_some());
let ret = retrieved.unwrap();
assert_eq!(ret.len(), 64);
let similarity = cosine_similarity(&ret, &pattern);
assert!(similarity > 0.8, "Hopfield retrieval similarity too low");
}
#[test]
fn test_config_builder() {
let config = NervousConfig::new(256)
.with_hopfield(5.0, 2000)
.with_pattern_separation(20000, 400)
.with_one_shot(true);
assert_eq!(config.input_dim, 256);
assert_eq!(config.hopfield_beta, 5.0);
assert_eq!(config.hopfield_capacity, 2000);
assert_eq!(config.separation_output_dim, 20000);
assert_eq!(config.separation_k, 400);
assert!(config.enable_one_shot);
}
}