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}