1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
15use std::io::{self, Read, Write};
16
17const MIN_PARTITION_SIZE: usize = 64;
19
20const MAX_PARTITION_SIZE: usize = 512;
22
23#[derive(Debug, Clone)]
25pub struct EFPartition {
26 lower_bits: Vec<u64>,
28 upper_bits: Vec<u64>,
30 len: u32,
32 first_value: u32,
34 last_value: u32,
36 lower_bit_width: u8,
38}
39
40impl EFPartition {
41 pub fn from_sorted_slice(values: &[u32]) -> Self {
44 if values.is_empty() {
45 return Self {
46 lower_bits: Vec::new(),
47 upper_bits: Vec::new(),
48 len: 0,
49 first_value: 0,
50 last_value: 0,
51 lower_bit_width: 0,
52 };
53 }
54
55 let first_value = values[0];
56 let last_value = *values.last().unwrap();
57 let n = values.len() as u64;
58
59 let local_universe = last_value - first_value + 1;
61
62 let lower_bit_width = if n <= 1 {
64 0
65 } else {
66 let ratio = (local_universe as u64).max(1) / n.max(1);
67 if ratio <= 1 {
68 0
69 } else {
70 (64 - ratio.leading_zeros() - 1) as u8
71 }
72 };
73
74 let lower_bits_total = (n as usize) * (lower_bit_width as usize);
76 let lower_words = lower_bits_total.div_ceil(64);
77 let mut lower_bits = vec![0u64; lower_words];
78
79 let max_relative = local_universe.saturating_sub(1) as u64;
80 let upper_bound = n + (max_relative >> lower_bit_width) + 1;
81 let upper_words = (upper_bound as usize).div_ceil(64);
82 let mut upper_bits = vec![0u64; upper_words];
83
84 let lower_mask = if lower_bit_width == 0 {
85 0
86 } else {
87 (1u64 << lower_bit_width) - 1
88 };
89
90 for (i, &val) in values.iter().enumerate() {
92 let relative_val = (val - first_value) as u64;
93
94 if lower_bit_width > 0 {
96 let lower = relative_val & lower_mask;
97 let bit_pos = i * (lower_bit_width as usize);
98 let word_idx = bit_pos / 64;
99 let bit_offset = bit_pos % 64;
100
101 lower_bits[word_idx] |= lower << bit_offset;
102 if bit_offset + (lower_bit_width as usize) > 64 && word_idx + 1 < lower_bits.len() {
103 lower_bits[word_idx + 1] |= lower >> (64 - bit_offset);
104 }
105 }
106
107 let upper = relative_val >> lower_bit_width;
109 let upper_pos = (i as u64) + upper;
110 let word_idx = (upper_pos / 64) as usize;
111 let bit_offset = upper_pos % 64;
112 if word_idx < upper_bits.len() {
113 upper_bits[word_idx] |= 1u64 << bit_offset;
114 }
115 }
116
117 Self {
118 lower_bits,
119 upper_bits,
120 len: values.len() as u32,
121 first_value,
122 last_value,
123 lower_bit_width,
124 }
125 }
126
127 #[inline]
129 pub fn get(&self, i: u32) -> Option<u32> {
130 if i >= self.len {
131 return None;
132 }
133
134 let i = i as usize;
135
136 let lower = if self.lower_bit_width == 0 {
138 0u64
139 } else {
140 let bit_pos = i * (self.lower_bit_width as usize);
141 let word_idx = bit_pos / 64;
142 let bit_offset = bit_pos % 64;
143 let lower_mask = (1u64 << self.lower_bit_width) - 1;
144
145 let mut val = (self.lower_bits[word_idx] >> bit_offset) & lower_mask;
146 if bit_offset + (self.lower_bit_width as usize) > 64
147 && word_idx + 1 < self.lower_bits.len()
148 {
149 val |= (self.lower_bits[word_idx + 1] << (64 - bit_offset)) & lower_mask;
150 }
151 val
152 };
153
154 let select_pos = self.select1(i as u32)?;
156 let upper = (select_pos as u64) - (i as u64);
157
158 let relative_val = (upper << self.lower_bit_width) | lower;
160 Some(self.first_value + relative_val as u32)
161 }
162
163 fn select1(&self, i: u32) -> Option<u32> {
165 if i >= self.len {
166 return None;
167 }
168
169 let mut remaining = i + 1;
170 let mut pos = 0u32;
171
172 #[cfg(target_arch = "aarch64")]
174 {
175 use std::arch::aarch64::*;
176
177 let (chunks, remainder) = self.upper_bits.as_chunks::<4>();
178
179 for chunk in chunks {
180 let words: [u64; 4] = [chunk[0], chunk[1], chunk[2], chunk[3]];
182
183 unsafe {
186 let bytes = std::mem::transmute::<[u64; 4], [u8; 32]>(words);
187
188 let v0 = vld1q_u8(bytes.as_ptr());
190 let cnt0 = vcntq_u8(v0);
191 let sum0 = vaddlvq_u8(cnt0) as u32;
192
193 let v1 = vld1q_u8(bytes.as_ptr().add(16));
195 let cnt1 = vcntq_u8(v1);
196 let sum1 = vaddlvq_u8(cnt1) as u32;
197
198 let total_popcount = sum0 + sum1;
199
200 if total_popcount >= remaining {
201 for &word in chunk {
203 let popcount = word.count_ones();
204 if popcount >= remaining {
205 let mut w = word;
206 for _ in 0..remaining - 1 {
207 w &= w - 1;
208 }
209 return Some(pos + w.trailing_zeros());
210 }
211 remaining -= popcount;
212 pos += 64;
213 }
214 }
215 remaining -= total_popcount;
216 pos += 256; }
218 }
219
220 for &word in remainder {
222 let popcount = word.count_ones();
223 if popcount >= remaining {
224 let mut w = word;
225 for _ in 0..remaining - 1 {
226 w &= w - 1;
227 }
228 return Some(pos + w.trailing_zeros());
229 }
230 remaining -= popcount;
231 pos += 64;
232 }
233 }
234
235 #[cfg(target_arch = "x86_64")]
237 {
238 #[cfg(target_feature = "popcnt")]
239 {
240 use std::arch::x86_64::*;
241
242 let chunks = self.upper_bits.chunks_exact(4);
243 let remainder = chunks.remainder();
244
245 for chunk in chunks {
246 unsafe {
248 let p0 = _popcnt64(chunk[0] as i64) as u32;
249 let p1 = _popcnt64(chunk[1] as i64) as u32;
250 let p2 = _popcnt64(chunk[2] as i64) as u32;
251 let p3 = _popcnt64(chunk[3] as i64) as u32;
252
253 let total_popcount = p0 + p1 + p2 + p3;
254
255 if total_popcount >= remaining {
256 for &word in chunk {
258 let popcount = word.count_ones();
259 if popcount >= remaining {
260 let mut w = word;
261 for _ in 0..remaining - 1 {
262 w &= w - 1;
263 }
264 return Some(pos + w.trailing_zeros());
265 }
266 remaining -= popcount;
267 pos += 64;
268 }
269 }
270 remaining -= total_popcount;
271 pos += 256; }
273 }
274
275 for &word in remainder {
277 let popcount = word.count_ones();
278 if popcount >= remaining {
279 let mut w = word;
280 for _ in 0..remaining - 1 {
281 w &= w - 1;
282 }
283 return Some(pos + w.trailing_zeros());
284 }
285 remaining -= popcount;
286 pos += 64;
287 }
288 }
289
290 #[cfg(not(target_feature = "popcnt"))]
292 {
293 for &word in &self.upper_bits {
294 let popcount = word.count_ones();
295 if popcount >= remaining {
296 let mut w = word;
297 for _ in 0..remaining - 1 {
298 w &= w - 1;
299 }
300 return Some(pos + w.trailing_zeros());
301 }
302 remaining -= popcount;
303 pos += 64;
304 }
305 }
306 }
307
308 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
310 {
311 for &word in &self.upper_bits {
312 let popcount = word.count_ones();
313 if popcount >= remaining {
314 let mut w = word;
315 for _ in 0..remaining - 1 {
316 w &= w - 1;
317 }
318 return Some(pos + w.trailing_zeros());
319 }
320 remaining -= popcount;
321 pos += 64;
322 }
323 }
324
325 None
326 }
327
328 pub fn next_geq(&self, target: u32) -> Option<(u32, u32)> {
331 if self.len == 0 || target > self.last_value {
332 return None;
333 }
334
335 if target <= self.first_value {
336 return Some((0, self.first_value));
337 }
338
339 let mut lo = 0u32;
341 let mut hi = self.len;
342
343 while lo < hi {
344 let mid = lo + (hi - lo) / 2;
345 if let Some(val) = self.get(mid) {
346 if val < target {
347 lo = mid + 1;
348 } else {
349 hi = mid;
350 }
351 } else {
352 break;
353 }
354 }
355
356 if lo < self.len {
357 self.get(lo).map(|v| (lo, v))
358 } else {
359 None
360 }
361 }
362
363 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
365 writer.write_u32::<LittleEndian>(self.len)?;
366 writer.write_u32::<LittleEndian>(self.first_value)?;
367 writer.write_u32::<LittleEndian>(self.last_value)?;
368 writer.write_u8(self.lower_bit_width)?;
370
371 writer.write_u32::<LittleEndian>(self.lower_bits.len() as u32)?;
372 for &word in &self.lower_bits {
373 writer.write_u64::<LittleEndian>(word)?;
374 }
375
376 writer.write_u32::<LittleEndian>(self.upper_bits.len() as u32)?;
377 for &word in &self.upper_bits {
378 writer.write_u64::<LittleEndian>(word)?;
379 }
380
381 Ok(())
382 }
383
384 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
386 let len = reader.read_u32::<LittleEndian>()?;
387 let first_value = reader.read_u32::<LittleEndian>()?;
388 let last_value = reader.read_u32::<LittleEndian>()?;
389 let lower_bit_width = reader.read_u8()?;
391
392 let lower_len = reader.read_u32::<LittleEndian>()? as usize;
393 let mut lower_bits = Vec::with_capacity(lower_len);
394 for _ in 0..lower_len {
395 lower_bits.push(reader.read_u64::<LittleEndian>()?);
396 }
397
398 let upper_len = reader.read_u32::<LittleEndian>()? as usize;
399 let mut upper_bits = Vec::with_capacity(upper_len);
400 for _ in 0..upper_len {
401 upper_bits.push(reader.read_u64::<LittleEndian>()?);
402 }
403
404 Ok(Self {
405 lower_bits,
406 upper_bits,
407 len,
408 first_value,
409 last_value,
410 lower_bit_width,
411 })
412 }
413
414 pub fn size_bytes(&self) -> usize {
416 13 + self.lower_bits.len() * 8 + self.upper_bits.len() * 8
417 }
418}
419
420fn estimate_partition_cost(values: &[u32]) -> usize {
422 if values.is_empty() {
423 return 0;
424 }
425
426 let n = values.len();
427 let first = values[0];
428 let last = *values.last().unwrap();
429 let local_universe = (last - first + 1) as usize;
430
431 let lower_bits = if n <= 1 || local_universe <= n {
433 0
434 } else {
435 let ratio = local_universe / n;
436 let l = (usize::BITS - ratio.leading_zeros()) as usize;
437 n * l
438 };
439
440 let upper_bits = 2 * n;
442
443 let overhead = 13 * 8; (lower_bits + upper_bits + overhead).div_ceil(8)
447}
448
449fn find_optimal_partitions(values: &[u32]) -> Vec<usize> {
452 let n = values.len();
453 if n <= MIN_PARTITION_SIZE {
454 return vec![n];
455 }
456
457 let mut dp = vec![usize::MAX; n + 1];
460 let mut parent = vec![0usize; n + 1];
461 dp[0] = 0;
462
463 for i in MIN_PARTITION_SIZE..=n {
464 let min_start = i.saturating_sub(MAX_PARTITION_SIZE);
466 let max_start = i.saturating_sub(MIN_PARTITION_SIZE);
467
468 for start in min_start..=max_start {
469 if dp[start] == usize::MAX {
470 continue;
471 }
472
473 let partition_cost = estimate_partition_cost(&values[start..i]);
474 let total_cost = dp[start].saturating_add(partition_cost);
475
476 if total_cost < dp[i] {
477 dp[i] = total_cost;
478 parent[i] = start;
479 }
480 }
481 }
482
483 if dp[n] == usize::MAX {
485 return vec![n];
486 }
487
488 let mut boundaries = Vec::new();
490 let mut pos = n;
491 while pos > 0 {
492 boundaries.push(pos);
493 pos = parent[pos];
494 }
495 boundaries.reverse();
496
497 boundaries
498}
499
500#[derive(Debug, Clone)]
502pub struct PartitionedEliasFano {
503 partitions: Vec<EFPartition>,
505 len: u32,
507 cumulative_counts: Vec<u32>,
509}
510
511impl PartitionedEliasFano {
512 pub fn from_sorted_slice(values: &[u32]) -> Self {
514 if values.is_empty() {
515 return Self {
516 partitions: Vec::new(),
517 len: 0,
518 cumulative_counts: Vec::new(),
519 };
520 }
521
522 let boundaries = find_optimal_partitions(values);
523 let mut partitions = Vec::with_capacity(boundaries.len());
524 let mut cumulative_counts = Vec::with_capacity(boundaries.len());
525
526 let mut start = 0;
527 let mut cumulative = 0u32;
528
529 for &end in &boundaries {
530 let partition = EFPartition::from_sorted_slice(&values[start..end]);
531 cumulative += partition.len;
532 cumulative_counts.push(cumulative);
533 partitions.push(partition);
534 start = end;
535 }
536
537 Self {
538 partitions,
539 len: values.len() as u32,
540 cumulative_counts,
541 }
542 }
543
544 #[inline]
546 pub fn len(&self) -> u32 {
547 self.len
548 }
549
550 #[inline]
552 pub fn is_empty(&self) -> bool {
553 self.len == 0
554 }
555
556 pub fn num_partitions(&self) -> usize {
558 self.partitions.len()
559 }
560
561 pub fn get(&self, pos: u32) -> Option<u32> {
563 if pos >= self.len {
564 return None;
565 }
566
567 let partition_idx = self
569 .cumulative_counts
570 .binary_search(&(pos + 1))
571 .unwrap_or_else(|x| x);
572
573 if partition_idx >= self.partitions.len() {
574 return None;
575 }
576
577 let local_pos = if partition_idx == 0 {
578 pos
579 } else {
580 pos - self.cumulative_counts[partition_idx - 1]
581 };
582
583 self.partitions[partition_idx].get(local_pos)
584 }
585
586 pub fn next_geq(&self, target: u32) -> Option<(u32, u32)> {
589 if self.partitions.is_empty() {
590 return None;
591 }
592
593 let partition_idx = self
595 .partitions
596 .binary_search_by(|p| {
597 if p.last_value < target {
598 std::cmp::Ordering::Less
599 } else if p.first_value > target {
600 std::cmp::Ordering::Greater
601 } else {
602 std::cmp::Ordering::Equal
603 }
604 })
605 .unwrap_or_else(|x| x);
606
607 for (i, partition) in self.partitions[partition_idx..].iter().enumerate() {
609 let actual_idx = partition_idx + i;
610
611 if let Some((local_pos, val)) = partition.next_geq(target) {
612 let global_pos = if actual_idx == 0 {
613 local_pos
614 } else {
615 self.cumulative_counts[actual_idx - 1] + local_pos
616 };
617 return Some((global_pos, val));
618 }
619 }
620
621 None
622 }
623
624 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
626 writer.write_u32::<LittleEndian>(self.len)?;
627 writer.write_u32::<LittleEndian>(self.partitions.len() as u32)?;
628
629 for &count in &self.cumulative_counts {
630 writer.write_u32::<LittleEndian>(count)?;
631 }
632
633 for partition in &self.partitions {
634 partition.serialize(writer)?;
635 }
636
637 Ok(())
638 }
639
640 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
642 let len = reader.read_u32::<LittleEndian>()?;
643 let num_partitions = reader.read_u32::<LittleEndian>()? as usize;
644
645 let mut cumulative_counts = Vec::with_capacity(num_partitions);
646 for _ in 0..num_partitions {
647 cumulative_counts.push(reader.read_u32::<LittleEndian>()?);
648 }
649
650 let mut partitions = Vec::with_capacity(num_partitions);
651 for _ in 0..num_partitions {
652 partitions.push(EFPartition::deserialize(reader)?);
653 }
654
655 Ok(Self {
656 partitions,
657 len,
658 cumulative_counts,
659 })
660 }
661
662 pub fn size_bytes(&self) -> usize {
664 let mut size = 8 + self.cumulative_counts.len() * 4;
665 for p in &self.partitions {
666 size += p.size_bytes();
667 }
668 size
669 }
670
671 pub fn iter(&self) -> PartitionedEFIterator<'_> {
673 PartitionedEFIterator {
674 pef: self,
675 partition_idx: 0,
676 local_pos: 0,
677 }
678 }
679}
680
681pub struct PartitionedEFIterator<'a> {
683 pef: &'a PartitionedEliasFano,
684 partition_idx: usize,
685 local_pos: u32,
686}
687
688impl<'a> Iterator for PartitionedEFIterator<'a> {
689 type Item = u32;
690
691 fn next(&mut self) -> Option<Self::Item> {
692 if self.partition_idx >= self.pef.partitions.len() {
693 return None;
694 }
695
696 let partition = &self.pef.partitions[self.partition_idx];
697 if let Some(val) = partition.get(self.local_pos) {
698 self.local_pos += 1;
699 if self.local_pos >= partition.len {
700 self.partition_idx += 1;
701 self.local_pos = 0;
702 }
703 Some(val)
704 } else {
705 None
706 }
707 }
708
709 fn size_hint(&self) -> (usize, Option<usize>) {
710 let current_global = if self.partition_idx == 0 {
711 self.local_pos
712 } else if self.partition_idx < self.pef.cumulative_counts.len() {
713 self.pef.cumulative_counts[self.partition_idx - 1] + self.local_pos
714 } else {
715 self.pef.len
716 };
717 let remaining = (self.pef.len - current_global) as usize;
718 (remaining, Some(remaining))
719 }
720}
721
722#[derive(Debug, Clone)]
724pub struct PEFBlockInfo {
725 pub first_doc_id: u32,
727 pub last_doc_id: u32,
729 pub max_tf: u32,
731 pub max_block_score: f32,
733 pub partition_idx: u16,
735 pub local_start: u32,
737 pub num_docs: u16,
739}
740
741impl PEFBlockInfo {
742 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
743 writer.write_u32::<LittleEndian>(self.first_doc_id)?;
744 writer.write_u32::<LittleEndian>(self.last_doc_id)?;
745 writer.write_u32::<LittleEndian>(self.max_tf)?;
746 writer.write_f32::<LittleEndian>(self.max_block_score)?;
747 writer.write_u16::<LittleEndian>(self.partition_idx)?;
748 writer.write_u32::<LittleEndian>(self.local_start)?;
749 writer.write_u16::<LittleEndian>(self.num_docs)?;
750 Ok(())
751 }
752
753 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
754 Ok(Self {
755 first_doc_id: reader.read_u32::<LittleEndian>()?,
756 last_doc_id: reader.read_u32::<LittleEndian>()?,
757 max_tf: reader.read_u32::<LittleEndian>()?,
758 max_block_score: reader.read_f32::<LittleEndian>()?,
759 partition_idx: reader.read_u16::<LittleEndian>()?,
760 local_start: reader.read_u32::<LittleEndian>()?,
761 num_docs: reader.read_u16::<LittleEndian>()?,
762 })
763 }
764}
765
766pub const PEF_BLOCK_SIZE: usize = 128;
768
769#[derive(Debug, Clone)]
771pub struct PartitionedEFPostingList {
772 pub doc_ids: PartitionedEliasFano,
774 pub term_freqs: Vec<u8>,
776 pub tf_bits: u8,
778 pub max_tf: u32,
780 pub blocks: Vec<PEFBlockInfo>,
782 pub max_score: f32,
784}
785
786impl PartitionedEFPostingList {
787 pub fn from_postings(doc_ids: &[u32], term_freqs: &[u32]) -> Self {
789 Self::from_postings_with_idf(doc_ids, term_freqs, 1.0)
790 }
791
792 pub fn from_postings_with_idf(doc_ids: &[u32], term_freqs: &[u32], idf: f32) -> Self {
794 assert_eq!(doc_ids.len(), term_freqs.len());
795
796 if doc_ids.is_empty() {
797 return Self {
798 doc_ids: PartitionedEliasFano::from_sorted_slice(&[]),
799 term_freqs: Vec::new(),
800 tf_bits: 0,
801 max_tf: 0,
802 blocks: Vec::new(),
803 max_score: 0.0,
804 };
805 }
806
807 let pef_doc_ids = PartitionedEliasFano::from_sorted_slice(doc_ids);
808
809 let max_tf = *term_freqs.iter().max().unwrap();
811 let tf_bits = if max_tf == 0 {
812 0
813 } else {
814 (32 - max_tf.leading_zeros()) as u8
815 };
816
817 let total_bits = doc_ids.len() * (tf_bits as usize);
819 let total_bytes = total_bits.div_ceil(8);
820 let mut packed_tfs = vec![0u8; total_bytes];
821
822 if tf_bits > 0 {
823 for (i, &tf) in term_freqs.iter().enumerate() {
824 let bit_pos = i * (tf_bits as usize);
825 let byte_idx = bit_pos / 8;
826 let bit_offset = bit_pos % 8;
827
828 let val = tf.saturating_sub(1);
829
830 packed_tfs[byte_idx] |= (val as u8) << bit_offset;
831 if bit_offset + (tf_bits as usize) > 8 && byte_idx + 1 < packed_tfs.len() {
832 packed_tfs[byte_idx + 1] |= (val >> (8 - bit_offset)) as u8;
833 }
834 if bit_offset + (tf_bits as usize) > 16 && byte_idx + 2 < packed_tfs.len() {
835 packed_tfs[byte_idx + 2] |= (val >> (16 - bit_offset)) as u8;
836 }
837 }
838 }
839
840 let mut blocks = Vec::new();
842 let mut max_score = 0.0f32;
843 let mut i = 0;
844
845 while i < doc_ids.len() {
846 let block_end = (i + PEF_BLOCK_SIZE).min(doc_ids.len());
847 let block_doc_ids = &doc_ids[i..block_end];
848 let block_tfs = &term_freqs[i..block_end];
849
850 let block_max_tf = *block_tfs.iter().max().unwrap_or(&1);
851 let block_score = crate::query::bm25_upper_bound(block_max_tf as f32, idf);
852 max_score = max_score.max(block_score);
853
854 let (partition_idx, local_start) =
856 Self::find_partition_position(&pef_doc_ids, i as u32);
857
858 blocks.push(PEFBlockInfo {
859 first_doc_id: block_doc_ids[0],
860 last_doc_id: *block_doc_ids.last().unwrap(),
861 max_tf: block_max_tf,
862 max_block_score: block_score,
863 partition_idx: partition_idx as u16,
864 local_start,
865 num_docs: (block_end - i) as u16,
866 });
867
868 i = block_end;
869 }
870
871 Self {
872 doc_ids: pef_doc_ids,
873 term_freqs: packed_tfs,
874 tf_bits,
875 max_tf,
876 blocks,
877 max_score,
878 }
879 }
880
881 fn find_partition_position(pef: &PartitionedEliasFano, global_pos: u32) -> (usize, u32) {
882 let partition_idx = pef
883 .cumulative_counts
884 .binary_search(&(global_pos + 1))
885 .unwrap_or_else(|x| x);
886
887 let local_pos = if partition_idx == 0 {
888 global_pos
889 } else {
890 global_pos - pef.cumulative_counts[partition_idx - 1]
891 };
892
893 (partition_idx, local_pos)
894 }
895
896 pub fn get_tf(&self, pos: u32) -> u32 {
898 if self.tf_bits == 0 || pos >= self.doc_ids.len() {
899 return 1;
900 }
901
902 let bit_pos = (pos as usize) * (self.tf_bits as usize);
903 let byte_idx = bit_pos / 8;
904 let bit_offset = bit_pos % 8;
905 let mask = (1u32 << self.tf_bits) - 1;
906
907 let mut val = (self.term_freqs[byte_idx] >> bit_offset) as u32;
908 if bit_offset + (self.tf_bits as usize) > 8 && byte_idx + 1 < self.term_freqs.len() {
909 val |= (self.term_freqs[byte_idx + 1] as u32) << (8 - bit_offset);
910 }
911 if bit_offset + (self.tf_bits as usize) > 16 && byte_idx + 2 < self.term_freqs.len() {
912 val |= (self.term_freqs[byte_idx + 2] as u32) << (16 - bit_offset);
913 }
914
915 (val & mask) + 1
916 }
917
918 pub fn len(&self) -> u32 {
920 self.doc_ids.len()
921 }
922
923 pub fn is_empty(&self) -> bool {
925 self.doc_ids.is_empty()
926 }
927
928 pub fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
930 self.doc_ids.serialize(writer)?;
931 writer.write_u8(self.tf_bits)?;
932 writer.write_u32::<LittleEndian>(self.max_tf)?;
933 writer.write_f32::<LittleEndian>(self.max_score)?;
934 writer.write_u32::<LittleEndian>(self.term_freqs.len() as u32)?;
935 writer.write_all(&self.term_freqs)?;
936
937 writer.write_u32::<LittleEndian>(self.blocks.len() as u32)?;
938 for block in &self.blocks {
939 block.serialize(writer)?;
940 }
941
942 Ok(())
943 }
944
945 pub fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
947 let doc_ids = PartitionedEliasFano::deserialize(reader)?;
948 let tf_bits = reader.read_u8()?;
949 let max_tf = reader.read_u32::<LittleEndian>()?;
950 let max_score = reader.read_f32::<LittleEndian>()?;
951 let tf_len = reader.read_u32::<LittleEndian>()? as usize;
952 let mut term_freqs = vec![0u8; tf_len];
953 reader.read_exact(&mut term_freqs)?;
954
955 let num_blocks = reader.read_u32::<LittleEndian>()? as usize;
956 let mut blocks = Vec::with_capacity(num_blocks);
957 for _ in 0..num_blocks {
958 blocks.push(PEFBlockInfo::deserialize(reader)?);
959 }
960
961 Ok(Self {
962 doc_ids,
963 term_freqs,
964 tf_bits,
965 max_tf,
966 blocks,
967 max_score,
968 })
969 }
970
971 pub fn iterator(&self) -> PartitionedEFPostingIterator<'_> {
973 PartitionedEFPostingIterator {
974 list: self,
975 pos: 0,
976 current_block: 0,
977 }
978 }
979
980 pub fn compression_info(&self) -> (usize, usize) {
982 let pef_size = self.doc_ids.size_bytes();
983 let n = self.len() as usize;
985 let max_val = if let Some(last_block) = self.blocks.last() {
986 last_block.last_doc_id
987 } else {
988 0
989 };
990 let ef_size = if n > 0 {
991 let l = if n <= 1 {
992 0
993 } else {
994 let ratio = (max_val as usize + 1) / n;
995 if ratio <= 1 {
996 0
997 } else {
998 (usize::BITS - ratio.leading_zeros()) as usize
999 }
1000 };
1001 (n * l + 2 * n).div_ceil(8) + 16
1002 } else {
1003 0
1004 };
1005 (pef_size, ef_size)
1006 }
1007}
1008
1009pub struct PartitionedEFPostingIterator<'a> {
1011 list: &'a PartitionedEFPostingList,
1012 pos: u32,
1013 current_block: usize,
1014}
1015
1016impl<'a> PartitionedEFPostingIterator<'a> {
1017 pub fn doc(&self) -> u32 {
1019 self.list.doc_ids.get(self.pos).unwrap_or(u32::MAX)
1020 }
1021
1022 pub fn term_freq(&self) -> u32 {
1024 self.list.get_tf(self.pos)
1025 }
1026
1027 pub fn advance(&mut self) -> u32 {
1029 self.pos += 1;
1030 if !self.list.blocks.is_empty() {
1031 let new_block = (self.pos as usize) / PEF_BLOCK_SIZE;
1032 if new_block < self.list.blocks.len() {
1033 self.current_block = new_block;
1034 }
1035 }
1036 self.doc()
1037 }
1038
1039 pub fn seek(&mut self, target: u32) -> u32 {
1041 if !self.list.blocks.is_empty() {
1043 let block_idx = self.list.blocks[self.current_block..].binary_search_by(|b| {
1044 if b.last_doc_id < target {
1045 std::cmp::Ordering::Less
1046 } else if b.first_doc_id > target {
1047 std::cmp::Ordering::Greater
1048 } else {
1049 std::cmp::Ordering::Equal
1050 }
1051 });
1052
1053 let target_block = match block_idx {
1054 Ok(idx) => self.current_block + idx,
1055 Err(idx) => {
1056 let abs_idx = self.current_block + idx;
1057 if abs_idx >= self.list.blocks.len() {
1058 self.pos = self.list.len();
1059 return u32::MAX;
1060 }
1061 abs_idx
1062 }
1063 };
1064
1065 if target_block > self.current_block {
1066 self.current_block = target_block;
1067 self.pos = (target_block * PEF_BLOCK_SIZE) as u32;
1068 }
1069 }
1070
1071 if let Some((pos, val)) = self.list.doc_ids.next_geq(target)
1073 && pos >= self.pos
1074 {
1075 self.pos = pos;
1076 if !self.list.blocks.is_empty() {
1077 self.current_block = (pos as usize) / PEF_BLOCK_SIZE;
1078 }
1079 return val;
1080 }
1081
1082 while self.pos < self.list.len() {
1084 let doc = self.doc();
1085 if doc >= target {
1086 return doc;
1087 }
1088 self.pos += 1;
1089 }
1090
1091 u32::MAX
1092 }
1093
1094 pub fn is_exhausted(&self) -> bool {
1096 self.pos >= self.list.len()
1097 }
1098
1099 pub fn current_block_max_score(&self) -> f32 {
1101 if self.is_exhausted() || self.list.blocks.is_empty() {
1102 return 0.0;
1103 }
1104 if self.current_block < self.list.blocks.len() {
1105 self.list.blocks[self.current_block].max_block_score
1106 } else {
1107 0.0
1108 }
1109 }
1110
1111 pub fn current_block_max_tf(&self) -> u32 {
1113 if self.is_exhausted() || self.list.blocks.is_empty() {
1114 return 0;
1115 }
1116 if self.current_block < self.list.blocks.len() {
1117 self.list.blocks[self.current_block].max_tf
1118 } else {
1119 0
1120 }
1121 }
1122
1123 pub fn max_remaining_score(&self) -> f32 {
1125 if self.is_exhausted() || self.list.blocks.is_empty() {
1126 return 0.0;
1127 }
1128 self.list.blocks[self.current_block..]
1129 .iter()
1130 .map(|b| b.max_block_score)
1131 .fold(0.0f32, |a, b| a.max(b))
1132 }
1133
1134 pub fn skip_to_block_with_doc(&mut self, target: u32) -> Option<(u32, f32)> {
1137 if self.list.blocks.is_empty() {
1138 return None;
1139 }
1140
1141 while self.current_block < self.list.blocks.len() {
1142 let block = &self.list.blocks[self.current_block];
1143 if block.last_doc_id >= target {
1144 self.pos = (self.current_block * PEF_BLOCK_SIZE) as u32;
1145 return Some((block.first_doc_id, block.max_block_score));
1146 }
1147 self.current_block += 1;
1148 }
1149
1150 self.pos = self.list.len();
1151 None
1152 }
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use super::*;
1158
1159 #[test]
1160 fn test_ef_partition_basic() {
1161 let values = vec![10, 20, 30, 40, 50];
1162 let partition = EFPartition::from_sorted_slice(&values);
1163
1164 assert_eq!(partition.len, 5);
1165 assert_eq!(partition.first_value, 10);
1166 assert_eq!(partition.last_value, 50);
1167
1168 for (i, &expected) in values.iter().enumerate() {
1169 assert_eq!(partition.get(i as u32), Some(expected));
1170 }
1171 }
1172
1173 #[test]
1174 fn test_ef_partition_next_geq() {
1175 let values = vec![10, 20, 30, 100, 200, 300];
1176 let partition = EFPartition::from_sorted_slice(&values);
1177
1178 assert_eq!(partition.next_geq(5), Some((0, 10)));
1179 assert_eq!(partition.next_geq(10), Some((0, 10)));
1180 assert_eq!(partition.next_geq(15), Some((1, 20)));
1181 assert_eq!(partition.next_geq(100), Some((3, 100)));
1182 assert_eq!(partition.next_geq(301), None);
1183 }
1184
1185 #[test]
1186 fn test_partitioned_ef_basic() {
1187 let values: Vec<u32> = (0..500).map(|i| i * 2).collect();
1188 let pef = PartitionedEliasFano::from_sorted_slice(&values);
1189
1190 assert_eq!(pef.len(), 500);
1191 assert!(pef.num_partitions() >= 1);
1192
1193 for (i, &expected) in values.iter().enumerate() {
1194 assert_eq!(pef.get(i as u32), Some(expected), "Mismatch at {}", i);
1195 }
1196 }
1197
1198 #[test]
1199 fn test_partitioned_ef_next_geq() {
1200 let values: Vec<u32> = (0..1000).map(|i| i * 3).collect();
1201 let pef = PartitionedEliasFano::from_sorted_slice(&values);
1202
1203 assert_eq!(pef.next_geq(0), Some((0, 0)));
1204 assert_eq!(pef.next_geq(100), Some((34, 102))); assert_eq!(pef.next_geq(1500), Some((500, 1500)));
1206 assert_eq!(pef.next_geq(3000), None);
1207 }
1208
1209 #[test]
1210 fn test_partitioned_ef_serialization() {
1211 let values: Vec<u32> = (0..500).map(|i| i * 5).collect();
1212 let pef = PartitionedEliasFano::from_sorted_slice(&values);
1213
1214 let mut buffer = Vec::new();
1215 pef.serialize(&mut buffer).unwrap();
1216
1217 let restored = PartitionedEliasFano::deserialize(&mut &buffer[..]).unwrap();
1218
1219 assert_eq!(restored.len(), pef.len());
1220 assert_eq!(restored.num_partitions(), pef.num_partitions());
1221
1222 for i in 0..pef.len() {
1223 assert_eq!(restored.get(i), pef.get(i));
1224 }
1225 }
1226
1227 #[test]
1228 fn test_partitioned_ef_posting_list() {
1229 let doc_ids: Vec<u32> = (0..300).map(|i| i * 2).collect();
1230 let term_freqs: Vec<u32> = (0..300).map(|i| (i % 10) + 1).collect();
1231
1232 let list = PartitionedEFPostingList::from_postings(&doc_ids, &term_freqs);
1233
1234 assert_eq!(list.len(), 300);
1235
1236 let mut iter = list.iterator();
1237 for (i, (&expected_doc, &expected_tf)) in doc_ids.iter().zip(term_freqs.iter()).enumerate()
1238 {
1239 assert_eq!(iter.doc(), expected_doc, "Doc mismatch at {}", i);
1240 assert_eq!(iter.term_freq(), expected_tf, "TF mismatch at {}", i);
1241 iter.advance();
1242 }
1243 }
1244
1245 #[test]
1246 fn test_partitioned_ef_seek() {
1247 let doc_ids: Vec<u32> = (0..500).map(|i| i * 3).collect();
1248 let term_freqs: Vec<u32> = vec![1; 500];
1249
1250 let list = PartitionedEFPostingList::from_postings(&doc_ids, &term_freqs);
1251 let mut iter = list.iterator();
1252
1253 assert_eq!(iter.seek(100), 102); assert_eq!(iter.seek(300), 300);
1255 assert_eq!(iter.seek(1500), u32::MAX);
1256 }
1257
1258 #[test]
1259 fn test_compression_improvement() {
1260 let values: Vec<u32> = (0..10000)
1263 .map(|i| {
1264 if i < 5000 {
1265 i * 2 } else {
1267 10000 + (i - 5000) * 100 }
1269 })
1270 .collect();
1271
1272 let pef = PartitionedEliasFano::from_sorted_slice(&values);
1273
1274 assert!(
1276 pef.num_partitions() > 1,
1277 "Expected multiple partitions, got {}",
1278 pef.num_partitions()
1279 );
1280
1281 for (i, &expected) in values.iter().enumerate() {
1283 assert_eq!(pef.get(i as u32), Some(expected));
1284 }
1285 }
1286
1287 #[test]
1288 fn test_partitioned_ef_block_max() {
1289 let doc_ids: Vec<u32> = (0..500).map(|i| i * 2).collect();
1291 let term_freqs: Vec<u32> = (0..500)
1293 .map(|i| {
1294 if i < 128 {
1295 1 } else if i < 256 {
1297 5 } else if i < 384 {
1299 10 } else {
1301 3 }
1303 })
1304 .collect();
1305
1306 let list = PartitionedEFPostingList::from_postings_with_idf(&doc_ids, &term_freqs, 2.0);
1307
1308 assert_eq!(list.blocks.len(), 4);
1310 assert_eq!(list.blocks[0].max_tf, 1);
1311 assert_eq!(list.blocks[1].max_tf, 5);
1312 assert_eq!(list.blocks[2].max_tf, 10);
1313 assert_eq!(list.blocks[3].max_tf, 3);
1314
1315 assert!(list.blocks[2].max_block_score > list.blocks[0].max_block_score);
1317 assert!(list.blocks[2].max_block_score > list.blocks[1].max_block_score);
1318 assert!(list.blocks[2].max_block_score > list.blocks[3].max_block_score);
1319
1320 assert_eq!(list.max_score, list.blocks[2].max_block_score);
1322
1323 let mut iter = list.iterator();
1325 assert_eq!(iter.current_block_max_tf(), 1); iter.seek(256); assert_eq!(iter.current_block_max_tf(), 5);
1330
1331 iter.seek(512); assert_eq!(iter.current_block_max_tf(), 10);
1334
1335 let mut iter2 = list.iterator();
1337 let result = iter2.skip_to_block_with_doc(300);
1338 assert!(result.is_some());
1339 let (first_doc, score) = result.unwrap();
1340 assert!(first_doc <= 300);
1341 assert!(score > 0.0);
1342
1343 let mut iter3 = list.iterator();
1345 let max_score = iter3.max_remaining_score();
1346 assert_eq!(max_score, list.max_score);
1347
1348 iter3.seek(768); let remaining = iter3.max_remaining_score();
1351 assert!(remaining < max_score);
1352 }
1353}