Skip to main content

diskann_vector/distance/
implementations.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use diskann_wide::{arch::Target2, Architecture, ARCH};
7
8/// Experimental traits for distance functions.
9use super::simd;
10use crate::{
11    AsUnaligned, Half, MathematicalValue, PureDistanceFunction, SimilarityScore, UnalignedSlice,
12};
13
14macro_rules! architecture_hook {
15    ($functor:ty, $impl:path) => {
16        impl<A, T, L, R> diskann_wide::arch::Target2<A, T, L, R> for $functor
17        where
18            A: Architecture,
19            L: AsUnaligned,
20            R: AsUnaligned,
21            $impl: simd::SIMDSchema<L::Element, R::Element, A>,
22            Self: PostOp<<$impl as simd::SIMDSchema<L::Element, R::Element, A>>::Return, T>,
23        {
24            #[inline(always)]
25            fn run(self, arch: A, left: L, right: R) -> T {
26                Self::post_op(simd::simd_op(
27                    &$impl,
28                    arch,
29                    left.as_unaligned(),
30                    right.as_unaligned(),
31                ))
32            }
33        }
34
35        impl<A, T, L, R> diskann_wide::arch::FTarget2<A, T, L, R> for $functor
36        where
37            A: Architecture,
38            L: AsUnaligned,
39            R: AsUnaligned,
40            Self: diskann_wide::arch::Target2<A, T, L, R>,
41        {
42            #[inline(always)]
43            fn run(arch: A, left: L, right: R) -> T {
44                arch.run2(Self::default(), left, right)
45            }
46        }
47    };
48}
49
50/// A utility for specializing distance computations for fixed-length slices.
51///
52/// When `F` implements [`diskann_wide::arch::Target2`] for [`UnalignedSlice`],
53/// `Specialize<N, F>` will implement [`diskann_wide::arch::FTarget2`] for slices of exactly
54/// length `N`. This can lead to must better generated code, especially when `N` is small.
55///
56/// Passing slices of length not equal to `N` will panic.
57/// ```
58/// use diskann_vector::{AsUnaligned, distance::{Specialize, SquaredL2}};
59/// use diskann_wide::{ARCH, arch::FTarget2};
60///
61/// let x = [1.0f32, 2.0, 3.0];
62/// let y = [2.0f32, 3.0, 4.0];
63///
64/// let result: f32 = Specialize::<3, SquaredL2>::run(
65///     ARCH,
66///     x.as_unaligned(),
67///     y.as_unaligned(),
68/// );
69///
70/// assert_eq!(result, 3.0);
71/// ```
72#[derive(Debug)]
73pub struct Specialize<const N: usize, F>(std::marker::PhantomData<F>);
74
75impl<const N: usize, F> Specialize<N, F> {
76    /// Create a new [`Specialize`].
77    pub const fn new() -> Self {
78        Self(std::marker::PhantomData)
79    }
80}
81
82impl<const N: usize, F> Default for Specialize<N, F> {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl<const N: usize, F> Clone for Specialize<N, F> {
89    fn clone(&self) -> Self {
90        *self
91    }
92}
93
94impl<const N: usize, F> Copy for Specialize<N, F> {}
95
96impl<A, T, L, R, const N: usize, F>
97    diskann_wide::arch::FTarget2<A, T, UnalignedSlice<'_, L>, UnalignedSlice<'_, R>>
98    for Specialize<N, F>
99where
100    A: Architecture,
101    F: for<'a, 'b> diskann_wide::arch::Target2<A, T, UnalignedSlice<'a, L>, UnalignedSlice<'b, R>>
102        + Default,
103{
104    #[inline(always)]
105    fn run(arch: A, x: UnalignedSlice<'_, L>, y: UnalignedSlice<'_, R>) -> T {
106        if (x.len() != N) | (y.len() != N) {
107            fail_length_check(x, y, N);
108        }
109
110        // The validation of `x.len()` and `y.len()` is sufficient (and indeed necessary)
111        // to trigger constant propagation and unrolling.
112        arch.run2(F::default(), x, y)
113    }
114}
115
116// Outline the panic formatting and keep the calling convention the same as
117// the top function. This keeps code generation extremely lightweight.
118#[inline(never)]
119#[allow(clippy::panic)]
120fn fail_length_check<L, R>(x: UnalignedSlice<'_, L>, y: UnalignedSlice<'_, R>, len: usize) -> ! {
121    let message = if x.len() != len {
122        ("first", x.len())
123    } else {
124        ("second", y.len())
125    };
126    panic!(
127        "expected {} argument to have length {}, instead it has length {}",
128        message.0, len, message.1
129    );
130}
131
132/// An internal trait to transform the result of the low-level SIMD ops into a value
133/// expected by the rest of DiskANN.
134///
135/// Keep this trait private as it is likely to either change or be removed completely in the
136/// near future once better integer implementations come online.
137pub(super) trait PostOp<From, To> {
138    fn post_op(x: From) -> To;
139}
140
141/// Provide explicit dynamic and sized implementations for a distance functor.
142macro_rules! use_simd_implementation {
143    ($functor:ty, $T:ty, $U:ty) => {
144        //////////////////////
145        // Similarity Score //
146        //////////////////////
147
148        // Dynamically Sized.
149        impl PureDistanceFunction<&[$T], &[$U], SimilarityScore<f32>> for $functor {
150            #[inline]
151            fn evaluate(x: &[$T], y: &[$U]) -> SimilarityScore<f32> {
152                <$functor>::default().run(ARCH, x, y)
153            }
154        }
155
156        // Statically Sized
157        impl<const N: usize> PureDistanceFunction<&[$T; N], &[$U; N], SimilarityScore<f32>>
158            for $functor
159        {
160            #[inline]
161            fn evaluate(x: &[$T; N], y: &[$U; N]) -> SimilarityScore<f32> {
162                <$functor>::default().run(ARCH, x, y)
163            }
164        }
165
166        ////////////////////////
167        // Mathematical Value //
168        ////////////////////////
169
170        // Dynamically Sized.
171        impl PureDistanceFunction<&[$T], &[$U], MathematicalValue<f32>> for $functor {
172            #[inline]
173            fn evaluate(x: &[$T], y: &[$U]) -> MathematicalValue<f32> {
174                <$functor>::default().run(ARCH, x, y)
175            }
176        }
177        // Statically Sized
178        impl<const N: usize> PureDistanceFunction<&[$T; N], &[$U; N], MathematicalValue<f32>>
179            for $functor
180        {
181            #[inline]
182            fn evaluate(x: &[$T; N], y: &[$U; N]) -> MathematicalValue<f32> {
183                <$functor>::default().run(ARCH, x, y)
184            }
185        }
186
187        /////////
188        // f32 //
189        /////////
190
191        // Dynamically Sized
192        impl PureDistanceFunction<&[$T], &[$U], f32> for $functor {
193            #[inline(always)]
194            fn evaluate(x: &[$T], y: &[$U]) -> f32 {
195                <$functor>::default().run(ARCH, x, y)
196            }
197        }
198
199        // Statically Sized
200        impl<const N: usize> PureDistanceFunction<&[$T; N], &[$U; N], f32> for $functor {
201            #[inline]
202            fn evaluate(x: &[$T; N], y: &[$U; N]) -> f32 {
203                <$functor>::default().run(ARCH, x, y)
204            }
205        }
206    };
207}
208
209///////////////
210// SquaredL2 //
211///////////////
212
213/// Compute the squared L2 distance between two vectors.
214#[derive(Debug, Clone, Copy, Default)]
215pub struct SquaredL2 {}
216
217impl PostOp<f32, SimilarityScore<f32>> for SquaredL2 {
218    #[inline(always)]
219    fn post_op(x: f32) -> SimilarityScore<f32> {
220        SimilarityScore::new(x)
221    }
222}
223
224impl PostOp<f32, f32> for SquaredL2 {
225    #[inline(always)]
226    fn post_op(x: f32) -> f32 {
227        x
228    }
229}
230
231impl PostOp<f32, MathematicalValue<f32>> for SquaredL2 {
232    #[inline(always)]
233    fn post_op(x: f32) -> MathematicalValue<f32> {
234        MathematicalValue::new(x)
235    }
236}
237
238architecture_hook!(SquaredL2, simd::L2);
239use_simd_implementation!(SquaredL2, f32, f32);
240use_simd_implementation!(SquaredL2, f32, Half);
241use_simd_implementation!(SquaredL2, Half, Half);
242use_simd_implementation!(SquaredL2, i8, i8);
243use_simd_implementation!(SquaredL2, u8, u8);
244
245////////////
246// FullL2 //
247////////////
248
249/// Computes the full L2 distance between two vectors.
250///
251/// Unlike `SquaredL2`, this function-like object will perform compute the full L2 distance
252/// including the trailing square root.
253#[derive(Debug, Clone, Copy, Default)]
254pub struct FullL2 {}
255
256impl PostOp<f32, SimilarityScore<f32>> for FullL2 {
257    #[inline(always)]
258    fn post_op(x: f32) -> SimilarityScore<f32> {
259        SimilarityScore::new(x.sqrt())
260    }
261}
262
263impl PostOp<f32, f32> for FullL2 {
264    #[inline(always)]
265    fn post_op(x: f32) -> f32 {
266        x.sqrt()
267    }
268}
269
270impl PostOp<f32, MathematicalValue<f32>> for FullL2 {
271    #[inline(always)]
272    fn post_op(x: f32) -> MathematicalValue<f32> {
273        MathematicalValue::new(x.sqrt())
274    }
275}
276
277architecture_hook!(FullL2, simd::L2);
278use_simd_implementation!(FullL2, f32, f32);
279use_simd_implementation!(FullL2, f32, Half);
280use_simd_implementation!(FullL2, Half, Half);
281use_simd_implementation!(FullL2, i8, i8);
282use_simd_implementation!(FullL2, u8, u8);
283
284//////////////////
285// InnerProduct //
286//////////////////
287
288/// Compute the inner product between two vectors.
289#[derive(Debug, Clone, Copy, Default)]
290pub struct InnerProduct {}
291
292impl PostOp<f32, SimilarityScore<f32>> for InnerProduct {
293    // The low-level operations compute the mathematical dot product.
294    // Similarity scores used in DiskANN expect the InnerProduct to be negated.
295    // This PostOp does that negation.
296    #[inline(always)]
297    fn post_op(x: f32) -> SimilarityScore<f32> {
298        SimilarityScore::new(-x)
299    }
300}
301
302impl PostOp<f32, MathematicalValue<f32>> for InnerProduct {
303    #[inline(always)]
304    fn post_op(x: f32) -> MathematicalValue<f32> {
305        MathematicalValue::new(x)
306    }
307}
308
309impl PostOp<f32, f32> for InnerProduct {
310    #[inline(always)]
311    fn post_op(x: f32) -> f32 {
312        <Self as PostOp<f32, SimilarityScore<f32>>>::post_op(x).into_inner()
313    }
314}
315
316architecture_hook!(InnerProduct, simd::IP);
317use_simd_implementation!(InnerProduct, f32, f32);
318use_simd_implementation!(InnerProduct, f32, Half);
319use_simd_implementation!(InnerProduct, Half, Half);
320use_simd_implementation!(InnerProduct, i8, i8);
321use_simd_implementation!(InnerProduct, u8, u8);
322
323////////////
324// Cosine //
325////////////
326
327/// Perform the conversion `x -> 1 - x`.
328///
329/// Don't clamp the output - assume the output is clamped from the inner computation.
330fn cosine_transformation(x: f32) -> f32 {
331    1.0 - x
332}
333
334/// Compute the cosine similarity between two vectors.
335#[derive(Debug, Clone, Copy, Default)]
336pub struct Cosine {}
337
338impl PostOp<f32, SimilarityScore<f32>> for Cosine {
339    fn post_op(x: f32) -> SimilarityScore<f32> {
340        debug_assert!(x >= -1.0);
341        debug_assert!(x <= 1.0);
342        SimilarityScore::new(cosine_transformation(x))
343    }
344}
345
346impl PostOp<f32, MathematicalValue<f32>> for Cosine {
347    fn post_op(x: f32) -> MathematicalValue<f32> {
348        debug_assert!(x >= -1.0);
349        debug_assert!(x <= 1.0);
350        MathematicalValue::new(x)
351    }
352}
353
354impl PostOp<f32, f32> for Cosine {
355    fn post_op(x: f32) -> f32 {
356        <Self as PostOp<f32, SimilarityScore<f32>>>::post_op(x).into_inner()
357    }
358}
359
360architecture_hook!(Cosine, simd::CosineStateless);
361use_simd_implementation!(Cosine, f32, f32);
362use_simd_implementation!(Cosine, f32, Half);
363use_simd_implementation!(Cosine, Half, Half);
364use_simd_implementation!(Cosine, i8, i8);
365use_simd_implementation!(Cosine, u8, u8);
366
367//////////////////////
368// CosineNormalized //
369//////////////////////
370
371/// Compute the cosine similarity between two normalized vectors.
372#[derive(Debug, Clone, Copy, Default)]
373pub struct CosineNormalized {}
374
375impl PostOp<f32, SimilarityScore<f32>> for CosineNormalized {
376    #[inline(always)]
377    fn post_op(x: f32) -> SimilarityScore<f32> {
378        // If the vectors are assumed to be normalized, then the implementation of
379        // normalized cosine can be expressed in terms of an inner product inner loop.
380        //
381        // Don't use `clamp` at the end since the simple non-vector implementations do not
382        // clamp their outputs.
383        SimilarityScore::new(cosine_transformation(x))
384    }
385}
386
387impl PostOp<f32, MathematicalValue<f32>> for CosineNormalized {
388    #[inline(always)]
389    fn post_op(x: f32) -> MathematicalValue<f32> {
390        MathematicalValue::new(x)
391    }
392}
393
394impl PostOp<f32, f32> for CosineNormalized {
395    #[inline(always)]
396    fn post_op(x: f32) -> f32 {
397        <Self as PostOp<f32, SimilarityScore<f32>>>::post_op(x).into_inner()
398    }
399}
400
401architecture_hook!(CosineNormalized, simd::IP);
402use_simd_implementation!(CosineNormalized, f32, f32);
403use_simd_implementation!(CosineNormalized, f32, Half);
404use_simd_implementation!(CosineNormalized, Half, Half);
405
406////////////
407// L1Norm //
408////////////
409
410/// Compute the L1 norm of a vector.
411#[derive(Debug, Clone, Copy, Default)]
412pub struct L1NormFunctor {}
413
414impl PostOp<f32, f32> for L1NormFunctor {
415    #[inline(always)]
416    fn post_op(x: f32) -> f32 {
417        x
418    }
419}
420
421architecture_hook!(L1NormFunctor, simd::L1Norm);
422
423impl PureDistanceFunction<&[f32], &[f32], f32> for L1NormFunctor {
424    #[inline]
425    fn evaluate(x: &[f32], y: &[f32]) -> f32 {
426        L1NormFunctor::default().run(ARCH, x, y)
427    }
428}
429
430////////////
431// Tests //
432////////////
433
434#[cfg(test)]
435mod tests {
436
437    use std::hash::{Hash, Hasher};
438
439    use approx::assert_relative_eq;
440    use rand::{Rng, SeedableRng};
441
442    use super::*;
443    use crate::{
444        distance::{
445            reference::{self, ReferenceProvider},
446            Metric,
447        },
448        test_util::{self, Normalize},
449    };
450
451    pub fn as_function_pointer<T, Left, Right, Return>(x: &[Left], y: &[Right]) -> Return
452    where
453        T: for<'a, 'b> PureDistanceFunction<&'a [Left], &'b [Right], Return>,
454    {
455        T::evaluate(x, y)
456    }
457
458    fn simd_provider(metric: Metric) -> fn(&[f32], &[f32]) -> f32 {
459        match metric {
460            Metric::L2 => as_function_pointer::<SquaredL2, _, _, _>,
461            Metric::InnerProduct => as_function_pointer::<InnerProduct, _, _, _>,
462            Metric::Cosine => as_function_pointer::<Cosine, _, _, _>,
463            Metric::CosineNormalized => as_function_pointer::<CosineNormalized, _, _, _>,
464        }
465    }
466
467    fn random_normal_arguments(dim: usize, lo: f32, hi: f32, seed: u64) -> (Vec<f32>, Vec<f32>) {
468        let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
469        let x: Vec<f32> = (0..dim).map(|_| rng.random_range(lo..hi)).collect();
470        let y: Vec<f32> = (0..dim).map(|_| rng.random_range(lo..hi)).collect();
471        (x, y)
472    }
473
474    struct LeftRightPair {
475        pub x: Vec<f32>,
476        pub y: Vec<f32>,
477    }
478
479    fn generate_corner_cases(dim: usize) -> Vec<LeftRightPair> {
480        let mut output = Vec::<LeftRightPair>::new();
481        let fixed_values = [0.0, -5.0, 5.0, 10.0];
482
483        for va in fixed_values.iter() {
484            for vb in fixed_values.iter() {
485                let x: Vec<f32> = vec![*va; dim];
486                let y: Vec<f32> = vec![*vb; dim];
487                output.push(LeftRightPair { x, y });
488            }
489        }
490        output
491    }
492
493    fn collect_random_arguments(
494        dim: usize,
495        num_trials: usize,
496        lo: f32,
497        hi: f32,
498        mut seed: u64,
499    ) -> Vec<LeftRightPair> {
500        (0..num_trials)
501            .map(|_| {
502                let (x, y) = random_normal_arguments(dim, lo, hi, seed);
503
504                // update the seed.
505                let mut hasher = std::hash::DefaultHasher::new();
506                seed.hash(&mut hasher);
507                seed = hasher.finish();
508
509                LeftRightPair { x, y }
510            })
511            .collect()
512    }
513
514    fn test_pure_functions_impl<T>(metric: Metric, _func: T, normalize: bool)
515    where
516        T: for<'a, 'b> PureDistanceFunction<&'a [f32], &'b [f32], f32> + Clone,
517    {
518        let epsilon: f32 = 1e-4;
519        let max_relative: f32 = 1e-4;
520
521        let max_dim = 256;
522        let num_trials = 10;
523
524        let f_reference = <f32 as ReferenceProvider<f32>>::reference_implementation(metric);
525        let f_simd = simd_provider(metric);
526
527        // Inner test that loops over a vector of arguments.
528        let run_tests = |argument_pairs: Vec<LeftRightPair>| {
529            for LeftRightPair { mut x, mut y } in argument_pairs {
530                if normalize {
531                    x.normalize();
532                    y.normalize();
533                }
534
535                let reference: f32 = f_reference(&x, &y).into_inner();
536                let simd = f_simd(&x, &y);
537
538                assert_relative_eq!(
539                    reference,
540                    simd,
541                    epsilon = epsilon,
542                    max_relative = max_relative
543                );
544
545                // Compute via direct call.
546                let simd_direct = T::evaluate(&x, &y);
547                assert_eq!(simd_direct, simd);
548            }
549        };
550
551        // Corner Cases
552        for dim in 0..max_dim {
553            run_tests(generate_corner_cases(dim));
554        }
555
556        // Generated tests
557        for dim in 0..max_dim {
558            run_tests(collect_random_arguments(
559                dim, num_trials, -10.0, 10.0, 0x5643,
560            ));
561        }
562    }
563
564    #[test]
565    fn test_pure_functions() {
566        println!("L2");
567        test_pure_functions_impl(Metric::L2, SquaredL2 {}, false);
568        println!("InnerProduct");
569        test_pure_functions_impl(Metric::InnerProduct, InnerProduct {}, false);
570        println!("Cosine");
571        test_pure_functions_impl(Metric::Cosine, Cosine {}, false);
572        println!("CosineNormalized");
573        test_pure_functions_impl(Metric::CosineNormalized, CosineNormalized {}, true);
574    }
575
576    /// Test that the constant function pointer implementation returns the same result as
577    /// non-sized counterpart..
578    #[test]
579    fn test_specialize() {
580        use diskann_wide::arch::FTarget2;
581
582        const DIM: usize = 123;
583        let (x, y) = random_normal_arguments(DIM, -100.0, 100.0, 0x023457AA);
584
585        let reference: f32 = SquaredL2::evaluate(x.as_slice(), y.as_slice());
586        let evaluated: f32 = Specialize::<DIM, SquaredL2>::run(
587            diskann_wide::ARCH,
588            x.as_slice().as_unaligned(),
589            y.as_slice().as_unaligned(),
590        );
591
592        // Equality should be exact.
593        assert_eq!(reference, evaluated);
594    }
595
596    #[test]
597    #[should_panic]
598    fn test_function_pointer_const_panics_left() {
599        use diskann_wide::arch::FTarget2;
600
601        const DIM: usize = 34;
602        let x = vec![0.0f32; DIM + 1];
603        let y = vec![0.0f32; DIM];
604        // Since `x` does not have the correct dimensions, this should panic.
605        let _: f32 = Specialize::<DIM, SquaredL2>::run(
606            diskann_wide::ARCH,
607            x.as_slice().as_unaligned(),
608            y.as_slice().as_unaligned(),
609        );
610    }
611
612    #[test]
613    #[should_panic]
614    fn test_function_pointer_const_panics_right() {
615        use diskann_wide::arch::FTarget2;
616
617        const DIM: usize = 34;
618        let x = vec![0.0f32; DIM];
619        let y = vec![0.0f32; DIM + 1];
620        // Since `y` does not have the correct dimensions, this should panic.
621        let _: f32 = Specialize::<DIM, SquaredL2>::run(
622            diskann_wide::ARCH,
623            x.as_slice().as_unaligned(),
624            y.as_slice().as_unaligned(),
625        );
626    }
627
628    ////////////////////
629    // Test Version 2 //
630    ////////////////////
631
632    trait GetInner {
633        fn get_inner(self) -> f32;
634    }
635
636    impl GetInner for f32 {
637        fn get_inner(self) -> f32 {
638            self
639        }
640    }
641
642    impl GetInner for SimilarityScore<f32> {
643        fn get_inner(self) -> f32 {
644            self.into_inner()
645        }
646    }
647
648    impl GetInner for MathematicalValue<f32> {
649        fn get_inner(self) -> f32 {
650            self.into_inner()
651        }
652    }
653
654    // Comparison Bounds
655    #[derive(Clone, Copy)]
656    struct EpsilonAndRelative {
657        epsilon: f32,
658        max_relative: f32,
659    }
660
661    #[allow(clippy::too_many_arguments)]
662    fn run_test<L, R, To, Distribution, Callback>(
663        under_test: fn(&[L], &[R]) -> To,
664        reference: fn(&[L], &[R]) -> To,
665        bounds: EpsilonAndRelative,
666        dim: usize,
667        num_trials: usize,
668        distribution: Distribution,
669        rng: &mut impl Rng,
670        mut cb: Callback,
671    ) where
672        L: test_util::CornerCases,
673        R: test_util::CornerCases,
674        Distribution:
675            test_util::GenerateRandomArguments<L> + test_util::GenerateRandomArguments<R> + Clone,
676        To: GetInner + Copy,
677        Callback: FnMut(To, To),
678    {
679        let mut checker =
680            test_util::Checker::<L, R, To>::new(under_test, reference, |got, expected| {
681                // Invoke the callback with the received numbers.
682                cb(got, expected);
683                assert_relative_eq!(
684                    got.get_inner(),
685                    expected.get_inner(),
686                    epsilon = bounds.epsilon,
687                    max_relative = bounds.max_relative
688                );
689            });
690
691        test_util::test_distance_function(
692            &mut checker,
693            distribution.clone(),
694            distribution.clone(),
695            dim,
696            num_trials,
697            rng,
698        );
699    }
700
701    /// The maximum dimension tested for these tests.
702    #[cfg(not(debug_assertions))]
703    const MAX_DIM: usize = 256;
704
705    #[cfg(debug_assertions)]
706    const MAX_DIM: usize = 160;
707
708    // Decrease the number of trials in debug mode to keep test run-time down.
709    #[cfg(not(debug_assertions))]
710    const INTEGER_TRIALS: usize = 10000;
711
712    #[cfg(debug_assertions)]
713    const INTEGER_TRIALS: usize = 100;
714
715    ////////////////////
716    // Integer Tester //
717    ////////////////////
718
719    // For integer tests - we expect exact reproducibility with the reference
720    // implementations.
721    fn run_integer_test<T, R>(
722        under_test: fn(&[T], &[T]) -> R,
723        reference: fn(&[T], &[T]) -> R,
724        rng: &mut impl Rng,
725    ) where
726        T: test_util::CornerCases,
727        R: GetInner + Copy,
728        rand::distr::StandardUniform: test_util::GenerateRandomArguments<T> + Clone,
729    {
730        let distribution = rand::distr::StandardUniform {};
731        let num_corner_cases = <T as test_util::CornerCases>::corner_cases().len();
732
733        for dim in 0..MAX_DIM {
734            let mut callcount = 0;
735            let callback = |_, _| {
736                callcount += 1;
737            };
738
739            run_test(
740                under_test,
741                reference,
742                EpsilonAndRelative {
743                    epsilon: 0.0,
744                    max_relative: 0.0,
745                },
746                dim,
747                INTEGER_TRIALS,
748                distribution,
749                rng,
750                callback,
751            );
752
753            // Make sure the expected number of callbacks were made.
754            assert_eq!(
755                callcount,
756                INTEGER_TRIALS + num_corner_cases * num_corner_cases
757            );
758        }
759    }
760
761    //////////////////
762    // L2 - Integer //
763    //////////////////
764
765    #[test]
766    fn test_l2_i8_mathematical() {
767        let mut rng = rand::rngs::StdRng::seed_from_u64(0x2bb701074c2b81c9);
768        run_integer_test(
769            as_function_pointer::<FullL2, i8, i8, MathematicalValue<f32>>,
770            reference::reference_l2_i8_mathematical,
771            &mut rng,
772        );
773    }
774
775    #[test]
776    fn test_l2_u8_mathematical() {
777        let mut rng = rand::rngs::StdRng::seed_from_u64(0x9284ced6d080808c);
778        run_integer_test(
779            as_function_pointer::<FullL2, u8, u8, MathematicalValue<f32>>,
780            reference::reference_l2_u8_mathematical,
781            &mut rng,
782        );
783    }
784
785    #[test]
786    fn test_l2_i8_similarity() {
787        let mut rng = rand::rngs::StdRng::seed_from_u64(0xb196fecc4def04fa);
788        run_integer_test(
789            as_function_pointer::<FullL2, i8, i8, SimilarityScore<f32>>,
790            reference::reference_l2_i8_similarity,
791            &mut rng,
792        );
793    }
794
795    #[test]
796    fn test_l2_u8_similarity() {
797        let mut rng = rand::rngs::StdRng::seed_from_u64(0x07f6463e4a654aea);
798        run_integer_test(
799            as_function_pointer::<FullL2, u8, u8, SimilarityScore<f32>>,
800            reference::reference_l2_u8_similarity,
801            &mut rng,
802        );
803    }
804
805    ////////////////////////////
806    // InnerProduct - Integer //
807    ////////////////////////////
808
809    #[test]
810    fn test_innerproduct_i8_mathematical() {
811        let mut rng = rand::rngs::StdRng::seed_from_u64(0x2c1b1bddda5774be);
812        run_integer_test(
813            as_function_pointer::<InnerProduct, i8, i8, MathematicalValue<f32>>,
814            reference::reference_innerproduct_i8_mathematical,
815            &mut rng,
816        );
817    }
818
819    #[test]
820    fn test_innerproduct_u8_mathematical() {
821        let mut rng = rand::rngs::StdRng::seed_from_u64(0x757e363832d7f215);
822        run_integer_test(
823            as_function_pointer::<InnerProduct, u8, u8, MathematicalValue<f32>>,
824            reference::reference_innerproduct_u8_mathematical,
825            &mut rng,
826        );
827    }
828
829    #[test]
830    fn test_innerproduct_i8_similarity() {
831        let mut rng = rand::rngs::StdRng::seed_from_u64(0x4788ce0b991eb15a);
832        run_integer_test(
833            as_function_pointer::<InnerProduct, i8, i8, SimilarityScore<f32>>,
834            reference::reference_innerproduct_i8_similarity,
835            &mut rng,
836        );
837    }
838
839    #[test]
840    fn test_innerproduct_u8_similarity() {
841        let mut rng = rand::rngs::StdRng::seed_from_u64(0x4994adb68f814d96);
842        run_integer_test(
843            as_function_pointer::<InnerProduct, u8, u8, SimilarityScore<f32>>,
844            reference::reference_innerproduct_u8_similarity,
845            &mut rng,
846        );
847    }
848
849    //////////////////////
850    // Cosine - Integer //
851    //////////////////////
852
853    #[test]
854    fn test_cosine_i8_mathematical() {
855        let mut rng = rand::rngs::StdRng::seed_from_u64(0xedef81c780491ada);
856        run_integer_test(
857            as_function_pointer::<Cosine, i8, i8, MathematicalValue<f32>>,
858            reference::reference_cosine_i8_mathematical,
859            &mut rng,
860        );
861    }
862
863    #[test]
864    fn test_cosine_u8_mathematical() {
865        let mut rng = rand::rngs::StdRng::seed_from_u64(0x107cee2adcc58b73);
866        run_integer_test(
867            as_function_pointer::<Cosine, u8, u8, MathematicalValue<f32>>,
868            reference::reference_cosine_u8_mathematical,
869            &mut rng,
870        );
871    }
872
873    #[test]
874    fn test_cosine_i8_similarity() {
875        let mut rng = rand::rngs::StdRng::seed_from_u64(0x02d95c1cc0843647);
876        run_integer_test(
877            as_function_pointer::<Cosine, i8, i8, SimilarityScore<f32>>,
878            reference::reference_cosine_i8_similarity,
879            &mut rng,
880        );
881    }
882
883    #[test]
884    fn test_cosine_u8_similarity() {
885        let mut rng = rand::rngs::StdRng::seed_from_u64(0xf5ea1974bf8d8b3b);
886        run_integer_test(
887            as_function_pointer::<Cosine, u8, u8, SimilarityScore<f32>>,
888            reference::reference_cosine_u8_similarity,
889            &mut rng,
890        );
891    }
892
893    //////////////////
894    // Float Tester //
895    //////////////////
896
897    // For integer tests - we expect exact reproducibility with the reference
898    // implementations.
899    fn run_float_test<L, R, To, Dist>(
900        under_test: fn(&[L], &[R]) -> To,
901        reference: fn(&[L], &[R]) -> To,
902        rng: &mut impl Rng,
903        distribution: Dist,
904        bounds: EpsilonAndRelative,
905    ) where
906        L: test_util::CornerCases,
907        R: test_util::CornerCases,
908        To: GetInner + Copy,
909        Dist: test_util::GenerateRandomArguments<L> + test_util::GenerateRandomArguments<R> + Clone,
910    {
911        let left_corner_cases = <L as test_util::CornerCases>::corner_cases().len();
912        let right_corner_cases = <R as test_util::CornerCases>::corner_cases().len();
913        for dim in 0..MAX_DIM {
914            let mut callcount = 0;
915            let callback = |_, _| {
916                callcount += 1;
917            };
918
919            run_test(
920                under_test,
921                reference,
922                bounds,
923                dim,
924                INTEGER_TRIALS,
925                distribution.clone(),
926                rng,
927                callback,
928            );
929
930            // Make sure the expected number of callbacks were made.
931            assert_eq!(
932                callcount,
933                INTEGER_TRIALS + left_corner_cases * right_corner_cases
934            );
935        }
936    }
937
938    ////////////////
939    // L2 - Float //
940    ////////////////
941
942    fn expected_l2_errors() -> EpsilonAndRelative {
943        EpsilonAndRelative {
944            epsilon: 0.0,
945            max_relative: 1.2e-6,
946        }
947    }
948
949    #[test]
950    fn test_l2_f32_mathematical() {
951        let mut rng = rand::rngs::StdRng::seed_from_u64(0x6d22d320bdf35aec);
952        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
953        run_float_test(
954            as_function_pointer::<FullL2, f32, f32, MathematicalValue<f32>>,
955            reference::reference_l2_f32_mathematical,
956            &mut rng,
957            distribution,
958            expected_l2_errors(),
959        );
960    }
961
962    #[test]
963    fn test_l2_f16_mathematical() {
964        let mut rng = rand::rngs::StdRng::seed_from_u64(0x755819460c190db4);
965        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
966        run_float_test(
967            as_function_pointer::<FullL2, Half, Half, MathematicalValue<f32>>,
968            reference::reference_l2_f16_mathematical,
969            &mut rng,
970            distribution,
971            expected_l2_errors(),
972        );
973    }
974
975    #[test]
976    fn test_l2_f32xf16_mathematical() {
977        let mut rng = rand::rngs::StdRng::seed_from_u64(0x755819460c190db4);
978        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
979
980        run_float_test(
981            as_function_pointer::<FullL2, f32, Half, MathematicalValue<f32>>,
982            reference::reference_l2_f32xf16_mathematical,
983            &mut rng,
984            distribution,
985            expected_l2_errors(),
986        );
987    }
988
989    #[test]
990    fn test_l2_f32_similarity() {
991        let mut rng = rand::rngs::StdRng::seed_from_u64(0xbfc5f4b42b5bc0c1);
992        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
993        run_float_test(
994            as_function_pointer::<FullL2, f32, f32, SimilarityScore<f32>>,
995            reference::reference_l2_f32_similarity,
996            &mut rng,
997            distribution,
998            expected_l2_errors(),
999        );
1000    }
1001
1002    #[test]
1003    fn test_l2_f16_similarity() {
1004        let mut rng = rand::rngs::StdRng::seed_from_u64(0x9d3809d84f54e4b6);
1005        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1006        run_float_test(
1007            as_function_pointer::<FullL2, Half, Half, SimilarityScore<f32>>,
1008            reference::reference_l2_f16_similarity,
1009            &mut rng,
1010            distribution,
1011            expected_l2_errors(),
1012        );
1013    }
1014
1015    #[test]
1016    fn test_l2_f32xf16_similarity() {
1017        let mut rng = rand::rngs::StdRng::seed_from_u64(0x755819460c190db4);
1018        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1019
1020        run_float_test(
1021            as_function_pointer::<FullL2, f32, Half, SimilarityScore<f32>>,
1022            reference::reference_l2_f32xf16_similarity,
1023            &mut rng,
1024            distribution,
1025            expected_l2_errors(),
1026        );
1027    }
1028
1029    ///////////////////////////
1030    // InnerProduct - Floats //
1031    ///////////////////////////
1032
1033    fn expected_innerproduct_errors() -> EpsilonAndRelative {
1034        EpsilonAndRelative {
1035            epsilon: 2.5e-5,
1036            max_relative: 1.6e-5,
1037        }
1038    }
1039
1040    #[test]
1041    fn test_innerproduct_f32_mathematical() {
1042        let mut rng = rand::rngs::StdRng::seed_from_u64(0x1ef6ac3b65869792);
1043        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1044        run_float_test(
1045            as_function_pointer::<InnerProduct, f32, f32, MathematicalValue<f32>>,
1046            reference::reference_innerproduct_f32_mathematical,
1047            &mut rng,
1048            distribution,
1049            expected_innerproduct_errors(),
1050        );
1051    }
1052
1053    #[test]
1054    fn test_innerproduct_f16_mathematical() {
1055        let mut rng = rand::rngs::StdRng::seed_from_u64(0x24c51e4b825b0329);
1056        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1057        run_float_test(
1058            as_function_pointer::<InnerProduct, Half, Half, MathematicalValue<f32>>,
1059            reference::reference_innerproduct_f16_mathematical,
1060            &mut rng,
1061            distribution,
1062            expected_innerproduct_errors(),
1063        );
1064    }
1065
1066    #[test]
1067    fn test_innerproduct_f32xf16_mathematical() {
1068        let mut rng = rand::rngs::StdRng::seed_from_u64(0x24c51e4b825b0329);
1069        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1070        run_float_test(
1071            as_function_pointer::<InnerProduct, f32, Half, MathematicalValue<f32>>,
1072            reference::reference_innerproduct_f32xf16_mathematical,
1073            &mut rng,
1074            distribution,
1075            expected_innerproduct_errors(),
1076        );
1077    }
1078
1079    #[test]
1080    fn test_innerproduct_f32_similarity() {
1081        let mut rng = rand::rngs::StdRng::seed_from_u64(0x40326b22a57db0d7);
1082        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1083        run_float_test(
1084            as_function_pointer::<InnerProduct, f32, f32, SimilarityScore<f32>>,
1085            reference::reference_innerproduct_f32_similarity,
1086            &mut rng,
1087            distribution,
1088            expected_innerproduct_errors(),
1089        );
1090    }
1091
1092    #[test]
1093    fn test_innerproduct_f16_similarity() {
1094        let mut rng = rand::rngs::StdRng::seed_from_u64(0xfb8cff47bcbc9528);
1095        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1096        run_float_test(
1097            as_function_pointer::<InnerProduct, Half, Half, SimilarityScore<f32>>,
1098            reference::reference_innerproduct_f16_similarity,
1099            &mut rng,
1100            distribution,
1101            expected_innerproduct_errors(),
1102        );
1103    }
1104
1105    #[test]
1106    fn test_innerproduct_f32xf16_similarity() {
1107        let mut rng = rand::rngs::StdRng::seed_from_u64(0x24c51e4b825b0329);
1108        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1109        run_float_test(
1110            as_function_pointer::<InnerProduct, f32, Half, SimilarityScore<f32>>,
1111            reference::reference_innerproduct_f32xf16_similarity,
1112            &mut rng,
1113            distribution,
1114            expected_innerproduct_errors(),
1115        );
1116    }
1117
1118    /////////////////////
1119    // Cosine - Floats //
1120    /////////////////////
1121
1122    fn expected_cosine_errors() -> EpsilonAndRelative {
1123        EpsilonAndRelative {
1124            epsilon: 3e-7,
1125            max_relative: 5e-6,
1126        }
1127    }
1128
1129    #[test]
1130    fn test_cosine_f32_mathematical() {
1131        let mut rng = rand::rngs::StdRng::seed_from_u64(0xca6eaac942999500);
1132        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1133        run_float_test(
1134            as_function_pointer::<Cosine, f32, f32, MathematicalValue<f32>>,
1135            reference::reference_cosine_f32_mathematical,
1136            &mut rng,
1137            distribution,
1138            expected_cosine_errors(),
1139        );
1140    }
1141
1142    #[test]
1143    fn test_cosine_f16_mathematical() {
1144        let mut rng = rand::rngs::StdRng::seed_from_u64(0xa736c789aa16ce86);
1145        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1146        run_float_test(
1147            as_function_pointer::<Cosine, Half, Half, MathematicalValue<f32>>,
1148            reference::reference_cosine_f16_mathematical,
1149            &mut rng,
1150            distribution,
1151            expected_cosine_errors(),
1152        );
1153    }
1154
1155    #[test]
1156    fn test_cosine_f32xf16_mathematical() {
1157        let mut rng = rand::rngs::StdRng::seed_from_u64(0xac550231088a0d5c);
1158        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1159        run_float_test(
1160            as_function_pointer::<Cosine, f32, Half, MathematicalValue<f32>>,
1161            reference::reference_cosine_f32xf16_mathematical,
1162            &mut rng,
1163            distribution,
1164            expected_cosine_errors(),
1165        );
1166    }
1167
1168    #[test]
1169    fn test_cosine_f32_similarity() {
1170        let mut rng = rand::rngs::StdRng::seed_from_u64(0x4a09ad987a6204f3);
1171        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1172        run_float_test(
1173            as_function_pointer::<Cosine, f32, f32, SimilarityScore<f32>>,
1174            reference::reference_cosine_f32_similarity,
1175            &mut rng,
1176            distribution,
1177            expected_cosine_errors(),
1178        );
1179    }
1180
1181    #[test]
1182    fn test_cosine_f16_similarity() {
1183        let mut rng = rand::rngs::StdRng::seed_from_u64(0x77a48d1914f850f2);
1184        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1185        run_float_test(
1186            as_function_pointer::<Cosine, Half, Half, SimilarityScore<f32>>,
1187            reference::reference_cosine_f16_similarity,
1188            &mut rng,
1189            distribution,
1190            expected_cosine_errors(),
1191        );
1192    }
1193
1194    #[test]
1195    fn test_cosine_f32xf16_similarity() {
1196        let mut rng = rand::rngs::StdRng::seed_from_u64(0xbd7471b815655ca1);
1197        let distribution = rand_distr::Normal::new(0.0, 1.0).unwrap();
1198        run_float_test(
1199            as_function_pointer::<Cosine, f32, Half, SimilarityScore<f32>>,
1200            reference::reference_cosine_f32xf16_similarity,
1201            &mut rng,
1202            distribution,
1203            expected_cosine_errors(),
1204        );
1205    }
1206
1207    ///////////////////////////////
1208    // CosineNormalized - Floats //
1209    ///////////////////////////////
1210
1211    fn expected_cosine_normalized_errors() -> EpsilonAndRelative {
1212        EpsilonAndRelative {
1213            epsilon: 3e-7,
1214            max_relative: 5e-6,
1215        }
1216    }
1217
1218    #[test]
1219    fn test_cosine_normalized_f32_mathematical() {
1220        let mut rng = rand::rngs::StdRng::seed_from_u64(0x1fda98112747f8dd);
1221        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1222        run_float_test(
1223            as_function_pointer::<CosineNormalized, f32, f32, MathematicalValue<f32>>,
1224            reference::reference_cosine_normalized_f32_mathematical,
1225            &mut rng,
1226            test_util::Normalized(distribution),
1227            expected_cosine_normalized_errors(),
1228        );
1229    }
1230
1231    #[test]
1232    fn test_cosine_normalized_f16_mathematical() {
1233        let mut rng = rand::rngs::StdRng::seed_from_u64(0x5e8c5d5e19cdd840);
1234        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1235        run_float_test(
1236            as_function_pointer::<CosineNormalized, Half, Half, MathematicalValue<f32>>,
1237            reference::reference_cosine_normalized_f16_mathematical,
1238            &mut rng,
1239            test_util::Normalized(distribution),
1240            expected_cosine_normalized_errors(),
1241        );
1242    }
1243
1244    #[test]
1245    fn test_cosine_normalized_f32xf16_mathematical() {
1246        let mut rng = rand::rngs::StdRng::seed_from_u64(0x3fd01e1c11c9bc45);
1247        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1248        run_float_test(
1249            as_function_pointer::<CosineNormalized, f32, Half, MathematicalValue<f32>>,
1250            reference::reference_cosine_normalized_f32xf16_mathematical,
1251            &mut rng,
1252            test_util::Normalized(distribution),
1253            expected_cosine_normalized_errors(),
1254        );
1255    }
1256
1257    #[test]
1258    fn test_cosine_normalized_f32_similarity() {
1259        let mut rng = rand::rngs::StdRng::seed_from_u64(0x9446d057870e5605);
1260        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1261        run_float_test(
1262            as_function_pointer::<CosineNormalized, f32, f32, SimilarityScore<f32>>,
1263            reference::reference_cosine_normalized_f32_similarity,
1264            &mut rng,
1265            test_util::Normalized(distribution),
1266            expected_cosine_normalized_errors(),
1267        );
1268    }
1269
1270    #[test]
1271    fn test_cosine_normalized_f16_similarity() {
1272        let mut rng = rand::rngs::StdRng::seed_from_u64(0x885c371801f18174);
1273        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1274        run_float_test(
1275            as_function_pointer::<CosineNormalized, Half, Half, SimilarityScore<f32>>,
1276            reference::reference_cosine_normalized_f16_similarity,
1277            &mut rng,
1278            test_util::Normalized(distribution),
1279            expected_cosine_normalized_errors(),
1280        );
1281    }
1282
1283    #[test]
1284    fn test_cosine_normalized_f32xf16_similarity() {
1285        let mut rng = rand::rngs::StdRng::seed_from_u64(0x1c356c92d0522c0f);
1286        let distribution = rand_distr::Normal::new(-1.0, 1.0).unwrap();
1287        run_float_test(
1288            as_function_pointer::<CosineNormalized, f32, Half, SimilarityScore<f32>>,
1289            reference::reference_cosine_normalized_f32xf16_similarity,
1290            &mut rng,
1291            test_util::Normalized(distribution),
1292            expected_cosine_normalized_errors(),
1293        );
1294    }
1295}