ceylon_next/memory/vector/
utils.rs

1//! Utility functions for vector operations.
2
3/// Computes the cosine similarity between two vectors.
4///
5/// Cosine similarity measures the cosine of the angle between two vectors,
6/// producing a value between -1.0 and 1.0, where:
7/// - 1.0 indicates identical direction (most similar)
8/// - 0.0 indicates orthogonal vectors (unrelated)
9/// - -1.0 indicates opposite direction (most dissimilar)
10///
11/// # Arguments
12///
13/// * `a` - First vector
14/// * `b` - Second vector
15///
16/// # Returns
17///
18/// The cosine similarity score, or an error if vectors have different dimensions.
19///
20/// # Example
21///
22/// ```
23/// use ceylon_next::memory::vector::cosine_similarity;
24///
25/// let v1 = vec![1.0, 2.0, 3.0];
26/// let v2 = vec![4.0, 5.0, 6.0];
27/// let similarity = cosine_similarity(&v1, &v2).unwrap();
28/// ```
29pub fn cosine_similarity(a: &[f32], b: &[f32]) -> Result<f32, String> {
30    if a.len() != b.len() {
31        return Err(format!(
32            "Vector dimensions mismatch: {} vs {}",
33            a.len(),
34            b.len()
35        ));
36    }
37
38    if a.is_empty() {
39        return Err("Cannot compute similarity of empty vectors".to_string());
40    }
41
42    let dot_product: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
43
44    let magnitude_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
45    let magnitude_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
46
47    if magnitude_a == 0.0 || magnitude_b == 0.0 {
48        return Ok(0.0);
49    }
50
51    Ok(dot_product / (magnitude_a * magnitude_b))
52}
53
54/// Normalizes a vector to unit length (L2 normalization).
55///
56/// This scales the vector so that its magnitude (Euclidean norm) is 1.0.
57/// Normalized vectors are useful for efficient similarity comparisons.
58///
59/// # Arguments
60///
61/// * `vector` - The vector to normalize (will be modified in place)
62///
63/// # Example
64///
65/// ```
66/// use ceylon_next::memory::vector::normalize_vector;
67///
68/// let mut v = vec![3.0, 4.0];
69/// normalize_vector(&mut v);
70/// assert!((v[0] - 0.6).abs() < 0.001);
71/// assert!((v[1] - 0.8).abs() < 0.001);
72/// ```
73pub fn normalize_vector(vector: &mut [f32]) {
74    let magnitude: f32 = vector.iter().map(|x| x * x).sum::<f32>().sqrt();
75
76    if magnitude > 0.0 {
77        for x in vector.iter_mut() {
78            *x /= magnitude;
79        }
80    }
81}
82
83/// Computes the Euclidean distance between two vectors.
84///
85/// # Arguments
86///
87/// * `a` - First vector
88/// * `b` - Second vector
89///
90/// # Returns
91///
92/// The Euclidean distance, or an error if vectors have different dimensions.
93pub fn euclidean_distance(a: &[f32], b: &[f32]) -> Result<f32, String> {
94    if a.len() != b.len() {
95        return Err(format!(
96            "Vector dimensions mismatch: {} vs {}",
97            a.len(),
98            b.len()
99        ));
100    }
101
102    let sum: f32 = a
103        .iter()
104        .zip(b.iter())
105        .map(|(x, y)| {
106            let diff = x - y;
107            diff * diff
108        })
109        .sum();
110
111    Ok(sum.sqrt())
112}
113
114/// Computes the dot product of two vectors.
115///
116/// # Arguments
117///
118/// * `a` - First vector
119/// * `b` - Second vector
120///
121/// # Returns
122///
123/// The dot product, or an error if vectors have different dimensions.
124pub fn dot_product(a: &[f32], b: &[f32]) -> Result<f32, String> {
125    if a.len() != b.len() {
126        return Err(format!(
127            "Vector dimensions mismatch: {} vs {}",
128            a.len(),
129            b.len()
130        ));
131    }
132
133    Ok(a.iter().zip(b.iter()).map(|(x, y)| x * y).sum())
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_cosine_similarity_identical() {
142        let v = vec![1.0, 2.0, 3.0];
143        let sim = cosine_similarity(&v, &v).unwrap();
144        assert!((sim - 1.0).abs() < 1e-6);
145    }
146
147    #[test]
148    fn test_cosine_similarity_orthogonal() {
149        let v1 = vec![1.0, 0.0];
150        let v2 = vec![0.0, 1.0];
151        let sim = cosine_similarity(&v1, &v2).unwrap();
152        assert!((sim - 0.0).abs() < 1e-6);
153    }
154
155    #[test]
156    fn test_cosine_similarity_opposite() {
157        let v1 = vec![1.0, 0.0];
158        let v2 = vec![-1.0, 0.0];
159        let sim = cosine_similarity(&v1, &v2).unwrap();
160        assert!((sim - (-1.0)).abs() < 1e-6);
161    }
162
163    #[test]
164    fn test_normalize_vector() {
165        let mut v = vec![3.0, 4.0];
166        normalize_vector(&mut v);
167        assert!((v[0] - 0.6).abs() < 1e-6);
168        assert!((v[1] - 0.8).abs() < 1e-6);
169
170        // Check magnitude is 1.0
171        let magnitude: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
172        assert!((magnitude - 1.0).abs() < 1e-6);
173    }
174
175    #[test]
176    fn test_euclidean_distance() {
177        let v1 = vec![1.0, 2.0];
178        let v2 = vec![4.0, 6.0];
179        let dist = euclidean_distance(&v1, &v2).unwrap();
180        assert!((dist - 5.0).abs() < 1e-6);
181    }
182
183    #[test]
184    fn test_dot_product() {
185        let v1 = vec![1.0, 2.0, 3.0];
186        let v2 = vec![4.0, 5.0, 6.0];
187        let dot = dot_product(&v1, &v2).unwrap();
188        assert!((dot - 32.0).abs() < 1e-6); // 1*4 + 2*5 + 3*6 = 32
189    }
190
191    #[test]
192    fn test_dimension_mismatch() {
193        let v1 = vec![1.0, 2.0];
194        let v2 = vec![1.0, 2.0, 3.0];
195        assert!(cosine_similarity(&v1, &v2).is_err());
196        assert!(euclidean_distance(&v1, &v2).is_err());
197        assert!(dot_product(&v1, &v2).is_err());
198    }
199}