1use std::cmp::Ordering;
10use std::collections::BinaryHeap;
11
12use log::{debug, warn};
13
14use crate::DocId;
15
16const MAX_INITIAL_SCORE_COLLECTOR_CAPACITY: usize = 8 * 1024;
20
21#[derive(Clone, Copy)]
23pub struct HeapEntry {
24 pub doc_id: DocId,
25 pub score: f32,
26 pub ordinal: u16,
27}
28
29impl PartialEq for HeapEntry {
30 fn eq(&self, other: &Self) -> bool {
31 self.score.to_bits() == other.score.to_bits()
32 && self.doc_id == other.doc_id
33 && self.ordinal == other.ordinal
34 }
35}
36
37impl Eq for HeapEntry {}
38
39impl Ord for HeapEntry {
40 fn cmp(&self, other: &Self) -> Ordering {
41 other
44 .score
45 .total_cmp(&self.score)
46 .then_with(|| self.doc_id.cmp(&other.doc_id))
47 .then_with(|| self.ordinal.cmp(&other.ordinal))
48 }
49}
50
51impl PartialOrd for HeapEntry {
52 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
53 Some(self.cmp(other))
54 }
55}
56
57pub struct ScoreCollector {
70 heap: BinaryHeap<HeapEntry>,
72 pub k: usize,
73 cached_threshold: f32,
76 virtual_threshold: Option<f32>,
80}
81
82impl ScoreCollector {
83 pub fn new(k: usize) -> Self {
85 Self {
86 heap: BinaryHeap::with_capacity(k.min(MAX_INITIAL_SCORE_COLLECTOR_CAPACITY)),
87 k,
88 cached_threshold: 0.0,
89 virtual_threshold: None,
90 }
91 }
92
93 #[inline]
95 pub fn threshold(&self) -> f32 {
96 self.cached_threshold
97 }
98
99 #[inline]
101 fn update_threshold(&mut self) {
102 self.cached_threshold = if let Some(threshold) = self.virtual_threshold {
103 threshold
104 } else if self.heap.len() >= self.k {
105 self.heap.peek().map(|e| e.score).unwrap_or(0.0)
106 } else {
107 0.0
108 };
109 }
110
111 #[inline]
114 pub fn insert(&mut self, doc_id: DocId, score: f32) -> bool {
115 self.insert_with_ordinal(doc_id, score, 0)
116 }
117
118 #[inline]
121 pub fn insert_with_ordinal(&mut self, doc_id: DocId, score: f32, ordinal: u16) -> bool {
122 if self.k == 0 {
123 return false;
124 }
125 let entry = HeapEntry {
126 doc_id,
127 score,
128 ordinal,
129 };
130 if self.heap.len() < self.k {
131 if let Some(threshold) = self.virtual_threshold {
132 let sentinel = HeapEntry {
133 doc_id: u32::MAX,
134 score: threshold,
135 ordinal: 0,
136 };
137 if entry >= sentinel {
138 return false;
139 }
140 }
141
142 self.heap.push(entry);
143 if self.heap.len() == self.k {
145 self.virtual_threshold = None;
146 self.update_threshold();
147 }
148 true
149 } else if self.heap.peek().is_some_and(|worst| entry < *worst) {
150 {
151 let mut worst = self.heap.peek_mut().expect("full heap has a root");
152 *worst = entry;
153 }
154 self.update_threshold();
155 true
156 } else {
157 false
158 }
159 }
160
161 #[inline]
163 pub fn would_enter(&self, score: f32) -> bool {
164 self.len() < self.k || score > self.cached_threshold
165 }
166
167 #[inline]
170 pub fn would_enter_candidate(&self, doc_id: DocId, score: f32, ordinal: u16) -> bool {
171 if self.k == 0 {
172 return false;
173 }
174 let entry = HeapEntry {
175 doc_id,
176 score,
177 ordinal,
178 };
179 if let Some(threshold) = self.virtual_threshold {
180 let sentinel = HeapEntry {
181 doc_id: u32::MAX,
182 score: threshold,
183 ordinal: 0,
184 };
185 entry < sentinel
186 } else {
187 self.heap.len() < self.k || self.heap.peek().is_some_and(|worst| entry < *worst)
188 }
189 }
190
191 #[inline]
193 pub fn len(&self) -> usize {
194 if self.virtual_threshold.is_some() {
195 self.k
196 } else {
197 self.heap.len()
198 }
199 }
200
201 #[inline]
203 pub fn real_len(&self) -> usize {
204 self.heap.len()
205 }
206
207 #[inline]
209 pub fn is_empty(&self) -> bool {
210 self.len() == 0
211 }
212
213 pub fn seed_threshold(&mut self, initial_threshold: f32) {
220 if initial_threshold <= 0.0
221 || self.k == 0
222 || (self.len() >= self.k && initial_threshold <= self.cached_threshold)
223 {
224 return;
225 }
226
227 let sentinel = HeapEntry {
228 doc_id: u32::MAX,
229 score: initial_threshold,
230 ordinal: 0,
231 };
232
233 if let Some(current_threshold) = self.virtual_threshold {
237 let current = HeapEntry {
238 doc_id: u32::MAX,
239 score: current_threshold,
240 ordinal: 0,
241 };
242 if sentinel >= current {
243 return;
244 }
245 } else if self.heap.len() >= self.k
246 && !self.heap.peek().is_some_and(|worst| sentinel < *worst)
247 {
248 return;
249 }
250
251 self.virtual_threshold = Some(initial_threshold);
252 while self.heap.peek().is_some_and(|worst| sentinel < *worst) {
253 self.heap.pop();
254 }
255 self.update_threshold();
256 }
257
258 pub fn into_sorted_results(self) -> Vec<(DocId, f32, u16)> {
261 let mut results: Vec<(DocId, f32, u16)> = self
262 .heap
263 .into_vec()
264 .into_iter()
265 .filter(|e| e.doc_id != u32::MAX)
266 .map(|e| (e.doc_id, e.score, e.ordinal))
267 .collect();
268
269 results.sort_unstable_by(|a, b| {
271 b.1.total_cmp(&a.1)
272 .then_with(|| a.0.cmp(&b.0))
273 .then_with(|| a.2.cmp(&b.2))
274 });
275
276 results
277 }
278}
279
280#[derive(Clone, Debug, Default)]
296pub struct SharedThreshold(std::sync::Arc<std::sync::atomic::AtomicU32>);
297
298impl SharedThreshold {
299 pub fn new() -> Self {
301 Self(std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)))
303 }
304
305 #[inline]
307 pub fn get(&self) -> f32 {
308 f32::from_bits(self.0.load(std::sync::atomic::Ordering::Relaxed))
309 }
310
311 pub fn raise(&self, score: f32) {
316 if score <= 0.0 {
319 return;
320 }
321 use std::sync::atomic::Ordering::Relaxed;
322 let bits = score.to_bits();
323 let mut cur = self.0.load(Relaxed);
324 while f32::from_bits(cur) < score {
325 match self.0.compare_exchange_weak(cur, bits, Relaxed, Relaxed) {
326 Ok(_) => break,
327 Err(actual) => cur = actual,
328 }
329 }
330 }
331}
332
333#[derive(Debug, Clone, Copy)]
335pub struct ScoredDoc {
336 pub doc_id: DocId,
337 pub score: f32,
338 pub ordinal: u16,
340}
341
342pub struct MaxScoreExecutor<'a> {
353 metric_index: &'a str,
357 metric_field: &'a str,
358 cursors: Vec<TermCursor<'a>>,
359 prefix_sums: Vec<f32>,
360 collector: ScoreCollector,
361 inv_heap_factor: f32,
362 predicate: Option<super::DocPredicate<'a>>,
363}
364
365pub(crate) struct TermCursor<'a> {
374 pub max_score: f32,
375 num_blocks: usize,
376 block_idx: usize,
378 doc_ids: Vec<u32>,
379 scores: Vec<f32>,
380 ordinals: Vec<u16>,
381 pos: usize,
382 block_loaded: bool,
383 exhausted: bool,
384 lazy_ordinals: bool,
388 ordinals_loaded: bool,
390 current_sparse_block: Option<crate::structures::SparseBlock>,
392 variant: CursorVariant<'a>,
394}
395
396enum CursorVariant<'a> {
397 Text {
399 list: crate::structures::BlockPostingList,
400 idf: f32,
401 idf_times_k1_plus_1: f32,
403 denom_tf_coeff: f32,
405 denom_const: f32,
407 tfs: Vec<u32>,
408 deferred_tf: Option<(usize, usize, usize)>,
411 },
412 Sparse {
414 si: &'a crate::segment::SparseIndex,
415 query_weight: f32,
416 skip_start: usize,
417 block_data_offset: u64,
418 },
419}
420
421macro_rules! cursor_ensure_block {
429 ($self:ident, $load_block_fn:ident, $($aw:tt)*) => {{
430 if $self.exhausted || $self.block_loaded {
431 return Ok(!$self.exhausted);
432 }
433 match &mut $self.variant {
434 CursorVariant::Text {
435 list,
436 deferred_tf,
437 ..
438 } => {
439 if let Some(state) = list.decode_block_doc_ids_only($self.block_idx, &mut $self.doc_ids) {
440 *deferred_tf = Some(state);
441 $self.scores.clear();
442 $self.pos = 0;
443 $self.block_loaded = true;
444 Ok(true)
445 } else {
446 $self.exhausted = true;
447 Ok(false)
448 }
449 }
450 CursorVariant::Sparse {
451 si,
452 query_weight,
453 skip_start,
454 block_data_offset,
455 ..
456 } => {
457 let block = si
458 .$load_block_fn(*skip_start, *block_data_offset, $self.block_idx)
459 $($aw)* ?;
460 match block {
461 Some(b) => {
462 b.decode_doc_ids_into(&mut $self.doc_ids);
463 b.decode_scored_weights_into(*query_weight, &mut $self.scores);
464 if $self.lazy_ordinals {
465 $self.current_sparse_block = Some(b);
468 $self.ordinals_loaded = false;
469 } else {
470 b.decode_ordinals_into(&mut $self.ordinals);
471 $self.ordinals_loaded = true;
472 $self.current_sparse_block = None;
473 }
474 $self.pos = 0;
475 $self.block_loaded = true;
476 Ok(true)
477 }
478 None => {
479 $self.exhausted = true;
480 Ok(false)
481 }
482 }
483 }
484 }
485 }};
486}
487
488macro_rules! cursor_advance {
489 ($self:ident, $ensure_fn:ident, $($aw:tt)*) => {{
490 if $self.exhausted {
491 return Ok(u32::MAX);
492 }
493 $self.$ensure_fn() $($aw)* ?;
494 if $self.exhausted {
495 return Ok(u32::MAX);
496 }
497 Ok($self.advance_pos())
498 }};
499}
500
501macro_rules! cursor_seek {
502 ($self:ident, $ensure_fn:ident, $target:expr, $($aw:tt)*) => {{
503 if let Some(doc) = $self.seek_prepare($target) {
504 return Ok(doc);
505 }
506 $self.$ensure_fn() $($aw)* ?;
507 if $self.seek_finish($target) {
508 $self.$ensure_fn() $($aw)* ?;
509 }
510 Ok($self.doc())
511 }};
512}
513
514impl<'a> TermCursor<'a> {
515 pub fn text(
517 posting_list: crate::structures::BlockPostingList,
518 idf: f32,
519 avg_field_len: f32,
520 ) -> Self {
521 let max_tf = posting_list.max_tf() as f32;
522 let max_score = super::bm25_upper_bound(max_tf.max(1.0), idf);
523 let num_blocks = posting_list.num_blocks();
524 let safe_avg = avg_field_len.max(1.0);
525 Self {
526 max_score,
527 num_blocks,
528 block_idx: 0,
529 doc_ids: Vec::with_capacity(128),
530 scores: Vec::with_capacity(128),
531 ordinals: Vec::new(),
532 pos: 0,
533 block_loaded: false,
534 exhausted: num_blocks == 0,
535 lazy_ordinals: false,
536 ordinals_loaded: true, current_sparse_block: None,
538 variant: CursorVariant::Text {
539 list: posting_list,
540 idf,
541 idf_times_k1_plus_1: idf * (super::BM25_K1 + 1.0),
542 denom_tf_coeff: 1.0 + super::BM25_K1 * (super::BM25_B / safe_avg),
543 denom_const: super::BM25_K1 * (1.0 - super::BM25_B),
544 tfs: Vec::with_capacity(128),
545 deferred_tf: None,
546 },
547 }
548 }
549
550 pub fn sparse(
553 si: &'a crate::segment::SparseIndex,
554 query_weight: f32,
555 skip_start: usize,
556 skip_count: usize,
557 global_max_weight: f32,
558 block_data_offset: u64,
559 ) -> Self {
560 Self {
561 max_score: query_weight.abs() * global_max_weight,
562 num_blocks: skip_count,
563 block_idx: 0,
564 doc_ids: Vec::with_capacity(256),
565 scores: Vec::with_capacity(256),
566 ordinals: Vec::with_capacity(256),
567 pos: 0,
568 block_loaded: false,
569 exhausted: skip_count == 0,
570 lazy_ordinals: false,
571 ordinals_loaded: true,
572 current_sparse_block: None,
573 variant: CursorVariant::Sparse {
574 si,
575 query_weight,
576 skip_start,
577 block_data_offset,
578 },
579 }
580 }
581
582 #[inline]
585 fn block_first_doc(&self, idx: usize) -> DocId {
586 match &self.variant {
587 CursorVariant::Text { list, .. } => list.block_first_doc(idx).unwrap_or(u32::MAX),
588 CursorVariant::Sparse { si, skip_start, .. } => {
589 si.read_skip_entry(*skip_start + idx).first_doc
590 }
591 }
592 }
593
594 #[inline]
595 fn block_last_doc(&self, idx: usize) -> DocId {
596 match &self.variant {
597 CursorVariant::Text { list, .. } => list.block_last_doc(idx).unwrap_or(0),
598 CursorVariant::Sparse { si, skip_start, .. } => {
599 si.read_skip_entry(*skip_start + idx).last_doc
600 }
601 }
602 }
603
604 #[inline]
607 pub fn doc(&self) -> DocId {
608 if self.exhausted {
609 return u32::MAX;
610 }
611 if self.block_loaded {
612 debug_assert!(self.pos < self.doc_ids.len());
613 unsafe { *self.doc_ids.get_unchecked(self.pos) }
615 } else {
616 self.block_first_doc(self.block_idx)
617 }
618 }
619
620 #[inline]
621 pub fn ordinal(&self) -> u16 {
622 if !self.block_loaded || self.ordinals.is_empty() {
623 return 0;
624 }
625 debug_assert!(self.pos < self.ordinals.len());
626 unsafe { *self.ordinals.get_unchecked(self.pos) }
628 }
629
630 #[inline]
636 pub fn ordinal_mut(&mut self) -> u16 {
637 if !self.block_loaded {
638 return 0;
639 }
640 if !self.ordinals_loaded {
641 if let Some(ref block) = self.current_sparse_block {
642 block.decode_ordinals_into(&mut self.ordinals);
643 }
644 self.ordinals_loaded = true;
645 }
646 if self.ordinals.is_empty() {
647 return 0;
648 }
649 debug_assert!(self.pos < self.ordinals.len());
650 unsafe { *self.ordinals.get_unchecked(self.pos) }
651 }
652
653 #[inline]
654 pub fn score(&self) -> f32 {
655 if !self.block_loaded {
656 return 0.0;
657 }
658 debug_assert!(self.pos < self.scores.len());
659 unsafe { *self.scores.get_unchecked(self.pos) }
661 }
662
663 #[inline]
669 pub fn ensure_scores(&mut self) {
670 if self.block_loaded && self.scores.is_empty() {
671 self.compute_deferred_scores();
672 }
673 }
674
675 #[inline]
676 pub fn current_block_max_score(&self) -> f32 {
677 if self.exhausted {
678 return 0.0;
679 }
680 match &self.variant {
681 CursorVariant::Text { list, idf, .. } => {
682 let block_max_tf = list.block_max_tf(self.block_idx).unwrap_or(0) as f32;
683 super::bm25_upper_bound(block_max_tf.max(1.0), *idf)
684 }
685 CursorVariant::Sparse {
686 si,
687 query_weight,
688 skip_start,
689 ..
690 } => query_weight.abs() * si.read_skip_entry(*skip_start + self.block_idx).max_weight,
691 }
692 }
693
694 pub fn skip_to_next_block(&mut self) -> DocId {
697 if self.exhausted {
698 return u32::MAX;
699 }
700 self.block_idx += 1;
701 self.block_loaded = false;
702 if self.block_idx >= self.num_blocks {
703 self.exhausted = true;
704 return u32::MAX;
705 }
706 self.block_first_doc(self.block_idx)
707 }
708
709 #[inline]
710 fn advance_pos(&mut self) -> DocId {
711 self.pos += 1;
712 if self.pos >= self.doc_ids.len() {
713 self.block_idx += 1;
714 self.block_loaded = false;
715 if self.block_idx >= self.num_blocks {
716 self.exhausted = true;
717 return u32::MAX;
718 }
719 }
720 self.doc()
721 }
722
723 #[inline(never)]
725 fn compute_deferred_scores(&mut self) {
726 if let CursorVariant::Text {
727 list,
728 idf_times_k1_plus_1,
729 denom_tf_coeff,
730 denom_const,
731 tfs,
732 deferred_tf,
733 ..
734 } = &mut self.variant
735 && let Some((block_offset, tf_start, count)) = deferred_tf.take()
736 {
737 list.decode_block_tfs_deferred(block_offset, tf_start, count, tfs);
738 let num_scale = *idf_times_k1_plus_1;
739 let d_tf = *denom_tf_coeff;
740 let d_const = *denom_const;
741 self.scores.clear();
742 self.scores.resize(count, 0.0);
743 for i in 0..count {
744 let tf = unsafe { *tfs.get_unchecked(i) } as f32;
745 let score = (num_scale * tf) / (d_tf * tf + d_const);
746 unsafe {
747 *self.scores.get_unchecked_mut(i) = score;
748 }
749 }
750 }
751 }
752
753 pub async fn ensure_block_loaded(&mut self) -> crate::Result<bool> {
759 cursor_ensure_block!(self, load_block_direct, .await)
760 }
761
762 pub fn ensure_block_loaded_sync(&mut self) -> crate::Result<bool> {
763 cursor_ensure_block!(self, load_block_direct_sync,)
764 }
765
766 pub async fn advance(&mut self) -> crate::Result<DocId> {
767 cursor_advance!(self, ensure_block_loaded, .await)
768 }
769
770 pub fn advance_sync(&mut self) -> crate::Result<DocId> {
771 cursor_advance!(self, ensure_block_loaded_sync,)
772 }
773
774 pub async fn seek(&mut self, target: DocId) -> crate::Result<DocId> {
775 cursor_seek!(self, ensure_block_loaded, target, .await)
776 }
777
778 pub fn seek_sync(&mut self, target: DocId) -> crate::Result<DocId> {
779 cursor_seek!(self, ensure_block_loaded_sync, target,)
780 }
781
782 fn seek_prepare(&mut self, target: DocId) -> Option<DocId> {
783 if self.exhausted {
784 return Some(u32::MAX);
785 }
786
787 if self.block_loaded
789 && let Some(&last) = self.doc_ids.last()
790 {
791 if last >= target && self.doc_ids[self.pos] < target {
792 let remaining = &self.doc_ids[self.pos..];
793 self.pos += crate::structures::simd::find_first_ge_u32(remaining, target);
794 if self.pos >= self.doc_ids.len() {
795 self.block_idx += 1;
796 self.block_loaded = false;
797 if self.block_idx >= self.num_blocks {
798 self.exhausted = true;
799 return Some(u32::MAX);
800 }
801 }
802 return Some(self.doc());
803 }
804 if self.doc_ids[self.pos] >= target {
805 return Some(self.doc());
806 }
807 }
808
809 let lo = match &self.variant {
811 CursorVariant::Text { list, .. } => match list.seek_block(target, self.block_idx) {
813 Some(idx) => idx,
814 None => {
815 self.exhausted = true;
816 return Some(u32::MAX);
817 }
818 },
819 CursorVariant::Sparse { .. } => {
821 let mut lo = self.block_idx;
822 let mut hi = self.num_blocks;
823 while lo < hi {
824 let mid = lo + (hi - lo) / 2;
825 if self.block_last_doc(mid) < target {
826 lo = mid + 1;
827 } else {
828 hi = mid;
829 }
830 }
831 lo
832 }
833 };
834 if lo >= self.num_blocks {
835 self.exhausted = true;
836 return Some(u32::MAX);
837 }
838 if lo != self.block_idx || !self.block_loaded {
839 self.block_idx = lo;
840 self.block_loaded = false;
841 }
842 None
843 }
844
845 #[inline]
846 fn seek_finish(&mut self, target: DocId) -> bool {
847 if self.exhausted {
848 return false;
849 }
850 self.pos = crate::structures::simd::find_first_ge_u32(&self.doc_ids, target);
851 if self.pos >= self.doc_ids.len() {
852 self.block_idx += 1;
853 self.block_loaded = false;
854 if self.block_idx >= self.num_blocks {
855 self.exhausted = true;
856 return false;
857 }
858 return true;
859 }
860 false
861 }
862}
863
864macro_rules! bms_execute_loop {
869 ($self:ident, $ensure:ident, $advance:ident, $seek:ident, $($aw:tt)*) => {{
870 let n = $self.cursors.len();
871
872 for cursor in &mut $self.cursors {
874 cursor.$ensure() $($aw)* ?;
875 }
876
877 let mut docs_scored = 0u64;
878 let mut docs_skipped = 0u64;
879 let mut blocks_skipped = 0u64;
880 let mut conjunction_skipped = 0u64;
881 let mut ordinal_scores: Vec<(u16, f32)> = Vec::with_capacity(n * 2);
882 let _bms_start = std::time::Instant::now();
883
884 let inv_heap_factor = $self.inv_heap_factor;
885 let mut adjusted_threshold = $self.collector.threshold() * inv_heap_factor - 1e-6;
886
887 loop {
888 let partition = $self.find_partition();
889 if partition >= n {
890 break;
891 }
892
893 let mut min_doc = u32::MAX;
897 let mut at_min_mask = 0u64; for i in partition..n {
899 let doc = $self.cursors[i].doc();
900 match doc.cmp(&min_doc) {
901 std::cmp::Ordering::Less => {
902 min_doc = doc;
903 at_min_mask = 1u64 << (i as u32);
904 }
905 std::cmp::Ordering::Equal => {
906 at_min_mask |= 1u64 << (i as u32);
907 }
908 _ => {}
909 }
910 }
911 if min_doc == u32::MAX {
912 break;
913 }
914
915 let non_essential_upper = if partition > 0 {
916 $self.prefix_sums[partition - 1]
917 } else {
918 0.0
919 };
920
921 if $self.collector.len() >= $self.collector.k {
923 let mut present_upper: f32 = 0.0;
924 let mut mask = at_min_mask;
925 while mask != 0 {
926 let i = mask.trailing_zeros() as usize;
927 present_upper += $self.cursors[i].max_score;
928 mask &= mask - 1;
929 }
930
931 if present_upper + non_essential_upper < adjusted_threshold {
932 let mut mask = at_min_mask;
933 while mask != 0 {
934 let i = mask.trailing_zeros() as usize;
935 $self.cursors[i].$ensure() $($aw)* ?;
936 $self.cursors[i].$advance() $($aw)* ?;
937 mask &= mask - 1;
938 }
939 conjunction_skipped += 1;
940 continue;
941 }
942 }
943
944 if $self.collector.len() >= $self.collector.k {
946 let mut block_max_sum: f32 = 0.0;
947 let mut mask = at_min_mask;
948 while mask != 0 {
949 let i = mask.trailing_zeros() as usize;
950 block_max_sum += $self.cursors[i].current_block_max_score();
951 mask &= mask - 1;
952 }
953
954 if block_max_sum + non_essential_upper < adjusted_threshold {
955 let mut mask = at_min_mask;
956 while mask != 0 {
957 let i = mask.trailing_zeros() as usize;
958 $self.cursors[i].skip_to_next_block();
959 $self.cursors[i].$ensure() $($aw)* ?;
960 mask &= mask - 1;
961 }
962 blocks_skipped += 1;
963 continue;
964 }
965 }
966
967 if let Some(ref pred) = $self.predicate {
969 if !pred(min_doc) {
970 let mut mask = at_min_mask;
971 while mask != 0 {
972 let i = mask.trailing_zeros() as usize;
973 $self.cursors[i].$ensure() $($aw)* ?;
974 $self.cursors[i].$advance() $($aw)* ?;
975 mask &= mask - 1;
976 }
977 continue;
978 }
979 }
980
981 ordinal_scores.clear();
983 {
984 let mut mask = at_min_mask;
985 while mask != 0 {
986 let i = mask.trailing_zeros() as usize;
987 $self.cursors[i].$ensure() $($aw)* ?;
988 $self.cursors[i].ensure_scores();
989 while $self.cursors[i].doc() == min_doc {
990 let ord = $self.cursors[i].ordinal_mut();
991 let sc = $self.cursors[i].score();
992 ordinal_scores.push((ord, sc));
993 $self.cursors[i].$advance() $($aw)* ?;
994 }
995 mask &= mask - 1;
996 }
997 }
998
999 let essential_total: f32 = ordinal_scores.iter().map(|(_, s)| *s).sum();
1000 if $self.collector.len() >= $self.collector.k
1001 && essential_total + non_essential_upper < adjusted_threshold
1002 {
1003 docs_skipped += 1;
1004 continue;
1005 }
1006
1007 let mut running_total = essential_total;
1009 for i in (0..partition).rev() {
1010 if $self.collector.len() >= $self.collector.k
1011 && running_total + $self.prefix_sums[i] < adjusted_threshold
1012 {
1013 break;
1014 }
1015
1016 let doc = $self.cursors[i].$seek(min_doc) $($aw)* ?;
1017 if doc == min_doc {
1018 $self.cursors[i].ensure_scores();
1019 while $self.cursors[i].doc() == min_doc {
1020 let s = $self.cursors[i].score();
1021 running_total += s;
1022 let ord = $self.cursors[i].ordinal_mut();
1023 ordinal_scores.push((ord, s));
1024 $self.cursors[i].$advance() $($aw)* ?;
1025 }
1026 }
1027 }
1028
1029 if ordinal_scores.len() == 1 {
1032 let (ord, score) = ordinal_scores[0];
1033 if $self.collector.insert_with_ordinal(min_doc, score, ord) {
1034 docs_scored += 1;
1035 adjusted_threshold = $self.collector.threshold() * inv_heap_factor - 1e-6;
1036 } else {
1037 docs_skipped += 1;
1038 }
1039 } else if !ordinal_scores.is_empty() {
1040 if ordinal_scores.len() > 2 {
1041 ordinal_scores.sort_unstable_by_key(|(ord, _)| *ord);
1042 } else if ordinal_scores.len() == 2 && ordinal_scores[0].0 > ordinal_scores[1].0 {
1043 ordinal_scores.swap(0, 1);
1044 }
1045 let mut j = 0;
1046 while j < ordinal_scores.len() {
1047 let current_ord = ordinal_scores[j].0;
1048 let mut score = 0.0f32;
1049 while j < ordinal_scores.len() && ordinal_scores[j].0 == current_ord {
1050 score += ordinal_scores[j].1;
1051 j += 1;
1052 }
1053 if $self
1054 .collector
1055 .insert_with_ordinal(min_doc, score, current_ord)
1056 {
1057 docs_scored += 1;
1058 adjusted_threshold = $self.collector.threshold() * inv_heap_factor - 1e-6;
1059 } else {
1060 docs_skipped += 1;
1061 }
1062 }
1063 }
1064 }
1065
1066 let results: Vec<ScoredDoc> = $self
1067 .collector
1068 .into_sorted_results()
1069 .into_iter()
1070 .map(|(doc_id, score, ordinal)| ScoredDoc {
1071 doc_id,
1072 score,
1073 ordinal,
1074 })
1075 .collect();
1076
1077 let _bms_elapsed_ms = _bms_start.elapsed().as_millis() as u64;
1078 if _bms_elapsed_ms > 500 {
1079 warn!(
1080 "slow MaxScore: {}ms, cursors={}, scored={}, skipped={}, blocks_skipped={}, conjunction_skipped={}, returned={}, top_score={:.4}",
1081 _bms_elapsed_ms,
1082 n,
1083 docs_scored,
1084 docs_skipped,
1085 blocks_skipped,
1086 conjunction_skipped,
1087 results.len(),
1088 results.first().map(|r| r.score).unwrap_or(0.0)
1089 );
1090 } else {
1091 debug!(
1092 "MaxScoreExecutor: {}ms, scored={}, skipped={}, blocks_skipped={}, conjunction_skipped={}, returned={}, top_score={:.4}",
1093 _bms_elapsed_ms,
1094 docs_scored,
1095 docs_skipped,
1096 blocks_skipped,
1097 conjunction_skipped,
1098 results.len(),
1099 results.first().map(|r| r.score).unwrap_or(0.0)
1100 );
1101 }
1102
1103 Ok(results)
1104 }};
1105}
1106
1107impl<'a> MaxScoreExecutor<'a> {
1108 pub(crate) fn new(mut cursors: Vec<TermCursor<'a>>, k: usize, heap_factor: f32) -> Self {
1113 if cursors.len() > super::MAX_QUERY_TERMS {
1117 cursors.sort_unstable_by(|a, b| b.max_score.total_cmp(&a.max_score));
1118 cursors.truncate(super::MAX_QUERY_TERMS);
1119 log::warn!(
1120 "MaxScore cursor count exceeded {}; retaining the strongest cursors",
1121 super::MAX_QUERY_TERMS
1122 );
1123 }
1124
1125 for c in &mut cursors {
1128 c.lazy_ordinals = true;
1129 }
1130
1131 cursors.sort_by(|a, b| {
1133 a.max_score
1134 .partial_cmp(&b.max_score)
1135 .unwrap_or(Ordering::Equal)
1136 });
1137
1138 let mut prefix_sums = Vec::with_capacity(cursors.len());
1139 let mut cumsum = 0.0f32;
1140 for c in &cursors {
1141 cumsum += c.max_score;
1142 prefix_sums.push(cumsum);
1143 }
1144
1145 let clamped_heap_factor = heap_factor.clamp(0.01, 1.0);
1146
1147 debug!(
1148 "Creating MaxScoreExecutor: num_cursors={}, k={}, total_upper={:.4}, heap_factor={:.2}",
1149 cursors.len(),
1150 k,
1151 cumsum,
1152 clamped_heap_factor
1153 );
1154
1155 Self {
1156 cursors,
1157 prefix_sums,
1158 collector: ScoreCollector::new(k),
1159 inv_heap_factor: 1.0 / clamped_heap_factor,
1160 predicate: None,
1161 metric_index: "unknown",
1162 metric_field: "unknown",
1163 }
1164 }
1165
1166 pub fn with_metric_labels(mut self, index: &'a str, field: &'a str) -> Self {
1168 self.metric_index = index;
1169 self.metric_field = field;
1170 self
1171 }
1172
1173 pub fn sparse(
1177 sparse_index: &'a crate::segment::SparseIndex,
1178 query_terms: Vec<(u32, f32)>,
1179 k: usize,
1180 heap_factor: f32,
1181 ) -> Self {
1182 let cursors: Vec<TermCursor<'a>> = query_terms
1183 .iter()
1184 .filter_map(|&(dim_id, qw)| {
1185 let (skip_start, skip_count, global_max, block_data_offset) =
1186 sparse_index.get_skip_range_full(dim_id)?;
1187 Some(TermCursor::sparse(
1188 sparse_index,
1189 qw,
1190 skip_start,
1191 skip_count,
1192 global_max,
1193 block_data_offset,
1194 ))
1195 })
1196 .collect();
1197 Self::new(cursors, k, heap_factor)
1198 }
1199
1200 pub fn text(
1204 posting_lists: Vec<(crate::structures::BlockPostingList, f32)>,
1205 avg_field_len: f32,
1206 k: usize,
1207 ) -> Self {
1208 let cursors: Vec<TermCursor<'a>> = posting_lists
1209 .into_iter()
1210 .map(|(pl, idf)| TermCursor::text(pl, idf, avg_field_len))
1211 .collect();
1212 Self::new(cursors, k, 1.0)
1213 }
1214
1215 #[inline]
1216 fn find_partition(&self) -> usize {
1217 let threshold = self.collector.threshold() * self.inv_heap_factor;
1221 self.prefix_sums.partition_point(|&sum| sum < threshold)
1224 }
1225
1226 pub fn with_predicate(mut self, predicate: super::DocPredicate<'a>) -> Self {
1232 self.predicate = Some(predicate);
1233 self
1234 }
1235
1236 pub fn seed_threshold(&mut self, initial_threshold: f32) {
1238 self.collector.seed_threshold(initial_threshold);
1239 }
1240
1241 pub async fn execute(mut self) -> crate::Result<Vec<ScoredDoc>> {
1243 if self.cursors.is_empty() {
1244 return Ok(Vec::new());
1245 }
1246 let t = crate::observe::Timer::start();
1247 let results = bms_execute_loop!(self, ensure_block_loaded, advance, seek, .await);
1248 if let Ok(r) = &results {
1249 crate::observe::maxscore_query(self.metric_index, self.metric_field, t.secs(), r.len());
1250 }
1251 results
1252 }
1253
1254 pub fn execute_sync(mut self) -> crate::Result<Vec<ScoredDoc>> {
1256 if self.cursors.is_empty() {
1257 return Ok(Vec::new());
1258 }
1259 let t = crate::observe::Timer::start();
1260 let results = bms_execute_loop!(self, ensure_block_loaded_sync, advance_sync, seek_sync,);
1261 if let Ok(r) = &results {
1262 crate::observe::maxscore_query(self.metric_index, self.metric_field, t.secs(), r.len());
1263 }
1264 results
1265 }
1266}
1267
1268#[cfg(test)]
1269mod tests {
1270 use super::*;
1271
1272 #[test]
1273 fn test_shared_threshold_monotonic_raise() {
1274 let shared = SharedThreshold::new();
1275 assert_eq!(shared.get(), 0.0);
1276
1277 shared.raise(2.5);
1278 assert_eq!(shared.get(), 2.5);
1279
1280 shared.raise(1.0);
1282 assert_eq!(shared.get(), 2.5);
1283
1284 shared.raise(4.0);
1286 assert_eq!(shared.get(), 4.0);
1287
1288 shared.raise(0.0);
1290 shared.raise(-3.0);
1291 shared.raise(f32::NAN);
1292 assert_eq!(shared.get(), 4.0);
1293
1294 let clone = shared.clone();
1296 clone.raise(9.0);
1297 assert_eq!(shared.get(), 9.0);
1298 }
1299
1300 #[test]
1301 fn test_shared_threshold_seed_matches_manual() {
1302 let mut seeded = ScoreCollector::new(2);
1305 seeded.seed_threshold(3.0);
1306 assert_eq!(seeded.threshold(), 3.0);
1307 assert!(!seeded.would_enter(3.0));
1309 assert!(seeded.would_enter(3.5));
1310 seeded.insert(1, 5.0);
1313 seeded.insert(2, 4.0);
1314 let results = seeded.into_sorted_results();
1315 assert_eq!(results.len(), 2);
1316 assert_eq!(results[0].0, 1);
1317 assert_eq!(results[1].0, 2);
1318 }
1319
1320 #[test]
1321 fn test_shared_threshold_can_raise_after_real_inserts() {
1322 let mut collector = ScoreCollector::new(3);
1323 collector.insert(1, 10.0);
1324 collector.insert(2, 4.0);
1325 assert_eq!(collector.real_len(), 2);
1326
1327 collector.seed_threshold(6.0);
1330 assert_eq!(collector.threshold(), 6.0);
1331 assert_eq!(collector.real_len(), 1);
1332
1333 assert!(collector.would_enter_candidate(3, 6.0, 0));
1336 assert!(collector.insert(3, 6.0));
1337 assert_eq!(collector.real_len(), 2);
1338 let results = collector.into_sorted_results();
1339 assert_eq!(results, vec![(1, 10.0, 0), (3, 6.0, 0)]);
1340 }
1341
1342 #[test]
1343 fn test_large_seed_uses_virtual_sentinels() {
1344 let k = 1_000_000_000;
1345 let mut collector = ScoreCollector::new(k);
1346 assert!(collector.heap.capacity() <= MAX_INITIAL_SCORE_COLLECTOR_CAPACITY);
1347
1348 collector.seed_threshold(42.0);
1349
1350 assert_eq!(collector.heap.len(), 0);
1353 assert!(collector.heap.capacity() <= MAX_INITIAL_SCORE_COLLECTOR_CAPACITY);
1354 assert_eq!(collector.len(), k);
1355 assert_eq!(collector.real_len(), 0);
1356 assert_eq!(collector.threshold(), 42.0);
1357 assert!(!collector.is_empty());
1358
1359 assert!(collector.insert_with_ordinal(9, 42.0, 7));
1362 assert!(!collector.insert(10, 41.0));
1363 assert!(collector.insert(11, 43.0));
1364 assert_eq!(collector.len(), k);
1365 assert_eq!(collector.real_len(), 2);
1366 assert_eq!(
1367 collector.into_sorted_results(),
1368 vec![(11, 43.0, 0), (9, 42.0, 7)]
1369 );
1370 }
1371
1372 #[test]
1373 fn test_virtual_sentinels_preserve_tie_order_when_filled() {
1374 let mut collector = ScoreCollector::new(3);
1375 collector.seed_threshold(5.0);
1376
1377 assert!(collector.insert_with_ordinal(3, 5.0, 2));
1378 assert!(collector.insert_with_ordinal(2, 5.0, 8));
1379 assert!(collector.insert_with_ordinal(1, 5.0, 4));
1380 assert_eq!(collector.real_len(), 3);
1381 assert!(collector.virtual_threshold.is_none());
1382
1383 assert!(collector.insert_with_ordinal(2, 5.0, 1));
1386 assert!(!collector.insert_with_ordinal(4, 5.0, 0));
1387 assert_eq!(
1388 collector.into_sorted_results(),
1389 vec![(1, 5.0, 4), (2, 5.0, 1), (2, 5.0, 8)]
1390 );
1391 }
1392
1393 #[test]
1394 fn test_score_collector_basic() {
1395 let mut collector = ScoreCollector::new(3);
1396
1397 collector.insert(1, 1.0);
1398 collector.insert(2, 2.0);
1399 collector.insert(3, 3.0);
1400 assert_eq!(collector.threshold(), 1.0);
1401
1402 collector.insert(4, 4.0);
1403 assert_eq!(collector.threshold(), 2.0);
1404
1405 let results = collector.into_sorted_results();
1406 assert_eq!(results.len(), 3);
1407 assert_eq!(results[0].0, 4); assert_eq!(results[1].0, 3);
1409 assert_eq!(results[2].0, 2);
1410 }
1411
1412 #[test]
1413 fn test_score_collector_threshold() {
1414 let mut collector = ScoreCollector::new(2);
1415
1416 collector.insert(1, 5.0);
1417 collector.insert(2, 3.0);
1418 assert_eq!(collector.threshold(), 3.0);
1419
1420 assert!(!collector.would_enter(2.0));
1422 assert!(!collector.insert(3, 2.0));
1423
1424 assert!(collector.would_enter(4.0));
1426 assert!(collector.insert(4, 4.0));
1427 assert_eq!(collector.threshold(), 4.0);
1428 }
1429
1430 #[test]
1431 fn test_heap_entry_ordering() {
1432 let mut heap = BinaryHeap::new();
1433 heap.push(HeapEntry {
1434 doc_id: 1,
1435 score: 3.0,
1436 ordinal: 0,
1437 });
1438 heap.push(HeapEntry {
1439 doc_id: 2,
1440 score: 1.0,
1441 ordinal: 0,
1442 });
1443 heap.push(HeapEntry {
1444 doc_id: 3,
1445 score: 2.0,
1446 ordinal: 0,
1447 });
1448
1449 assert_eq!(heap.pop().unwrap().score, 1.0);
1451 assert_eq!(heap.pop().unwrap().score, 2.0);
1452 assert_eq!(heap.pop().unwrap().score, 3.0);
1453 }
1454}