use crate::fastscan::BatchData;
use crate::{quantizer, Metric, QuantizedVector, RabitqConfig};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostingList {
pub cluster_id: u32,
pub centroid: Vec<f32>,
pub size: u32,
pub rabitq_config: RabitqConfig,
pub ids: Vec<u64>,
#[serde(skip)]
pub vectors: Option<Vec<QuantizedVectorWithId>>,
pub batch_data: BatchData,
pub ex_codes_packed: Vec<Vec<u8>>,
pub f_add_ex: Vec<f32>,
pub f_rescale_ex: Vec<f32>,
pub padded_dim: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuantizedVectorWithId {
pub vector_id: u64,
pub quantized: QuantizedVector,
}
impl PostingList {
pub fn new(cluster_id: u32, centroid: Vec<f32>) -> Self {
let padded_dim = centroid.len();
Self {
cluster_id,
centroid,
size: 0,
rabitq_config: RabitqConfig::default(),
ids: Vec::new(),
vectors: Some(Vec::new()),
batch_data: BatchData {
data: Vec::new(),
padded_dim,
},
ex_codes_packed: Vec::new(),
f_add_ex: Vec::new(),
f_rescale_ex: Vec::new(),
padded_dim,
}
}
pub fn add_vector(&mut self, vector_id: u64, quantized: QuantizedVector) {
if let Some(ref mut vectors) = self.vectors {
vectors.push(QuantizedVectorWithId {
vector_id,
quantized,
});
self.size = vectors.len() as u32;
}
}
pub fn quantize_vectors(
&mut self,
vectors: &[Vec<f32>],
vector_ids: &[u64],
rabitq_bits: usize,
metric: Metric,
faster_config: bool,
) -> Result<(), String> {
if vectors.is_empty() {
return Ok(());
}
assert_eq!(
vectors.len(),
vector_ids.len(),
"vectors and vector_ids must have same length"
);
let dim = vectors[0].len();
self.rabitq_config = if faster_config {
RabitqConfig::faster(dim, rabitq_bits, 42)
} else {
RabitqConfig::new(rabitq_bits)
};
for (i, vector) in vectors.iter().enumerate() {
let quantized = quantizer::quantize_with_centroid(
vector,
&self.centroid,
&self.rabitq_config,
metric,
);
self.add_vector(vector_ids[i], quantized);
}
Ok(())
}
pub fn ex_bits(&self) -> u8 {
self.rabitq_config.total_bits.saturating_sub(1) as u8
}
pub fn memory_size(&self) -> usize {
let vectors_heap: usize = self
.vectors
.as_ref()
.map(|v| v.iter().map(|qv| qv.quantized.heap_size()).sum())
.unwrap_or(0);
let ex_codes_heap: usize = self.ex_codes_packed.iter().map(|v| v.capacity()).sum();
std::mem::size_of::<Self>()
+ self.centroid.len() * std::mem::size_of::<f32>()
+ self.ids.capacity() * 8
+ self
.vectors
.as_ref()
.map(|v| v.capacity() * std::mem::size_of::<QuantizedVectorWithId>())
.unwrap_or(0)
+ vectors_heap
+ self.batch_data.data.capacity()
+ ex_codes_heap
+ self.f_add_ex.capacity() * 4
+ self.f_rescale_ex.capacity() * 4
}
pub fn len(&self) -> usize {
self.size as usize
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn build_batch_layout(&mut self) {
let vectors_opt = self.vectors.take();
if let Some(vectors) = vectors_opt {
if vectors.is_empty() {
self.ids = Vec::new(); return;
}
self.ids = vectors.iter().map(|v| v.vector_id).collect();
let quantized_vecs: Vec<QuantizedVector> =
vectors.into_iter().map(|v| v.quantized).collect();
let ex_bits = self.rabitq_config.total_bits.saturating_sub(1);
let (batch_data, ex_codes, f_add_ex_vals, f_rescale_ex_vals) =
crate::fastscan::BatchData::pack_batch(&quantized_vecs, self.padded_dim, ex_bits);
self.batch_data = batch_data;
self.ex_codes_packed = ex_codes;
self.f_add_ex = f_add_ex_vals;
self.f_rescale_ex = f_rescale_ex_vals;
}
}
pub fn num_complete_batches(&self) -> usize {
self.size as usize / crate::fastscan::BATCH_SIZE
}
pub fn num_remainder_vectors(&self) -> usize {
self.size as usize % crate::fastscan::BATCH_SIZE
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_posting_list_creation() {
let centroid = vec![1.0, 2.0, 3.0];
let plist = PostingList::new(0, centroid.clone());
assert_eq!(plist.cluster_id, 0);
assert_eq!(plist.centroid, centroid);
assert_eq!(plist.size, 0);
assert!(plist.is_empty());
}
}