Skip to main content

oxios_kernel/memory/
normalizer.rs

1//! Embedding vector normalization utilities.
2//!
3//! Provides L2 normalization for dense vectors, ensuring they lie on the
4//! unit hypersphere. Required for cosine similarity via dot product.
5
6/// Normalize a dense vector in-place using L2 normalization.
7///
8/// After normalization, the vector has unit length (L2 norm = 1.0).
9/// If the vector is zero, it is left unchanged.
10pub fn l2_normalize_f32(vector: &mut [f32]) {
11    let norm: f32 = vector.iter().map(|v| v * v).sum::<f32>().sqrt();
12    if norm > 0.0 {
13        for v in vector.iter_mut() {
14            *v /= norm;
15        }
16    }
17}
18
19/// Normalize a dense f64 vector in-place using L2 normalization.
20pub fn l2_normalize_f64(vector: &mut [f64]) {
21    let norm: f64 = vector.iter().map(|v| v * v).sum::<f64>().sqrt();
22    if norm > 0.0 {
23        for v in vector.iter_mut() {
24            *v /= norm;
25        }
26    }
27}
28
29/// Return the L2 norm of a vector.
30pub fn l2_norm_f32(vector: &[f32]) -> f32 {
31    vector.iter().map(|v| v * v).sum::<f32>().sqrt()
32}
33
34/// Return the L2 norm of an f64 vector.
35pub fn l2_norm_f64(vector: &[f64]) -> f64 {
36    vector.iter().map(|v| v * v).sum::<f64>().sqrt()
37}
38
39/// Compute dot product of two f32 vectors.
40pub fn dot_product_f32(a: &[f32], b: &[f32]) -> f32 {
41    a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
42}
43
44/// Compute cosine similarity between two f32 vectors.
45pub fn cosine_similarity_f32(a: &[f32], b: &[f32]) -> f32 {
46    let dot = dot_product_f32(a, b);
47    let na = l2_norm_f32(a);
48    let nb = l2_norm_f32(b);
49    if na == 0.0 || nb == 0.0 {
50        return 0.0;
51    }
52    dot / (na * nb)
53}
54
55// ---------------------------------------------------------------------------
56// Tests
57// ---------------------------------------------------------------------------
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_l2_normalize_unit_vector() {
65        let mut v = vec![1.0f32, 0.0, 0.0];
66        l2_normalize_f32(&mut v);
67        assert!((l2_norm_f32(&v) - 1.0).abs() < 1e-6);
68    }
69
70    #[test]
71    fn test_l2_normalize_general() {
72        let mut v = vec![3.0f32, 4.0];
73        l2_normalize_f32(&mut v);
74        assert!((l2_norm_f32(&v) - 1.0).abs() < 1e-6);
75        assert!((v[0] - 0.6).abs() < 1e-6);
76        assert!((v[1] - 0.8).abs() < 1e-6);
77    }
78
79    #[test]
80    fn test_l2_normalize_zero() {
81        let mut v = vec![0.0f32, 0.0, 0.0];
82        l2_normalize_f32(&mut v);
83        assert_eq!(v, vec![0.0, 0.0, 0.0]);
84    }
85
86    #[test]
87    fn test_cosine_similarity_identical() {
88        let v = vec![1.0f32, 0.0, 0.0];
89        let sim = cosine_similarity_f32(&v, &v);
90        assert!((sim - 1.0).abs() < 1e-6);
91    }
92
93    #[test]
94    fn test_cosine_similarity_orthogonal() {
95        let a = vec![1.0f32, 0.0, 0.0];
96        let b = vec![0.0f32, 1.0, 0.0];
97        let sim = cosine_similarity_f32(&a, &b);
98        assert!(sim.abs() < 1e-6);
99    }
100
101    #[test]
102    fn test_cosine_similarity_opposite() {
103        let a = vec![1.0f32, 0.0];
104        let b = vec![-1.0f32, 0.0];
105        let sim = cosine_similarity_f32(&a, &b);
106        assert!((sim - (-1.0)).abs() < 1e-6);
107    }
108
109    #[test]
110    fn test_dot_product() {
111        let a = vec![1.0f32, 2.0, 3.0];
112        let b = vec![4.0f32, 5.0, 6.0];
113        assert_eq!(dot_product_f32(&a, &b), 32.0);
114    }
115
116    #[test]
117    fn test_l2_normalize_f64() {
118        let mut v = vec![3.0f64, 4.0];
119        l2_normalize_f64(&mut v);
120        assert!((l2_norm_f64(&v) - 1.0).abs() < 1e-12);
121    }
122}