velesdb_core/quantization/
mod.rs1use std::io;
25
26use serde::{Deserialize, Serialize};
27
28pub(crate) fn validate_rotation_len(
34 len: usize,
35 dimension: usize,
36 label: &str,
37) -> Result<(), crate::error::Error> {
38 let Some(expected) = dimension.checked_mul(dimension) else {
43 return Err(crate::error::Error::IndexCorrupted(format!(
44 "{label} rotation dimension {dimension} squared overflows usize"
45 )));
46 };
47 if len != expected {
48 return Err(crate::error::Error::IndexCorrupted(format!(
49 "{label} rotation has {len} elements, expected dimension^2 = {expected}"
50 )));
51 }
52 Ok(())
53}
54
55mod binary;
56pub(crate) mod codec_helpers;
57mod pq;
58pub(crate) mod pq_kmeans;
59pub(crate) mod pq_opq;
60#[cfg(feature = "persistence")]
61mod pq_persistence;
62mod rabitq;
63pub(crate) mod rabitq_store;
64mod scalar;
65
66pub use binary::BinaryQuantizedVector;
68#[allow(unused_imports)] pub(crate) use pq::distance_pq_l2;
70#[allow(unused_imports)] pub(crate) use pq::pq_adc_batch_rescore;
72pub use pq::{PQCodebook, PQVector, ProductQuantizer};
73#[cfg(feature = "persistence")]
74pub use pq_opq::train_opq;
75
76#[cfg(feature = "persistence")]
78pub(crate) use rabitq::PreparedQuery;
79pub use rabitq::{RaBitQCorrection, RaBitQIndex, RaBitQVector};
80#[cfg(feature = "persistence")]
81pub(crate) use rabitq_store::RaBitQVectorStore;
82
83pub use scalar::{
85 cosine_similarity_quantized, cosine_similarity_quantized_simd, dot_product_quantized,
86 dot_product_quantized_simd, euclidean_squared_quantized, euclidean_squared_quantized_simd,
87 QuantizedVector,
88};
89
90pub trait QuantizationCodec: Sized {
95 fn to_bytes(&self) -> Vec<u8>;
97
98 fn from_bytes(bytes: &[u8]) -> io::Result<Self>;
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
108#[serde(rename_all = "lowercase")]
109#[non_exhaustive]
110pub enum StorageMode {
111 #[default]
113 Full,
114 SQ8,
116 Binary,
119 ProductQuantization,
121 RaBitQ,
123}
124
125impl StorageMode {
126 #[must_use]
131 pub const fn canonical_name(self) -> &'static str {
132 match self {
133 Self::Full => "full",
134 Self::SQ8 => "sq8",
135 Self::Binary => "binary",
136 Self::ProductQuantization => "pq",
137 Self::RaBitQ => "rabitq",
138 }
139 }
140
141 #[must_use]
160 pub fn parse_alias(value: &str) -> Option<Self> {
161 match value.trim().to_lowercase().as_str() {
162 "full" | "f32" => Some(Self::Full),
163 "sq8" | "int8" => Some(Self::SQ8),
164 "binary" | "bit" => Some(Self::Binary),
165 "pq" | "product_quantization" => Some(Self::ProductQuantization),
166 "rabitq" => Some(Self::RaBitQ),
167 _ => None,
168 }
169 }
170}
171
172impl std::fmt::Display for StorageMode {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 f.write_str(self.canonical_name())
175 }
176}
177
178impl std::str::FromStr for StorageMode {
179 type Err = String;
180
181 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 Self::parse_alias(s).ok_or_else(|| {
183 format!(
184 "Unknown storage mode '{s}'. Valid options: full, f32, sq8, int8, binary, bit, pq, product_quantization, rabitq"
185 )
186 })
187 }
188}
189
190#[cfg(test)]
191mod storage_mode_parsing_tests {
192 use super::StorageMode;
193
194 #[test]
195 fn test_parse_all_canonical_names() {
196 assert_eq!("full".parse::<StorageMode>().unwrap(), StorageMode::Full);
197 assert_eq!("sq8".parse::<StorageMode>().unwrap(), StorageMode::SQ8);
198 assert_eq!(
199 "binary".parse::<StorageMode>().unwrap(),
200 StorageMode::Binary
201 );
202 assert_eq!(
203 "pq".parse::<StorageMode>().unwrap(),
204 StorageMode::ProductQuantization
205 );
206 assert_eq!(
207 "rabitq".parse::<StorageMode>().unwrap(),
208 StorageMode::RaBitQ
209 );
210 }
211
212 #[test]
213 fn test_parse_aliases() {
214 assert_eq!("f32".parse::<StorageMode>().unwrap(), StorageMode::Full);
215 assert_eq!("int8".parse::<StorageMode>().unwrap(), StorageMode::SQ8);
216 assert_eq!("bit".parse::<StorageMode>().unwrap(), StorageMode::Binary);
217 assert_eq!(
218 "product_quantization".parse::<StorageMode>().unwrap(),
219 StorageMode::ProductQuantization
220 );
221 }
222
223 #[test]
224 fn test_parse_case_insensitive() {
225 assert_eq!("SQ8".parse::<StorageMode>().unwrap(), StorageMode::SQ8);
226 assert_eq!("FULL".parse::<StorageMode>().unwrap(), StorageMode::Full);
227 assert_eq!(
228 "RaBitQ".parse::<StorageMode>().unwrap(),
229 StorageMode::RaBitQ
230 );
231 }
232
233 #[test]
234 fn test_parse_unknown_returns_error() {
235 assert!("unknown".parse::<StorageMode>().is_err());
236 assert!("".parse::<StorageMode>().is_err());
237 }
238
239 #[test]
240 fn test_canonical_name_roundtrip() {
241 for mode in [
242 StorageMode::Full,
243 StorageMode::SQ8,
244 StorageMode::Binary,
245 StorageMode::ProductQuantization,
246 StorageMode::RaBitQ,
247 ] {
248 let name = mode.canonical_name();
249 assert_eq!(name.parse::<StorageMode>().unwrap(), mode);
250 }
251 }
252
253 #[test]
254 fn test_display_uses_canonical_name() {
255 assert_eq!(format!("{}", StorageMode::Full), "full");
256 assert_eq!(format!("{}", StorageMode::SQ8), "sq8");
257 assert_eq!(format!("{}", StorageMode::Binary), "binary");
258 assert_eq!(format!("{}", StorageMode::ProductQuantization), "pq");
259 assert_eq!(format!("{}", StorageMode::RaBitQ), "rabitq");
260 }
261}