1use crate::structures::simd;
14use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
15use std::io::{self, Read, Write};
16
17pub const VERTICAL_BP128_BLOCK_SIZE: usize = 128;
19
20pub fn pack_vertical(
49 values: &[u32; VERTICAL_BP128_BLOCK_SIZE],
50 bit_width: u8,
51 output: &mut Vec<u8>,
52) {
53 if bit_width == 0 {
54 return;
55 }
56
57 let total_bytes = 16 * bit_width as usize;
59 let start = output.len();
60 output.resize(start + total_bytes, 0);
61
62 for bit_pos in 0..bit_width as usize {
64 let byte_offset = start + bit_pos * 16;
65
66 for byte_idx in 0..16 {
68 let base_int = byte_idx * 8;
69 let mut byte_val = 0u8;
70
71 byte_val |= ((values[base_int] >> bit_pos) & 1) as u8;
73 byte_val |= (((values[base_int + 1] >> bit_pos) & 1) as u8) << 1;
74 byte_val |= (((values[base_int + 2] >> bit_pos) & 1) as u8) << 2;
75 byte_val |= (((values[base_int + 3] >> bit_pos) & 1) as u8) << 3;
76 byte_val |= (((values[base_int + 4] >> bit_pos) & 1) as u8) << 4;
77 byte_val |= (((values[base_int + 5] >> bit_pos) & 1) as u8) << 5;
78 byte_val |= (((values[base_int + 6] >> bit_pos) & 1) as u8) << 6;
79 byte_val |= (((values[base_int + 7] >> bit_pos) & 1) as u8) << 7;
80
81 output[byte_offset + byte_idx] = byte_val;
82 }
83 }
84}
85
86pub fn unpack_vertical(input: &[u8], bit_width: u8, output: &mut [u32; VERTICAL_BP128_BLOCK_SIZE]) {
91 if bit_width == 0 {
92 output.fill(0);
93 return;
94 }
95
96 #[cfg(target_arch = "aarch64")]
97 {
98 unsafe { unpack_vertical_neon(input, bit_width, output) }
99 }
100
101 #[cfg(target_arch = "x86_64")]
102 {
103 if is_x86_feature_detected!("sse2") {
104 unsafe { unpack_vertical_sse(input, bit_width, output) }
105 } else {
106 unpack_vertical_scalar(input, bit_width, output)
107 }
108 }
109
110 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
111 {
112 unpack_vertical_scalar(input, bit_width, output)
113 }
114}
115
116#[cfg_attr(target_arch = "aarch64", allow(dead_code))]
118#[inline]
119fn unpack_vertical_scalar(
120 input: &[u8],
121 bit_width: u8,
122 output: &mut [u32; VERTICAL_BP128_BLOCK_SIZE],
123) {
124 output.fill(0);
125
126 for bit_pos in 0..bit_width as usize {
128 let byte_offset = bit_pos * 16;
129 let bit_mask = 1u32 << bit_pos;
130
131 for byte_idx in 0..16 {
133 let byte_val = input[byte_offset + byte_idx];
134 let base_int = byte_idx * 8;
135
136 if byte_val & 0x01 != 0 {
138 output[base_int] |= bit_mask;
139 }
140 if byte_val & 0x02 != 0 {
141 output[base_int + 1] |= bit_mask;
142 }
143 if byte_val & 0x04 != 0 {
144 output[base_int + 2] |= bit_mask;
145 }
146 if byte_val & 0x08 != 0 {
147 output[base_int + 3] |= bit_mask;
148 }
149 if byte_val & 0x10 != 0 {
150 output[base_int + 4] |= bit_mask;
151 }
152 if byte_val & 0x20 != 0 {
153 output[base_int + 5] |= bit_mask;
154 }
155 if byte_val & 0x40 != 0 {
156 output[base_int + 6] |= bit_mask;
157 }
158 if byte_val & 0x80 != 0 {
159 output[base_int + 7] |= bit_mask;
160 }
161 }
162 }
163}
164
165#[cfg(target_arch = "aarch64")]
167#[target_feature(enable = "neon")]
168unsafe fn unpack_vertical_neon(
169 input: &[u8],
170 bit_width: u8,
171 output: &mut [u32; VERTICAL_BP128_BLOCK_SIZE],
172) {
173 use std::arch::aarch64::*;
174
175 unsafe {
176 let zero = vdupq_n_u32(0);
178 for i in (0..VERTICAL_BP128_BLOCK_SIZE).step_by(4) {
179 vst1q_u32(output[i..].as_mut_ptr(), zero);
180 }
181
182 for bit_pos in 0..bit_width as usize {
184 let byte_offset = bit_pos * 16;
185 let bit_mask = 1u32 << bit_pos;
186
187 let bytes = vld1q_u8(input.as_ptr().add(byte_offset));
189
190 let mut byte_array = [0u8; 16];
192 vst1q_u8(byte_array.as_mut_ptr(), bytes);
193
194 for (byte_idx, &byte_val) in byte_array.iter().enumerate() {
196 let base_int = byte_idx * 8;
197
198 output[base_int] |= ((byte_val & 0x01) as u32) * bit_mask;
200 output[base_int + 1] |= (((byte_val >> 1) & 0x01) as u32) * bit_mask;
201 output[base_int + 2] |= (((byte_val >> 2) & 0x01) as u32) * bit_mask;
202 output[base_int + 3] |= (((byte_val >> 3) & 0x01) as u32) * bit_mask;
203 output[base_int + 4] |= (((byte_val >> 4) & 0x01) as u32) * bit_mask;
204 output[base_int + 5] |= (((byte_val >> 5) & 0x01) as u32) * bit_mask;
205 output[base_int + 6] |= (((byte_val >> 6) & 0x01) as u32) * bit_mask;
206 output[base_int + 7] |= (((byte_val >> 7) & 0x01) as u32) * bit_mask;
207 }
208 }
209 }
210}
211
212#[cfg(target_arch = "x86_64")]
214#[target_feature(enable = "sse2")]
215unsafe fn unpack_vertical_sse(
216 input: &[u8],
217 bit_width: u8,
218 output: &mut [u32; VERTICAL_BP128_BLOCK_SIZE],
219) {
220 use std::arch::x86_64::*;
221
222 unsafe {
223 let zero = _mm_setzero_si128();
225 for i in (0..VERTICAL_BP128_BLOCK_SIZE).step_by(4) {
226 _mm_storeu_si128(output[i..].as_mut_ptr() as *mut __m128i, zero);
227 }
228
229 for bit_pos in 0..bit_width as usize {
231 let byte_offset = bit_pos * 16;
232
233 let bytes = _mm_loadu_si128(input.as_ptr().add(byte_offset) as *const __m128i);
235
236 let mut byte_array = [0u8; 16];
238 _mm_storeu_si128(byte_array.as_mut_ptr() as *mut __m128i, bytes);
239
240 for (byte_idx, &byte_val) in byte_array.iter().enumerate() {
241 let base_int = byte_idx * 8;
242
243 if byte_val & 0x01 != 0 {
245 output[base_int] |= 1u32 << bit_pos;
246 }
247 if byte_val & 0x02 != 0 {
248 output[base_int + 1] |= 1u32 << bit_pos;
249 }
250 if byte_val & 0x04 != 0 {
251 output[base_int + 2] |= 1u32 << bit_pos;
252 }
253 if byte_val & 0x08 != 0 {
254 output[base_int + 3] |= 1u32 << bit_pos;
255 }
256 if byte_val & 0x10 != 0 {
257 output[base_int + 4] |= 1u32 << bit_pos;
258 }
259 if byte_val & 0x20 != 0 {
260 output[base_int + 5] |= 1u32 << bit_pos;
261 }
262 if byte_val & 0x40 != 0 {
263 output[base_int + 6] |= 1u32 << bit_pos;
264 }
265 if byte_val & 0x80 != 0 {
266 output[base_int + 7] |= 1u32 << bit_pos;
267 }
268 }
269 }
270 }
271}
272
273pub fn unpack_vertical_d1(
282 input: &[u8],
283 bit_width: u8,
284 first_doc_id: u32,
285 output: &mut [u32; VERTICAL_BP128_BLOCK_SIZE],
286 count: usize,
287) {
288 if count == 0 {
289 return;
290 }
291
292 if bit_width == 0 {
293 let mut current = first_doc_id;
295 output[0] = current;
296 for out_val in output.iter_mut().take(count).skip(1) {
297 current = current.wrapping_add(1);
298 *out_val = current;
299 }
300 return;
301 }
302
303 let mut deltas = [0u32; VERTICAL_BP128_BLOCK_SIZE];
305 unpack_vertical(input, bit_width, &mut deltas);
306
307 output[0] = first_doc_id;
309 let mut current = first_doc_id;
310
311 for i in 1..count {
312 current = current.wrapping_add(deltas[i - 1]).wrapping_add(1);
314 output[i] = current;
315 }
316}
317
318#[derive(Debug, Clone)]
320pub struct VerticalBP128Block {
321 pub doc_data: Vec<u8>,
323 pub doc_bit_width: u8,
325 pub tf_data: Vec<u8>,
327 pub tf_bit_width: u8,
329 pub first_doc_id: u32,
331 pub last_doc_id: u32,
333 pub num_docs: u16,
335 pub max_tf: u32,
337 pub max_block_score: f32,
339}
340
341impl VerticalBP128Block {
342 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
344 writer.write_u32::<LittleEndian>(self.first_doc_id)?;
345 writer.write_u32::<LittleEndian>(self.last_doc_id)?;
346 writer.write_u16::<LittleEndian>(self.num_docs)?;
347 writer.write_u8(self.doc_bit_width)?;
348 writer.write_u8(self.tf_bit_width)?;
349 writer.write_u32::<LittleEndian>(self.max_tf)?;
350 writer.write_f32::<LittleEndian>(self.max_block_score)?;
351
352 writer.write_u16::<LittleEndian>(self.doc_data.len() as u16)?;
353 writer.write_all(&self.doc_data)?;
354
355 writer.write_u16::<LittleEndian>(self.tf_data.len() as u16)?;
356 writer.write_all(&self.tf_data)?;
357
358 Ok(())
359 }
360
361 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
363 let first_doc_id = reader.read_u32::<LittleEndian>()?;
364 let last_doc_id = reader.read_u32::<LittleEndian>()?;
365 let num_docs = reader.read_u16::<LittleEndian>()?;
366 let doc_bit_width = reader.read_u8()?;
367 let tf_bit_width = reader.read_u8()?;
368 let max_tf = reader.read_u32::<LittleEndian>()?;
369 let max_block_score = reader.read_f32::<LittleEndian>()?;
370
371 let doc_len = reader.read_u16::<LittleEndian>()? as usize;
372 let mut doc_data = vec![0u8; doc_len];
373 reader.read_exact(&mut doc_data)?;
374
375 let tf_len = reader.read_u16::<LittleEndian>()? as usize;
376 let mut tf_data = vec![0u8; tf_len];
377 reader.read_exact(&mut tf_data)?;
378
379 Ok(Self {
380 doc_data,
381 doc_bit_width,
382 tf_data,
383 tf_bit_width,
384 first_doc_id,
385 last_doc_id,
386 num_docs,
387 max_tf,
388 max_block_score,
389 })
390 }
391
392 pub fn decode_doc_ids(&self) -> Vec<u32> {
394 let mut output = vec![0u32; self.num_docs as usize];
395 self.decode_doc_ids_into(&mut output);
396 output
397 }
398
399 #[inline]
401 pub fn decode_doc_ids_into(&self, output: &mut [u32]) -> usize {
402 let count = self.num_docs as usize;
403 if count == 0 {
404 return 0;
405 }
406
407 if count == VERTICAL_BP128_BLOCK_SIZE && output.len() >= VERTICAL_BP128_BLOCK_SIZE {
410 let out_array: &mut [u32; VERTICAL_BP128_BLOCK_SIZE] = (&mut output
412 [..VERTICAL_BP128_BLOCK_SIZE])
413 .try_into()
414 .unwrap();
415 unpack_vertical_d1(
416 &self.doc_data,
417 self.doc_bit_width,
418 self.first_doc_id,
419 out_array,
420 count,
421 );
422 } else {
423 let mut temp = [0u32; VERTICAL_BP128_BLOCK_SIZE];
425 unpack_vertical_d1(
426 &self.doc_data,
427 self.doc_bit_width,
428 self.first_doc_id,
429 &mut temp,
430 count,
431 );
432 output[..count].copy_from_slice(&temp[..count]);
433 }
434
435 count
436 }
437
438 pub fn decode_term_freqs(&self) -> Vec<u32> {
440 let mut output = vec![0u32; self.num_docs as usize];
441 self.decode_term_freqs_into(&mut output);
442 output
443 }
444
445 #[inline]
447 pub fn decode_term_freqs_into(&self, output: &mut [u32]) -> usize {
448 let count = self.num_docs as usize;
449 if count == 0 {
450 return 0;
451 }
452
453 if count == VERTICAL_BP128_BLOCK_SIZE && output.len() >= VERTICAL_BP128_BLOCK_SIZE {
455 let out_array: &mut [u32; VERTICAL_BP128_BLOCK_SIZE] = (&mut output
456 [..VERTICAL_BP128_BLOCK_SIZE])
457 .try_into()
458 .unwrap();
459 unpack_vertical(&self.tf_data, self.tf_bit_width, out_array);
460 } else {
461 let mut temp = [0u32; VERTICAL_BP128_BLOCK_SIZE];
463 unpack_vertical(&self.tf_data, self.tf_bit_width, &mut temp);
464 output[..count].copy_from_slice(&temp[..count]);
465 }
466
467 simd::add_one(output, count);
469
470 count
471 }
472}
473
474#[derive(Debug, Clone)]
476pub struct VerticalBP128PostingList {
477 pub blocks: Vec<VerticalBP128Block>,
479 pub doc_count: u32,
481 pub max_score: f32,
483}
484
485impl VerticalBP128PostingList {
486 pub fn from_postings(doc_ids: &[u32], term_freqs: &[u32], idf: f32) -> Self {
488 assert_eq!(doc_ids.len(), term_freqs.len());
489
490 if doc_ids.is_empty() {
491 return Self {
492 blocks: Vec::new(),
493 doc_count: 0,
494 max_score: 0.0,
495 };
496 }
497
498 let mut blocks = Vec::new();
499 let mut max_score = 0.0f32;
500 let mut i = 0;
501
502 while i < doc_ids.len() {
503 let block_end = (i + VERTICAL_BP128_BLOCK_SIZE).min(doc_ids.len());
504 let block_docs = &doc_ids[i..block_end];
505 let block_tfs = &term_freqs[i..block_end];
506
507 let block = Self::create_block(block_docs, block_tfs, idf);
508 max_score = max_score.max(block.max_block_score);
509 blocks.push(block);
510
511 i = block_end;
512 }
513
514 Self {
515 blocks,
516 doc_count: doc_ids.len() as u32,
517 max_score,
518 }
519 }
520
521 fn create_block(doc_ids: &[u32], term_freqs: &[u32], idf: f32) -> VerticalBP128Block {
522 let num_docs = doc_ids.len();
523 let first_doc_id = doc_ids[0];
524 let last_doc_id = *doc_ids.last().unwrap();
525
526 let mut deltas = [0u32; VERTICAL_BP128_BLOCK_SIZE];
528 let mut max_delta = 0u32;
529 for j in 1..num_docs {
530 let delta = doc_ids[j] - doc_ids[j - 1] - 1;
531 deltas[j - 1] = delta;
532 max_delta = max_delta.max(delta);
533 }
534
535 let mut tfs = [0u32; VERTICAL_BP128_BLOCK_SIZE];
537 let mut max_tf = 0u32;
538 for (j, &tf) in term_freqs.iter().enumerate() {
539 tfs[j] = tf.saturating_sub(1);
540 max_tf = max_tf.max(tf);
541 }
542
543 let doc_bit_width = simd::bits_needed(max_delta);
544 let tf_bit_width = simd::bits_needed(max_tf.saturating_sub(1));
545
546 let mut doc_data = Vec::new();
547 pack_vertical(&deltas, doc_bit_width, &mut doc_data);
548
549 let mut tf_data = Vec::new();
550 pack_vertical(&tfs, tf_bit_width, &mut tf_data);
551
552 let max_block_score = crate::query::bm25_upper_bound(max_tf as f32, idf);
553
554 VerticalBP128Block {
555 doc_data,
556 doc_bit_width,
557 tf_data,
558 tf_bit_width,
559 first_doc_id,
560 last_doc_id,
561 num_docs: num_docs as u16,
562 max_tf,
563 max_block_score,
564 }
565 }
566
567 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
569 writer.write_u32::<LittleEndian>(self.doc_count)?;
570 writer.write_f32::<LittleEndian>(self.max_score)?;
571 writer.write_u32::<LittleEndian>(self.blocks.len() as u32)?;
572
573 for block in &self.blocks {
574 block.serialize(writer)?;
575 }
576
577 Ok(())
578 }
579
580 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
582 let doc_count = reader.read_u32::<LittleEndian>()?;
583 let max_score = reader.read_f32::<LittleEndian>()?;
584 let num_blocks = reader.read_u32::<LittleEndian>()? as usize;
585
586 let mut blocks = Vec::with_capacity(num_blocks);
587 for _ in 0..num_blocks {
588 blocks.push(VerticalBP128Block::deserialize(reader)?);
589 }
590
591 Ok(Self {
592 blocks,
593 doc_count,
594 max_score,
595 })
596 }
597
598 pub fn iterator(&self) -> VerticalBP128Iterator<'_> {
600 VerticalBP128Iterator::new(self)
601 }
602
603 pub fn size_bytes(&self) -> usize {
605 let mut size = 12; for block in &self.blocks {
607 size += 22 + block.doc_data.len() + block.tf_data.len();
608 }
609 size
610 }
611}
612
613pub struct VerticalBP128Iterator<'a> {
615 list: &'a VerticalBP128PostingList,
616 current_block: usize,
617 current_block_len: usize,
619 block_doc_ids: Vec<u32>,
621 block_term_freqs: Vec<u32>,
623 pos_in_block: usize,
624 exhausted: bool,
625}
626
627impl<'a> VerticalBP128Iterator<'a> {
628 pub fn new(list: &'a VerticalBP128PostingList) -> Self {
629 let mut iter = Self {
631 list,
632 current_block: 0,
633 current_block_len: 0,
634 block_doc_ids: vec![0u32; VERTICAL_BP128_BLOCK_SIZE],
635 block_term_freqs: vec![0u32; VERTICAL_BP128_BLOCK_SIZE],
636 pos_in_block: 0,
637 exhausted: list.blocks.is_empty(),
638 };
639
640 if !iter.exhausted {
641 iter.decode_current_block();
642 }
643
644 iter
645 }
646
647 #[inline]
648 fn decode_current_block(&mut self) {
649 let block = &self.list.blocks[self.current_block];
650 self.current_block_len = block.decode_doc_ids_into(&mut self.block_doc_ids);
652 block.decode_term_freqs_into(&mut self.block_term_freqs);
653 self.pos_in_block = 0;
654 }
655
656 #[inline]
658 pub fn doc(&self) -> u32 {
659 if self.exhausted {
660 u32::MAX
661 } else {
662 self.block_doc_ids[self.pos_in_block]
663 }
664 }
665
666 #[inline]
668 pub fn term_freq(&self) -> u32 {
669 if self.exhausted {
670 0
671 } else {
672 self.block_term_freqs[self.pos_in_block]
673 }
674 }
675
676 #[inline]
678 pub fn advance(&mut self) -> u32 {
679 if self.exhausted {
680 return u32::MAX;
681 }
682
683 self.pos_in_block += 1;
684
685 if self.pos_in_block >= self.current_block_len {
686 self.current_block += 1;
687 if self.current_block >= self.list.blocks.len() {
688 self.exhausted = true;
689 return u32::MAX;
690 }
691 self.decode_current_block();
692 }
693
694 self.doc()
695 }
696
697 pub fn seek(&mut self, target: u32) -> u32 {
699 if self.exhausted {
700 return u32::MAX;
701 }
702
703 let block_idx = self.list.blocks[self.current_block..].binary_search_by(|block| {
705 if block.last_doc_id < target {
706 std::cmp::Ordering::Less
707 } else if block.first_doc_id > target {
708 std::cmp::Ordering::Greater
709 } else {
710 std::cmp::Ordering::Equal
711 }
712 });
713
714 let target_block = match block_idx {
715 Ok(idx) => self.current_block + idx,
716 Err(idx) => {
717 if self.current_block + idx >= self.list.blocks.len() {
718 self.exhausted = true;
719 return u32::MAX;
720 }
721 self.current_block + idx
722 }
723 };
724
725 if target_block != self.current_block {
726 self.current_block = target_block;
727 self.decode_current_block();
728 }
729
730 let pos = self.block_doc_ids[self.pos_in_block..self.current_block_len]
732 .binary_search(&target)
733 .unwrap_or_else(|x| x);
734 self.pos_in_block += pos;
735
736 if self.pos_in_block >= self.current_block_len {
737 self.current_block += 1;
738 if self.current_block >= self.list.blocks.len() {
739 self.exhausted = true;
740 return u32::MAX;
741 }
742 self.decode_current_block();
743 }
744
745 self.doc()
746 }
747
748 pub fn max_remaining_score(&self) -> f32 {
750 if self.exhausted {
751 return 0.0;
752 }
753 self.list.blocks[self.current_block..]
754 .iter()
755 .map(|b| b.max_block_score)
756 .fold(0.0f32, |a, b| a.max(b))
757 }
758
759 pub fn current_block_max_score(&self) -> f32 {
761 if self.exhausted {
762 0.0
763 } else {
764 self.list.blocks[self.current_block].max_block_score
765 }
766 }
767
768 pub fn current_block_max_tf(&self) -> u32 {
770 if self.exhausted {
771 0
772 } else {
773 self.list.blocks[self.current_block].max_tf
774 }
775 }
776
777 pub fn skip_to_block_with_doc(&mut self, target: u32) -> Option<(u32, f32)> {
780 while self.current_block < self.list.blocks.len() {
781 let block = &self.list.blocks[self.current_block];
782 if block.last_doc_id >= target {
783 self.decode_current_block();
785 return Some((block.first_doc_id, block.max_block_score));
786 }
787 self.current_block += 1;
788 }
789 self.exhausted = true;
790 None
791 }
792
793 pub fn is_exhausted(&self) -> bool {
795 self.exhausted
796 }
797}
798
799#[cfg(test)]
800mod tests {
801 use super::*;
802
803 #[test]
804 fn test_pack_unpack_vertical() {
805 let mut values = [0u32; VERTICAL_BP128_BLOCK_SIZE];
806 for (i, v) in values.iter_mut().enumerate() {
807 *v = (i * 3) as u32;
808 }
809
810 let max_val = values.iter().max().copied().unwrap();
811 let bit_width = simd::bits_needed(max_val);
812
813 let mut packed = Vec::new();
814 pack_vertical(&values, bit_width, &mut packed);
815
816 let mut unpacked = [0u32; VERTICAL_BP128_BLOCK_SIZE];
817 unpack_vertical(&packed, bit_width, &mut unpacked);
818
819 assert_eq!(values, unpacked);
820 }
821
822 #[test]
823 fn test_pack_unpack_vertical_various_widths() {
824 for bit_width in 1..=20 {
825 let mut values = [0u32; VERTICAL_BP128_BLOCK_SIZE];
826 let max_val = (1u32 << bit_width) - 1;
827 for (i, v) in values.iter_mut().enumerate() {
828 *v = (i as u32) % (max_val + 1);
829 }
830
831 let mut packed = Vec::new();
832 pack_vertical(&values, bit_width, &mut packed);
833
834 let mut unpacked = [0u32; VERTICAL_BP128_BLOCK_SIZE];
835 unpack_vertical(&packed, bit_width, &mut unpacked);
836
837 assert_eq!(values, unpacked, "Failed for bit_width={}", bit_width);
838 }
839 }
840
841 #[test]
842 fn test_simd_bp128_posting_list() {
843 let doc_ids: Vec<u32> = (0..200).map(|i| i * 2).collect();
844 let term_freqs: Vec<u32> = (0..200).map(|i| (i % 10) + 1).collect();
845
846 let list = VerticalBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
847
848 assert_eq!(list.doc_count, 200);
849 assert_eq!(list.blocks.len(), 2); let mut iter = list.iterator();
852 for (i, &expected_doc) in doc_ids.iter().enumerate() {
853 assert_eq!(iter.doc(), expected_doc, "Doc mismatch at {}", i);
854 assert_eq!(iter.term_freq(), term_freqs[i], "TF mismatch at {}", i);
855 if i < doc_ids.len() - 1 {
856 iter.advance();
857 }
858 }
859 }
860
861 #[test]
862 fn test_simd_bp128_seek() {
863 let doc_ids: Vec<u32> = vec![10, 20, 30, 100, 200, 300, 1000, 2000];
864 let term_freqs: Vec<u32> = vec![1, 2, 3, 4, 5, 6, 7, 8];
865
866 let list = VerticalBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.0);
867 let mut iter = list.iterator();
868
869 assert_eq!(iter.seek(25), 30);
870 assert_eq!(iter.seek(100), 100);
871 assert_eq!(iter.seek(500), 1000);
872 assert_eq!(iter.seek(3000), u32::MAX);
873 }
874
875 #[test]
876 fn test_simd_bp128_serialization() {
877 let doc_ids: Vec<u32> = (0..300).map(|i| i * 3).collect();
878 let term_freqs: Vec<u32> = (0..300).map(|i| (i % 5) + 1).collect();
879
880 let list = VerticalBP128PostingList::from_postings(&doc_ids, &term_freqs, 1.5);
881
882 let mut buffer = Vec::new();
883 list.serialize(&mut buffer).unwrap();
884
885 let restored = VerticalBP128PostingList::deserialize(&mut &buffer[..]).unwrap();
886
887 assert_eq!(restored.doc_count, list.doc_count);
888 assert_eq!(restored.blocks.len(), list.blocks.len());
889
890 let mut iter1 = list.iterator();
891 let mut iter2 = restored.iterator();
892
893 while iter1.doc() != u32::MAX {
894 assert_eq!(iter1.doc(), iter2.doc());
895 assert_eq!(iter1.term_freq(), iter2.term_freq());
896 iter1.advance();
897 iter2.advance();
898 }
899 }
900
901 #[test]
902 fn test_vertical_layout_size() {
903 let mut values = [0u32; VERTICAL_BP128_BLOCK_SIZE];
905 for (i, v) in values.iter_mut().enumerate() {
906 *v = i as u32;
907 }
908
909 let bit_width = simd::bits_needed(127); assert_eq!(bit_width, 7);
911
912 let mut packed = Vec::new();
913 pack_vertical(&values, bit_width, &mut packed);
914
915 let expected_bytes = (VERTICAL_BP128_BLOCK_SIZE * bit_width as usize) / 8;
917 assert_eq!(expected_bytes, 112);
918 assert_eq!(packed.len(), expected_bytes);
919 }
920
921 #[test]
922 fn test_simd_bp128_block_max() {
923 let doc_ids: Vec<u32> = (0..500).map(|i| i * 2).collect();
925 let term_freqs: Vec<u32> = (0..500)
927 .map(|i| {
928 if i < 128 {
929 1 } else if i < 256 {
931 5 } else if i < 384 {
933 10 } else {
935 3 }
937 })
938 .collect();
939
940 let list = VerticalBP128PostingList::from_postings(&doc_ids, &term_freqs, 2.0);
941
942 assert_eq!(list.blocks.len(), 4);
944 assert_eq!(list.blocks[0].max_tf, 1);
945 assert_eq!(list.blocks[1].max_tf, 5);
946 assert_eq!(list.blocks[2].max_tf, 10);
947 assert_eq!(list.blocks[3].max_tf, 3);
948
949 assert!(list.blocks[2].max_block_score > list.blocks[0].max_block_score);
951 assert!(list.blocks[2].max_block_score > list.blocks[1].max_block_score);
952 assert!(list.blocks[2].max_block_score > list.blocks[3].max_block_score);
953
954 assert_eq!(list.max_score, list.blocks[2].max_block_score);
956
957 let mut iter = list.iterator();
959 assert_eq!(iter.current_block_max_tf(), 1); iter.seek(256); assert_eq!(iter.current_block_max_tf(), 5);
964
965 iter.seek(512); assert_eq!(iter.current_block_max_tf(), 10);
968
969 let mut iter2 = list.iterator();
971 let result = iter2.skip_to_block_with_doc(300);
972 assert!(result.is_some());
973 let (first_doc, score) = result.unwrap();
974 assert!(first_doc <= 300);
975 assert!(score > 0.0);
976 }
977}