use crate::structures::simd::{self, RoundedBitWidth};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Read, Write};
pub const ROUNDED_BP128_BLOCK_SIZE: usize = 128;
#[derive(Debug, Clone)]
pub struct RoundedBP128Block {
pub doc_deltas: Vec<u8>,
pub doc_bit_width: u8,
pub term_freqs: Vec<u8>,
pub tf_bit_width: u8,
pub first_doc_id: u32,
pub last_doc_id: u32,
pub num_docs: u16,
pub max_tf: u32,
pub max_block_score: f32,
}
impl RoundedBP128Block {
pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_u32::<LittleEndian>(self.first_doc_id)?;
writer.write_u32::<LittleEndian>(self.last_doc_id)?;
writer.write_u16::<LittleEndian>(self.num_docs)?;
writer.write_u8(self.doc_bit_width)?;
writer.write_u8(self.tf_bit_width)?;
writer.write_u32::<LittleEndian>(self.max_tf)?;
writer.write_f32::<LittleEndian>(self.max_block_score)?;
writer.write_u16::<LittleEndian>(self.doc_deltas.len() as u16)?;
writer.write_all(&self.doc_deltas)?;
writer.write_u16::<LittleEndian>(self.term_freqs.len() as u16)?;
writer.write_all(&self.term_freqs)?;
Ok(())
}
pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
let first_doc_id = reader.read_u32::<LittleEndian>()?;
let last_doc_id = reader.read_u32::<LittleEndian>()?;
let num_docs = reader.read_u16::<LittleEndian>()?;
let doc_bit_width = reader.read_u8()?;
let tf_bit_width = reader.read_u8()?;
let max_tf = reader.read_u32::<LittleEndian>()?;
let max_block_score = reader.read_f32::<LittleEndian>()?;
let doc_deltas_len = reader.read_u16::<LittleEndian>()? as usize;
let mut doc_deltas = vec![0u8; doc_deltas_len];
reader.read_exact(&mut doc_deltas)?;
let term_freqs_len = reader.read_u16::<LittleEndian>()? as usize;
let mut term_freqs = vec![0u8; term_freqs_len];
reader.read_exact(&mut term_freqs)?;
Ok(Self {
doc_deltas,
doc_bit_width,
term_freqs,
tf_bit_width,
first_doc_id,
last_doc_id,
num_docs,
max_tf,
max_block_score,
})
}
pub fn decode_doc_ids(&self) -> Vec<u32> {
let mut doc_ids = vec![0u32; self.num_docs as usize];
self.decode_doc_ids_into(&mut doc_ids);
doc_ids
}
#[inline]
pub fn decode_doc_ids_into(&self, output: &mut [u32]) -> usize {
let n = self.num_docs as usize;
if n == 0 {
return 0;
}
output[0] = self.first_doc_id;
if n == 1 {
return 1;
}
let rounded_width = RoundedBitWidth::from_u8(self.doc_bit_width);
simd::unpack_rounded_delta_decode(
&self.doc_deltas,
rounded_width,
output,
self.first_doc_id,
n,
);
n
}
pub fn decode_term_freqs(&self) -> Vec<u32> {
let mut tfs = vec![0u32; self.num_docs as usize];
self.decode_term_freqs_into(&mut tfs);
tfs
}
#[inline]
pub fn decode_term_freqs_into(&self, output: &mut [u32]) -> usize {
let n = self.num_docs as usize;
if n == 0 {
return 0;
}
let rounded_width = RoundedBitWidth::from_u8(self.tf_bit_width);
simd::unpack_rounded(&self.term_freqs, rounded_width, output, n);
simd::add_one(output, n);
n
}
}
#[derive(Debug, Clone)]
pub struct RoundedBP128PostingList {
pub blocks: Vec<RoundedBP128Block>,
pub doc_count: u32,
pub max_score: f32,
}
impl RoundedBP128PostingList {
pub fn from_postings(doc_ids: &[u32], term_freqs: &[u32], idf: f32) -> Self {
assert_eq!(doc_ids.len(), term_freqs.len());
if doc_ids.is_empty() {
return Self {
blocks: Vec::new(),
doc_count: 0,
max_score: 0.0,
};
}
let mut blocks = Vec::new();
let mut max_score = 0.0f32;
let mut i = 0;
while i < doc_ids.len() {
let block_end = (i + ROUNDED_BP128_BLOCK_SIZE).min(doc_ids.len());
let block_docs = &doc_ids[i..block_end];
let block_tfs = &term_freqs[i..block_end];
let block = Self::create_block(block_docs, block_tfs, idf);
max_score = max_score.max(block.max_block_score);
blocks.push(block);
i = block_end;
}
Self {
blocks,
doc_count: doc_ids.len() as u32,
max_score,
}
}
fn create_block(doc_ids: &[u32], term_freqs: &[u32], idf: f32) -> RoundedBP128Block {
let num_docs = doc_ids.len();
let first_doc_id = doc_ids[0];
let last_doc_id = *doc_ids.last().unwrap();
let mut deltas = [0u32; ROUNDED_BP128_BLOCK_SIZE];
let mut max_delta = 0u32;
for j in 1..num_docs {
let delta = doc_ids[j] - doc_ids[j - 1] - 1;
deltas[j - 1] = delta;
max_delta = max_delta.max(delta);
}
let mut tfs = [0u32; ROUNDED_BP128_BLOCK_SIZE];
let mut max_tf = 0u32;
for (j, &tf) in term_freqs.iter().enumerate() {
tfs[j] = tf - 1; max_tf = max_tf.max(tf);
}
let max_block_score = crate::query::bm25_upper_bound(max_tf as f32, idf);
let exact_doc_bits = simd::bits_needed(max_delta);
let exact_tf_bits = simd::bits_needed(max_tf.saturating_sub(1));
let doc_rounded = RoundedBitWidth::from_exact(exact_doc_bits);
let tf_rounded = RoundedBitWidth::from_exact(exact_tf_bits);
let mut doc_deltas = vec![0u8; num_docs.saturating_sub(1) * doc_rounded.bytes_per_value()];
if num_docs > 1 {
simd::pack_rounded(&deltas[..num_docs - 1], doc_rounded, &mut doc_deltas);
}
let mut term_freqs_packed = vec![0u8; num_docs * tf_rounded.bytes_per_value()];
simd::pack_rounded(&tfs[..num_docs], tf_rounded, &mut term_freqs_packed);
RoundedBP128Block {
doc_deltas,
doc_bit_width: doc_rounded.as_u8(),
term_freqs: term_freqs_packed,
tf_bit_width: tf_rounded.as_u8(),
first_doc_id,
last_doc_id,
num_docs: num_docs as u16,
max_tf,
max_block_score,
}
}
pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_u32::<LittleEndian>(self.doc_count)?;
writer.write_f32::<LittleEndian>(self.max_score)?;
writer.write_u32::<LittleEndian>(self.blocks.len() as u32)?;
for block in &self.blocks {
block.serialize(writer)?;
}
Ok(())
}
pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
let doc_count = reader.read_u32::<LittleEndian>()?;
let max_score = reader.read_f32::<LittleEndian>()?;
let num_blocks = reader.read_u32::<LittleEndian>()? as usize;
let mut blocks = Vec::with_capacity(num_blocks);
for _ in 0..num_blocks {
blocks.push(RoundedBP128Block::deserialize(reader)?);
}
Ok(Self {
blocks,
doc_count,
max_score,
})
}
pub fn iterator(&self) -> RoundedBP128Iterator<'_> {
RoundedBP128Iterator::new(self)
}
pub fn len(&self) -> u32 {
self.doc_count
}
pub fn is_empty(&self) -> bool {
self.doc_count == 0
}
}
pub struct RoundedBP128Iterator<'a> {
posting_list: &'a RoundedBP128PostingList,
current_block: usize,
position_in_block: usize,
current_block_len: usize,
decoded_doc_ids: Vec<u32>,
decoded_tfs: Vec<u32>,
}
impl<'a> RoundedBP128Iterator<'a> {
pub fn new(posting_list: &'a RoundedBP128PostingList) -> Self {
let mut iter = Self {
posting_list,
current_block: 0,
position_in_block: 0,
current_block_len: 0,
decoded_doc_ids: vec![0u32; ROUNDED_BP128_BLOCK_SIZE],
decoded_tfs: vec![0u32; ROUNDED_BP128_BLOCK_SIZE],
};
if !posting_list.blocks.is_empty() {
iter.decode_current_block();
}
iter
}
#[inline]
fn decode_current_block(&mut self) {
if self.current_block < self.posting_list.blocks.len() {
let block = &self.posting_list.blocks[self.current_block];
self.current_block_len = block.decode_doc_ids_into(&mut self.decoded_doc_ids);
block.decode_term_freqs_into(&mut self.decoded_tfs);
} else {
self.current_block_len = 0;
}
}
#[inline]
pub fn doc(&self) -> u32 {
if self.current_block >= self.posting_list.blocks.len() {
return u32::MAX;
}
if self.position_in_block >= self.current_block_len {
return u32::MAX;
}
self.decoded_doc_ids[self.position_in_block]
}
#[inline]
pub fn term_freq(&self) -> u32 {
if self.current_block >= self.posting_list.blocks.len() {
return 0;
}
if self.position_in_block >= self.current_block_len {
return 0;
}
self.decoded_tfs[self.position_in_block]
}
#[inline]
pub fn advance(&mut self) -> u32 {
self.position_in_block += 1;
if self.position_in_block >= self.current_block_len {
self.current_block += 1;
self.position_in_block = 0;
if self.current_block < self.posting_list.blocks.len() {
self.decode_current_block();
}
}
self.doc()
}
pub fn seek(&mut self, target: u32) -> u32 {
while self.current_block < self.posting_list.blocks.len() {
let block = &self.posting_list.blocks[self.current_block];
if block.last_doc_id >= target {
break;
}
self.current_block += 1;
self.position_in_block = 0;
}
if self.current_block >= self.posting_list.blocks.len() {
return u32::MAX;
}
let block = &self.posting_list.blocks[self.current_block];
if self.current_block_len == 0
|| self.position_in_block >= self.current_block_len
|| (self.position_in_block == 0 && self.decoded_doc_ids[0] != block.first_doc_id)
{
self.decode_current_block();
self.position_in_block = 0;
}
let start = self.position_in_block;
let slice = &self.decoded_doc_ids[start..self.current_block_len];
match slice.binary_search(&target) {
Ok(pos) => {
self.position_in_block = start + pos;
}
Err(pos) => {
if pos < slice.len() {
self.position_in_block = start + pos;
} else {
self.current_block += 1;
self.position_in_block = 0;
if self.current_block < self.posting_list.blocks.len() {
self.decode_current_block();
return self.seek(target);
}
return u32::MAX;
}
}
}
self.doc()
}
#[inline]
pub fn block_max_score(&self) -> f32 {
if self.current_block < self.posting_list.blocks.len() {
self.posting_list.blocks[self.current_block].max_block_score
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rounded_bp128_basic() {
let doc_ids: Vec<u32> = vec![1, 5, 10, 15, 20];
let term_freqs: Vec<u32> = vec![1, 2, 3, 4, 5];
let posting_list = RoundedBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
assert_eq!(posting_list.doc_count, 5);
let mut iter = posting_list.iterator();
for (i, (&expected_doc, &expected_tf)) in doc_ids.iter().zip(term_freqs.iter()).enumerate()
{
assert_eq!(iter.doc(), expected_doc, "Doc mismatch at {}", i);
assert_eq!(iter.term_freq(), expected_tf, "TF mismatch at {}", i);
iter.advance();
}
assert_eq!(iter.doc(), u32::MAX);
}
#[test]
fn test_rounded_bp128_large_block() {
let doc_ids: Vec<u32> = (0..128).map(|i| i * 5 + 100).collect();
let term_freqs: Vec<u32> = vec![1; 128];
let posting_list = RoundedBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
let decoded = posting_list.blocks[0].decode_doc_ids();
assert_eq!(decoded.len(), 128);
for (i, (&expected, &actual)) in doc_ids.iter().zip(decoded.iter()).enumerate() {
assert_eq!(expected, actual, "Mismatch at position {}", i);
}
}
#[test]
fn test_rounded_bp128_serialization() {
let doc_ids: Vec<u32> = (0..200).map(|i| i * 7 + 100).collect();
let term_freqs: Vec<u32> = (0..200).map(|i| (i % 5) as u32 + 1).collect();
let posting_list = RoundedBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
let mut buffer = Vec::new();
posting_list.serialize(&mut buffer).unwrap();
let restored = RoundedBP128PostingList::deserialize(&mut &buffer[..]).unwrap();
assert_eq!(restored.doc_count, posting_list.doc_count);
let mut iter1 = posting_list.iterator();
let mut iter2 = restored.iterator();
while iter1.doc() != u32::MAX {
assert_eq!(iter1.doc(), iter2.doc());
assert_eq!(iter1.term_freq(), iter2.term_freq());
iter1.advance();
iter2.advance();
}
}
#[test]
fn test_rounded_bp128_seek() {
let doc_ids: Vec<u32> = vec![10, 20, 30, 100, 200, 300, 1000, 2000];
let term_freqs: Vec<u32> = vec![1, 2, 3, 4, 5, 6, 7, 8];
let posting_list = RoundedBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
let mut iter = posting_list.iterator();
assert_eq!(iter.seek(25), 30);
assert_eq!(iter.seek(100), 100);
assert_eq!(iter.seek(500), 1000);
assert_eq!(iter.seek(3000), u32::MAX);
}
#[test]
fn test_rounded_bit_widths() {
let doc_ids: Vec<u32> = (0..128).map(|i| i * 100).collect(); let term_freqs: Vec<u32> = vec![1; 128];
let posting_list = RoundedBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
let block = &posting_list.blocks[0];
assert!(
block.doc_bit_width == 0
|| block.doc_bit_width == 8
|| block.doc_bit_width == 16
|| block.doc_bit_width == 32,
"Doc bit width {} is not rounded",
block.doc_bit_width
);
assert!(
block.tf_bit_width == 0
|| block.tf_bit_width == 8
|| block.tf_bit_width == 16
|| block.tf_bit_width == 32,
"TF bit width {} is not rounded",
block.tf_bit_width
);
}
#[test]
fn test_rounded_vs_exact_correctness() {
use super::super::horizontal_bp128::HorizontalBP128PostingList;
let doc_ids: Vec<u32> = (0..200).map(|i| i * 7 + 100).collect();
let term_freqs: Vec<u32> = (0..200).map(|i| (i % 5) as u32 + 1).collect();
let exact = HorizontalBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
let rounded = RoundedBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
let mut exact_buf = Vec::new();
exact.serialize(&mut exact_buf).unwrap();
let mut rounded_buf = Vec::new();
rounded.serialize(&mut rounded_buf).unwrap();
assert!(
rounded_buf.len() >= exact_buf.len(),
"Rounded ({}) should be >= exact ({})",
rounded_buf.len(),
exact_buf.len()
);
let mut exact_iter = exact.iterator();
let mut rounded_iter = rounded.iterator();
while exact_iter.doc() != u32::MAX {
assert_eq!(exact_iter.doc(), rounded_iter.doc());
assert_eq!(exact_iter.term_freq(), rounded_iter.term_freq());
exact_iter.advance();
rounded_iter.advance();
}
assert_eq!(rounded_iter.doc(), u32::MAX);
}
}