use serde::{Deserialize, Serialize};
use crate::simd;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Embedding {
pub vector: Vec<f32>,
pub text_hash: u64,
}
impl Embedding {
pub fn new(mut vector: Vec<f32>, text_hash: u64) -> Self {
simd::normalize(&mut vector);
Self { vector, text_hash }
}
pub fn from_normalized(vector: Vec<f32>, text_hash: u64) -> Self {
Self { vector, text_hash }
}
pub fn dimensions(&self) -> usize {
self.vector.len()
}
pub fn cosine_similarity(&self, other: &Embedding) -> f32 {
simd::dot_product(&self.vector, &other.vector)
}
pub fn dot_product(&self, other: &Embedding) -> f32 {
simd::dot_product(&self.vector, &other.vector)
}
pub fn euclidean_distance(&self, other: &Embedding) -> f32 {
let mut sum = 0.0f32;
for (a, b) in self.vector.iter().zip(other.vector.iter()) {
let diff = a - b;
sum += diff * diff;
}
sum.sqrt()
}
pub fn norm(&self) -> f32 {
simd::dot_product(&self.vector, &self.vector).sqrt()
}
pub fn is_normalized(&self) -> bool {
let norm = self.norm();
(norm - 1.0).abs() < 1e-5
}
}
impl PartialEq for Embedding {
fn eq(&self, other: &Self) -> bool {
self.text_hash == other.text_hash && self.vector == other.vector
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_embedding(values: &[f32]) -> Embedding {
Embedding::new(values.to_vec(), 0)
}
#[test]
fn test_embedding_normalization() {
let embed = make_embedding(&[3.0, 4.0]);
assert!(embed.is_normalized());
assert!((embed.vector[0] - 0.6).abs() < 1e-5);
assert!((embed.vector[1] - 0.8).abs() < 1e-5);
}
#[test]
fn test_cosine_similarity_identical() {
let embed = make_embedding(&[1.0, 0.0, 0.0]);
let similarity = embed.cosine_similarity(&embed);
assert!((similarity - 1.0).abs() < 1e-5);
}
#[test]
fn test_cosine_similarity_orthogonal() {
let a = make_embedding(&[1.0, 0.0]);
let b = make_embedding(&[0.0, 1.0]);
let similarity = a.cosine_similarity(&b);
assert!(similarity.abs() < 1e-5);
}
#[test]
fn test_cosine_similarity_opposite() {
let a = make_embedding(&[1.0, 0.0]);
let b = make_embedding(&[-1.0, 0.0]);
let similarity = a.cosine_similarity(&b);
assert!((similarity + 1.0).abs() < 1e-5);
}
#[test]
fn test_euclidean_distance() {
let a = make_embedding(&[1.0, 0.0]);
let b = make_embedding(&[0.0, 1.0]);
let dist = a.euclidean_distance(&b);
assert!(dist > 0.0);
}
#[test]
fn test_from_normalized() {
let embed = Embedding::from_normalized(vec![0.6, 0.8], 123);
assert!(embed.is_normalized());
assert_eq!(embed.text_hash, 123);
}
#[test]
fn test_dimensions() {
let embed = make_embedding(&[1.0, 2.0, 3.0, 4.0]);
assert_eq!(embed.dimensions(), 4);
}
}