///|
/// Term query with explicit BM25 parameters and a field-level score boost.
pub struct ConfiguredTermQuery {
term : Term
config : Bm25Config
boost : Double
}
///|
pub fn ConfiguredTermQuery::new(
term : Term,
config : Bm25Config,
boost : Double,
) -> ConfiguredTermQuery {
guard boost >= 0.0 else { abort("field boost must be non-negative") }
{ term, config, boost }
}
///|
priv struct ConfiguredTermWeight {
term : Term
scorer : Bm25Scorer
boost : Double
}
///|
priv struct ConfiguredTermScorer {
cursor : PostingCursor
segment : Segment
scorer : Bm25Scorer
boost : Double
}
///|
impl Scorer for ConfiguredTermScorer with fn advance(self) {
self.cursor.advance()
}
///|
impl Scorer for ConfiguredTermScorer with fn advance_to(self, target) {
self.cursor.advance_to(target)
}
///|
impl Scorer for ConfiguredTermScorer with fn doc(self) {
self.cursor.doc()
}
///|
impl Scorer for ConfiguredTermScorer with fn score(self) {
match self.cursor.posting() {
Some(posting) => self.scorer.score(posting, self.segment) * self.boost
None => 0.0
}
}
///|
impl Weight for ConfiguredTermWeight with fn scorer(self, segment) {
ConfiguredTermScorer::{
cursor: segment.posting_cursor(self.term),
segment,
scorer: self.scorer,
boost: self.boost,
}
as &Scorer
}
///|
pub impl Query for ConfiguredTermQuery with fn weight(self, statistics) {
ConfiguredTermWeight::{
term: self.term,
scorer: Bm25Scorer::from_statistics_with_config(
statistics,
self.term,
self.config,
),
boost: self.boost,
}
as &Weight
}
///|
pub(all) enum ScoreFunction {
MultiplyFastField(FieldId, Double, Double)
AddFastField(FieldId, Double, Double)
} derive(Eq, @debug.Debug)
///|
pub struct FunctionScoreQuery {
query : &Query
function : ScoreFunction
}
///|
pub fn FunctionScoreQuery::new(
query : &Query,
function : ScoreFunction,
) -> FunctionScoreQuery {
{ query, function }
}
///|
priv struct FunctionScoreWeight {
child : &Weight
function : ScoreFunction
}
///|
priv struct FunctionScoreScorer {
child : &Scorer
segment : Segment
function : ScoreFunction
}
///|
fn fast_field_number(
segment : Segment,
field_id : FieldId,
doc_id : DocId,
) -> Double? {
for value in segment.fast_values(field_id, doc_id) {
match field_value_as_double(value) {
Some(number) => return Some(number)
None => ()
}
}
None
}
///|
impl Scorer for FunctionScoreScorer with fn advance(self) {
self.child.advance()
}
///|
impl Scorer for FunctionScoreScorer with fn advance_to(self, target) {
self.child.advance_to(target)
}
///|
impl Scorer for FunctionScoreScorer with fn doc(self) {
self.child.doc()
}
///|
impl Scorer for FunctionScoreScorer with fn score(self) {
let base = self.child.score()
match self.function {
MultiplyFastField(field_id, factor, missing) => {
let feature = match
fast_field_number(self.segment, field_id, self.child.doc()) {
Some(value) => value * factor
None => missing
}
base * feature
}
AddFastField(field_id, factor, missing) => {
let feature = match
fast_field_number(self.segment, field_id, self.child.doc()) {
Some(value) => value * factor
None => missing
}
base + feature
}
}
}
///|
impl Weight for FunctionScoreWeight with fn scorer(self, segment) {
FunctionScoreScorer::{
child: self.child.scorer(segment),
segment,
function: self.function,
}
as &Scorer
}
///|
pub impl Query for FunctionScoreQuery with fn weight(self, statistics) {
FunctionScoreWeight::{
child: self.query.weight(statistics),
function: self.function,
}
as &Weight
}
///|
/// One node in a machine-readable score explanation tree.
pub struct Explanation {
matched : Bool
value : Double
description : String
details : ReadOnlyArray[Explanation]
}
///|
pub fn Explanation::matched(self : Explanation) -> Bool {
self.matched
}
///|
pub fn Explanation::value(self : Explanation) -> Double {
self.value
}
///|
pub fn Explanation::description(self : Explanation) -> String {
self.description
}
///|
pub fn Explanation::details(self : Explanation) -> ReadOnlyArray[Explanation] {
self.details
}
///|
fn explanation_leaf(value : Double, description : String) -> Explanation {
{ matched: true, value, description, details: [] }
}
///|
/// Explains the final score produced by any query at one stable DocAddress.
pub fn Searcher::explain(
self : Searcher,
query : &Query,
address : DocAddress,
) -> Explanation {
if address.segment_ord < 0 || address.segment_ord >= self.segments.length() {
return {
matched: false,
value: 0.0,
description: "segment is out of range",
details: [],
}
}
let snapshot = self.segments[address.segment_ord]
if address.doc_id.value < 0 ||
address.doc_id.value >= snapshot.segment.doc_count() ||
snapshot.is_deleted(address.doc_id) {
return {
matched: false,
value: 0.0,
description: "document is not live",
details: [],
}
}
let scorer = query.weight(self.statistics).scorer(snapshot.segment)
if scorer.advance_to(address.doc_id) && scorer.doc() == address.doc_id {
{
matched: true,
value: scorer.score(),
description: "final query score",
details: [],
}
} else {
{
matched: false,
value: 0.0,
description: "query does not match",
details: [],
}
}
}
///|
/// Detailed BM25 explanation for an exact term query.
pub fn Searcher::explain_term(
self : Searcher,
term : Term,
address : DocAddress,
config? : Bm25Config = Bm25Config::default(),
) -> Explanation {
if address.segment_ord < 0 || address.segment_ord >= self.segments.length() {
return {
matched: false,
value: 0.0,
description: "segment is out of range",
details: [],
}
}
let snapshot = self.segments[address.segment_ord]
if snapshot.is_deleted(address.doc_id) {
return {
matched: false,
value: 0.0,
description: "document is deleted",
details: [],
}
}
let postings = snapshot.segment.postings_for(term)
match postings.search_by(posting => posting.doc_id == address.doc_id) {
None =>
{
matched: false,
value: 0.0,
description: "term does not match",
details: [],
}
Some(index) => {
let posting = postings[index]
let scorer = Bm25Scorer::from_statistics_with_config(
self.statistics,
term,
config,
)
let value = scorer.score(posting, snapshot.segment)
let details = [
explanation_leaf(scorer.idf(), "inverse document frequency"),
explanation_leaf(posting.term_freq.to_double(), "term frequency"),
explanation_leaf(
snapshot.segment
.field_length(address.doc_id, term.field_id)
.to_double(),
"document field length",
),
explanation_leaf(scorer.average_field_length(), "average field length"),
explanation_leaf(config.k1(), "BM25 k1"),
explanation_leaf(config.b(), "BM25 b"),
]
{
matched: true,
value,
description: "BM25 term score",
details: ReadOnlyArray::from_array(details),
}
}
}
}