mirror of
https://github.com/apache/lucene.git
synced 2025-03-04 07:19:18 +00:00
LUCENE-8135: Implement block-max WAND.
This commit is contained in:
parent
890e8a51f8
commit
4fb7e3d02c
@ -71,6 +71,9 @@ Improvements
|
||||
* LUCENE-4198: Codecs now have the ability to index score impacts.
|
||||
(Adrien Grand)
|
||||
|
||||
* LUCENE-8135: Boolean queries now implement the block-max WAND algorithm in
|
||||
order to speed up selection of top scored documents. (Adrien Grand)
|
||||
|
||||
Optimizations
|
||||
|
||||
* LUCENE-8040: Optimize IndexSearcher.collectionStatistics, avoiding MultiFields/MultiTerms
|
||||
|
@ -466,7 +466,7 @@ final class Sorter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Scorer for conjunctions that checks the maximum scores of each clause in
|
||||
* order to potentially skip over blocks that can'h have competitive matches.
|
||||
*/
|
||||
final class BlockMaxConjunctionScorer extends Scorer {
|
||||
|
||||
final Scorer[] scorers;
|
||||
final MaxScoreSumPropagator maxScorePropagator;
|
||||
float minScore;
|
||||
final double[] minScores; // stores the min value of the sum of scores between 0..i for a hit to be competitive
|
||||
double score;
|
||||
|
||||
/** Create a new {@link BlockMaxConjunctionScorer} from scoring clauses. */
|
||||
BlockMaxConjunctionScorer(Weight weight, Collection<Scorer> scorersList) throws IOException {
|
||||
super(weight);
|
||||
this.scorers = scorersList.toArray(new Scorer[scorersList.size()]);
|
||||
this.maxScorePropagator = new MaxScoreSumPropagator(scorersList);
|
||||
|
||||
// Put scorers with the higher max scores first
|
||||
// We tie-break on cost
|
||||
Comparator<Scorer> comparator = (s1, s2) -> {
|
||||
int cmp;
|
||||
try {
|
||||
cmp = Float.compare(s2.getMaxScore(DocIdSetIterator.NO_MORE_DOCS), s1.getMaxScore(DocIdSetIterator.NO_MORE_DOCS));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (cmp == 0) {
|
||||
cmp = Long.compare(s1.iterator().cost(), s2.iterator().cost());
|
||||
}
|
||||
return cmp;
|
||||
};
|
||||
Arrays.sort(this.scorers, comparator);
|
||||
minScores = new double[this.scorers.length];
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocIdSetIterator iterator() {
|
||||
// TODO: support two-phase
|
||||
final Scorer leadScorer = this.scorers[0]; // higher max score
|
||||
final DocIdSetIterator[] iterators = Arrays.stream(this.scorers)
|
||||
.map(Scorer::iterator)
|
||||
.toArray(DocIdSetIterator[]::new);
|
||||
final DocIdSetIterator lead = iterators[0];
|
||||
|
||||
return new DocIdSetIterator() {
|
||||
|
||||
float maxScore;
|
||||
int upTo = -1;
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return lead.docID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return lead.cost();
|
||||
}
|
||||
|
||||
private void moveToNextBlock(int target) throws IOException {
|
||||
upTo = advanceShallow(target);
|
||||
maxScore = getMaxScore(upTo);
|
||||
|
||||
// Also compute the minimum required scores for a hit to be competitive
|
||||
// A double that is less than 'score' might still be converted to 'score'
|
||||
// when casted to a float, so we go to the previous float to avoid this issue
|
||||
minScores[minScores.length - 1] = minScore > 0 ? Math.nextDown(minScore) : 0;
|
||||
for (int i = scorers.length - 1; i > 0; --i) {
|
||||
double minScore = minScores[i];
|
||||
float clauseMaxScore = scorers[i].getMaxScore(upTo);
|
||||
if (minScore > clauseMaxScore) {
|
||||
minScores[i - 1] = minScore - clauseMaxScore;
|
||||
assert minScores[i - 1] + clauseMaxScore <= minScore;
|
||||
} else {
|
||||
minScores[i - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int advanceTarget(int target) throws IOException {
|
||||
if (target > upTo) {
|
||||
moveToNextBlock(target);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
assert upTo >= target;
|
||||
|
||||
if (maxScore >= minScore) {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (upTo == NO_MORE_DOCS) {
|
||||
return NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
target = upTo + 1;
|
||||
|
||||
moveToNextBlock(target);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
return advance(docID() + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
return doNext(lead.advance(advanceTarget(target)));
|
||||
}
|
||||
|
||||
private int doNext(int doc) throws IOException {
|
||||
advanceHead: for(;;) {
|
||||
assert doc == lead.docID();
|
||||
|
||||
if (doc == NO_MORE_DOCS) {
|
||||
return NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
if (minScore > 0) {
|
||||
score = leadScorer.score();
|
||||
if (score < minScores[0]) {
|
||||
// computing a score is usually less costly than advancing other clauses
|
||||
doc = lead.advance(advanceTarget(doc + 1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// then find agreement with other iterators
|
||||
for (int i = 1; i < iterators.length; ++i) {
|
||||
final DocIdSetIterator other = iterators[i];
|
||||
// other.doc may already be equal to doc if we "continued advanceHead"
|
||||
// on the previous iteration and the advance on the lead scorer exactly matched.
|
||||
if (other.docID() < doc) {
|
||||
final int next = other.advance(doc);
|
||||
|
||||
if (next > doc) {
|
||||
// iterator beyond the current doc - advance lead and continue to the new highest doc.
|
||||
doc = lead.advance(advanceTarget(next));
|
||||
continue advanceHead;
|
||||
}
|
||||
}
|
||||
|
||||
assert other.docID() == doc;
|
||||
if (minScore > 0) {
|
||||
score += scorers[i].score();
|
||||
|
||||
if (score < minScores[i]) {
|
||||
// computing a score is usually less costly than advancing the next clause
|
||||
doc = lead.advance(advanceTarget(doc + 1));
|
||||
continue advanceHead;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (minScore > 0 == false) {
|
||||
// the score hasn't been computed on the fly, do it now
|
||||
score = 0;
|
||||
for (Scorer scorer : scorers) {
|
||||
score += scorer.score();
|
||||
}
|
||||
}
|
||||
|
||||
// success - all iterators are on the same doc and the score is competitive
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return scorers[0].docID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
return (float) score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
// We use block boundaries of the lead scorer.
|
||||
// It is tempting to fold in other clauses as well to have better bounds of
|
||||
// the score, but then there is a risk of not progressing fast enough.
|
||||
int result = scorers[0].advanceShallow(target);
|
||||
// But we still need to shallow-advance other clauses, in order to have
|
||||
// better score upper bounds
|
||||
for (int i = 1; i < scorers.length; ++i) {
|
||||
scorers[i].advanceShallow(target);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
double sum = 0;
|
||||
for (Scorer scorer : scorers) {
|
||||
sum += scorer.getMaxScore(upTo);
|
||||
}
|
||||
return (float) sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinCompetitiveScore(float score) {
|
||||
minScore = score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ChildScorer> getChildren() {
|
||||
ArrayList<ChildScorer> children = new ArrayList<>();
|
||||
for (Scorer scorer : scorers) {
|
||||
children.add(new ChildScorer(scorer, "MUST"));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.OptionalLong;
|
||||
@ -135,7 +136,7 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
|
||||
return 0f;
|
||||
}
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return 0f;
|
||||
}
|
||||
};
|
||||
@ -150,9 +151,16 @@ final class Boolean2ScorerSupplier extends ScorerSupplier {
|
||||
}
|
||||
for (ScorerSupplier s : requiredScoring) {
|
||||
Scorer scorer = s.get(leadCost);
|
||||
requiredScorers.add(scorer);
|
||||
scoringScorers.add(scorer);
|
||||
}
|
||||
if (scoreMode == ScoreMode.TOP_SCORES && scoringScorers.size() > 1) {
|
||||
Scorer blockMaxScorer = new BlockMaxConjunctionScorer(weight, scoringScorers);
|
||||
if (requiredScorers.isEmpty()) {
|
||||
return blockMaxScorer;
|
||||
}
|
||||
scoringScorers = Collections.singletonList(blockMaxScorer);
|
||||
}
|
||||
requiredScorers.addAll(scoringScorers);
|
||||
return new ConjunctionScorer(weight, requiredScorers, scoringScorers);
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ public abstract class CachingCollector extends FilterCollector {
|
||||
public final float score() { return score; }
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ class ConjunctionScorer extends Scorer {
|
||||
final MaxScoreSumPropagator maxScorePropagator;
|
||||
|
||||
/** Create a new {@link ConjunctionScorer}, note that {@code scorers} must be a subset of {@code required}. */
|
||||
ConjunctionScorer(Weight weight, Collection<Scorer> required, Collection<Scorer> scorers) {
|
||||
ConjunctionScorer(Weight weight, Collection<Scorer> required, Collection<Scorer> scorers) throws IOException {
|
||||
super(weight);
|
||||
assert required.containsAll(scorers);
|
||||
this.disi = ConjunctionDISI.intersectScorers(required);
|
||||
@ -64,14 +64,32 @@ class ConjunctionScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return maxScorePropagator.maxScore();
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
// This scorer is only used for TOP_SCORES when there is at most one scoring clause
|
||||
switch (scorers.length) {
|
||||
case 0:
|
||||
return 0;
|
||||
case 1:
|
||||
return scorers[0].getMaxScore(upTo);
|
||||
default:
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinCompetitiveScore(float score) {
|
||||
// Propagate to sub clauses.
|
||||
maxScorePropagator.setMinCompetitiveScore(score);
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
if (scorers.length == 1) {
|
||||
return scorers[0].advanceShallow(target);
|
||||
}
|
||||
return super.advanceShallow(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinCompetitiveScore(float minScore) {
|
||||
// This scorer is only used for TOP_SCORES when there is a single scoring clause
|
||||
if (scorers.length == 1) {
|
||||
scorers[0].setMinCompetitiveScore(minScore);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,7 +95,7 @@ public final class ConstantScoreQuery extends Query {
|
||||
return theScore;
|
||||
}
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return theScore;
|
||||
}
|
||||
});
|
||||
@ -141,7 +141,7 @@ public final class ConstantScoreQuery extends Query {
|
||||
return score;
|
||||
}
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return score;
|
||||
}
|
||||
@Override
|
||||
|
@ -54,7 +54,7 @@ public final class ConstantScoreScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return score;
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class DisiWrapper {
|
||||
// two-phase iteration
|
||||
public final TwoPhaseIterator twoPhaseView;
|
||||
|
||||
// For MaxScoreScorer
|
||||
// For WANDScorer
|
||||
long maxScore;
|
||||
|
||||
// FOR SPANS
|
||||
|
@ -43,7 +43,7 @@ final class DisjunctionMaxScorer extends DisjunctionScorer {
|
||||
* @param subScorers
|
||||
* The sub scorers this Scorer should iterate on
|
||||
*/
|
||||
DisjunctionMaxScorer(Weight weight, float tieBreakerMultiplier, List<Scorer> subScorers, boolean needsScores) {
|
||||
DisjunctionMaxScorer(Weight weight, float tieBreakerMultiplier, List<Scorer> subScorers, boolean needsScores) throws IOException {
|
||||
super(weight, subScorers, needsScores);
|
||||
this.tieBreakerMultiplier = tieBreakerMultiplier;
|
||||
if (tieBreakerMultiplier < 0 || tieBreakerMultiplier > 1) {
|
||||
@ -53,7 +53,7 @@ final class DisjunctionMaxScorer extends DisjunctionScorer {
|
||||
float scoreMax = 0;
|
||||
double otherScoreSum = 0;
|
||||
for (Scorer scorer : subScorers) {
|
||||
float subScore = scorer.maxScore();
|
||||
float subScore = scorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
if (subScore >= scoreMax) {
|
||||
otherScoreSum += scoreMax;
|
||||
scoreMax = subScore;
|
||||
@ -91,7 +91,7 @@ final class DisjunctionMaxScorer extends DisjunctionScorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return maxScore;
|
||||
}
|
||||
}
|
||||
|
@ -32,11 +32,11 @@ final class DisjunctionSumScorer extends DisjunctionScorer {
|
||||
* @param weight The weight to be used.
|
||||
* @param subScorers Array of at least two subscorers.
|
||||
*/
|
||||
DisjunctionSumScorer(Weight weight, List<Scorer> subScorers, boolean needsScores) {
|
||||
DisjunctionSumScorer(Weight weight, List<Scorer> subScorers, boolean needsScores) throws IOException {
|
||||
super(weight, subScorers, needsScores);
|
||||
double maxScore = 0;
|
||||
for (Scorer scorer : subScorers) {
|
||||
maxScore += scorer.maxScore();
|
||||
maxScore += scorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
}
|
||||
// The error of sums depends on the order in which values are summed up. In
|
||||
// order to avoid this issue, we compute an upper bound of the value that
|
||||
@ -57,7 +57,7 @@ final class DisjunctionSumScorer extends DisjunctionScorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
|
@ -121,7 +121,7 @@ final class ExactPhraseScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return docScorer.maxScore();
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
package org.apache.lucene.search;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/** Used by {@link BulkScorer}s that need to pass a {@link
|
||||
@ -40,7 +41,7 @@ final class FakeScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,9 @@
|
||||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.lucene.util.InPlaceMergeSorter;
|
||||
import org.apache.lucene.util.MathUtil;
|
||||
|
||||
/**
|
||||
@ -28,114 +28,33 @@ import org.apache.lucene.util.MathUtil;
|
||||
*/
|
||||
final class MaxScoreSumPropagator {
|
||||
|
||||
/**
|
||||
* Return an array which, at index i, stores the sum of all entries of
|
||||
* {@code v} except the one at index i.
|
||||
*/
|
||||
private static double[] computeSumOfComplement(float[] v) {
|
||||
// We do not use subtraction on purpose because it would defeat the
|
||||
// upperbound formula that we use for sums.
|
||||
// Naive approach would be O(n^2), but we can do O(n) by computing the
|
||||
// sum for i<j and i>j and then sum them.
|
||||
double[] sum1 = new double[v.length];
|
||||
for (int i = 1; i < sum1.length; ++i) {
|
||||
sum1[i] = sum1[i-1] + v[i-1];
|
||||
}
|
||||
|
||||
double[] sum2 = new double[v.length];
|
||||
for (int i = sum2.length - 2; i >= 0; --i) {
|
||||
sum2[i] = sum2[i+1] + v[i+1];
|
||||
}
|
||||
|
||||
double[] result = new double[v.length];
|
||||
for (int i = 0; i < result.length; ++i) {
|
||||
result[i] = sum1[i] + sum2[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private final int numClauses;
|
||||
private final float maxScore;
|
||||
private final Scorer[] scorers;
|
||||
private final double[] sumOfOtherMaxScores;
|
||||
|
||||
MaxScoreSumPropagator(Collection<? extends Scorer> scorerList) {
|
||||
MaxScoreSumPropagator(Collection<? extends Scorer> scorerList) throws IOException {
|
||||
numClauses = scorerList.size();
|
||||
scorers = scorerList.toArray(new Scorer[numClauses]);
|
||||
// We'll need max scores multiple times so we cache them
|
||||
float[] maxScores = new float[numClauses];
|
||||
for (int i = 0; i < numClauses; ++i) {
|
||||
maxScores[i] = scorers[i].maxScore();
|
||||
}
|
||||
// Sort by decreasing max score
|
||||
new InPlaceMergeSorter() {
|
||||
@Override
|
||||
protected void swap(int i, int j) {
|
||||
Scorer tmp = scorers[i];
|
||||
scorers[i] = scorers[j];
|
||||
scorers[j] = tmp;
|
||||
float tmpF = maxScores[i];
|
||||
maxScores[i] = maxScores[j];
|
||||
maxScores[j] = tmpF;
|
||||
}
|
||||
|
||||
void advanceShallow(int target) throws IOException {
|
||||
for (Scorer s : scorers) {
|
||||
if (s.docID() < target) {
|
||||
s.advanceShallow(target);
|
||||
}
|
||||
@Override
|
||||
protected int compare(int i, int j) {
|
||||
return Float.compare(maxScores[j], maxScores[i]);
|
||||
}
|
||||
}
|
||||
|
||||
float getMaxScore(int upTo) throws IOException {
|
||||
double maxScore = 0;
|
||||
for (Scorer s : scorers) {
|
||||
if (s.docID() <= upTo) {
|
||||
maxScore += s.getMaxScore(upTo);
|
||||
}
|
||||
}.sort(0, scorers.length);
|
||||
|
||||
sumOfOtherMaxScores = computeSumOfComplement(maxScores);
|
||||
if (numClauses == 0) {
|
||||
maxScore = 0;
|
||||
} else {
|
||||
maxScore = sumUpperBound(maxScores[0] + sumOfOtherMaxScores[0]);
|
||||
}
|
||||
return scoreSumUpperBound(maxScore);
|
||||
}
|
||||
|
||||
public float maxScore() {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
public void setMinCompetitiveScore(float minScoreSum) {
|
||||
for (int i = 0; i < numClauses; ++i) {
|
||||
double sumOfOtherMaxScores = this.sumOfOtherMaxScores[i];
|
||||
float minCompetitiveScore = getMinCompetitiveScore(minScoreSum, sumOfOtherMaxScores);
|
||||
if (minCompetitiveScore <= 0) {
|
||||
// given that scorers are sorted by decreasing max score, next scorers will
|
||||
// have 0 as a minimum competitive score too
|
||||
break;
|
||||
}
|
||||
scorers[i].setMinCompetitiveScore(minCompetitiveScore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum score that a Scorer must produce in order for a hit to
|
||||
* be competitive.
|
||||
*/
|
||||
private float getMinCompetitiveScore(float minScoreSum, double sumOfOtherMaxScores) {
|
||||
assert numClauses > 0;
|
||||
if (minScoreSum <= sumOfOtherMaxScores) {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// We need to find a value 'minScore' so that 'minScore + sumOfOtherMaxScores <= minScoreSum'
|
||||
// TODO: is there an efficient way to find the greatest value that meets this requirement?
|
||||
float minScore = (float) (minScoreSum - sumOfOtherMaxScores);
|
||||
int iters = 0;
|
||||
while (sumUpperBound(minScore + sumOfOtherMaxScores) > minScoreSum) {
|
||||
// Important: use ulp of minScoreSum and not minScore to make sure that we
|
||||
// converge quickly.
|
||||
minScore -= Math.ulp(minScoreSum);
|
||||
// this should converge in at most two iterations:
|
||||
// - one because of the subtraction rounding error
|
||||
// - one because of the error introduced by sumUpperBound
|
||||
assert ++iters <= 2: iters;
|
||||
}
|
||||
return Math.max(minScore, 0f);
|
||||
}
|
||||
|
||||
private float sumUpperBound(double sum) {
|
||||
private float scoreSumUpperBound(double sum) {
|
||||
if (numClauses <= 2) {
|
||||
// When there are only two clauses, the sum is always the same regardless
|
||||
// of the order.
|
||||
|
@ -326,7 +326,7 @@ final class MinShouldMatchSumScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
// TODO: implement but be careful about floating-point errors.
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
@ -77,8 +77,8 @@ class ReqExclScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return reqScorer.maxScore();
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return reqScorer.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -44,7 +44,7 @@ class ReqOptSumScorer extends Scorer {
|
||||
*/
|
||||
public ReqOptSumScorer(
|
||||
Scorer reqScorer,
|
||||
Scorer optScorer)
|
||||
Scorer optScorer) throws IOException
|
||||
{
|
||||
super(reqScorer.weight);
|
||||
assert reqScorer != null;
|
||||
@ -52,7 +52,7 @@ class ReqOptSumScorer extends Scorer {
|
||||
this.reqScorer = reqScorer;
|
||||
this.optScorer = optScorer;
|
||||
|
||||
this.reqMaxScore = reqScorer.maxScore();
|
||||
this.reqMaxScore = reqScorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
this.maxScorePropagator = new MaxScoreSumPropagator(Arrays.asList(reqScorer, optScorer));
|
||||
|
||||
final TwoPhaseIterator reqTwoPhase = reqScorer.twoPhaseIterator();
|
||||
@ -210,8 +210,12 @@ class ReqOptSumScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return maxScorePropagator.maxScore();
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
float maxScore = reqScorer.getMaxScore(upTo);
|
||||
if (optScorer.docID() <= upTo) {
|
||||
maxScore += optScorer.getMaxScore(upTo);
|
||||
}
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -220,8 +224,6 @@ class ReqOptSumScorer extends Scorer {
|
||||
if (optIsRequired == false && minScore > reqMaxScore) {
|
||||
optIsRequired = true;
|
||||
}
|
||||
// And also propagate to sub clauses.
|
||||
maxScorePropagator.setMinCompetitiveScore(minScore);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,8 +54,13 @@ public final class ScoreCachingWrappingScorer extends FilterScorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return in.maxScore();
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return in.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
return in.advanceShallow(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -157,7 +157,25 @@ public abstract class Scorer {
|
||||
// no-op by default
|
||||
}
|
||||
|
||||
/** Return the maximum score that this scorer may produce. If scores are not
|
||||
* bounded, {@link Float#POSITIVE_INFINITY} must be returned. */
|
||||
public abstract float maxScore();
|
||||
/**
|
||||
* Advance to the block of documents that contains {@code target} in order to
|
||||
* get scoring information about this block. This method is implicitly called
|
||||
* by {@link DocIdSetIterator#advance(int)} and
|
||||
* {@link DocIdSetIterator#nextDoc()}. Calling this method doesn't modify the
|
||||
* current {@link DocIdSetIterator#docID()}.
|
||||
* It returns a number that is greater than or equal to all documents
|
||||
* contained in the current block, but less than any doc IDS of the next block.
|
||||
* {@code target} must be >= {@link #docID()} as well as all targets that
|
||||
* have been passed to {@link #advanceShallow(int)} so far.
|
||||
*/
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
return DocIdSetIterator.NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum score that documents between the last {@code target}
|
||||
* that this iterator was {@link #advanceShallow(int) shallow-advanced} to
|
||||
* included and {@code upTo} included.
|
||||
*/
|
||||
public abstract float getMaxScore(int upTo) throws IOException;
|
||||
}
|
||||
|
@ -556,7 +556,7 @@ final class SloppyPhraseScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return docScorer.maxScore();
|
||||
}
|
||||
|
||||
|
@ -254,7 +254,7 @@ public final class SynonymQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return similarity.maxScore();
|
||||
}
|
||||
|
||||
|
@ -21,12 +21,14 @@ import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.index.ImpactsEnum;
|
||||
import org.apache.lucene.index.PostingsEnum;
|
||||
import org.apache.lucene.index.SlowImpactsEnum;
|
||||
import org.apache.lucene.index.TermsEnum;
|
||||
|
||||
/** Expert: A <code>Scorer</code> for documents matching a <code>Term</code>.
|
||||
*/
|
||||
final class TermScorer extends Scorer {
|
||||
private final PostingsEnum postingsEnum;
|
||||
private final ImpactsEnum impactsEnum;
|
||||
private final DocIdSetIterator iterator;
|
||||
private final LeafSimScorer docScorer;
|
||||
private float minCompetitiveScore;
|
||||
@ -45,7 +47,7 @@ final class TermScorer extends Scorer {
|
||||
super(weight);
|
||||
this.docScorer = docScorer;
|
||||
if (scoreMode == ScoreMode.TOP_SCORES) {
|
||||
ImpactsEnum impactsEnum = te.impacts(docScorer.getSimScorer(), PostingsEnum.FREQS);
|
||||
impactsEnum = te.impacts(docScorer.getSimScorer(), PostingsEnum.FREQS);
|
||||
postingsEnum = impactsEnum;
|
||||
iterator = new DocIdSetIterator() {
|
||||
|
||||
@ -103,6 +105,7 @@ final class TermScorer extends Scorer {
|
||||
};
|
||||
} else {
|
||||
postingsEnum = te.postings(null, scoreMode.needsScores() ? PostingsEnum.FREQS : PostingsEnum.NONE);
|
||||
impactsEnum = new SlowImpactsEnum(postingsEnum, docScorer.getSimScorer().score(Float.MAX_VALUE, 1));
|
||||
iterator = postingsEnum;
|
||||
}
|
||||
}
|
||||
@ -128,8 +131,13 @@ final class TermScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return docScorer.maxScore();
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
return impactsEnum.advanceShallow(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return impactsEnum.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -29,10 +29,12 @@ import java.util.OptionalInt;
|
||||
/**
|
||||
* This implements the WAND (Weak AND) algorithm for dynamic pruning
|
||||
* described in "Efficient Query Evaluation using a Two-Level Retrieval
|
||||
* Process" by Broder, Carmel, Herscovici, Soffer and Zien.
|
||||
* Process" by Broder, Carmel, Herscovici, Soffer and Zien. Enhanced with
|
||||
* techniques described in "Faster Top-k Document Retrieval Using Block-Max
|
||||
* Indexes" by Ding and Suel.
|
||||
* This scorer maintains a feedback loop with the collector in order to
|
||||
* know at any time the minimum score that is required in order for a hit
|
||||
* to be competitive. Then it leverages the {@link Scorer#maxScore() max score}
|
||||
* to be competitive. Then it leverages the {@link Scorer#getMaxScore(int) max score}
|
||||
* from each scorer in order to know when it may call
|
||||
* {@link DocIdSetIterator#advance} rather than {@link DocIdSetIterator#nextDoc}
|
||||
* to move to the next competitive hit.
|
||||
@ -122,19 +124,22 @@ final class WANDScorer extends Scorer {
|
||||
final long cost;
|
||||
final MaxScoreSumPropagator maxScorePropagator;
|
||||
|
||||
WANDScorer(Weight weight, Collection<Scorer> scorers) {
|
||||
int upTo; // upper bound for which max scores are valid
|
||||
|
||||
WANDScorer(Weight weight, Collection<Scorer> scorers) throws IOException {
|
||||
super(weight);
|
||||
|
||||
this.minCompetitiveScore = 0;
|
||||
this.doc = -1;
|
||||
this.upTo = -1; // will be computed on the first call to nextDoc/advance
|
||||
|
||||
head = new DisiPriorityQueue(scorers.size());
|
||||
// there can be at most num_scorers - 1 scorers beyond the current position
|
||||
tail = new DisiWrapper[scorers.size() - 1];
|
||||
tail = new DisiWrapper[scorers.size()];
|
||||
|
||||
OptionalInt scalingFactor = OptionalInt.empty();
|
||||
for (Scorer scorer : scorers) {
|
||||
float maxScore = scorer.maxScore();
|
||||
float maxScore = scorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
if (maxScore != 0 && Float.isFinite(maxScore)) {
|
||||
// 0 and +Infty should not impact the scale
|
||||
scalingFactor = OptionalInt.of(Math.min(scalingFactor.orElse(Integer.MAX_VALUE), scalingFactor(maxScore)));
|
||||
@ -142,17 +147,12 @@ final class WANDScorer extends Scorer {
|
||||
}
|
||||
// Use a scaling factor of 0 if all max scores are either 0 or +Infty
|
||||
this.scalingFactor = scalingFactor.orElse(0);
|
||||
|
||||
for (Scorer scorer : scorers) {
|
||||
DisiWrapper w = new DisiWrapper(scorer);
|
||||
float maxScore = scorer.maxScore();
|
||||
w.maxScore = scaleMaxScore(maxScore, this.scalingFactor);
|
||||
addLead(w);
|
||||
}
|
||||
|
||||
long cost = 0;
|
||||
for (DisiWrapper w = lead; w != null; w = w.next) {
|
||||
for (Scorer scorer : scorers) {
|
||||
DisiWrapper w = new DisiWrapper(scorer);
|
||||
cost += w.cost;
|
||||
addLead(w);
|
||||
}
|
||||
this.cost = cost;
|
||||
this.maxScorePropagator = new MaxScoreSumPropagator(scorers);
|
||||
@ -179,7 +179,7 @@ final class WANDScorer extends Scorer {
|
||||
assert w.doc > doc;
|
||||
}
|
||||
|
||||
assert tailSize == 0 || tailMaxScore < minCompetitiveScore;
|
||||
assert minCompetitiveScore == 0 || tailMaxScore < minCompetitiveScore;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -192,15 +192,12 @@ final class WANDScorer extends Scorer {
|
||||
long scaledMinScore = scaleMinScore(minScore, scalingFactor);
|
||||
assert scaledMinScore >= minCompetitiveScore;
|
||||
minCompetitiveScore = scaledMinScore;
|
||||
|
||||
// And also propagate to sub clauses.
|
||||
maxScorePropagator.setMinCompetitiveScore(minScore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<ChildScorer> getChildren() throws IOException {
|
||||
List<ChildScorer> matchingChildren = new ArrayList<>();
|
||||
updateFreq();
|
||||
advanceAllTail();
|
||||
for (DisiWrapper s = lead; s != null; s = s.next) {
|
||||
matchingChildren.add(new ChildScorer(s.scorer, "SHOULD"));
|
||||
}
|
||||
@ -236,13 +233,17 @@ final class WANDScorer extends Scorer {
|
||||
// Advance 'head' as well
|
||||
advanceHead(target);
|
||||
|
||||
// Pop the new 'lead' from the 'head'
|
||||
setDocAndFreq();
|
||||
// Pop the new 'lead' from 'head'
|
||||
moveToNextCandidate(target);
|
||||
|
||||
if (doc == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return DocIdSetIterator.NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
assert ensureConsistent();
|
||||
|
||||
// Advance to the next possible match
|
||||
return doNextCandidate();
|
||||
return doNextCompetitiveCandidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -275,12 +276,14 @@ final class WANDScorer extends Scorer {
|
||||
};
|
||||
}
|
||||
|
||||
/** Add a disi to the linked list of leads. */
|
||||
private void addLead(DisiWrapper lead) {
|
||||
lead.next = this.lead;
|
||||
this.lead = lead;
|
||||
leadMaxScore += lead.maxScore;
|
||||
}
|
||||
|
||||
/** Move disis that are in 'lead' back to the tail. */
|
||||
private void pushBackLeads(int target) throws IOException {
|
||||
for (DisiWrapper s = lead; s != null; s = s.next) {
|
||||
final DisiWrapper evicted = insertTailWithOverFlow(s);
|
||||
@ -289,11 +292,14 @@ final class WANDScorer extends Scorer {
|
||||
head.add(evicted);
|
||||
}
|
||||
}
|
||||
lead = null;
|
||||
leadMaxScore = 0;
|
||||
}
|
||||
|
||||
/** Make sure all disis in 'head' are on or after 'target'. */
|
||||
private void advanceHead(int target) throws IOException {
|
||||
DisiWrapper headTop = head.top();
|
||||
while (headTop.doc < target) {
|
||||
while (headTop != null && headTop.doc < target) {
|
||||
final DisiWrapper evicted = insertTailWithOverFlow(headTop);
|
||||
if (evicted != null) {
|
||||
evicted.doc = evicted.iterator.advance(target);
|
||||
@ -314,14 +320,83 @@ final class WANDScorer extends Scorer {
|
||||
}
|
||||
}
|
||||
|
||||
/** Pop the entry from the 'tail' that has the greatest score contribution,
|
||||
* advance it to the current doc and then add it to 'lead' or 'head'
|
||||
* depending on whether it matches. */
|
||||
private void advanceTail() throws IOException {
|
||||
final DisiWrapper top = popTail();
|
||||
advanceTail(top);
|
||||
}
|
||||
|
||||
/** Reinitializes head, freq and doc from 'head' */
|
||||
private void setDocAndFreq() {
|
||||
assert head.size() > 0;
|
||||
private void updateMaxScores(int target) throws IOException {
|
||||
if (head.size() == 0) {
|
||||
// If the head is empty we use the greatest score contributor as a lead
|
||||
// like for conjunctions.
|
||||
upTo = tail[0].scorer.advanceShallow(target);
|
||||
} else {
|
||||
// If we still have entries in 'head', we treat them all as leads and
|
||||
// take the minimum of their next block boundaries as a next boundary.
|
||||
// We don't take entries in 'tail' into account on purpose: 'tail' is
|
||||
// supposed to contain the least score contributors, and taking them
|
||||
// into account might not move the boundary fast enough, so we'll waste
|
||||
// CPU re-computing the next boundary all the time.
|
||||
int newUpTo = DocIdSetIterator.NO_MORE_DOCS;
|
||||
for (DisiWrapper w : head) {
|
||||
if (w.doc <= newUpTo) {
|
||||
newUpTo = Math.min(w.scorer.advanceShallow(w.doc), newUpTo);
|
||||
w.maxScore = scaleMaxScore(w.scorer.getMaxScore(newUpTo), scalingFactor);
|
||||
}
|
||||
}
|
||||
upTo = newUpTo;
|
||||
}
|
||||
|
||||
tailMaxScore = 0;
|
||||
for (int i = 0; i < tailSize; ++i) {
|
||||
DisiWrapper w = tail[i];
|
||||
w.scorer.advanceShallow(target);
|
||||
w.maxScore = scaleMaxScore(w.scorer.getMaxScore(upTo), scalingFactor);
|
||||
upHeapMaxScore(tail, i); // the heap might need to be reordered
|
||||
tailMaxScore += w.maxScore;
|
||||
}
|
||||
|
||||
// We need to make sure that entries in 'tail' alone cannot match
|
||||
// a competitive hit.
|
||||
while (tailSize > 0 && tailMaxScore >= minCompetitiveScore) {
|
||||
DisiWrapper w = popTail();
|
||||
w.doc = w.iterator.advance(target);
|
||||
head.add(w);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMaxScoresIfNecessary(int target) throws IOException {
|
||||
assert lead == null;
|
||||
|
||||
if (head.size() == 0) { // no matches in the current block
|
||||
if (upTo != DocIdSetIterator.NO_MORE_DOCS) {
|
||||
updateMaxScores(Math.max(target, upTo + 1));
|
||||
}
|
||||
} else if (head.top().doc > upTo) { // the next candidate is in a different block
|
||||
assert head.top().doc >= target;
|
||||
updateMaxScores(target);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set 'doc' to the next potential match, and move all disis of 'head' that
|
||||
* are on this doc into 'lead'. */
|
||||
private void moveToNextCandidate(int target) throws IOException {
|
||||
// Update score bounds if necessary so
|
||||
updateMaxScoresIfNecessary(target);
|
||||
assert upTo >= target;
|
||||
|
||||
// If the head is empty, it means that the sum of all max scores is not
|
||||
// enough to produce a competitive score. So we jump to the next block.
|
||||
while (head.size() == 0) {
|
||||
if (upTo == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
doc = DocIdSetIterator.NO_MORE_DOCS;
|
||||
return;
|
||||
}
|
||||
updateMaxScores(upTo + 1);
|
||||
}
|
||||
|
||||
// The top of `head` defines the next potential match
|
||||
// pop all documents which are on this doc
|
||||
@ -335,16 +410,15 @@ final class WANDScorer extends Scorer {
|
||||
}
|
||||
|
||||
/** Move iterators to the tail until there is a potential match. */
|
||||
private int doNextCandidate() throws IOException {
|
||||
private int doNextCompetitiveCandidate() throws IOException {
|
||||
while (leadMaxScore + tailMaxScore < minCompetitiveScore) {
|
||||
// no match on doc is possible, move to the next potential match
|
||||
if (head.size() == 0) {
|
||||
// special case: the total max score is less than the min competitive score, there are no more matches
|
||||
return doc = DocIdSetIterator.NO_MORE_DOCS;
|
||||
}
|
||||
pushBackLeads(doc + 1);
|
||||
setDocAndFreq();
|
||||
moveToNextCandidate(doc + 1);
|
||||
assert ensureConsistent();
|
||||
if (doc == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
@ -352,15 +426,12 @@ final class WANDScorer extends Scorer {
|
||||
|
||||
/** Advance all entries from the tail to know about all matches on the
|
||||
* current doc. */
|
||||
private void updateFreq() throws IOException {
|
||||
private void advanceAllTail() throws IOException {
|
||||
// we return the next doc when the sum of the scores of the potential
|
||||
// matching clauses is high enough but some of the clauses in 'tail' might
|
||||
// match as well
|
||||
// in general we want to advance least-costly clauses first in order to
|
||||
// skip over non-matching documents as fast as possible. However here,
|
||||
// we are advancing everything anyway so iterating over clauses in
|
||||
// (roughly) cost-descending order might help avoid some permutations in
|
||||
// the head heap
|
||||
// since we are advancing all clauses in tail, we just iterate the array
|
||||
// without reorganizing the PQ
|
||||
for (int i = tailSize - 1; i >= 0; --i) {
|
||||
advanceTail(tail[i]);
|
||||
}
|
||||
@ -372,7 +443,7 @@ final class WANDScorer extends Scorer {
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
// we need to know about all matches
|
||||
updateFreq();
|
||||
advanceAllTail();
|
||||
double score = 0;
|
||||
for (DisiWrapper s = lead; s != null; s = s.next) {
|
||||
score += s.scorer.score();
|
||||
@ -381,8 +452,19 @@ final class WANDScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return maxScorePropagator.maxScore();
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
// Propagate to improve score bounds
|
||||
maxScorePropagator.advanceShallow(target);
|
||||
if (target <= upTo) {
|
||||
return upTo;
|
||||
}
|
||||
// TODO: implement
|
||||
return DocIdSetIterator.NO_MORE_DOCS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return maxScorePropagator.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -392,7 +474,7 @@ final class WANDScorer extends Scorer {
|
||||
|
||||
/** Insert an entry in 'tail' and evict the least-costly scorer if full. */
|
||||
private DisiWrapper insertTailWithOverFlow(DisiWrapper s) {
|
||||
if (tailSize < tail.length && tailMaxScore + s.maxScore < minCompetitiveScore) {
|
||||
if (tailMaxScore + s.maxScore < minCompetitiveScore) {
|
||||
// we have free room for this new entry
|
||||
addTail(s);
|
||||
tailMaxScore += s.maxScore;
|
||||
|
@ -135,7 +135,7 @@ public class SpanScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -176,7 +176,7 @@ final class JustCompileSearch {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
throw new UnsupportedOperationException(UNSUPPORTED_MSG);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.document.Field.Store;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BooleanClause.Occur;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
|
||||
public class TestBlockMaxConjunction extends LuceneTestCase {
|
||||
|
||||
private Query maybeWrap(Query query) {
|
||||
if (random().nextBoolean()) {
|
||||
query = new BlockScoreQueryWrapper(query, TestUtil.nextInt(random(), 2, 8));
|
||||
query = new AssertingQuery(random(), query);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public void testRandom() throws IOException {
|
||||
Directory dir = newDirectory();
|
||||
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig());
|
||||
int numDocs = atLeast(1000);
|
||||
for (int i = 0; i < numDocs; ++i) {
|
||||
Document doc = new Document();
|
||||
int numValues = random().nextInt(1 << random().nextInt(5));
|
||||
int start = random().nextInt(10);
|
||||
for (int j = 0; j < numValues; ++j) {
|
||||
doc.add(new StringField("foo", Integer.toString(start + j), Store.NO));
|
||||
}
|
||||
w.addDocument(doc);
|
||||
}
|
||||
IndexReader reader = DirectoryReader.open(w);
|
||||
w.close();
|
||||
IndexSearcher searcher = newSearcher(reader);
|
||||
|
||||
for (int iter = 0; iter < 100; ++iter) {
|
||||
int start = random().nextInt(10);
|
||||
int numClauses = random().nextInt(1 << random().nextInt(5));
|
||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||
for (int i = 0; i < numClauses; ++i) {
|
||||
builder.add(maybeWrap(new TermQuery(new Term("foo", Integer.toString(start + i)))), Occur.MUST);
|
||||
}
|
||||
Query query = builder.build();
|
||||
|
||||
CheckHits.checkTopScores(random(), query, searcher);
|
||||
|
||||
int filterTerm = random().nextInt(30);
|
||||
Query filteredQuery = new BooleanQuery.Builder()
|
||||
.add(query, Occur.MUST)
|
||||
.add(new TermQuery(new Term("foo", Integer.toString(filterTerm))), Occur.FILTER)
|
||||
.build();
|
||||
|
||||
CheckHits.checkTopScores(random(), filteredQuery, searcher);
|
||||
}
|
||||
reader.close();
|
||||
dir.close();
|
||||
}
|
||||
|
||||
}
|
@ -51,7 +51,7 @@ public class TestBoolean2ScorerSupplier extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class TestCachingCollector extends LuceneTestCase {
|
||||
public float score() throws IOException { return 0; }
|
||||
|
||||
@Override
|
||||
public float maxScore() { return 0; }
|
||||
public float getMaxScore(int upTo) throws IOException { return 0; }
|
||||
|
||||
@Override
|
||||
public int docID() { return 0; }
|
||||
|
@ -147,7 +147,7 @@ public class TestConjunctionDISI extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
|
||||
public class TestMaxScoreSumPropagator extends LuceneTestCase {
|
||||
|
||||
private static class FakeScorer extends Scorer {
|
||||
|
||||
final float maxScore;
|
||||
float minCompetitiveScore;
|
||||
|
||||
FakeScorer(float maxScore) {
|
||||
super(null);
|
||||
this.maxScore = maxScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocIdSetIterator iterator() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinCompetitiveScore(float minCompetitiveScore) {
|
||||
this.minCompetitiveScore = minCompetitiveScore;
|
||||
}
|
||||
}
|
||||
|
||||
public void test0Clause() {
|
||||
MaxScoreSumPropagator p = new MaxScoreSumPropagator(Collections.emptyList());
|
||||
assertEquals(0f, p.maxScore(), 0f);
|
||||
p.setMinCompetitiveScore(0f); // no exception
|
||||
p.setMinCompetitiveScore(0.5f); // no exception
|
||||
}
|
||||
|
||||
public void test1Clause() {
|
||||
FakeScorer a = new FakeScorer(1);
|
||||
|
||||
MaxScoreSumPropagator p = new MaxScoreSumPropagator(Collections.singletonList(a));
|
||||
assertEquals(1f, p.maxScore(), 0f);
|
||||
p.setMinCompetitiveScore(0f);
|
||||
assertEquals(0f, a.minCompetitiveScore, 0f);
|
||||
p.setMinCompetitiveScore(0.5f);
|
||||
assertEquals(0.5f, a.minCompetitiveScore, 0f);
|
||||
p.setMinCompetitiveScore(1f);
|
||||
assertEquals(1f, a.minCompetitiveScore, 0f);
|
||||
}
|
||||
|
||||
public void test2Clauses() {
|
||||
FakeScorer a = new FakeScorer(1);
|
||||
FakeScorer b = new FakeScorer(2);
|
||||
|
||||
MaxScoreSumPropagator p = new MaxScoreSumPropagator(Arrays.asList(a, b));
|
||||
assertEquals(3f, p.maxScore(), 0f);
|
||||
|
||||
p.setMinCompetitiveScore(1f);
|
||||
assertEquals(0f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(0f, b.minCompetitiveScore, 0f);
|
||||
|
||||
p.setMinCompetitiveScore(2f);
|
||||
assertEquals(0f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(1f, b.minCompetitiveScore, 0f);
|
||||
|
||||
p.setMinCompetitiveScore(2.5f);
|
||||
assertEquals(0.5f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(1.5f, b.minCompetitiveScore, 0f);
|
||||
|
||||
p.setMinCompetitiveScore(3f);
|
||||
assertEquals(1f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(2f, b.minCompetitiveScore, 0f);
|
||||
}
|
||||
|
||||
public void test3Clauses() {
|
||||
FakeScorer a = new FakeScorer(1);
|
||||
FakeScorer b = new FakeScorer(2);
|
||||
FakeScorer c = new FakeScorer(1.5f);
|
||||
|
||||
MaxScoreSumPropagator p = new MaxScoreSumPropagator(Arrays.asList(a, b, c));
|
||||
assertEquals(4.5f, p.maxScore(), 0f);
|
||||
|
||||
p.setMinCompetitiveScore(1f);
|
||||
assertEquals(0f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(0f, b.minCompetitiveScore, 0f);
|
||||
assertEquals(0f, c.minCompetitiveScore, 0f);
|
||||
|
||||
p.setMinCompetitiveScore(2f);
|
||||
assertEquals(0f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(0f, b.minCompetitiveScore, 0f);
|
||||
assertEquals(0f, c.minCompetitiveScore, 0f);
|
||||
|
||||
p.setMinCompetitiveScore(3f);
|
||||
assertEquals(0f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(0.5f, b.minCompetitiveScore, 0f);
|
||||
assertEquals(0f, c.minCompetitiveScore, 0f);
|
||||
|
||||
p.setMinCompetitiveScore(4f);
|
||||
assertEquals(0.5f, a.minCompetitiveScore, 0f);
|
||||
assertEquals(1.5f, b.minCompetitiveScore, 0f);
|
||||
assertEquals(1f, c.minCompetitiveScore, 0f);
|
||||
}
|
||||
|
||||
public void test2ClausesRandomScore() {
|
||||
for (int iter = 0; iter < 10; ++iter) {
|
||||
FakeScorer a = new FakeScorer(random().nextFloat());
|
||||
FakeScorer b = new FakeScorer(Math.nextUp(a.maxScore()) + random().nextFloat());
|
||||
|
||||
MaxScoreSumPropagator p = new MaxScoreSumPropagator(Arrays.asList(a, b));
|
||||
assertEquals(a.maxScore() + b.maxScore(), p.maxScore(), 0f);
|
||||
assertMinCompetitiveScore(Arrays.asList(a, b), p, Math.nextUp(a.maxScore()));
|
||||
assertMinCompetitiveScore(Arrays.asList(a, b), p, (a.maxScore() + b.maxScore()) / 2);
|
||||
assertMinCompetitiveScore(Arrays.asList(a, b), p, Math.nextDown(a.maxScore() + b.maxScore()));
|
||||
assertMinCompetitiveScore(Arrays.asList(a, b), p, a.maxScore() + b.maxScore());
|
||||
}
|
||||
}
|
||||
|
||||
public void testNClausesRandomScore() {
|
||||
for (int iter = 0; iter < 100; ++iter) {
|
||||
List<FakeScorer> scorers = new ArrayList<>();
|
||||
int numScorers = TestUtil.nextInt(random(), 3, 4 << random().nextInt(8));
|
||||
double sumOfMaxScore = 0;
|
||||
for (int i = 0; i < numScorers; ++i) {
|
||||
float maxScore = random().nextFloat();
|
||||
scorers.add(new FakeScorer(maxScore));
|
||||
sumOfMaxScore += maxScore;
|
||||
}
|
||||
|
||||
MaxScoreSumPropagator p = new MaxScoreSumPropagator(scorers);
|
||||
assertTrue(p.maxScore() >= (float) sumOfMaxScore);
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
final float minCompetitiveScore = random().nextFloat() * numScorers;
|
||||
assertMinCompetitiveScore(scorers, p, minCompetitiveScore);
|
||||
// reset
|
||||
for (FakeScorer scorer : scorers) {
|
||||
scorer.minCompetitiveScore = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertMinCompetitiveScore(Collection<FakeScorer> scorers, MaxScoreSumPropagator p, float minCompetitiveScore) {
|
||||
p.setMinCompetitiveScore(minCompetitiveScore);
|
||||
|
||||
for (FakeScorer scorer : scorers) {
|
||||
if (scorer.minCompetitiveScore == 0f) {
|
||||
// no propagation is performed, still visiting all hits
|
||||
break;
|
||||
}
|
||||
double scoreSum = scorer.minCompetitiveScore;
|
||||
for (FakeScorer scorer2 : scorers) {
|
||||
if (scorer2 != scorer) {
|
||||
scoreSum += scorer2.maxScore();
|
||||
}
|
||||
}
|
||||
assertTrue(
|
||||
"scoreSum=" + scoreSum + ", minCompetitiveScore=" + minCompetitiveScore,
|
||||
(float) scoreSum <= minCompetitiveScore);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -345,7 +345,7 @@ public class TestMinShouldMatch2 extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
package org.apache.lucene.search;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.RandomIndexWriter;
|
||||
@ -38,7 +40,7 @@ public class TestPositiveScoresOnlyCollector extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -481,7 +481,7 @@ public class TestQueryRescorer extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
};
|
||||
|
@ -44,7 +44,7 @@ public class TestScoreCachingWrappingScorer extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -237,7 +237,7 @@ public class TestTopDocsCollector extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -251,8 +251,8 @@ public class TestTopFieldCollector extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return scorer.maxScore();
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return scorer.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -30,6 +30,7 @@ import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.search.BooleanClause.Occur;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.util.LuceneTestCase;
|
||||
import org.apache.lucene.util.TestUtil;
|
||||
|
||||
public class TestWANDScorer extends LuceneTestCase {
|
||||
|
||||
@ -53,6 +54,14 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
assertTrue(""+scaled, scaled <= 1 << 16);
|
||||
}
|
||||
|
||||
private Query maybeWrap(Query query) {
|
||||
if (random().nextBoolean()) {
|
||||
query = new BlockScoreQueryWrapper(query, TestUtil.nextInt(random(), 2, 8));
|
||||
query = new AssertingQuery(random(), query);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
public void testBasics() throws Exception {
|
||||
Directory dir = newDirectory();
|
||||
IndexWriter w = new IndexWriter(dir, newIndexWriterConfig().setMergePolicy(newLogMergePolicy()));
|
||||
@ -127,7 +136,7 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
|
||||
assertEquals(DocIdSetIterator.NO_MORE_DOCS, scorer.iterator().nextDoc());
|
||||
|
||||
// Now test a filtered disjunction
|
||||
// test a filtered disjunction
|
||||
query = new BooleanQuery.Builder()
|
||||
.add(
|
||||
new BooleanQuery.Builder()
|
||||
@ -216,19 +225,14 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
|
||||
for (int iter = 0; iter < 100; ++iter) {
|
||||
int start = random().nextInt(10);
|
||||
int numClauses = random().nextInt(1 << random().nextInt(5));
|
||||
int numClauses = 2;//random().nextInt(1 << random().nextInt(5));
|
||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||
for (int i = 0; i < numClauses; ++i) {
|
||||
builder.add(new TermQuery(new Term("foo", Integer.toString(start + i))), Occur.SHOULD);
|
||||
builder.add(maybeWrap(new TermQuery(new Term("foo", Integer.toString(start + i)))), Occur.SHOULD);
|
||||
}
|
||||
Query query = builder.build();
|
||||
|
||||
TopScoreDocCollector collector1 = TopScoreDocCollector.create(10, null, true); // COMPLETE
|
||||
TopScoreDocCollector collector2 = TopScoreDocCollector.create(10, null, false); // TOP_SCORES
|
||||
|
||||
searcher.search(query, collector1);
|
||||
searcher.search(query, collector2);
|
||||
assertTopDocsEquals(collector1.topDocs(), collector2.topDocs());
|
||||
CheckHits.checkTopScores(random(), query, searcher);
|
||||
|
||||
int filterTerm = random().nextInt(30);
|
||||
Query filteredQuery = new BooleanQuery.Builder()
|
||||
@ -236,11 +240,7 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
.add(new TermQuery(new Term("foo", Integer.toString(filterTerm))), Occur.FILTER)
|
||||
.build();
|
||||
|
||||
collector1 = TopScoreDocCollector.create(10, null, true); // COMPLETE
|
||||
collector2 = TopScoreDocCollector.create(10, null, false); // TOP_SCORES
|
||||
searcher.search(filteredQuery, collector1);
|
||||
searcher.search(filteredQuery, collector2);
|
||||
assertTopDocsEquals(collector1.topDocs(), collector2.topDocs());
|
||||
CheckHits.checkTopScores(random(), filteredQuery, searcher);
|
||||
}
|
||||
reader.close();
|
||||
dir.close();
|
||||
@ -276,11 +276,7 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
}
|
||||
Query query = builder.build();
|
||||
|
||||
TopScoreDocCollector collector1 = TopScoreDocCollector.create(10, null, true); // COMPLETE
|
||||
TopScoreDocCollector collector2 = TopScoreDocCollector.create(10, null, false); // TOP_SCORES
|
||||
searcher.search(query, collector1);
|
||||
searcher.search(query, collector2);
|
||||
assertTopDocsEquals(collector1.topDocs(), collector2.topDocs());
|
||||
CheckHits.checkTopScores(random(), query, searcher);
|
||||
|
||||
int filterTerm = random().nextInt(30);
|
||||
Query filteredQuery = new BooleanQuery.Builder()
|
||||
@ -288,11 +284,7 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
.add(new TermQuery(new Term("foo", Integer.toString(filterTerm))), Occur.FILTER)
|
||||
.build();
|
||||
|
||||
collector1 = TopScoreDocCollector.create(10, null, true); // COMPLETE
|
||||
collector2 = TopScoreDocCollector.create(10, null, false); // TOP_SCORES
|
||||
searcher.search(filteredQuery, collector1);
|
||||
searcher.search(filteredQuery, collector2);
|
||||
assertTopDocsEquals(collector1.topDocs(), collector2.topDocs());
|
||||
CheckHits.checkTopScores(random(), filteredQuery, searcher);
|
||||
}
|
||||
reader.close();
|
||||
dir.close();
|
||||
@ -305,7 +297,7 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
@ -378,17 +370,6 @@ public class TestWANDScorer extends LuceneTestCase {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void assertTopDocsEquals(TopDocs td1, TopDocs td2) {
|
||||
assertEquals(td1.scoreDocs.length, td2.scoreDocs.length);
|
||||
for (int i = 0; i < td1.scoreDocs.length; ++i) {
|
||||
ScoreDoc sd1 = td1.scoreDocs[i];
|
||||
ScoreDoc sd2 = td2.scoreDocs[i];
|
||||
assertEquals(sd1.doc, sd2.doc);
|
||||
assertEquals(sd1.score, sd2.score, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class FakeScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -602,7 +602,7 @@ class DrillSidewaysScorer extends BulkScorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ class FakeScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ abstract class BaseGlobalOrdinalScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ class FakeScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ public class ParentChildrenBlockJoinQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
@Override
|
||||
|
@ -170,7 +170,7 @@ abstract class PointInSetIncludingScoreQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ class TermsIncludingScoreQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -281,8 +281,8 @@ public class ToChildBlockJoinQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return parentScorer.maxScore();
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return parentScorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
}
|
||||
|
||||
int getParentDoc() {
|
||||
|
@ -287,11 +287,11 @@ public class ToParentBlockJoinQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
switch(scoreMode) {
|
||||
case Max:
|
||||
case Min:
|
||||
return childScorer.maxScore();
|
||||
return childScorer.getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
default:
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
@ -543,7 +543,7 @@ public class TestJoinUtil extends LuceneTestCase {
|
||||
return (float) price.longValue();
|
||||
}
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
};
|
||||
|
@ -505,7 +505,7 @@ public class TestDiversifiedTopDocsCollector extends LuceneTestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ public class FunctionQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,7 @@ public final class FunctionScoreQuery extends Query {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
};
|
||||
|
@ -107,7 +107,7 @@ public abstract class ValueSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public abstract class ValueSourceScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -212,7 +212,7 @@ final class CoveringScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
// TODO: implement but beware of floating-point errors
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ class TermAutomatonScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return docScorer.maxScore();
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ import java.util.Random;
|
||||
/** Wraps a Scorer with additional checks */
|
||||
public class AssertingScorer extends Scorer {
|
||||
|
||||
static enum IteratorState { START, APPROXIMATING, ITERATING, FINISHED };
|
||||
static enum IteratorState { APPROXIMATING, ITERATING, SHALLOW_ADVANCING, FINISHED };
|
||||
|
||||
public static Scorer wrap(Random random, Scorer other, ScoreMode scoreMode) {
|
||||
if (other == null) {
|
||||
@ -37,9 +37,10 @@ public class AssertingScorer extends Scorer {
|
||||
final Scorer in;
|
||||
final ScoreMode scoreMode;
|
||||
|
||||
IteratorState state = IteratorState.START;
|
||||
IteratorState state = IteratorState.ITERATING;
|
||||
int doc;
|
||||
float minCompetitiveScore = 0;
|
||||
int lastShallowTarget;
|
||||
|
||||
private AssertingScorer(Random random, Scorer in, ScoreMode scoreMode) {
|
||||
super(in.weight);
|
||||
@ -60,7 +61,7 @@ public class AssertingScorer extends Scorer {
|
||||
case DocIdSetIterator.NO_MORE_DOCS:
|
||||
return false;
|
||||
default:
|
||||
return state != IteratorState.APPROXIMATING; // Matches must be confirmed before calling freq() or score()
|
||||
return state == IteratorState.ITERATING;
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,19 +75,30 @@ public class AssertingScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
float maxScore = in.maxScore();
|
||||
assert Float.isNaN(maxScore) == false;
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
assert target >= lastShallowTarget : "called on decreasing targets: target = " + target + " < last target = " + lastShallowTarget;
|
||||
assert target >= docID() : "target = " + target + " < docID = " + docID();
|
||||
int upTo = in.advanceShallow(target);
|
||||
assert upTo >= target : "upTo = " + upTo + " < target = " + target;
|
||||
lastShallowTarget = target;
|
||||
state = target != doc ? IteratorState.SHALLOW_ADVANCING : state;
|
||||
return upTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
assert upTo >= lastShallowTarget : "uTo = " + upTo + " < last target = " + lastShallowTarget;
|
||||
float maxScore = in.getMaxScore(upTo);
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
assert scoreMode.needsScores();
|
||||
assert iterating();
|
||||
assert iterating() : state;
|
||||
final float score = in.score();
|
||||
assert !Float.isNaN(score) : "NaN score for in="+in;
|
||||
assert score <= maxScore();
|
||||
assert score <= getMaxScore(DocIdSetIterator.NO_MORE_DOCS);
|
||||
assert Float.compare(score, 0f) >= 0 : score;
|
||||
return score;
|
||||
}
|
||||
@ -125,6 +137,7 @@ public class AssertingScorer extends Scorer {
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
assert state != IteratorState.FINISHED : "nextDoc() called after NO_MORE_DOCS";
|
||||
assert docID() + 1 >= lastShallowTarget;
|
||||
int nextDoc = in.nextDoc();
|
||||
assert nextDoc > doc : "backwards nextDoc from " + doc + " to " + nextDoc + " " + in;
|
||||
if (nextDoc == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
@ -141,6 +154,7 @@ public class AssertingScorer extends Scorer {
|
||||
public int advance(int target) throws IOException {
|
||||
assert state != IteratorState.FINISHED : "advance() called after NO_MORE_DOCS";
|
||||
assert target > doc : "target must be > docID(), got " + target + " <= " + doc;
|
||||
assert target >= lastShallowTarget;
|
||||
int advanced = in.advance(target);
|
||||
assert advanced >= target : "backwards advance from: " + target + " to: " + advanced;
|
||||
if (advanced == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
@ -178,6 +192,7 @@ public class AssertingScorer extends Scorer {
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
assert state != IteratorState.FINISHED : "advance() called after NO_MORE_DOCS";
|
||||
assert docID() + 1 >= lastShallowTarget;
|
||||
final int nextDoc = inApproximation.nextDoc();
|
||||
assert nextDoc > doc : "backwards advance from: " + doc + " to: " + nextDoc;
|
||||
if (nextDoc == NO_MORE_DOCS) {
|
||||
@ -193,6 +208,7 @@ public class AssertingScorer extends Scorer {
|
||||
public int advance(int target) throws IOException {
|
||||
assert state != IteratorState.FINISHED : "advance() called after NO_MORE_DOCS";
|
||||
assert target > doc : "target must be > docID(), got " + target + " <= " + doc;
|
||||
assert target >= lastShallowTarget;
|
||||
final int advanced = inApproximation.advance(target);
|
||||
assert advanced >= target : "backwards advance from: " + target + " to: " + advanced;
|
||||
if (advanced == NO_MORE_DOCS) {
|
||||
@ -213,7 +229,7 @@ public class AssertingScorer extends Scorer {
|
||||
return new TwoPhaseIterator(assertingApproximation) {
|
||||
@Override
|
||||
public boolean matches() throws IOException {
|
||||
assert state == IteratorState.APPROXIMATING;
|
||||
assert state == IteratorState.APPROXIMATING : state;
|
||||
final boolean matches = in.matches();
|
||||
if (matches) {
|
||||
assert AssertingScorer.this.in.iterator().docID() == inApproximation.docID() : "Approximation and scorer don't advance synchronously";
|
||||
|
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.util.ArrayUtil;
|
||||
|
||||
/**
|
||||
* Query wrapper that reduces the size of max-score blocks to more easily detect
|
||||
* problems with the max-score logic.
|
||||
*/
|
||||
public final class BlockScoreQueryWrapper extends Query {
|
||||
|
||||
private final Query query;
|
||||
private final int blockLength;
|
||||
|
||||
/** Sole constructor. */
|
||||
public BlockScoreQueryWrapper(Query query, int blockLength) {
|
||||
this.query = Objects.requireNonNull(query);
|
||||
this.blockLength = blockLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(String field) {
|
||||
return query.toString(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
BlockScoreQueryWrapper that = (BlockScoreQueryWrapper) obj;
|
||||
return Objects.equals(query, that.query) && blockLength == that.blockLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = classHash();
|
||||
h = 31 * h + query.hashCode();
|
||||
h = 31 * h + blockLength;
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query rewrite(IndexReader reader) throws IOException {
|
||||
final Query rewritten = query.rewrite(reader);
|
||||
if (rewritten != query) {
|
||||
return new BlockScoreQueryWrapper(rewritten, blockLength);
|
||||
}
|
||||
return super.rewrite(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost) throws IOException {
|
||||
final Weight inWeight = query.createWeight(searcher, scoreMode, boost);
|
||||
if (scoreMode.needsScores() == false) {
|
||||
return inWeight;
|
||||
}
|
||||
return new Weight(this) {
|
||||
@Override
|
||||
public boolean isCacheable(LeafReaderContext ctx) {
|
||||
return inWeight.isCacheable(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scorer scorer(LeafReaderContext context) throws IOException {
|
||||
Scorer inScorer = inWeight.scorer(context);
|
||||
if (inScorer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int[] tmpDocs = new int[2];
|
||||
float[] tmpScores = new float[2];
|
||||
tmpDocs[0] = -1;
|
||||
DocIdSetIterator it = inScorer.iterator();
|
||||
int i = 1;
|
||||
for (int doc = it.nextDoc(); ; doc = it.nextDoc()) {
|
||||
if (i == tmpDocs.length) {
|
||||
tmpDocs = ArrayUtil.grow(tmpDocs);
|
||||
tmpScores = Arrays.copyOf(tmpScores, tmpDocs.length);
|
||||
}
|
||||
tmpDocs[i] = doc;
|
||||
if (doc == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
tmpScores[i] = inScorer.score();
|
||||
i++;
|
||||
}
|
||||
final int[] docs = Arrays.copyOf(tmpDocs, i);
|
||||
final float[] scores = Arrays.copyOf(tmpScores, i);
|
||||
|
||||
return new Scorer(inWeight) {
|
||||
|
||||
int i = 0;
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return docs[i];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float score() throws IOException {
|
||||
return scores[i];
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocIdSetIterator iterator() {
|
||||
return new DocIdSetIterator() {
|
||||
|
||||
@Override
|
||||
public int nextDoc() throws IOException {
|
||||
assert docs[i] != NO_MORE_DOCS;
|
||||
return docs[++i];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int docID() {
|
||||
return docs[i];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long cost() {
|
||||
return docs.length - 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int advance(int target) throws IOException {
|
||||
i = Arrays.binarySearch(docs, target);
|
||||
if (i < 0) {
|
||||
i = -1 - i;
|
||||
}
|
||||
assert docs[i] >= target;
|
||||
return docs[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private int startOfBlock(int target) {
|
||||
int i = Arrays.binarySearch(docs, target);
|
||||
if (i < 0) {
|
||||
i = -1 - i;
|
||||
}
|
||||
return i - i % blockLength;
|
||||
}
|
||||
|
||||
private int endOfBlock(int target) {
|
||||
return Math.min(startOfBlock(target) + blockLength, docs.length - 1);
|
||||
}
|
||||
|
||||
int lastShallowTarget = -1;
|
||||
|
||||
@Override
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
lastShallowTarget = target;
|
||||
if (target == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
return DocIdSetIterator.NO_MORE_DOCS;
|
||||
}
|
||||
return docs[endOfBlock(target)] - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
float max = 0;
|
||||
for (int j = startOfBlock(Math.max(docs[i], lastShallowTarget)); ; ++j) {
|
||||
if (docs[j] > upTo) {
|
||||
break;
|
||||
}
|
||||
max = Math.max(max, scores[j]);
|
||||
if (j == docs.length - 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractTerms(Set<Term> terms) {
|
||||
inWeight.extractTerms(terms);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
|
||||
return inWeight.explain(context, doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -70,7 +70,7 @@ public class BulkScorerWrapperScorer extends Scorer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@
|
||||
*/
|
||||
package org.apache.lucene.search;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
@ -520,6 +523,150 @@ public class CheckHits {
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkTopScores(Random random, Query query, IndexSearcher searcher) throws IOException {
|
||||
// Check it computed the top hits correctly
|
||||
doCheckTopScores(query, searcher, 1);
|
||||
doCheckTopScores(query, searcher, 10);
|
||||
|
||||
// Now check that the exposed max scores and block boundaries are valid
|
||||
doCheckMaxScores(random, query, searcher);
|
||||
}
|
||||
|
||||
private static void doCheckTopScores(Query query, IndexSearcher searcher, int numHits) throws IOException {
|
||||
TopScoreDocCollector collector1 = TopScoreDocCollector.create(numHits, null, true); // COMPLETE
|
||||
TopScoreDocCollector collector2 = TopScoreDocCollector.create(numHits, null, false); // TOP_SCORES
|
||||
searcher.search(query, collector1);
|
||||
searcher.search(query, collector2);
|
||||
checkEqual(query, collector1.topDocs().scoreDocs, collector2.topDocs().scoreDocs);
|
||||
}
|
||||
|
||||
private static void doCheckMaxScores(Random random, Query query, IndexSearcher searcher) throws IOException {
|
||||
Weight w1 = searcher.createNormalizedWeight(query, ScoreMode.COMPLETE);
|
||||
Weight w2 = searcher.createNormalizedWeight(query, ScoreMode.TOP_SCORES);
|
||||
|
||||
// Check boundaries and max scores when iterating all matches
|
||||
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
|
||||
Scorer s1 = w1.scorer(ctx);
|
||||
Scorer s2 = w2.scorer(ctx);
|
||||
if (s1 == null) {
|
||||
Assert.assertTrue(s2 == null || s2.iterator().nextDoc() == DocIdSetIterator.NO_MORE_DOCS);
|
||||
continue;
|
||||
}
|
||||
TwoPhaseIterator twoPhase1 = s1.twoPhaseIterator();
|
||||
TwoPhaseIterator twoPhase2 = s2.twoPhaseIterator();
|
||||
DocIdSetIterator approx1 = twoPhase1 == null ? s1.iterator() : twoPhase1.approximation;
|
||||
DocIdSetIterator approx2 = twoPhase2 == null ? s2.iterator() : twoPhase2.approximation;
|
||||
int upTo = -1;
|
||||
float maxScore = 0;
|
||||
float minScore = 0;
|
||||
for (int doc2 = approx2.nextDoc(); ; doc2 = approx2.nextDoc()) {
|
||||
int doc1;
|
||||
for (doc1 = approx1.nextDoc(); doc1 < doc2; doc1 = approx1.nextDoc()) {
|
||||
if (twoPhase1 == null || twoPhase1.matches()) {
|
||||
Assert.assertTrue(s1.score() < minScore);
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(doc1, doc2);
|
||||
if (doc2 == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (doc2 > upTo) {
|
||||
upTo = s2.advanceShallow(doc2);
|
||||
Assert.assertTrue(upTo >= doc2);
|
||||
maxScore = s2.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
if (twoPhase2 == null || twoPhase2.matches()) {
|
||||
Assert.assertTrue(twoPhase1 == null || twoPhase1.matches());
|
||||
float score = s2.score();
|
||||
Assert.assertEquals(s1.score(), score);
|
||||
Assert.assertTrue(score <= maxScore);
|
||||
|
||||
if (score >= minScore && random.nextInt(10) == 0) {
|
||||
// On some scorers, changing the min score changes the way that docs are iterated
|
||||
minScore = score;
|
||||
s2.setMinCompetitiveScore(minScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now check advancing
|
||||
for (LeafReaderContext ctx : searcher.getIndexReader().leaves()) {
|
||||
Scorer s1 = w1.scorer(ctx);
|
||||
Scorer s2 = w2.scorer(ctx);
|
||||
if (s1 == null) {
|
||||
Assert.assertTrue(s2 == null || s2.iterator().nextDoc() == DocIdSetIterator.NO_MORE_DOCS);
|
||||
continue;
|
||||
}
|
||||
TwoPhaseIterator twoPhase1 = s1.twoPhaseIterator();
|
||||
TwoPhaseIterator twoPhase2 = s2.twoPhaseIterator();
|
||||
DocIdSetIterator approx1 = twoPhase1 == null ? s1.iterator() : twoPhase1.approximation;
|
||||
DocIdSetIterator approx2 = twoPhase2 == null ? s2.iterator() : twoPhase2.approximation;
|
||||
|
||||
int upTo = -1;
|
||||
float minScore = 0;
|
||||
float maxScore = 0;
|
||||
while (true) {
|
||||
int doc2 = s2.docID();
|
||||
boolean advance;
|
||||
int target;
|
||||
if (random.nextBoolean()) {
|
||||
advance = false;
|
||||
target = doc2 + 1;
|
||||
} else {
|
||||
advance = true;
|
||||
int delta = Math.min(1 + random.nextInt(512), DocIdSetIterator.NO_MORE_DOCS - doc2);
|
||||
target = s2.docID() + delta;
|
||||
}
|
||||
|
||||
if (target > upTo && random.nextBoolean()) {
|
||||
int delta = Math.min(random.nextInt(512), DocIdSetIterator.NO_MORE_DOCS - target);
|
||||
upTo = target + delta;
|
||||
int m = s2.advanceShallow(target);
|
||||
assertTrue(m >= target);
|
||||
maxScore = s2.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
if (advance) {
|
||||
doc2 = approx2.advance(target);
|
||||
} else {
|
||||
doc2 = approx2.nextDoc();
|
||||
}
|
||||
|
||||
int doc1;
|
||||
for (doc1 = approx1.advance(target); doc1 < doc2; doc1 = approx1.nextDoc()) {
|
||||
if (twoPhase1 == null || twoPhase1.matches()) {
|
||||
Assert.assertTrue(s1.score() < minScore);
|
||||
}
|
||||
}
|
||||
assertEquals(doc1, doc2);
|
||||
|
||||
if (doc2 == DocIdSetIterator.NO_MORE_DOCS) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (twoPhase2 == null || twoPhase2.matches()) {
|
||||
Assert.assertTrue(twoPhase1 == null || twoPhase1.matches());
|
||||
float score = s2.score();
|
||||
Assert.assertEquals(s1.score(), score);
|
||||
|
||||
if (doc2 > upTo) {
|
||||
upTo = s2.advanceShallow(doc2);
|
||||
Assert.assertTrue(upTo >= doc2);
|
||||
maxScore = s2.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
Assert.assertTrue(score <= maxScore);
|
||||
|
||||
if (score >= minScore && random.nextInt(10) == 0) {
|
||||
// On some scorers, changing the min score changes the way that docs are iterated
|
||||
minScore = score;
|
||||
s2.setMinCompetitiveScore(minScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -109,8 +109,13 @@ public class RandomApproximationQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
return scorer.maxScore();
|
||||
public int advanceShallow(int target) throws IOException {
|
||||
return scorer.advanceShallow(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return scorer.getMaxScore(upTo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -523,7 +523,7 @@ public class LTRScoringQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
@ -582,7 +582,7 @@ public class LTRScoringQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
@ -669,7 +669,7 @@ public class LTRScoringQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -328,7 +328,7 @@ public abstract class Feature extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return constScore;
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ public class FieldLengthFeature extends Feature {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ public class FieldValueFeature extends Feature {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public class OriginalScoreFeature extends Feature {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -288,7 +288,7 @@ public class SolrFeature extends Feature {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
@ -1481,7 +1481,7 @@ public class QueryComponent extends SearchComponent
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -486,7 +486,7 @@ class SpatialDistanceQuery extends ExtendedQueryBase implements PostFilter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -452,7 +452,7 @@ public class CollapsingQParserPlugin extends QParserPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
|
@ -307,7 +307,7 @@ public class GraphQuery extends Query {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return score;
|
||||
}
|
||||
|
||||
|
@ -458,7 +458,7 @@ public class TestRankQueryPlugin extends QParserPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public float maxScore() {
|
||||
public float getMaxScore(int upTo) throws IOException {
|
||||
return score;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user