Skip to main content

slate_core/
metric.rs

1//! Distance metrics and the engine's ranking convention.
2
3use serde::{Deserialize, Serialize};
4
5/// Distance metric used to compare query and database vectors.
6///
7/// ## Ranking convention
8///
9/// Throughout Slate-ANN, search ranks candidates by an **ascending score**:
10/// smaller means closer. Each metric defines how a raw similarity maps onto
11/// that convention, so a single "smaller-is-better" priority queue works for
12/// every metric:
13///
14/// | Metric         | Score computed by kernels        | Smaller = closer? |
15/// |----------------|----------------------------------|-------------------|
16/// | `L2`           | squared Euclidean distance       | yes (natural)     |
17/// | `InnerProduct` | negated inner product (`-<a,b>`) | yes (negated)     |
18/// | `Cosine`       | `1 - cosine_similarity`          | yes               |
19///
20/// `L2` uses the **squared** distance to avoid a per-comparison `sqrt`; the
21/// ordering is identical to true Euclidean distance and the square root can be
22/// applied once to final results if an actual distance is needed.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
24pub enum Metric {
25    /// Squared Euclidean (L2) distance.
26    #[default]
27    L2,
28    /// Inner (dot) product similarity, scored as its negation.
29    InnerProduct,
30    /// Cosine distance (`1 - cosine_similarity`).
31    Cosine,
32}
33
34impl Metric {
35    /// Whether this metric requires inputs to be L2-normalized for correct
36    /// results.
37    ///
38    /// `Cosine` is implemented as inner product over unit-normalized vectors,
39    /// so the engine normalizes both database and query vectors when this is
40    /// `true`.
41    #[inline]
42    pub const fn requires_normalized_input(self) -> bool {
43        matches!(self, Metric::Cosine)
44    }
45
46    /// Lower-case identifier used in the on-disk metadata file.
47    #[inline]
48    pub const fn as_str(self) -> &'static str {
49        match self {
50            Metric::L2 => "l2",
51            Metric::InnerProduct => "inner_product",
52            Metric::Cosine => "cosine",
53        }
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use super::*;
60
61    #[test]
62    fn normalization_requirement() {
63        assert!(Metric::Cosine.requires_normalized_input());
64        assert!(!Metric::L2.requires_normalized_input());
65        assert!(!Metric::InnerProduct.requires_normalized_input());
66    }
67
68    #[test]
69    fn stable_string_tags() {
70        assert_eq!(Metric::L2.as_str(), "l2");
71        assert_eq!(Metric::InnerProduct.as_str(), "inner_product");
72        assert_eq!(Metric::Cosine.as_str(), "cosine");
73    }
74}