Skip to main content

ruvector_turboquant/
lib.rs

1//! # ruvector-turboquant
2//!
3//! **Turbo4**: a 4-bit Lloyd-Max quantized vector *datatype* (ADR-296) —
4//! Qdrant-style primary-storage quantization, not a search-time cache:
5//!
6//! * deterministic randomized Hadamard rotation (sign ⊙ permute ⊙ block-FWHT
7//!   rounds, seeded SplitMix64 — bit-stable across platforms and versions,
8//!   no `rand` dependency);
9//! * precomputed 16-level Lloyd-Max tables for the rotated (≈ Gaussian)
10//!   coordinates — no training pass, online ingest;
11//! * packed nibble codes: `D/2 + 8` bytes per vector (≈ 7.9× vs f32 at
12//!   1536-D) — **the original float vector is never stored**;
13//! * direct scoring on packed codes: symmetric (code×code, for graph
14//!   construction), asymmetric (int8 query×code, for traversal), and exact
15//!   f32 rescoring — with runtime-dispatched AVX2 kernels and a scalar
16//!   oracle they are tested bit-exact against.
17//!
18//! ```
19//! use ruvector_turboquant::{Metric, Turbo4Codec, score};
20//!
21//! let dim = 128;
22//! let codec = Turbo4Codec::new(dim, 42).unwrap();
23//! let a: Vec<f32> = (0..dim).map(|i| (i as f32 * 0.37).sin()).collect();
24//! let b: Vec<f32> = (0..dim).map(|i| (i as f32 * 0.11).cos()).collect();
25//!
26//! let code_a = codec.encode(&a).unwrap();   // 64 + 8 bytes, floats discarded
27//! let code_b = codec.encode(&b).unwrap();
28//! let query = codec.encode_query(&a).unwrap();
29//!
30//! let d_sym = score::symmetric_distance(Metric::Euclidean, &code_a, &code_b, dim);
31//! let d_asym = score::asymmetric_distance(Metric::Euclidean, &query.blob, &code_b, dim);
32//! let d_exact = score::rescore(Metric::Euclidean, &query, &code_b, dim);
33//! assert!((d_asym - d_sym).abs() < 0.15 * d_sym.max(1.0));
34//! assert!((d_exact - d_asym).abs() < 0.1 * d_asym.max(1.0));
35//! ```
36
37pub mod bits1;
38pub mod codec;
39pub mod rotation;
40pub mod score;
41pub mod simd;
42pub mod tables;
43
44pub use bits1::{encode_bits, Bits1Query};
45pub use codec::{Turbo4Codec, Turbo4Query, META_BYTES};
46pub use rotation::Rotation;
47pub use score::{asymmetric_distance, rescore, symmetric_distance, Metric};
48
49/// Errors from the Turbo4 codec.
50#[derive(Debug, thiserror::Error)]
51pub enum TurboQuantError {
52    /// Dimension must be even and ≥ 2 (the two-run nibble layout splits D in half).
53    #[error("Turbo4 requires an even dimension >= 2, got {0}")]
54    InvalidDimension(usize),
55    /// Input vector length differs from the codec dimension.
56    #[error("dimension mismatch: codec expects {expected}, got {actual}")]
57    DimensionMismatch { expected: usize, actual: usize },
58}