Section navigation

Boosting and scoring

LeanCorpus defaults to BM25 (Bm25Similarity).

Available similarities

Similarity Model Notes
Bm25Similarity BM25 (k1=1.2, b=0.75) Default
Bm25PlusSimilarity BM25+ with lower-bound delta Avoids over-penalising long docs
Bm25LSimilarity BM25L with tf/(1+tf) modulated delta More nuanced lower-bound than BM25+
TfIdfSimilarity Classic TF-IDF sqrt(tf) * idf / sqrt(dl)
TfIdfAugmentedSimilarity Augmented TF-IDF 0.5 + 0.5 * tf/max_tf
TfIdfDoubleNormSimilarity Double-normalised TF-IDF Two-stage normalisation
TfIdfPivotedSimilarity Pivoted TF-IDF Pivoted document length normalisation
DirichletSimilarity LM with Dirichlet smoothing (μ=2000) Bayesian smoothing towards collection
LMAbsoluteDiscountingSimilarity LM with absolute discounting Subtracts constant δ from counts
LMJelinekMercerSimilarity LM with Jelinek-Mercer (λ=0.7) Linear interpolation with collection

All implement ISimilarity. Set on both writer (for norms) and searcher (for scoring):

var config = new IndexWriterConfig { Similarity = new Bm25PlusSimilarity() };
var searcherConfig = new IndexSearcherConfig { Similarity = new Bm25PlusSimilarity() };

Per-query boost

Every Query has a Boost (default 1.0). Multiplies that query's contribution within a BooleanQuery:

var q = new BooleanQuery.Builder()
    .Add(new TermQuery("title", "fox") { Boost = 3.0f }, Occur.Should)
    .Add(new TermQuery("body",  "fox") { Boost = 1.0f }, Occur.Should)
    .Build();

Constant scores

ConstantScoreQuery assigns a fixed score; skips BM25:

var filter = new ConstantScoreQuery(new TermQuery("status", "published"), score: 1.0f);

Function scores

FunctionScoreQuery blends BM25 with a numeric field:

ScoreMode Effect
Multiply (default) score * fieldValue
Replace fieldValue
Sum score + fieldValue
Max max(score, fieldValue)
var boosted = new FunctionScoreQuery(
    new TermQuery("body", "phone"), "popularity", ScoreMode.Multiply);

For composed numeric fields, constants, and query scores, pass a DoubleValuesSource instead:

var source = DoubleValuesSource.FromDoubleField("popularity")
    .Add(DoubleValuesSource.Constant(1));
var boosted = new FunctionScoreQuery(
    new TermQuery("body", "phone"), source, ScoreMode.Multiply);

FunctionQuery uses a value source as the score for every live document. Derive from DoubleValuesSource for application-specific freshness or distance calculations.

Index-time field boosting

Set a boost on each indexed field value. It persists in segment norms and applies to matching queries:

var document = new LeanDocument();
document.Add(new TextField(
    "title",
    "A compact corpus",
    stored: true,
    boost: 3.0f));
document.Add(new TextField(
    "body",
    "Searchable article text",
    stored: true,
    boost: 1.0f));

A field boost must be finite and greater than zero. Use an unindexed stored field or a separate filter-only field when content must not contribute to ranking.

Block-Max WAND

Block-Max WAND can skip postings blocks whose score upper bound cannot enter the current top-N:

var searcherConfig = new IndexSearcherConfig
{
    EnableBlockMaxWand = true,
};

The current optimised path applies to should-only Boolean term queries when every postings stream has block metadata and there are no MustNot clauses. Other shapes fall back to exhaustive scoring.

WAND changes work performed, not the intended result ordering or scores. Validate parity against the disabled path and benchmark broad disjunctions with a small top-N. Selective queries or large requested result sets may not benefit enough to offset bound management.

Use score explanations for individual factors and search internals for the skipping model.

See also