This document describes how Reindexer computes term variant relevancy (proc) and the final document rank (0–255). For full-text index configuration, query syntax, and BM25 parameters, see fulltext.md.
In the ranking pipeline two related notions are used:
fast, black~, termina*, or one word inside a phrase "one two"). A term is what the user wrote; options such as + / -, ^boost, *, and ~ belong to that term.proc (see below) and its own list of document occurrences (positions in fields).One query term typically expands into several subterms. Ranking first scores individual subterm occurrences in a document, then aggregates those scores into the document rank.
Before BM25 and field-level boosts are applied, the search engine assigns a base relevancy (proc) to each subterm. This value is stored as subterm.Proc() and later multiplies the score of every occurrence of that subterm.
The scoring pipeline works as follows:
Starting value. Each DSL term begins with proc = FullMatch (default 100). Concatenated adjacent terms (e.g. di caprio → dicaprio) start from ConcatProc (default 90).
proc using coefficients from Base ranking config. Each coefficient is effectively configValue / FullMatch, capped at 1.0:
proc *= KbLayoutCoeff() (whether kb-layout variants are generated depends on EnableKbLayout, including heuristic skip for heavy prefix/suffix queries; see Wrong keyboard layout)proc *= TranslitCoeff()proc *= SynonymsCoeff()EnableTermsSplit: each part gets (proc / 2) * SplitCoeff()WordPartDelimiters: proc *= DelimitedCoeff()proc = max(proc - StemmerPenalty, 1)proc = max(patternProc * TypoCoeff() - typoPenalty, 1), where the penalty grows with the number of character changes and shrinks for longer words (see Typos algorithm)penalty = PartialMatchDecrease * non_matched_symbols / max(matched_symbols, 3)
proc = min(max(variantProc - penalty, PrefixMin or SuffixMin), variantProc)
Optional per-word terms_boost from config may further multiply proc.
proc is kept.The resulting subterm.Proc() is then used when scoring subterm occurrences (see Basic document ranking algorithms and How document rank is built).
iterator.Rank() returns an integer from 0 to 255 for each found document. That value is not computed in one step:
proc, boosts, distance, and so on).MinRank are dropped.255 / maxRawScore; otherwise scores are left as-is and then stored as uint8.So the float arithmetic happens first; the 0–255 conversion is only a final packaging step for the API.
A subterm may appear in several fields of the same document (and several times inside a field). For one occurrence (one document hit of one subterm, with positions across fields), the engine:
SumRanksByFieldsRatio = K > 0 and some fields are marked with + in the @field selector, additional field scores are added to the winner with decreasing weights K, K², … — the same rule as in Field selection.The score of that single occurrence is:
occurrenceScore = queryBoost * subtermProc * fieldBoost * bm25Norm * termLenBoost * positionRank
(with optional summation over + fields on top of the winning field score).
This is not “the rank of the whole query term”. It is the score of one subterm hit in one document. The same term can produce many such hits (different subterms, different documents, different positions). debug_rank() reports this value as term_rank.
Where:
queryBoost — ^boost from the DSL term (default 1)subtermProc — variant relevancy from How term variants are scoredfieldBoost — ^boost from @field selection for the winning field (default 1)bm25Norm — (1 - bm25Weight) + bm25 * bm25Boost * bm25WeighttermLenBoost — length factor blended with termLenWeightpositionRank — earlier word positions in the field score higher (positionWeight, positionBoost)If the query contains only one term, the document’s raw score is the maximum occurrenceScore among all matching subterms of that term in the document.
For queries with two or more terms (including phrases):
occurrenceScore with no distance factor.occurrenceScore * normDist, where:
normDist = (1 - distanceWeight) + distanceBoost / max(distance, 1) * distanceWeight
distance is measured in word positions inside the same field: how many steps separate the current match from the match chosen for the previous query term. Adjacent words have distance 1; one word between them means distance 2, and so on. If the two matches are not in the same field, they do not form a usable pair for this step (distance is treated as absent / zero contribution path).
Closer matches get a higher normDist (and therefore a higher contribution). distanceWeight / distanceBoost control how strongly that gap affects the score.
For a phrase "word1 word2"~N, the same distance formula is used, but only positions of word2 that lie within at most N word positions after a kept position of word1 (same order, same field) are accepted. Positions outside that window are ignored for the phrase.
One best hit per query-term step. While processing the next term, a document may contain many candidate occurrences (several subterms, several positions). The engine does not sum all of them. It keeps only the candidate with the highest occurrenceScore * normDist relative to the positions already chosen for the previous term. That contribution replaces the previous best for the current term step (it is not added on top of other candidates of the same term).
occurrenceScore, andPhrases. Phrase matching additionally requires that word positions can be chained left-to-right across all phrase terms within the ~N distance limit. Documents that fail this phrase alignment receive zero rank from the phrase.
If a document contains all required query terms and the number of words in the matching field equals the query length, the raw score is multiplied by FullMatchBoost (default 1.1).
Before results are returned:
MinRank (default 5) are removed.The value exposed as iterator.Rank() is this final normalized integer.