mod dentate;
mod projection;
mod sparsification;
pub use dentate::DentateGyrus;
pub use projection::SparseProjection;
pub use sparsification::SparseBitVector;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pattern_separation_decorrelation() {
let dg = DentateGyrus::new(128, 10000, 200, 42);
let mut input1 = vec![0.0; 128];
let mut input2 = vec![0.0; 128];
for i in 0..115 {
input1[i] = 1.0;
input2[i] = 1.0;
}
input1[120] = 1.0;
input2[121] = 1.0;
let sparse1 = dg.encode(&input1);
let sparse2 = dg.encode(&input2);
let input_overlap = 115.0 / 128.0; let output_similarity = sparse1.jaccard_similarity(&sparse2);
assert!(
output_similarity < input_overlap,
"Output similarity ({}) should be less than input overlap ({})",
output_similarity,
input_overlap
);
}
#[test]
fn test_collision_rate() {
let dg = DentateGyrus::new(128, 10000, 200, 42);
let num_samples = 1000;
let mut encodings = Vec::new();
for i in 0..num_samples {
let input: Vec<f32> = (0..128).map(|j| ((i * 128 + j) as f32).sin()).collect();
encodings.push(dg.encode(&input));
}
let mut collisions = 0;
for i in 0..encodings.len() {
for j in (i + 1)..encodings.len() {
if encodings[i].indices == encodings[j].indices {
collisions += 1;
}
}
}
let collision_rate = collisions as f32 / (num_samples * (num_samples - 1) / 2) as f32;
assert!(
collision_rate < 0.01,
"Collision rate ({:.4}) exceeds 1%",
collision_rate
);
}
#[test]
fn test_sparsity_level() {
let output_dim = 10000;
let k = 200; let dg = DentateGyrus::new(128, output_dim, k, 42);
let input: Vec<f32> = (0..128).map(|i| (i as f32).sin()).collect();
let sparse = dg.encode(&input);
let sparsity = sparse.indices.len() as f32 / output_dim as f32;
assert_eq!(
sparse.indices.len(),
k,
"Should have exactly k active neurons"
);
assert!(
sparsity >= 0.02 && sparsity <= 0.05,
"Sparsity ({:.4}) should be in 2-5% range",
sparsity
);
}
#[test]
fn test_encoding_performance() {
let dg = DentateGyrus::new(512, 10000, 200, 42);
let input: Vec<f32> = (0..512).map(|i| (i as f32).sin()).collect();
let start = std::time::Instant::now();
let iterations = 100;
for _ in 0..iterations {
let _ = dg.encode(&input);
}
let elapsed = start.elapsed();
let avg_time = elapsed / iterations;
assert!(
avg_time.as_secs() < 2,
"Average encoding time ({:?}) exceeds 2s",
avg_time
);
}
#[test]
fn test_similarity_performance() {
let dg = DentateGyrus::new(512, 10000, 200, 42);
let input1: Vec<f32> = (0..512).map(|i| (i as f32).sin()).collect();
let input2: Vec<f32> = (0..512).map(|i| (i as f32).cos()).collect();
let sparse1 = dg.encode(&input1);
let sparse2 = dg.encode(&input2);
let start = std::time::Instant::now();
let iterations = 1000;
for _ in 0..iterations {
let _ = sparse1.jaccard_similarity(&sparse2);
}
let elapsed = start.elapsed();
let avg_time = elapsed / iterations;
assert!(
avg_time.as_micros() < 100,
"Average similarity time ({:?}) exceeds 100μs",
avg_time
);
}
#[test]
fn test_retrieval_quality() {
let dg = DentateGyrus::new(128, 10000, 200, 42);
let original: Vec<f32> = (0..128).map(|i| (i as f32).sin()).collect();
let similar: Vec<f32> = original
.iter()
.map(|&x| x + 0.1 * ((x * 10.0).cos()))
.collect();
let different: Vec<f32> = (0..128).map(|i| (i as f32).cos()).collect();
let enc_original = dg.encode(&original);
let enc_similar = dg.encode(&similar);
let enc_different = dg.encode(&different);
let sim_to_similar = enc_original.jaccard_similarity(&enc_similar);
let sim_to_different = enc_original.jaccard_similarity(&enc_different);
assert!(
sim_to_similar > sim_to_different,
"Similar input similarity ({}) should be higher than different input ({})",
sim_to_similar,
sim_to_different
);
}
}