iqdb_quantize/traits.rs
1//! The shared [`Quantizer`] trait.
2//!
3//! Every quantization scheme in this crate — scalar (SQ8) and binary (BQ)
4//! today, product quantization (PQ) eventually — implements this trait.
5//! The shape mirrors the iqdb specification, with one deviation:
6//! [`Quantizer::quantize`], [`Quantizer::dequantize`], and
7//! [`Quantizer::distance`] are fallible (each returns
8//! [`iqdb_types::Result`]) so that bad input becomes a typed
9//! [`iqdb_types::IqdbError`] instead of a panic.
10
11use iqdb_types::{DistanceMetric, Result};
12
13/// A vector quantizer.
14///
15/// Implementations compress `f32` vectors into a compact [`Self::Quantized`]
16/// representation and provide an asymmetric distance function that takes a
17/// raw `f32` query against a stored code.
18///
19/// All methods returning [`Result`] surface failure as
20/// [`iqdb_types::IqdbError`]. The library never panics on bad input.
21///
22/// # Calibration
23///
24/// Quantizers MUST be trained before any hot method is called. Calling
25/// [`Quantizer::quantize`], [`Quantizer::dequantize`], or
26/// [`Quantizer::distance`] before [`Quantizer::train`] returns
27/// [`iqdb_types::IqdbError::InvalidConfig`].
28///
29/// # Examples
30///
31/// ```
32/// use iqdb_quantize::{Quantizer, ScalarQuantizer};
33/// use iqdb_types::DistanceMetric;
34///
35/// let mut sq = ScalarQuantizer::new();
36/// sq.train(&[&[0.0_f32, 1.0][..], &[1.0_f32, 0.0][..]])
37/// .expect("two non-empty, finite vectors of equal dim");
38///
39/// let code = sq.quantize(&[0.5_f32, 0.5]).expect("dim matches");
40/// let d = sq
41/// .distance(&[0.5_f32, 0.5], &code, DistanceMetric::Euclidean)
42/// .expect("dim matches");
43/// assert!(d.is_finite());
44/// ```
45pub trait Quantizer {
46 /// The compact code produced by [`Quantizer::quantize`].
47 type Quantized;
48
49 /// Train the quantizer from a sample of representative vectors.
50 ///
51 /// The implementation derives whatever calibration the scheme needs —
52 /// per-dimension `(min, max)` for SQ8, per-dimension means for BQ —
53 /// from `vectors`.
54 ///
55 /// # Errors
56 ///
57 /// Returns [`iqdb_types::IqdbError::InvalidConfig`] if `vectors` is
58 /// empty. Returns [`iqdb_types::IqdbError::InvalidVector`] if any
59 /// training vector is empty or contains non-finite components. Returns
60 /// [`iqdb_types::IqdbError::DimensionMismatch`] if the training
61 /// vectors disagree on dimension.
62 fn train(&mut self, vectors: &[&[f32]]) -> Result<()>;
63
64 /// Encode `vector` as a compact code.
65 ///
66 /// # Errors
67 ///
68 /// Returns [`iqdb_types::IqdbError::InvalidConfig`] if the quantizer
69 /// has not been trained. Returns [`iqdb_types::IqdbError::InvalidVector`]
70 /// if `vector` is empty or contains non-finite components. Returns
71 /// [`iqdb_types::IqdbError::DimensionMismatch`] if `vector` does not
72 /// match the dimension the quantizer was trained on.
73 fn quantize(&self, vector: &[f32]) -> Result<Self::Quantized>;
74
75 /// Decode `quantized` back to an `f32` vector.
76 ///
77 /// The result is an approximation of the original input — quantization
78 /// is lossy.
79 ///
80 /// # Errors
81 ///
82 /// Returns [`iqdb_types::IqdbError::InvalidConfig`] if the quantizer
83 /// has not been trained, or [`iqdb_types::IqdbError::DimensionMismatch`]
84 /// if `quantized` was produced by a quantizer with a different trained
85 /// dimension.
86 fn dequantize(&self, quantized: &Self::Quantized) -> Result<Vec<f32>>;
87
88 /// Compute the asymmetric distance between a raw `f32` query and a
89 /// stored code under `metric`.
90 ///
91 /// "Smaller is nearer", matching the convention in `iqdb-distance` and
92 /// [`iqdb_types::Hit`](iqdb_types::Hit).
93 ///
94 /// # Errors
95 ///
96 /// Returns [`iqdb_types::IqdbError::InvalidConfig`] if the quantizer
97 /// has not been trained. Returns [`iqdb_types::IqdbError::InvalidVector`]
98 /// if `query` is empty or contains non-finite components. Returns
99 /// [`iqdb_types::IqdbError::DimensionMismatch`] if `query` does not
100 /// match the trained dimension. Implementations MAY return
101 /// [`iqdb_types::IqdbError::InvalidMetric`] for metrics they do not
102 /// support — for example, [`BinaryQuantizer`](crate::BinaryQuantizer)
103 /// supports [`DistanceMetric::Hamming`] only.
104 fn distance(
105 &self,
106 query: &[f32],
107 quantized: &Self::Quantized,
108 metric: DistanceMetric,
109 ) -> Result<f32>;
110}