pub trait MemoryEmbedder: Send + Sync {
fn embed(&self, text: &str) -> Vec<f32>;
}
pub const HYBRID_COSINE_WEIGHT: f64 = 1.0;
pub const SEMANTIC_FLOOR: f64 = 0.25;
pub fn cosine(a: &[f32], b: &[f32]) -> f64 {
if a.is_empty() || a.len() != b.len() {
return 0.0;
}
let mut dot = 0.0f64;
let mut na = 0.0f64;
let mut nb = 0.0f64;
for (x, y) in a.iter().zip(b) {
let (x, y) = (*x as f64, *y as f64);
dot += x * y;
na += x * x;
nb += y * y;
}
let denom = na.sqrt() * nb.sqrt();
if denom <= f64::EPSILON {
return 0.0;
}
let sim = dot / denom;
if sim.is_finite() {
sim
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cosine_identical_is_one_orthogonal_is_zero() {
assert!((cosine(&[1.0, 0.0], &[2.0, 0.0]) - 1.0).abs() < 1e-9);
assert!(cosine(&[1.0, 0.0], &[0.0, 1.0]).abs() < 1e-9);
}
#[test]
fn cosine_degenerate_and_mismatch_are_zero() {
assert_eq!(cosine(&[], &[1.0]), 0.0);
assert_eq!(cosine(&[1.0, 2.0], &[1.0]), 0.0);
assert_eq!(cosine(&[0.0, 0.0], &[1.0, 1.0]), 0.0);
}
#[test]
fn cosine_non_finite_component_is_zero() {
assert_eq!(cosine(&[f32::NAN, 1.0], &[1.0, 1.0]), 0.0);
assert_eq!(cosine(&[f32::INFINITY, 0.0], &[1.0, 1.0]), 0.0);
}
}