1pub mod binary;
47
48#[cfg(not(feature = "scalar"))]
49mod simd;
50
51#[cfg(feature = "scalar")]
52mod scalar;
53
54pub mod sparse;
55
56#[cfg(not(feature = "scalar"))]
58pub use simd::{
59 chebyshev_distance, cosine_distance, cosine_similarity, cosine_similarity_with_norms,
60 dot_product, euclidean_distance, euclidean_distance_squared, l2_norm, manhattan_distance,
61 sum_of_squares, CachedNorm,
62};
63
64#[cfg(feature = "scalar")]
65pub use scalar::{
66 chebyshev_distance, cosine_distance, cosine_similarity, cosine_similarity_with_norms,
67 dot_product, euclidean_distance, euclidean_distance_squared, l2_norm, manhattan_distance,
68 sum_of_squares, CachedNorm,
69};
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
73pub enum DistanceMetric {
74 Euclidean,
76 Cosine,
78 DotProduct,
80 Manhattan,
82 Chebyshev,
84}
85
86impl DistanceMetric {
87 #[inline]
89 #[must_use]
90 pub fn calculate(&self, a: &[f32], b: &[f32]) -> f32 {
91 match self {
92 Self::Euclidean => euclidean_distance(a, b),
93 Self::Cosine => cosine_distance(a, b),
94 Self::DotProduct => -dot_product(a, b),
95 Self::Manhattan => manhattan_distance(a, b),
96 Self::Chebyshev => chebyshev_distance(a, b),
97 }
98 }
99
100 #[inline]
105 #[must_use]
106 pub fn calculate_with_norms(&self, a: &[f32], b: &[f32], norm_a: f32, norm_b: f32) -> f32 {
107 match self {
108 Self::Euclidean => euclidean_distance(a, b),
109 Self::Cosine => {
110 cosine_similarity_with_norms(a, b, norm_a, norm_b).map_or(1.0, |s| 1.0 - s)
111 }
112 Self::DotProduct => -dot_product(a, b),
113 Self::Manhattan => manhattan_distance(a, b),
114 Self::Chebyshev => chebyshev_distance(a, b),
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 const EPSILON: f32 = 1e-5;
124
125 fn assert_near(a: f32, b: f32, epsilon: f32) {
126 assert!(
127 (a - b).abs() < epsilon,
128 "assertion failed: {} !~ {} (diff: {})",
129 a,
130 b,
131 (a - b).abs()
132 );
133 }
134
135 #[test]
136 fn test_euclidean_distance() {
137 let a = [0.0, 0.0];
138 let b = [3.0, 4.0];
139 assert_near(euclidean_distance(&a, &b), 5.0, EPSILON);
140 }
141
142 #[test]
143 fn test_euclidean_distance_squared() {
144 let a = [0.0, 0.0];
145 let b = [3.0, 4.0];
146 assert_near(euclidean_distance_squared(&a, &b), 25.0, EPSILON);
147 }
148
149 #[test]
150 fn test_euclidean_distance_large() {
151 let a: Vec<f32> = (0..1536).map(|i| i as f32 * 0.001).collect();
153 let b: Vec<f32> = (0..1536).map(|i| (i + 1) as f32 * 0.001).collect();
154
155 let dist = euclidean_distance(&a, &b);
156 assert!(dist > 0.039 && dist < 0.040, "Expected ~0.0392, got {}", dist);
159 }
160
161 #[test]
162 fn test_cosine_similarity_identical() {
163 let a = [1.0, 0.0];
164 let b = [1.0, 0.0];
165 assert_near(cosine_similarity(&a, &b), 1.0, EPSILON);
166 }
167
168 #[test]
169 fn test_cosine_similarity_orthogonal() {
170 let c = [1.0, 0.0];
171 let d = [0.0, 1.0];
172 assert_near(cosine_similarity(&c, &d), 0.0, EPSILON);
173 }
174
175 #[test]
176 fn test_cosine_similarity_opposite() {
177 let a = [1.0, 0.0];
178 let b = [-1.0, 0.0];
179 assert_near(cosine_similarity(&a, &b), -1.0, EPSILON);
180 }
181
182 #[test]
183 fn test_cosine_distance() {
184 let a = [1.0, 0.0];
185 let b = [1.0, 0.0];
186 assert_near(cosine_distance(&a, &b), 0.0, EPSILON);
187
188 let c = [1.0, 0.0];
189 let d = [0.0, 1.0];
190 assert_near(cosine_distance(&c, &d), 1.0, EPSILON);
191 }
192
193 #[test]
194 fn test_dot_product() {
195 let a = [1.0, 2.0, 3.0];
196 let b = [4.0, 5.0, 6.0];
197 assert_near(dot_product(&a, &b), 32.0, EPSILON);
198 }
199
200 #[test]
201 fn test_dot_product_large() {
202 let a: Vec<f32> = (0..1536).map(|i| 1.0 / (i + 1) as f32).collect();
204 let b: Vec<f32> = (0..1536).map(|i| (i + 1) as f32).collect();
205
206 let dot = dot_product(&a, &b);
207 assert_near(dot, 1536.0, EPSILON);
209 }
210
211 #[test]
212 fn test_distance_metric_calculate() {
213 let a = [3.0, 4.0];
214 let b = [0.0, 0.0];
215
216 assert_near(DistanceMetric::Euclidean.calculate(&a, &b), 5.0, EPSILON);
217
218 let c = [1.0, 0.0];
220 let d = [1.0, 0.0];
221 assert_near(DistanceMetric::Cosine.calculate(&c, &d), 0.0, EPSILON);
222
223 assert_near(DistanceMetric::DotProduct.calculate(&a, &b), 0.0, EPSILON);
224
225 assert_near(DistanceMetric::Manhattan.calculate(&a, &b), 7.0, EPSILON);
227
228 assert_near(DistanceMetric::Chebyshev.calculate(&a, &b), 4.0, EPSILON);
230 }
231
232 #[test]
233 fn test_manhattan_distance_metric() {
234 let a = [1.0, 2.0, 3.0];
235 let b = [4.0, 6.0, 9.0];
236 assert_near(DistanceMetric::Manhattan.calculate(&a, &b), 13.0, EPSILON);
238 }
239
240 #[test]
241 fn test_chebyshev_distance_metric() {
242 let a = [1.0, 2.0, 3.0];
243 let b = [4.0, 6.0, 9.0];
244 assert_near(DistanceMetric::Chebyshev.calculate(&a, &b), 6.0, EPSILON);
246 }
247
248 #[test]
249 fn test_cached_norm() {
250 let v = [3.0, 4.0];
251 let cached = CachedNorm::new(&v);
252
253 assert_near(cached.norm(), 5.0, EPSILON);
254 assert_near(cached.norm_squared(), 25.0, EPSILON);
255 }
256
257 #[test]
258 fn test_cosine_similarity_with_norms() {
259 let a = [1.0, 0.0];
260 let b = [1.0, 0.0];
261 let norm_a = 1.0;
262 let norm_b = 1.0;
263
264 let sim = cosine_similarity_with_norms(&a, &b, norm_a, norm_b);
265 assert!(sim.is_some());
266 assert_near(sim.unwrap(), 1.0, EPSILON);
267 }
268
269 #[test]
270 fn test_cosine_similarity_with_zero_norm() {
271 let a = [0.0, 0.0];
272 let b = [1.0, 0.0];
273
274 let sim = cosine_similarity_with_norms(&a, &b, 0.0, 1.0);
275 assert!(sim.is_none());
276 }
277}