nitrite_vector/
distance.rs1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum Metric {
14 Cosine,
17 Euclidean,
20 Dot,
23}
24
25impl Metric {
26 #[inline]
28 pub fn normalizes(&self) -> bool {
29 matches!(self, Metric::Cosine)
30 }
31
32 #[inline]
38 pub fn prepare(&self, mut v: Vec<f32>) -> Vec<f32> {
39 if self.normalizes() {
40 normalize(&mut v);
41 }
42 v
43 }
44
45 #[inline]
50 pub fn distance(&self, a: &[f32], b: &[f32]) -> f32 {
51 debug_assert_eq!(a.len(), b.len(), "vector dimension mismatch");
52 match self {
53 Metric::Cosine => 1.0 - dot(a, b),
54 Metric::Euclidean => squared_l2(a, b),
55 Metric::Dot => -dot(a, b),
56 }
57 }
58
59 #[inline]
65 pub fn score(&self, distance: f32) -> f32 {
66 match self {
67 Metric::Cosine => 1.0 - distance,
68 Metric::Dot => -distance,
69 Metric::Euclidean => 1.0 / (1.0 + distance.max(0.0).sqrt()),
70 }
71 }
72}
73
74#[inline]
76pub fn normalize(v: &mut [f32]) {
77 let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
78 if norm > 0.0 {
79 let inv = 1.0 / norm;
80 for x in v.iter_mut() {
81 *x *= inv;
82 }
83 }
84}
85
86#[inline]
92pub fn dot(a: &[f32], b: &[f32]) -> f32 {
93 use wide::f32x8;
94 let n = a.len();
95 let mut acc = f32x8::splat(0.0);
96 let mut i = 0;
97 while i + 8 <= n {
98 let va = f32x8::from(&a[i..i + 8]);
99 let vb = f32x8::from(&b[i..i + 8]);
100 acc = va.mul_add(vb, acc);
101 i += 8;
102 }
103 let mut sum = acc.reduce_add();
104 while i < n {
105 sum += a[i] * b[i];
106 i += 1;
107 }
108 sum
109}
110
111#[inline]
113pub fn squared_l2(a: &[f32], b: &[f32]) -> f32 {
114 use wide::f32x8;
115 let n = a.len();
116 let mut acc = f32x8::splat(0.0);
117 let mut i = 0;
118 while i + 8 <= n {
119 let va = f32x8::from(&a[i..i + 8]);
120 let vb = f32x8::from(&b[i..i + 8]);
121 let d = va - vb;
122 acc = d.mul_add(d, acc);
123 i += 8;
124 }
125 let mut sum = acc.reduce_add();
126 while i < n {
127 let d = a[i] - b[i];
128 sum += d * d;
129 i += 1;
130 }
131 sum
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn approx(a: f32, b: f32) {
139 assert!((a - b).abs() < 1e-5, "expected {a} ≈ {b}");
140 }
141
142 #[test]
143 fn dot_and_l2_basics() {
144 let a = [1.0, 2.0, 3.0];
145 let b = [4.0, 5.0, 6.0];
146 approx(dot(&a, &b), 32.0);
147 approx(squared_l2(&a, &b), 27.0); }
149
150 #[test]
151 fn cosine_of_identical_normalized_vectors_is_zero_distance() {
152 let v = Metric::Cosine.prepare(vec![3.0, 4.0]); approx(v[0], 0.6);
154 approx(v[1], 0.8);
155 approx(Metric::Cosine.distance(&v, &v), 0.0);
156 approx(Metric::Cosine.score(Metric::Cosine.distance(&v, &v)), 1.0);
157 }
158
159 #[test]
160 fn cosine_of_orthogonal_vectors_is_one() {
161 let a = Metric::Cosine.prepare(vec![1.0, 0.0]);
162 let b = Metric::Cosine.prepare(vec![0.0, 1.0]);
163 approx(Metric::Cosine.distance(&a, &b), 1.0);
164 approx(Metric::Cosine.score(Metric::Cosine.distance(&a, &b)), 0.0);
165 }
166
167 #[test]
168 fn cosine_of_opposite_vectors_is_two() {
169 let a = Metric::Cosine.prepare(vec![1.0, 0.0]);
170 let b = Metric::Cosine.prepare(vec![-1.0, 0.0]);
171 approx(Metric::Cosine.distance(&a, &b), 2.0);
172 approx(Metric::Cosine.score(Metric::Cosine.distance(&a, &b)), -1.0);
173 }
174
175 #[test]
176 fn euclidean_distance_and_score() {
177 let a = vec![0.0, 0.0];
178 let b = vec![3.0, 4.0];
179 approx(Metric::Euclidean.distance(&a, &b), 25.0);
181 approx(Metric::Euclidean.score(25.0), 1.0 / 6.0);
183 approx(Metric::Euclidean.score(Metric::Euclidean.distance(&a, &a)), 1.0);
185 }
186
187 #[test]
188 fn dot_metric_orders_larger_dot_first() {
189 let q = vec![1.0, 1.0];
190 let near = vec![2.0, 2.0]; let far = vec![0.5, 0.0]; assert!(Metric::Dot.distance(&q, &near) < Metric::Dot.distance(&q, &far));
193 approx(Metric::Dot.score(Metric::Dot.distance(&q, &near)), 4.0);
194 }
195
196 #[test]
197 fn zero_vector_normalize_is_noop() {
198 let mut v = vec![0.0, 0.0, 0.0];
199 normalize(&mut v);
200 assert_eq!(v, vec![0.0, 0.0, 0.0]);
201 }
202}