1use crate::types::DistanceMetric;
2
3pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
7 let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
8 let mag_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
9 let mag_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
10 if mag_a == 0.0 || mag_b == 0.0 {
11 return 0.0;
12 }
13 dot / (mag_a * mag_b)
14}
15
16pub fn cosine_distance(a: &[f32], b: &[f32]) -> f32 {
18 1.0 - cosine_similarity(a, b)
19}
20
21pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
23 a.iter()
24 .zip(b.iter())
25 .map(|(x, y)| (x - y) * (x - y))
26 .sum::<f32>()
27 .sqrt()
28}
29
30pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
32 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
33}
34
35pub fn dot_product_distance(a: &[f32], b: &[f32]) -> f32 {
38 1.0 - dot_product(a, b)
39}
40
41pub fn normalize(v: &[f32]) -> Vec<f32> {
44 let magnitude: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
45 if magnitude < 1e-10 {
46 return v.to_vec();
47 }
48 v.iter().map(|x| x / magnitude).collect()
49}
50
51pub fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
60 let n = a.len().min(b.len());
61 if n < 8 {
62 return dot_product(&a[..n], &b[..n]);
63 }
64 let chunks = n / 8;
65 let rem = n % 8;
66 let (mut s0, mut s1, mut s2, mut s3) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
67 let (mut s4, mut s5, mut s6, mut s7) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
68 for i in 0..chunks {
69 let base = i * 8;
70 s0 += a[base] * b[base];
71 s1 += a[base + 1] * b[base + 1];
72 s2 += a[base + 2] * b[base + 2];
73 s3 += a[base + 3] * b[base + 3];
74 s4 += a[base + 4] * b[base + 4];
75 s5 += a[base + 5] * b[base + 5];
76 s6 += a[base + 6] * b[base + 6];
77 s7 += a[base + 7] * b[base + 7];
78 }
79 let mut acc = s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7;
80 let start = chunks * 8;
81 for i in 0..rem {
82 acc += a[start + i] * b[start + i];
83 }
84 acc
85}
86
87pub fn cosine_similarity_simd(a: &[f32], b: &[f32]) -> f32 {
97 let n = a.len().min(b.len());
98 if n < 8 {
99 return cosine_similarity(&a[..n], &b[..n]);
100 }
101 let chunks = n / 8;
102 let rem = n % 8;
103 let (mut d0, mut d1, mut d2, mut d3) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
104 let (mut d4, mut d5, mut d6, mut d7) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
105 let (mut aa0, mut aa1, mut aa2, mut aa3) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
106 let (mut aa4, mut aa5, mut aa6, mut aa7) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
107 let (mut bb0, mut bb1, mut bb2, mut bb3) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
108 let (mut bb4, mut bb5, mut bb6, mut bb7) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
109 for i in 0..chunks {
110 let base = i * 8;
111 let (a0, a1, a2, a3) = (a[base], a[base + 1], a[base + 2], a[base + 3]);
112 let (a4, a5, a6, a7) = (a[base + 4], a[base + 5], a[base + 6], a[base + 7]);
113 let (b0, b1, b2, b3) = (b[base], b[base + 1], b[base + 2], b[base + 3]);
114 let (b4, b5, b6, b7) = (b[base + 4], b[base + 5], b[base + 6], b[base + 7]);
115 d0 += a0 * b0;
116 d1 += a1 * b1;
117 d2 += a2 * b2;
118 d3 += a3 * b3;
119 d4 += a4 * b4;
120 d5 += a5 * b5;
121 d6 += a6 * b6;
122 d7 += a7 * b7;
123 aa0 += a0 * a0;
124 aa1 += a1 * a1;
125 aa2 += a2 * a2;
126 aa3 += a3 * a3;
127 aa4 += a4 * a4;
128 aa5 += a5 * a5;
129 aa6 += a6 * a6;
130 aa7 += a7 * a7;
131 bb0 += b0 * b0;
132 bb1 += b1 * b1;
133 bb2 += b2 * b2;
134 bb3 += b3 * b3;
135 bb4 += b4 * b4;
136 bb5 += b5 * b5;
137 bb6 += b6 * b6;
138 bb7 += b7 * b7;
139 }
140 let mut dot = d0 + d1 + d2 + d3 + d4 + d5 + d6 + d7;
141 let mut mag_a_sq = aa0 + aa1 + aa2 + aa3 + aa4 + aa5 + aa6 + aa7;
142 let mut mag_b_sq = bb0 + bb1 + bb2 + bb3 + bb4 + bb5 + bb6 + bb7;
143 let start = chunks * 8;
144 for i in 0..rem {
145 let ai = a[start + i];
146 let bi = b[start + i];
147 dot += ai * bi;
148 mag_a_sq += ai * ai;
149 mag_b_sq += bi * bi;
150 }
151 let mag_a = mag_a_sq.sqrt();
152 let mag_b = mag_b_sq.sqrt();
153 if mag_a == 0.0 || mag_b == 0.0 {
154 return 0.0;
155 }
156 dot / (mag_a * mag_b)
157}
158
159pub fn euclidean_distance_simd(a: &[f32], b: &[f32]) -> f32 {
167 let n = a.len().min(b.len());
168 if n < 8 {
169 return euclidean_distance(&a[..n], &b[..n]);
170 }
171 let chunks = n / 8;
172 let rem = n % 8;
173 let (mut s0, mut s1, mut s2, mut s3) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
174 let (mut s4, mut s5, mut s6, mut s7) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
175 for i in 0..chunks {
176 let base = i * 8;
177 let d0 = a[base] - b[base];
178 let d1 = a[base + 1] - b[base + 1];
179 let d2 = a[base + 2] - b[base + 2];
180 let d3 = a[base + 3] - b[base + 3];
181 let d4 = a[base + 4] - b[base + 4];
182 let d5 = a[base + 5] - b[base + 5];
183 let d6 = a[base + 6] - b[base + 6];
184 let d7 = a[base + 7] - b[base + 7];
185 s0 += d0 * d0;
186 s1 += d1 * d1;
187 s2 += d2 * d2;
188 s3 += d3 * d3;
189 s4 += d4 * d4;
190 s5 += d5 * d5;
191 s6 += d6 * d6;
192 s7 += d7 * d7;
193 }
194 let mut acc = s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7;
195 let start = chunks * 8;
196 for i in 0..rem {
197 let d = a[start + i] - b[start + i];
198 acc += d * d;
199 }
200 acc.sqrt()
201}
202
203pub fn compute_distance(a: &[f32], b: &[f32], metric: &DistanceMetric) -> f32 {
206 match metric {
207 DistanceMetric::Cosine => 1.0 - cosine_similarity_simd(a, b),
208 DistanceMetric::Euclidean => euclidean_distance_simd(a, b),
209 DistanceMetric::DotProduct => 1.0 - dot_product_simd(a, b),
210 }
211}
212
213pub fn to_score(dist: f32, metric: &DistanceMetric) -> f32 {
216 match metric {
217 DistanceMetric::Cosine => 1.0 - dist,
218 DistanceMetric::Euclidean => 1.0 / (1.0 + dist),
219 DistanceMetric::DotProduct => 1.0 - dist,
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_cosine_identical() {
229 let v = vec![1.0f32, 2.0, 3.0];
230 let sim = cosine_similarity(&v, &v);
231 assert!((sim - 1.0).abs() < f32::EPSILON * 10.0);
232 }
233
234 #[test]
235 fn test_cosine_orthogonal() {
236 let a = vec![1.0f32, 0.0];
237 let b = vec![0.0f32, 1.0];
238 let sim = cosine_similarity(&a, &b);
239 assert!(sim.abs() < f32::EPSILON * 10.0);
240 }
241
242 #[test]
243 fn test_cosine_opposite() {
244 let a = vec![1.0f32, 0.0];
245 let b = vec![-1.0f32, 0.0];
246 let sim = cosine_similarity(&a, &b);
247 assert!((sim + 1.0).abs() < f32::EPSILON * 10.0);
248 }
249
250 #[test]
251 fn test_euclidean_zero() {
252 let v = vec![3.0f32, 1.0, -2.0];
253 assert_eq!(euclidean_distance(&v, &v), 0.0);
254 }
255
256 #[test]
257 fn test_euclidean_known() {
258 let a = vec![0.0f32, 0.0];
259 let b = vec![3.0f32, 4.0];
260 let d = euclidean_distance(&a, &b);
261 assert!((d - 5.0).abs() < 1e-6);
262 }
263
264 #[test]
265 fn test_euclidean_simd_matches_scalar() {
266 let a: Vec<f32> = (0..130).map(|i| (i as f32) * 0.3 - 7.0).collect();
268 let b: Vec<f32> = (0..130).map(|i| (i as f32).sin() * 5.0).collect();
269 let s = euclidean_distance(&a, &b);
270 let v = euclidean_distance_simd(&a, &b);
271 assert!((s - v).abs() < 1e-2, "scalar {s} vs simd {v}");
272 assert_eq!(
274 euclidean_distance(&a[..5], &b[..5]),
275 euclidean_distance_simd(&a[..5], &b[..5])
276 );
277 }
278
279 #[test]
280 fn test_normalize() {
281 let v = vec![3.0f32, 4.0];
282 let n = normalize(&v);
283 let mag: f32 = n.iter().map(|x| x * x).sum::<f32>().sqrt();
284 assert!((mag - 1.0).abs() < 1e-6);
285 }
286
287 #[test]
288 fn test_zero_vector_cosine() {
289 let zero = vec![0.0f32, 0.0, 0.0];
290 let other = vec![1.0f32, 2.0, 3.0];
291 let sim = cosine_similarity(&zero, &other);
293 assert_eq!(sim, 0.0);
294 }
295}