Skip to main content

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}