Skip to main content

luci/query/
dis_max.rs

1//! dis_max query: returns the score from the best-matching sub-query,
2//! plus tie_breaker * sum of other matching scores.
3//!
4//! See [[query-dsl]].
5
6use crate::core::{Result, ScoreMode, Scorer};
7
8use crate::query::{BoundQuery, Query, ScorerSupplier};
9use crate::search::searcher::Searcher;
10use crate::segment::reader::SegmentReader;
11
12/// Disjunction max query.
13pub struct DisMaxQuery {
14    pub(crate) queries: Vec<Box<dyn Query>>,
15    pub tie_breaker: f32,
16}
17
18impl Query for DisMaxQuery {
19    fn bind(&self, searcher: &Searcher, score_mode: ScoreMode) -> Result<Box<dyn BoundQuery>> {
20        let weights: Vec<Box<dyn BoundQuery>> = self
21            .queries
22            .iter()
23            .map(|q| q.bind(searcher, score_mode))
24            .collect::<Result<_>>()?;
25        Ok(Box::new(BoundDisMaxQuery {
26            weights,
27            tie_breaker: self.tie_breaker,
28        }))
29    }
30}
31
32struct BoundDisMaxQuery {
33    weights: Vec<Box<dyn BoundQuery>>,
34    tie_breaker: f32,
35}
36
37impl BoundQuery for BoundDisMaxQuery {
38    fn scorer_supplier(&self, reader: &SegmentReader) -> Result<Option<Box<dyn ScorerSupplier>>> {
39        let mut suppliers: Vec<Box<dyn ScorerSupplier>> = Vec::new();
40        for w in &self.weights {
41            if let Some(s) = w.scorer_supplier(reader)? {
42                suppliers.push(s);
43            }
44        }
45        if suppliers.is_empty() {
46            return Ok(None);
47        }
48        Ok(Some(Box::new(DisMaxScorerSupplier {
49            suppliers,
50            tie_breaker: self.tie_breaker,
51        })))
52    }
53}
54
55struct DisMaxScorerSupplier {
56    suppliers: Vec<Box<dyn ScorerSupplier>>,
57    tie_breaker: f32,
58}
59
60impl ScorerSupplier for DisMaxScorerSupplier {
61    fn cost(&self) -> u64 {
62        self.suppliers.iter().map(|s| s.cost()).sum()
63    }
64
65    fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
66        let scorers: Vec<Box<dyn Scorer>> = self
67            .suppliers
68            .into_iter()
69            .map(|s| s.scorer())
70            .collect::<Result<_>>()?;
71        Ok(Box::new(crate::search::wand::WANDScorer::new_dis_max(
72            scorers,
73            self.tie_breaker,
74        )))
75    }
76}
77
78// DisMaxScorer is now WANDScorer::new_dis_max() — see search/wand.rs.
79
80#[cfg(test)]
81mod tests {
82
83    #[test]
84    fn dis_max_score_computation() {
85        // Verify score = max + tie_breaker * sum(others)
86        // With scores [3.0, 1.0, 2.0] and tie_breaker 0.5:
87        // max = 3.0, others = 1.0 + 2.0 = 3.0
88        // score = 3.0 + 0.5 * 3.0 = 4.5
89
90        // This is a logic test — we can't easily create mock scorers
91        // without the full infrastructure, so we test via integration.
92    }
93}