1#![feature(binary_heap_into_iter_sorted)]
2use half::f16;
3use ordered_float::OrderedFloat;
4use rayon::prelude::*;
5use simsimd::{f16 as simd_f16, SpatialSimilarity};
6use std::{cmp::Reverse, collections::BinaryHeap};
7
8pub trait DistanceMetric<T>: Send + Sync {
14 type HeapItem: Ord + Send + Copy;
15 fn build_item(query: &[T], chunk: &[T], i: usize) -> Self::HeapItem;
16 fn extract_item(item: Self::HeapItem) -> (f32, usize);
17}
18
19pub struct L2;
23impl<T: VectorType> DistanceMetric<T> for L2 {
24 type HeapItem = (OrderedFloat<f32>, usize);
25
26 #[inline(always)]
27 fn build_item(query: &[T], chunk: &[T], i: usize) -> Self::HeapItem {
28 (OrderedFloat(T::l2_squared(query, chunk)), i)
29 }
30
31 #[inline(always)]
32 fn extract_item(item: Self::HeapItem) -> (f32, usize) {
33 (item.0 .0, item.1)
34 }
35}
36
37pub struct Dot;
42impl<T: VectorType> DistanceMetric<T> for Dot {
43 type HeapItem = Reverse<(OrderedFloat<f32>, usize)>;
44
45 #[inline(always)]
46 fn build_item(query: &[T], chunk: &[T], i: usize) -> Self::HeapItem {
47 Reverse((OrderedFloat(T::dot_product(query, chunk)), i))
48 }
49
50 #[inline(always)]
51 fn extract_item(item: Self::HeapItem) -> (f32, usize) {
52 (item.0 .0 .0, item.0 .1)
53 }
54}
55
56pub trait Indexable<T>: Send + Sync {
62 fn get(&self, i: usize) -> &[T];
63 fn len(&self) -> usize;
64 fn is_empty(&self) -> bool {
65 self.len() == 0
66 }
67}
68
69impl<T, C> Indexable<T> for (C, usize)
70where
71 T: Send + Sync,
72 C: AsRef<[T]> + Send + Sync,
73{
74 #[inline(always)]
75 fn get(&self, i: usize) -> &[T] {
76 &self.0.as_ref()[i * self.1..(i + 1) * self.1]
77 }
78
79 #[inline(always)]
80 fn len(&self) -> usize {
81 self.0.as_ref().len() / self.1
82 }
83}
84
85impl<T, U> Indexable<T> for &[U]
86where
87 T: Send + Sync,
88 U: AsRef<[T]> + Send + Sync,
89{
90 #[inline(always)]
91 fn get(&self, i: usize) -> &[T] {
92 self[i].as_ref()
93 }
94
95 #[inline(always)]
96 fn len(&self) -> usize {
97 <[U]>::len(self)
98 }
99}
100
101impl<T, U> Indexable<T> for Vec<U>
103where
104 T: Send + Sync,
105 U: AsRef<[T]> + Send + Sync,
106{
107 #[inline(always)]
108 fn get(&self, i: usize) -> &[T] {
109 self[i].as_ref()
110 }
111
112 #[inline(always)]
113 fn len(&self) -> usize {
114 self.len()
115 }
116}
117
118impl<T, U> Indexable<T> for &Vec<U>
120where
121 T: Send + Sync,
122 U: AsRef<[T]> + Send + Sync,
123{
124 #[inline(always)]
125 fn get(&self, i: usize) -> &[T] {
126 self[i].as_ref()
127 }
128
129 #[inline(always)]
130 fn len(&self) -> usize {
131 Vec::len(self)
132 }
133}
134
135pub fn knn<T: VectorType, M: DistanceMetric<T>>(
174 data: impl Indexable<T>,
175 query: &[T],
176 k: usize,
177) -> Vec<(f32, usize)> {
178 let heap = (0..data.len())
179 .into_par_iter()
180 .map(|i| M::build_item(query, data.get(i), i))
181 .fold(
182 || BinaryHeap::with_capacity(k + 1),
183 |mut local_heap, item| {
184 if local_heap.len() < k {
185 local_heap.push(item);
186 } else if let Some(mut top) = local_heap.peek_mut() {
187 if item < *top {
188 *top = item;
189 }
190 }
191 local_heap
192 },
193 )
194 .reduce(
195 || BinaryHeap::with_capacity(k + 1),
196 |mut heap1, heap2| {
197 for item in heap2.into_iter() {
198 if heap1.len() < k {
199 heap1.push(item);
200 } else if let Some(mut top) = heap1.peek_mut() {
201 if item < *top {
202 *top = item;
203 }
204 }
205 }
206 heap1
207 },
208 );
209
210 let res: Vec<_> = heap.into_iter_sorted().map(M::extract_item).collect();
211 res.into_iter().rev().collect()
212}
213
214pub trait VectorType: Send + Sync + Sized {
215 fn l2_squared(query: &[Self], chunk: &[Self]) -> f32;
216 fn dot_product(query: &[Self], chunk: &[Self]) -> f32;
217}
218
219impl VectorType for f32 {
220 #[inline(always)]
221 fn l2_squared(query: &[f32], chunk: &[f32]) -> f32 {
222 f32::l2sq(query, chunk).unwrap() as f32
223 }
224
225 #[inline(always)]
226 fn dot_product(query: &[f32], chunk: &[f32]) -> f32 {
227 f32::dot(query, chunk).unwrap() as f32
228 }
229}
230
231#[inline(always)]
232fn as_simsimd_slice(slice: &[f16]) -> &[simd_f16] {
233 unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const simd_f16, slice.len()) }
236}
237
238impl VectorType for f16 {
239 #[inline(always)]
240 fn l2_squared(query: &[f16], chunk: &[f16]) -> f32 {
241 simd_f16::l2sq(as_simsimd_slice(query), as_simsimd_slice(chunk)).unwrap() as f32
242 }
243
244 #[inline(always)]
245 fn dot_product(query: &[f16], chunk: &[f16]) -> f32 {
246 simd_f16::dot(as_simsimd_slice(query), as_simsimd_slice(chunk)).unwrap() as f32
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use linfa_nn::{distance::L2Dist, LinearSearch, NearestNeighbour};
253 use ndarray::Array2;
254 use rand::{distr::Uniform, Rng};
255
256 use super::*;
257
258 const DIM: usize = 768;
259
260 fn generate_random_data(num_vectors: usize) -> Vec<f32> {
261 let mut rng = rand::rng();
262 let uniform = Uniform::new(0.0, 10.0).unwrap();
263 let mut data = Vec::with_capacity(num_vectors * DIM);
264
265 for _ in 0..(num_vectors * DIM) {
266 data.push(rng.sample(uniform));
267 }
268 data
269 }
270
271 #[test]
272 fn test_knn_search_l2() {
273 let data = vec![
274 1.0, 2.0, 3.0, 4.0, 8.0, 7.0, 6.0, 5.0, 1.0, 2.0, 3.0, 9.0, 4.0, 3.0, 2.0, 1.0, ];
279 let dim: usize = 4;
280 let query = [1.0, 2.0, 3.0, 5.0];
281 let k = 4;
282 let neighbors = knn::<_, L2>((&data, dim), &query, k);
283
284 assert_eq!(neighbors.len(), 4);
285 assert_eq!(neighbors[0], (1.0, 0));
286 assert_eq!(neighbors[1], (16.0, 2));
287 }
288
289 #[test]
290 fn test_knn_search_dot() {
291 let data = vec![1.0, -0.1, 0.3, 1.0, -1.0, 0.0, 0.0, -1.0];
292 let dim = 2;
293 let query = [0.9, 0.1];
294 let k = 4;
295 let neighbors = knn::<_, Dot>((&data, dim), &query, k);
296
297 assert_eq!(neighbors.len(), 4);
298 assert_eq!(neighbors[0], (0.89, 0));
299 assert_eq!(neighbors[1], (0.37, 1));
300 assert_eq!(neighbors[2], (-0.1, 3));
301 assert_eq!(neighbors[3], (-0.9, 2));
302 }
303
304 #[test]
305 fn test_compare_with_linfa_l2() {
306 let num_vectors = 10_000;
307 let data = generate_random_data(num_vectors);
308 let dataset = Array2::from_shape_vec((num_vectors, DIM), data.clone())
309 .expect("Failed to reshape data.");
310 let query = generate_random_data(1);
311
312 let index = LinearSearch::new()
313 .from_batch(&dataset, L2Dist {})
314 .expect("Failed to build LinearSearch index");
315
316 let gt = index
317 .k_nearest((&query).into(), 30)
318 .unwrap()
319 .iter()
320 .map(|(_, i)| *i)
321 .collect::<Vec<_>>();
322 let pred = knn::<_, L2>((&data, DIM), &query, 30)
323 .iter()
324 .map(|(_, i)| *i)
325 .collect::<Vec<_>>();
326 assert_eq!(gt, pred);
327 }
328
329 #[test]
330 fn test_knn_f16_l2() {
331 let data = vec![
332 f16::from_f32(1.0),
333 f16::from_f32(2.0),
334 f16::from_f32(3.0),
335 f16::from_f32(4.0),
336 f16::from_f32(8.0),
337 f16::from_f32(7.0),
338 f16::from_f32(6.0),
339 f16::from_f32(5.0),
340 f16::from_f32(1.0),
341 f16::from_f32(2.0),
342 f16::from_f32(3.0),
343 f16::from_f32(9.0),
344 ];
345 let dim = 4;
346 let query = [
347 f16::from_f32(1.0),
348 f16::from_f32(2.0),
349 f16::from_f32(3.0),
350 f16::from_f32(5.0),
351 ];
352 let k = 2;
353 let neighbors = knn::<_, L2>((&data, dim), &query, k);
354
355 assert_eq!(neighbors.len(), 2);
356 assert_eq!(neighbors[0].1, 0); assert_eq!(neighbors[1].1, 2); }
359
360 #[test]
361 fn test_knn_f16_dot() {
362 let data = vec![
363 f16::from_f32(1.0),
364 f16::from_f32(-0.1),
365 f16::from_f32(0.3),
366 f16::from_f32(1.0),
367 f16::from_f32(-1.0),
368 f16::from_f32(0.0),
369 f16::from_f32(0.0),
370 f16::from_f32(-1.0),
371 ];
372 let dim = 2;
373 let query = [f16::from_f32(0.9), f16::from_f32(0.1)];
374 let k = 4;
375 let neighbors = knn::<_, Dot>((&data, dim), &query, k);
376
377 assert_eq!(neighbors.len(), 4);
378 assert_eq!(neighbors[0].1, 0); assert_eq!(neighbors[1].1, 1); assert_eq!(neighbors[2].1, 3); assert_eq!(neighbors[3].1, 2); }
383}