use super::{dequantize_i8_to_f32, quantize_f32_to_i8};
pub struct QuantizedVectorStore {
data: Vec<i8>,
dim: usize,
count: usize,
scales: Vec<f32>,
zero_points: Vec<f32>,
}
impl QuantizedVectorStore {
pub fn new(dim: usize) -> Self {
Self {
data: Vec::new(),
dim,
count: 0,
scales: Vec::new(),
zero_points: Vec::new(),
}
}
pub fn push(&mut self, vector: &[f32]) -> usize {
debug_assert_eq!(
vector.len(),
self.dim,
"push: vector length {} != dim {}",
vector.len(),
self.dim
);
let (q, scale, zero_point) = quantize_f32_to_i8(vector);
self.data.extend_from_slice(&q);
self.scales.push(scale);
self.zero_points.push(zero_point);
let id = self.count;
self.count += 1;
id
}
pub fn get(&self, id: usize) -> Option<Vec<f32>> {
if id >= self.count {
return None;
}
let start = id * self.dim;
let end = start + self.dim;
let q = &self.data[start..end];
Some(dequantize_i8_to_f32(
q,
self.scales[id],
self.zero_points[id],
))
}
pub fn cosine_similarity_q(&self, a_id: usize, b_id: usize) -> f32 {
let a = match self.get(a_id) {
Some(v) => v,
None => return 0.0,
};
let b = match self.get(b_id) {
Some(v) => v,
None => return 0.0,
};
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a < 1e-9 || norm_b < 1e-9 {
return 0.0;
}
dot / (norm_a * norm_b)
}
pub fn bytes_per_vector(&self) -> f64 {
self.dim as f64
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn dim(&self) -> usize {
self.dim
}
}
pub struct BinaryVectorStore {
data: Vec<u64>,
dim: usize,
count: usize,
dim_words: usize,
}
impl BinaryVectorStore {
pub fn new(dim: usize) -> Self {
let dim_words = dim.div_ceil(64);
Self {
data: Vec::new(),
dim,
count: 0,
dim_words,
}
}
pub fn push(&mut self, vector: &[f32]) -> usize {
debug_assert_eq!(
vector.len(),
self.dim,
"push: vector length {} != dim {}",
vector.len(),
self.dim
);
let mean = if vector.is_empty() {
0.0_f32
} else {
vector.iter().sum::<f32>() / vector.len() as f32
};
let mut words = vec![0u64; self.dim_words];
for (i, &val) in vector.iter().enumerate() {
if val >= mean {
let word_idx = i / 64;
let bit_idx = i % 64;
words[word_idx] |= 1u64 << bit_idx;
}
}
self.data.extend_from_slice(&words);
let id = self.count;
self.count += 1;
id
}
pub fn hamming_distance(&self, a_id: usize, b_id: usize) -> u32 {
if a_id >= self.count || b_id >= self.count {
return u32::MAX;
}
let a_start = a_id * self.dim_words;
let b_start = b_id * self.dim_words;
let a_words = &self.data[a_start..a_start + self.dim_words];
let b_words = &self.data[b_start..b_start + self.dim_words];
a_words
.iter()
.zip(b_words.iter())
.map(|(a, b)| (a ^ b).count_ones())
.sum()
}
pub fn bytes_per_vector(&self) -> f64 {
(self.dim_words * 8) as f64
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn dim(&self) -> usize {
self.dim
}
}