You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by jb...@apache.org on 2023/06/26 21:21:43 UTC
[solr] branch branch_9x updated: SOLR-16827: Add min/max scaling to the reranker (#1692) (#1723)
This is an automated email from the ASF dual-hosted git repository.
jbernste pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new ce3b412ef66 SOLR-16827: Add min/max scaling to the reranker (#1692) (#1723)
ce3b412ef66 is described below
commit ce3b412ef664c5c5837042fc19cf3b8e2aa3240a
Author: Joel Bernstein <jb...@apache.org>
AuthorDate: Mon Jun 26 17:21:36 2023 -0400
SOLR-16827: Add min/max scaling to the reranker (#1692) (#1723)
---
.../apache/solr/search/AbstractReRankQuery.java | 27 +-
.../org/apache/solr/search/ReRankCollector.java | 56 ++-
.../apache/solr/search/ReRankQParserPlugin.java | 82 +++-
.../java/org/apache/solr/search/ReRankScaler.java | 470 ++++++++++++++++++
.../java/org/apache/solr/search/ReRankWeight.java | 25 +-
.../solr/search/TestReRankQParserPlugin.java | 534 +++++++++++++++++++++
.../query-guide/pages/query-re-ranking.adoc | 20 +
7 files changed, 1200 insertions(+), 14 deletions(-)
diff --git a/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java b/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
index f0ac6ed41e6..f2eb6de2c3b 100644
--- a/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
+++ b/solr/core/src/java/org/apache/solr/search/AbstractReRankQuery.java
@@ -38,6 +38,21 @@ public abstract class AbstractReRankQuery extends RankQuery {
protected final int reRankDocs;
protected final Rescorer reRankQueryRescorer;
protected Set<BytesRef> boostedPriority;
+ protected ReRankOperator reRankOperator;
+ protected ReRankScaler reRankScaler;
+
+ public AbstractReRankQuery(
+ Query mainQuery,
+ int reRankDocs,
+ Rescorer reRankQueryRescorer,
+ ReRankScaler reRankScaler,
+ ReRankOperator reRankOperator) {
+ this.mainQuery = mainQuery;
+ this.reRankDocs = reRankDocs;
+ this.reRankQueryRescorer = reRankQueryRescorer;
+ this.reRankScaler = reRankScaler;
+ this.reRankOperator = reRankOperator;
+ }
public AbstractReRankQuery(Query mainQuery, int reRankDocs, Rescorer reRankQueryRescorer) {
this.mainQuery = mainQuery;
@@ -71,7 +86,14 @@ public abstract class AbstractReRankQuery extends RankQuery {
}
return new ReRankCollector(
- reRankDocs, len, reRankQueryRescorer, cmd, searcher, boostedPriority);
+ reRankDocs,
+ len,
+ reRankQueryRescorer,
+ cmd,
+ searcher,
+ boostedPriority,
+ reRankScaler,
+ reRankOperator);
}
@Override
@@ -89,7 +111,8 @@ public abstract class AbstractReRankQuery extends RankQuery {
public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float boost)
throws IOException {
final Weight mainWeight = mainQuery.createWeight(searcher, scoreMode, boost);
- return new ReRankWeight(mainQuery, reRankQueryRescorer, searcher, mainWeight);
+ return new ReRankWeight(
+ mainQuery, reRankQueryRescorer, searcher, mainWeight, reRankScaler, reRankOperator);
}
@Override
diff --git a/solr/core/src/java/org/apache/solr/search/ReRankCollector.java b/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
index 47d4b5fda73..17f206de646 100644
--- a/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
+++ b/solr/core/src/java/org/apache/solr/search/ReRankCollector.java
@@ -51,6 +51,23 @@ public class ReRankCollector extends TopDocsCollector<ScoreDoc> {
private final Rescorer reRankQueryRescorer;
private final Sort sort;
private final Query query;
+ private ReRankScaler reRankScaler;
+ private ReRankOperator reRankOperator;
+
+ public ReRankCollector(
+ int reRankDocs,
+ int length,
+ Rescorer reRankQueryRescorer,
+ QueryCommand cmd,
+ IndexSearcher searcher,
+ Set<BytesRef> boostedPriority,
+ ReRankScaler reRankScaler,
+ ReRankOperator reRankOperator)
+ throws IOException {
+ this(reRankDocs, length, reRankQueryRescorer, cmd, searcher, boostedPriority);
+ this.reRankScaler = reRankScaler;
+ this.reRankOperator = reRankOperator;
+ }
public ReRankCollector(
int reRankDocs,
@@ -111,13 +128,22 @@ public class ReRankCollector extends TopDocsCollector<ScoreDoc> {
}
ScoreDoc[] mainScoreDocs = mainDocs.scoreDocs;
+ ScoreDoc[] mainScoreDocsClone =
+ (reRankScaler != null && reRankScaler.scaleScores())
+ ? deepCloneAndZeroOut(mainScoreDocs)
+ : null;
ScoreDoc[] reRankScoreDocs = new ScoreDoc[Math.min(mainScoreDocs.length, reRankDocs)];
System.arraycopy(mainScoreDocs, 0, reRankScoreDocs, 0, reRankScoreDocs.length);
mainDocs.scoreDocs = reRankScoreDocs;
+ // If we're scaling scores use the replace rescorer because we just want the re-rank score.
TopDocs rescoredDocs =
- reRankQueryRescorer.rescore(searcher, mainDocs, mainDocs.scoreDocs.length);
+ reRankScaler != null && reRankScaler.scaleScores()
+ ? reRankScaler
+ .getReplaceRescorer()
+ .rescore(searcher, mainDocs, mainDocs.scoreDocs.length)
+ : reRankQueryRescorer.rescore(searcher, mainDocs, mainDocs.scoreDocs.length);
// Lower howMany to return if we've collected fewer documents.
howMany = Math.min(howMany, mainScoreDocs.length);
@@ -140,6 +166,11 @@ public class ReRankCollector extends TopDocsCollector<ScoreDoc> {
}
if (howMany == rescoredDocs.scoreDocs.length) {
+ if (reRankScaler != null && reRankScaler.scaleScores()) {
+ rescoredDocs.scoreDocs =
+ reRankScaler.scaleScores(
+ mainScoreDocsClone, rescoredDocs.scoreDocs, reRankScoreDocs.length);
+ }
return rescoredDocs; // Just return the rescoredDocs
} else if (howMany > rescoredDocs.scoreDocs.length) {
// We need to return more then we've reRanked, so create the combined page.
@@ -153,9 +184,20 @@ public class ReRankCollector extends TopDocsCollector<ScoreDoc> {
0,
rescoredDocs.scoreDocs.length); // overlay the re-ranked docs.
rescoredDocs.scoreDocs = scoreDocs;
+ if (reRankScaler != null && reRankScaler.scaleScores()) {
+ rescoredDocs.scoreDocs =
+ reRankScaler.scaleScores(
+ mainScoreDocsClone, rescoredDocs.scoreDocs, reRankScoreDocs.length);
+ }
return rescoredDocs;
} else {
// We've rescored more then we need to return.
+
+ if (reRankScaler != null && reRankScaler.scaleScores()) {
+ rescoredDocs.scoreDocs =
+ reRankScaler.scaleScores(
+ mainScoreDocsClone, rescoredDocs.scoreDocs, rescoredDocs.scoreDocs.length);
+ }
ScoreDoc[] scoreDocs = new ScoreDoc[howMany];
System.arraycopy(rescoredDocs.scoreDocs, 0, scoreDocs, 0, howMany);
rescoredDocs.scoreDocs = scoreDocs;
@@ -166,6 +208,18 @@ public class ReRankCollector extends TopDocsCollector<ScoreDoc> {
}
}
+ private ScoreDoc[] deepCloneAndZeroOut(ScoreDoc[] scoreDocs) {
+ ScoreDoc[] scoreDocs1 = new ScoreDoc[scoreDocs.length];
+ for (int i = 0; i < scoreDocs.length; i++) {
+ ScoreDoc scoreDoc = scoreDocs[i];
+ if (scoreDoc != null) {
+ scoreDocs1[i] = new ScoreDoc(scoreDoc.doc, scoreDoc.score);
+ scoreDoc.score = 0f;
+ }
+ }
+ return scoreDocs1;
+ }
+
public static class BoostedComp implements Comparator<ScoreDoc> {
IntFloatHashMap boostedMap;
diff --git a/solr/core/src/java/org/apache/solr/search/ReRankQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/ReRankQParserPlugin.java
index 12204f2a196..2ae72837218 100644
--- a/solr/core/src/java/org/apache/solr/search/ReRankQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/ReRankQParserPlugin.java
@@ -21,6 +21,7 @@ import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryRescorer;
import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.request.SolrQueryRequest;
@@ -47,6 +48,9 @@ public class ReRankQParserPlugin extends QParserPlugin {
public static final String RERANK_OPERATOR = "reRankOperator";
public static final String RERANK_OPERATOR_DEFAULT = "add";
+ public static final String RERANK_SCALE = "reRankScale";
+ public static final String RERANK_MAIN_SCALE = "reRankMainScale";
+
@Override
public QParser createParser(
String query, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
@@ -78,7 +82,27 @@ public class ReRankQParserPlugin extends QParserPlugin {
ReRankOperator reRankOperator =
ReRankOperator.get(localParams.get(RERANK_OPERATOR, RERANK_OPERATOR_DEFAULT));
- return new ReRankQuery(reRankQuery, reRankDocs, reRankWeight, reRankOperator);
+ String mainScale = localParams.get(RERANK_MAIN_SCALE);
+ String reRankScale = localParams.get(RERANK_SCALE);
+ boolean debugQuery = params.getBool(CommonParams.DEBUG_QUERY, false);
+ double reRankScaleWeight = reRankWeight;
+
+ ReRankScaler reRankScaler =
+ new ReRankScaler(
+ mainScale,
+ reRankScale,
+ reRankScaleWeight,
+ reRankOperator,
+ new ReRankQueryRescorer(reRankQuery, 1, ReRankOperator.REPLACE),
+ debugQuery);
+
+ if (reRankScaler.scaleScores()) {
+ // Scaler applies the weighting instead of the rescorer
+ reRankWeight = 1;
+ }
+
+ return new ReRankQuery(
+ reRankQuery, reRankDocs, reRankWeight, reRankOperator, reRankScaler, debugQuery);
}
}
@@ -124,7 +148,7 @@ public class ReRankQParserPlugin extends QParserPlugin {
private static final class ReRankQuery extends AbstractReRankQuery {
private final Query reRankQuery;
private final double reRankWeight;
- private final ReRankOperator reRankOperator;
+ private final boolean debugQuery;
@Override
public int hashCode() {
@@ -133,7 +157,8 @@ public class ReRankQParserPlugin extends QParserPlugin {
+ reRankQuery.hashCode()
+ (int) reRankWeight
+ reRankDocs
- + reRankOperator.hashCode();
+ + reRankOperator.hashCode()
+ + reRankScaler.hashCode();
}
@Override
@@ -146,18 +171,26 @@ public class ReRankQParserPlugin extends QParserPlugin {
&& reRankQuery.equals(rrq.reRankQuery)
&& reRankWeight == rrq.reRankWeight
&& reRankDocs == rrq.reRankDocs
- && reRankOperator.equals(rrq.reRankOperator);
+ && reRankOperator.equals(rrq.reRankOperator)
+ && reRankScaler.equals(rrq.reRankScaler);
}
public ReRankQuery(
- Query reRankQuery, int reRankDocs, double reRankWeight, ReRankOperator reRankOperator) {
+ Query reRankQuery,
+ int reRankDocs,
+ double reRankWeight,
+ ReRankOperator reRankOperator,
+ ReRankScaler reRankScaler,
+ boolean debugQuery) {
super(
defaultQuery,
reRankDocs,
- new ReRankQueryRescorer(reRankQuery, reRankWeight, reRankOperator));
+ new ReRankQueryRescorer(reRankQuery, reRankWeight, reRankOperator),
+ reRankScaler,
+ reRankOperator);
this.reRankQuery = reRankQuery;
this.reRankWeight = reRankWeight;
- this.reRankOperator = reRankOperator;
+ this.debugQuery = debugQuery;
}
@Override
@@ -168,15 +201,46 @@ public class ReRankQParserPlugin extends QParserPlugin {
sb.append(" mainQuery='").append(mainQuery.toString()).append("' ");
sb.append(RERANK_QUERY).append("='").append(reRankQuery.toString()).append("' ");
sb.append(RERANK_DOCS).append('=').append(reRankDocs).append(' ');
- sb.append(RERANK_WEIGHT).append('=').append(reRankWeight).append(' ');
+ if (reRankScaler.scaleScores()) {
+ // The reRankScaler applies the weight
+ sb.append(RERANK_WEIGHT)
+ .append('=')
+ .append(reRankScaler.getReRankScaleWeight())
+ .append(' ');
+ } else {
+ sb.append(RERANK_WEIGHT).append('=').append(reRankWeight).append(' ');
+ }
+ if (reRankScaler.getReRankScalerExplain().getReRankScale() != null) {
+ sb.append(RERANK_SCALE)
+ .append('=')
+ .append(reRankScaler.getReRankScalerExplain().getReRankScale())
+ .append(' ');
+ }
+ if (reRankScaler.getReRankScalerExplain().getMainScale() != null) {
+ sb.append(RERANK_MAIN_SCALE)
+ .append('=')
+ .append(reRankScaler.getReRankScalerExplain().getMainScale())
+ .append(' ');
+ }
sb.append(RERANK_OPERATOR).append('=').append(reRankOperator.toLower()).append('}');
return sb.toString();
}
@Override
protected Query rewrite(Query rewrittenMainQuery) throws IOException {
- return new ReRankQuery(reRankQuery, reRankDocs, reRankWeight, reRankOperator)
+ return new ReRankQuery(
+ reRankQuery, reRankDocs, reRankWeight, reRankOperator, reRankScaler, debugQuery)
.wrap(rewrittenMainQuery);
}
+
+ @Override
+ public boolean getCache() {
+ if (reRankScaler.scaleScores() && debugQuery) {
+ // Caching breaks explain when reRankScaling is used.
+ return false;
+ } else {
+ return super.getCache();
+ }
+ }
}
}
diff --git a/solr/core/src/java/org/apache/solr/search/ReRankScaler.java b/solr/core/src/java/org/apache/solr/search/ReRankScaler.java
new file mode 100644
index 00000000000..686faa4ae1d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/search/ReRankScaler.java
@@ -0,0 +1,470 @@
+/*
+ * 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.solr.search;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.QueryRescorer;
+import org.apache.lucene.search.ScoreDoc;
+
+public class ReRankScaler {
+
+ protected int mainQueryMin = -1;
+ protected int mainQueryMax = -1;
+ protected int reRankQueryMin = -1;
+ protected int reRankQueryMax = -1;
+ protected boolean debugQuery;
+ protected ReRankOperator reRankOperator;
+ protected ReRankScalerExplain reRankScalerExplain;
+ private QueryRescorer replaceRescorer;
+ private Set<Integer> reRankSet;
+ private double reRankScaleWeight;
+
+ public ReRankScaler(
+ String mainScale,
+ String reRankScale,
+ double reRankScaleWeight,
+ ReRankOperator reRankOperator,
+ QueryRescorer replaceRescorer,
+ boolean debugQuery)
+ throws SyntaxError {
+
+ this.reRankScaleWeight = reRankScaleWeight;
+ this.debugQuery = debugQuery;
+ this.reRankScalerExplain = new ReRankScalerExplain(mainScale, reRankScale);
+ this.replaceRescorer = replaceRescorer;
+ if (reRankOperator != ReRankOperator.ADD
+ && reRankOperator != ReRankOperator.MULTIPLY
+ && reRankOperator != ReRankOperator.REPLACE) {
+ throw new SyntaxError("ReRank scaling only supports ADD, Multiply and Replace operators");
+ } else {
+ this.reRankOperator = reRankOperator;
+ }
+
+ if (reRankScalerExplain.getMainScale() != null) {
+ String[] mainMinMax = reRankScalerExplain.getMainScale().split("-");
+ this.mainQueryMin = Integer.parseInt(mainMinMax[0]);
+ this.mainQueryMax = Integer.parseInt(mainMinMax[1]);
+ }
+
+ if (reRankScalerExplain.getReRankScale() != null) {
+ String[] reRankMinMax = reRankScalerExplain.getReRankScale().split("-");
+ this.reRankQueryMin = Integer.parseInt(reRankMinMax[0]);
+ this.reRankQueryMax = Integer.parseInt(reRankMinMax[1]);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Integer.hashCode(mainQueryMax)
+ + Integer.hashCode(mainQueryMin)
+ + Integer.hashCode(reRankQueryMin)
+ + Integer.hashCode(reRankQueryMax)
+ + reRankOperator.toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ReRankScaler) {
+ ReRankScaler _reRankScaler = (ReRankScaler) o;
+ if (mainQueryMin == _reRankScaler.mainQueryMin
+ && mainQueryMax == _reRankScaler.mainQueryMax
+ && reRankQueryMin == _reRankScaler.reRankQueryMin
+ && reRankQueryMax == _reRankScaler.reRankQueryMax
+ && reRankOperator.equals(_reRankScaler.reRankOperator)) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ public QueryRescorer getReplaceRescorer() {
+ return replaceRescorer;
+ }
+
+ public int getMainQueryMin() {
+ return mainQueryMin;
+ }
+
+ public int getMainQueryMax() {
+ return mainQueryMax;
+ }
+
+ public int getReRankQueryMin() {
+ return reRankQueryMin;
+ }
+
+ public int getReRankQueryMax() {
+ return reRankQueryMax;
+ }
+
+ public ReRankScalerExplain getReRankScalerExplain() {
+ return this.reRankScalerExplain;
+ }
+
+ public double getReRankScaleWeight() {
+ return this.reRankScaleWeight;
+ }
+
+ public boolean scaleScores() {
+ if (scaleMainScores() || scaleReRankScores()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean scaleMainScores() {
+ if (mainQueryMin > -1 && mainQueryMax > -1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean scaleReRankScores() {
+ if (reRankQueryMin > -1 && reRankQueryMax > -1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public ScoreDoc[] scaleScores(ScoreDoc[] originalDocs, ScoreDoc[] rescoredDocs, int howMany) {
+
+ Map<Integer, Float> originalScoreMap = new HashMap<>();
+ Map<Integer, Float> scaledOriginalScoreMap = null;
+ Map<Integer, Float> scaledRescoredMap = null;
+ Map<Integer, Float> rescoredMap = new HashMap<>();
+
+ for (ScoreDoc scoreDoc : originalDocs) {
+ originalScoreMap.put(scoreDoc.doc, scoreDoc.score);
+ }
+
+ if (scaleMainScores()) {
+ MinMaxExplain mainExplain = getMinMaxExplain(mainQueryMin, mainQueryMax, originalScoreMap);
+ scaledOriginalScoreMap = minMaxScaleScores(originalScoreMap, mainExplain);
+ reRankScalerExplain.setMainScaleExplain(mainExplain);
+ } else {
+ scaledOriginalScoreMap = originalScoreMap;
+ }
+
+ this.reRankSet = debugQuery ? new HashSet<>() : null;
+
+ for (int i = 0; i < howMany; i++) {
+ ScoreDoc rescoredDoc = rescoredDocs[i];
+ int doc = rescoredDoc.doc;
+ if (debugQuery) {
+ reRankSet.add(doc);
+ }
+ float score = rescoredDoc.score;
+ if (score > 0) {
+ rescoredMap.put(doc, score);
+ }
+ }
+
+ if (scaleReRankScores()) {
+ MinMaxExplain reRankExplain = getMinMaxExplain(reRankQueryMin, reRankQueryMax, rescoredMap);
+ scaledRescoredMap = minMaxScaleScores(rescoredMap, reRankExplain);
+ reRankScalerExplain.setReRankScaleExplain(reRankExplain);
+ } else {
+ scaledRescoredMap = rescoredMap;
+ }
+
+ ScoreDoc[] scaledScoreDocs = new ScoreDoc[originalDocs.length];
+ int index = 0;
+ for (Map.Entry<Integer, Float> entry : scaledOriginalScoreMap.entrySet()) {
+ int doc = entry.getKey();
+ float scaledScore = entry.getValue();
+ ScoreDoc scoreDoc = null;
+ if (scaledRescoredMap.containsKey(doc)) {
+ scoreDoc =
+ new ScoreDoc(
+ doc,
+ combineScores(
+ scaledScore, scaledRescoredMap.get(doc), reRankScaleWeight, reRankOperator));
+ } else {
+ scoreDoc = new ScoreDoc(doc, scaledScore);
+ }
+
+ scaledScoreDocs[index++] = scoreDoc;
+ }
+
+ Comparator<ScoreDoc> sortDocComparator =
+ new Comparator<ScoreDoc>() {
+ @Override
+ public int compare(ScoreDoc a, ScoreDoc b) {
+ // Sort by score descending, then docID ascending:
+ if (a.score > b.score) {
+ return -1;
+ } else if (a.score < b.score) {
+ return 1;
+ } else {
+ // This subtraction can't overflow int
+ // because docIDs are >= 0:
+ return a.doc - b.doc;
+ }
+ }
+ };
+
+ Arrays.sort(scaledScoreDocs, sortDocComparator);
+ return scaledScoreDocs;
+ }
+
+ public static float combineScores(
+ float orginalScore,
+ float reRankScore,
+ double reRankScaleWeight,
+ ReRankOperator reRankOperator) {
+ switch (reRankOperator) {
+ case ADD:
+ return (float) (orginalScore + reRankScaleWeight * reRankScore);
+ case REPLACE:
+ return (float) (reRankScaleWeight * reRankScore);
+ case MULTIPLY:
+ return (float) (orginalScore * reRankScaleWeight * reRankScore);
+ default:
+ return -1;
+ }
+ }
+
+ public static final class ReRankScalerExplain {
+ private MinMaxExplain mainScaleExplain;
+ private MinMaxExplain reRankScaleExplain;
+ private String mainScale;
+ private String reRankScale;
+
+ public ReRankScalerExplain(String mainScale, String reRankScale) {
+ this.mainScale = mainScale;
+ this.reRankScale = reRankScale;
+ }
+
+ public MinMaxExplain getMainScaleExplain() {
+ return mainScaleExplain;
+ }
+
+ public MinMaxExplain getReRankScaleExplain() {
+ return reRankScaleExplain;
+ }
+
+ public void setMainScaleExplain(MinMaxExplain mainScaleExplain) {
+ this.mainScaleExplain = mainScaleExplain;
+ }
+
+ public void setReRankScaleExplain(MinMaxExplain reRankScaleExplain) {
+ this.reRankScaleExplain = reRankScaleExplain;
+ }
+
+ public boolean reRankScale() {
+ return (getMainScale() != null || getReRankScale() != null);
+ }
+
+ public String getMainScale() {
+ return this.mainScale;
+ }
+
+ public String getReRankScale() {
+ return this.reRankScale;
+ }
+
+ public Explanation explain() {
+ if (getMainScale() != null && getReRankScale() != null) {
+ return Explanation.noMatch(
+ "ReRankScaler Explain",
+ mainScaleExplain.explain("first pass scale"),
+ reRankScaleExplain.explain("second pass scale"));
+ } else if (getMainScale() != null) {
+ return mainScaleExplain.explain("first pass scale");
+ } else if (getReRankScale() != null) {
+ return reRankScaleExplain.explain("second pass scale");
+ }
+ return null;
+ }
+ }
+
+ public static final class MinMaxExplain {
+ public final float scaleMin;
+ public final float scaleMax;
+ public final float localMin;
+ public final float localMax;
+ private float diff;
+
+ public MinMaxExplain(float scaleMin, float scaleMax, float localMin, float localMax) {
+ this.scaleMin = scaleMin;
+ this.scaleMax = scaleMax;
+ this.localMin = localMin;
+ this.localMax = localMax;
+ this.diff = scaleMax - scaleMin;
+ }
+
+ public Explanation explain(String message) {
+ return Explanation.noMatch(
+ message,
+ Explanation.match(localMin, "min score"),
+ Explanation.match(localMax, "max score"));
+ }
+
+ public float scale(float score) {
+ if (localMin == localMax) {
+ return (scaleMin + scaleMax) / 2;
+ } else {
+ float scaledScore = (score - localMin) / (localMax - localMin);
+ if (scaleMin != 0 || scaleMax != 1) {
+ scaledScore = (diff * scaledScore) + scaleMin;
+ return scaledScore;
+ } else {
+ return scaledScore;
+ }
+ }
+ }
+ }
+
+ public Explanation explain(
+ int doc, Explanation mainQueryExplain, Explanation reRankQueryExplain) {
+ float reRankScore = reRankQueryExplain.getDetails()[1].getValue().floatValue();
+ float mainScore = mainQueryExplain.getValue().floatValue();
+ if (reRankSet.contains(doc)) {
+ if (scaleMainScores() && scaleReRankScores()) {
+ if (reRankScore > 0) {
+ MinMaxExplain mainScaleExplain = reRankScalerExplain.getMainScaleExplain();
+ MinMaxExplain reRankScaleExplain = reRankScalerExplain.getReRankScaleExplain();
+ float scaledMainScore = mainScaleExplain.scale(mainScore);
+ float scaledReRankScore = reRankScaleExplain.scale(reRankScore);
+ float combinedScaleScore =
+ combineScores(scaledMainScore, scaledReRankScore, reRankScaleWeight, reRankOperator);
+
+ return Explanation.match(
+ combinedScaleScore,
+ "combined scaled first and second pass score",
+ Explanation.match(
+ scaledMainScore,
+ "first pass score scaled between: " + reRankScalerExplain.getMainScale(),
+ reRankQueryExplain.getDetails()[0],
+ Explanation.match(mainScaleExplain.localMin, "min first pass score"),
+ Explanation.match(mainScaleExplain.localMax, "max first pass score")),
+ Explanation.match(
+ scaledReRankScore,
+ "second pass score scaled between: " + reRankScalerExplain.getReRankScale(),
+ reRankQueryExplain.getDetails()[1],
+ Explanation.match(reRankScaleExplain.localMin, "min second pass score"),
+ Explanation.match(reRankScaleExplain.localMax, "max second pass score")),
+ Explanation.match(reRankScaleWeight, "rerank weight"));
+
+ } else {
+ MinMaxExplain mainScaleExplain = reRankScalerExplain.getMainScaleExplain();
+ float scaledMainScore = mainScaleExplain.scale(mainScore);
+ return Explanation.match(
+ scaledMainScore,
+ "combined scaled first and second pass score",
+ Explanation.match(
+ scaledMainScore,
+ "scaled first pass score",
+ reRankQueryExplain.getDetails()[0],
+ Explanation.match(mainScaleExplain.localMin, "min first pass score"),
+ Explanation.match(mainScaleExplain.localMax, "max first pass score")),
+ reRankQueryExplain.getDetails()[1]);
+ }
+ } else if (scaleMainScores() && !scaleReRankScores()) {
+ MinMaxExplain mainScaleExplain = reRankScalerExplain.getMainScaleExplain();
+ float scaledMainScore = mainScaleExplain.scale(mainScore);
+ float combinedScaleScore =
+ combineScores(scaledMainScore, reRankScore, reRankScaleWeight, reRankOperator);
+ return Explanation.match(
+ combinedScaleScore,
+ "combined scaled first and unscaled second pass score ",
+ Explanation.match(
+ scaledMainScore,
+ "first pass score scaled between: " + reRankScalerExplain.getMainScale(),
+ reRankQueryExplain.getDetails()[0],
+ Explanation.match(mainScaleExplain.localMin, "min first pass score"),
+ Explanation.match(mainScaleExplain.localMax, "max first pass score")),
+ reRankQueryExplain.getDetails()[1],
+ Explanation.match(reRankScaleWeight, "rerank weight"));
+
+ } else if (!scaleMainScores() && scaleReRankScores()) {
+ if (reRankScore > 0) {
+ MinMaxExplain reRankScaleExplain = reRankScalerExplain.getReRankScaleExplain();
+ float scaledReRankScore = reRankScaleExplain.scale(reRankScore);
+ float combinedScaleScore =
+ combineScores(mainScore, scaledReRankScore, reRankScaleWeight, reRankOperator);
+ return Explanation.match(
+ combinedScaleScore,
+ "combined unscaled first and scaled second pass score ",
+ reRankQueryExplain.getDetails()[0],
+ Explanation.match(
+ scaledReRankScore,
+ "second pass score scaled between:" + reRankScalerExplain.reRankScale,
+ reRankQueryExplain.getDetails()[1],
+ Explanation.match(reRankScaleExplain.localMin, "min second pass score"),
+ Explanation.match(reRankScaleExplain.localMax, "max sceond pass score")),
+ Explanation.match(reRankScaleWeight, "rerank weight"));
+ } else {
+ return null;
+ }
+ } else {
+ // If we get here nothing has been scaled so return null
+ return null;
+ }
+ } else {
+ if (scaleMainScores()) {
+ MinMaxExplain mainScaleExplain = reRankScalerExplain.getMainScaleExplain();
+ float scaledMainScore = mainScaleExplain.scale(mainScore);
+ return Explanation.match(
+ scaledMainScore,
+ "scaled main query score between: " + reRankScalerExplain.mainScale,
+ mainQueryExplain,
+ Explanation.match(mainScaleExplain.localMin, "min main query score"),
+ Explanation.match(mainScaleExplain.localMax, "max main query score"));
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public static MinMaxExplain getMinMaxExplain(
+ float scaleMin, float scaleMax, Map<Integer, Float> docScoreMap) {
+ float localMin = Float.MAX_VALUE;
+ float localMax = Float.MIN_VALUE;
+
+ for (float score : docScoreMap.values()) {
+ localMin = Math.min(localMin, score);
+ localMax = Math.max(localMax, score);
+ }
+ return new MinMaxExplain(scaleMin, scaleMax, localMin, localMax);
+ }
+
+ public static Map<Integer, Float> minMaxScaleScores(
+ Map<Integer, Float> docScoreMap, MinMaxExplain explain) {
+
+ Map<Integer, Float> scaledScores = new HashMap<>();
+ for (Map.Entry<Integer, Float> entry : docScoreMap.entrySet()) {
+ int doc = entry.getKey();
+ float score = entry.getValue();
+ scaledScores.put(doc, explain.scale(score));
+ }
+
+ return scaledScores;
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/search/ReRankWeight.java b/solr/core/src/java/org/apache/solr/search/ReRankWeight.java
index 1d74b6e8bd0..594c285b5b1 100644
--- a/solr/core/src/java/org/apache/solr/search/ReRankWeight.java
+++ b/solr/core/src/java/org/apache/solr/search/ReRankWeight.java
@@ -30,18 +30,39 @@ public class ReRankWeight extends FilterWeight {
private final IndexSearcher searcher;
private final Rescorer reRankQueryRescorer;
+ private final ReRankScaler reRankScaler;
+ private final ReRankOperator reRankOperator;
public ReRankWeight(
- Query mainQuery, Rescorer reRankQueryRescorer, IndexSearcher searcher, Weight mainWeight)
+ Query mainQuery,
+ Rescorer reRankQueryRescorer,
+ IndexSearcher searcher,
+ Weight mainWeight,
+ ReRankScaler reRankScaler,
+ ReRankOperator reRankOperator)
throws IOException {
super(mainQuery, mainWeight);
this.searcher = searcher;
this.reRankQueryRescorer = reRankQueryRescorer;
+ this.reRankScaler = reRankScaler;
+ this.reRankOperator = reRankOperator;
}
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
final Explanation mainExplain = in.explain(context, doc);
- return reRankQueryRescorer.explain(searcher, mainExplain, context.docBase + doc);
+ final Explanation reRankExplain =
+ reRankQueryRescorer.explain(searcher, mainExplain, context.docBase + doc);
+ if (reRankScaler != null && reRankScaler.scaleScores()) {
+ Explanation reScaleExplain =
+ reRankScaler.explain(context.docBase + doc, mainExplain, reRankExplain);
+ if (reScaleExplain != null) {
+ // Can be null if only reRankScore is scaled and is not a reRank match
+ return reScaleExplain;
+ } else {
+ return reRankExplain;
+ }
+ }
+ return reRankExplain;
}
}
diff --git a/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java b/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java
index b25d725c0a7..f551ba82fca 100644
--- a/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java
+++ b/solr/core/src/test/org/apache/solr/search/TestReRankQParserPlugin.java
@@ -16,6 +16,7 @@
*/
package org.apache.solr.search;
+import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
@@ -1076,4 +1077,537 @@ public class TestReRankQParserPlugin extends SolrTestCaseJ4 {
"//result/doc[9]/str[@name='id'][.='7']",
"//result/doc[10]/str[@name='id'][.='8']");
}
+
+ @Test
+ public void testReRankScaler() throws Exception {
+
+ ReRankScaler reRankScaler = new ReRankScaler("0-1", "5-100", 1, ReRankOperator.ADD, null, true);
+ assertTrue(reRankScaler.scaleReRankScores());
+ assertTrue(reRankScaler.scaleMainScores());
+ assertEquals(reRankScaler.getMainQueryMin(), 0);
+ assertEquals(reRankScaler.getMainQueryMax(), 1);
+ assertEquals(reRankScaler.getReRankQueryMin(), 5);
+ assertEquals(reRankScaler.getReRankQueryMax(), 100);
+
+ Map<Integer, Float> scores = new HashMap<>();
+ scores.put(1, 180.25f);
+ scores.put(2, 90.125f);
+ scores.put(3, (180.25f + 90.125f) / 2); // halfway
+ ReRankScaler.MinMaxExplain minMaxExplain = ReRankScaler.getMinMaxExplain(0, 1, scores);
+ Map<Integer, Float> scaled = ReRankScaler.minMaxScaleScores(scores, minMaxExplain);
+ assertEquals(scaled.size(), 3);
+ assertTrue(scaled.containsKey(1));
+ assertTrue(scaled.containsKey(2));
+ assertTrue(scaled.containsKey(3));
+ float scaled1 = scaled.get(1);
+ float scaled2 = scaled.get(2);
+ float scaled3 = scaled.get(3);
+ assertEquals(scaled1, 1.0f, 0);
+ assertEquals(scaled2, 0.0f, 0);
+ // scaled3 should be halfway between scaled1 and scaled2
+ assertEquals((scaled1 + scaled2) / 2, scaled3, 0);
+ minMaxExplain = ReRankScaler.getMinMaxExplain(50, 100, scores);
+ scaled = ReRankScaler.minMaxScaleScores(scores, minMaxExplain);
+ scaled1 = scaled.get(1);
+ scaled2 = scaled.get(2);
+ scaled3 = scaled.get(3);
+ assertEquals(scaled1, 100.0f, 0);
+ assertEquals(scaled2, 50.0f, 0);
+ // scaled3 should be halfway between scaled1 and scaled2
+ assertEquals((scaled1 + scaled2) / 2, scaled3, 0);
+
+ scores.put(1, 10f);
+ scores.put(2, 10f);
+ scores.put(3, 10f);
+ minMaxExplain = ReRankScaler.getMinMaxExplain(0, 1, scores);
+
+ scaled = ReRankScaler.minMaxScaleScores(scores, minMaxExplain);
+ scaled1 = scaled.get(1);
+ scaled2 = scaled.get(2);
+ scaled3 = scaled.get(3);
+ assertEquals(scaled1, .5f, 0);
+ assertEquals(scaled2, .5f, 0);
+ assertEquals(scaled3, .5f, 0);
+ }
+
+ @Test
+ public void testReRankScaleQueries() throws Exception {
+
+ assertU(delQ("*:*"));
+ assertU(commit());
+
+ String[] doc = {
+ "id", "1", "term_t", "YYYY", "group_s", "group1", "test_ti", "5", "test_tl", "10", "test_tf",
+ "2000"
+ };
+ assertU(adoc(doc));
+ assertU(commit());
+ String[] doc1 = {
+ "id",
+ "2",
+ "term_t",
+ "YYYY YYYY",
+ "group_s",
+ "group1",
+ "test_ti",
+ "50",
+ "test_tl",
+ "100",
+ "test_tf",
+ "200"
+ };
+ assertU(adoc(doc1));
+
+ String[] doc2 = {
+ "id", "3", "term_t", "YYYY YYYY YYYY", "test_ti", "5000", "test_tl", "100", "test_tf", "200"
+ };
+ assertU(adoc(doc2));
+ assertU(commit());
+ String[] doc3 = {
+ "id",
+ "4",
+ "term_t",
+ "YYYY YYYY YYYY YYYY",
+ "test_ti",
+ "500",
+ "test_tl",
+ "1000",
+ "test_tf",
+ "2000"
+ };
+ assertU(adoc(doc3));
+
+ String[] doc4 = {
+ "id",
+ "5",
+ "term_t",
+ "YYYY YYYY YYYY YYYY YYYY",
+ "group_s",
+ "group2",
+ "test_ti",
+ "4",
+ "test_tl",
+ "10",
+ "test_tf",
+ "2000"
+ };
+ assertU(adoc(doc4));
+ assertU(commit());
+ String[] doc5 = {
+ "id",
+ "6",
+ "term_t",
+ "YYYY YYYY YYYY YYYY YYYY YYYY",
+ "group_s",
+ "group2",
+ "test_ti",
+ "10",
+ "test_tl",
+ "100",
+ "test_tf",
+ "200"
+ };
+ assertU(adoc(doc5));
+ assertU(commit());
+
+ ModifiableSolrParams params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=200}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}*:*");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+ assertQ(
+ req(params),
+ "*[count(//doc)=6]",
+ "//result/doc[1]/str[@name='id'][.='3']",
+ "//result/doc[1]/float[@name='score'][.='37.70526']",
+ "//result/doc[2]/str[@name='id'][.='6']",
+ "//result/doc[2]/float[@name='score'][.='30.012009']",
+ "//result/doc[3]/str[@name='id'][.='4']",
+ "//result/doc[3]/float[@name='score'][.='29.82389']",
+ "//result/doc[4]/str[@name='id'][.='5']",
+ "//result/doc[4]/float[@name='score'][.='29.527113']",
+ "//result/doc[5]/str[@name='id'][.='2']",
+ "//result/doc[5]/float[@name='score'][.='25.665672']",
+ "//result/doc[6]/str[@name='id'][.='1']",
+ "//result/doc[6]/float[@name='score'][.='20.002003']");
+
+ // Test with fewer rows then matching docs.
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=200}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}*:*");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "4");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+ assertQ(
+ req(params),
+ "*[count(//doc)=4]",
+ "//result/doc[1]/str[@name='id'][.='3']",
+ "//result/doc[1]/float[@name='score'][.='37.70526']",
+ "//result/doc[2]/str[@name='id'][.='6']",
+ "//result/doc[2]/float[@name='score'][.='30.012009']",
+ "//result/doc[3]/str[@name='id'][.='4']",
+ "//result/doc[3]/float[@name='score'][.='29.82389']",
+ "//result/doc[4]/str[@name='id'][.='5']",
+ "//result/doc[4]/float[@name='score'][.='29.527113']");
+
+ // Test no-rerank hits.
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=200}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}BBBBBBBB"); // No hit re-rank.
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+
+ assertQ(
+ req(params),
+ "*[count(//doc)=6]",
+ "//result/doc[1]/str[@name='id'][.='6']",
+ "//result/doc[1]/float[@name='score'][.='20.0']",
+ "//result/doc[2]/str[@name='id'][.='5']",
+ "//result/doc[2]/float[@name='score'][.='19.527113']",
+ "//result/doc[3]/str[@name='id'][.='4']",
+ "//result/doc[3]/float[@name='score'][.='18.831097']",
+ "//result/doc[4]/str[@name='id'][.='3']",
+ "//result/doc[4]/float[@name='score'][.='17.705261']",
+ "//result/doc[5]/str[@name='id'][.='2']",
+ "//result/doc[5]/float[@name='score'][.='15.5736']",
+ "//result/doc[6]/str[@name='id'][.='1']",
+ "//result/doc[6]/float[@name='score'][.='10.0']");
+
+ // Test reRank only top 4
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=4}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}*:*");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ assertQ(
+ req(params),
+ "*[count(//doc)=6]",
+ "//result/doc[1]/str[@name='id'][.='3']",
+ "//result/doc[1]/float[@name='score'][.='37.70526']",
+ "//result/doc[2]/str[@name='id'][.='6']",
+ "//result/doc[2]/float[@name='score'][.='30.012009']",
+ "//result/doc[3]/str[@name='id'][.='4']",
+ "//result/doc[3]/float[@name='score'][.='29.82389']",
+ "//result/doc[4]/str[@name='id'][.='5']",
+ "//result/doc[4]/float[@name='score'][.='29.527113']",
+ "//result/doc[5]/str[@name='id'][.='2']",
+ "//result/doc[5]/float[@name='score'][.='15.5736']",
+ "//result/doc[6]/str[@name='id'][.='1']",
+ "//result/doc[6]/float[@name='score'][.='10.0']");
+
+ // Test reRank more than found
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=4}");
+ params.add("q", "term_t:YYYY");
+ params.add("fq", "id:(4 OR 5)");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}*:*");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ assertQ(
+ req(params),
+ "*[count(//doc)=2]",
+ "//result/doc[1]/str[@name='id'][.='4']",
+ "//result/doc[1]/float[@name='score'][.='30.0']",
+ "//result/doc[2]/str[@name='id'][.='5']",
+ "//result/doc[2]/float[@name='score'][.='30.0']");
+
+ // Test reRank more than found
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=4}");
+ params.add("q", "term_t:YYYY");
+ params.add("fq", "id:(4 OR 5)");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}*:*");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+ assertQ(
+ req(params),
+ "*[count(//doc)=2]",
+ "//result/doc[1]/str[@name='id'][.='4']",
+ "//result/doc[1]/float[@name='score'][.='30.0']",
+ "//result/doc[2]/str[@name='id'][.='5']",
+ "//result/doc[2]/float[@name='score'][.='30.0']");
+
+ String explainResponse = JQ(req(params));
+ assertTrue(explainResponse.contains("30.0 = combined scaled first and second pass score"));
+
+ assertTrue(explainResponse.contains("10.0 = first pass score scaled between: 10-20"));
+ assertTrue(explainResponse.contains("20.0 = second pass score scaled between: 10-20"));
+
+ assertTrue(explainResponse.contains("20.0 = first pass score scaled between: 10-20"));
+
+ assertTrue(explainResponse.contains("10.0 = second pass score scaled between: 10-20"));
+
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=4}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}*:*");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+ assertQ(
+ req(params),
+ "*[count(//doc)=6]",
+ "//result/doc[1]/str[@name='id'][.='3']",
+ "//result/doc[1]/float[@name='score'][.='5018.705']",
+ "//result/doc[2]/str[@name='id'][.='4']",
+ "//result/doc[2]/float[@name='score'][.='519.8311']",
+ "//result/doc[3]/str[@name='id'][.='6']",
+ "//result/doc[3]/float[@name='score'][.='31.0']",
+ "//result/doc[4]/str[@name='id'][.='5']",
+ "//result/doc[4]/float[@name='score'][.='24.527113']",
+ "//result/doc[5]/str[@name='id'][.='2']",
+ "//result/doc[5]/float[@name='score'][.='15.5736']",
+ "//result/doc[6]/str[@name='id'][.='1']",
+ "//result/doc[6]/float[@name='score'][.='10.0']");
+
+ explainResponse = JQ(req(params));
+ assertTrue(
+ explainResponse.contains(
+ "5018.705 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains(
+ "519.8311 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains("31.0 = combined scaled first and unscaled second pass score "));
+ assertTrue(
+ explainResponse.contains(
+ "24.527113 = combined scaled first and unscaled second pass score"));
+
+ assertTrue(explainResponse.contains("15.5736 = scaled main query score between: 10-20"));
+
+ assertTrue(explainResponse.contains("10.0 = scaled main query score between: 10-20"));
+
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_WEIGHT
+ + "=1 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=4}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}id:(3 OR 4 OR 6 OR 5)");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+ assertQ(
+ req(params),
+ "*[count(//doc)=6]",
+ "//result/doc[1]/str[@name='id'][.='3']",
+ "//result/doc[1]/float[@name='score'][.='5018.4053']",
+ "//result/doc[2]/str[@name='id'][.='4']",
+ "//result/doc[2]/float[@name='score'][.='519.5313']",
+ "//result/doc[3]/str[@name='id'][.='6']",
+ "//result/doc[3]/float[@name='score'][.='30.700203']",
+ "//result/doc[4]/str[@name='id'][.='5']",
+ "//result/doc[4]/float[@name='score'][.='24.227316']",
+ "//result/doc[5]/str[@name='id'][.='2']",
+ "//result/doc[5]/float[@name='score'][.='15.5736']",
+ "//result/doc[6]/str[@name='id'][.='1']",
+ "//result/doc[6]/float[@name='score'][.='10.0']");
+
+ assertTrue(
+ explainResponse.contains(
+ "5018.705 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains(
+ "519.8311 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains("31.0 = combined scaled first and unscaled second pass score "));
+ assertTrue(
+ explainResponse.contains(
+ "24.527113 = combined scaled first and unscaled second pass score"));
+
+ assertTrue(explainResponse.contains("15.5736 = scaled main query score between: 10-20"));
+
+ assertTrue(explainResponse.contains("10.0 = scaled main query score between: 10-20"));
+
+ // Use default reRankWeight of 2
+ params = new ModifiableSolrParams();
+ params.add(
+ "rq",
+ "{!"
+ + ReRankQParserPlugin.NAME
+ + " "
+ + ReRankQParserPlugin.RERANK_MAIN_SCALE
+ + "=10-20 "
+ + ReRankQParserPlugin.RERANK_QUERY
+ + "=$rqq "
+ + ReRankQParserPlugin.RERANK_DOCS
+ + "=4}");
+ params.add("q", "term_t:YYYY");
+ params.add("fl", "id,score");
+ params.add("rqq", "{!edismax bf=$bff}id:(3 OR 4 OR 6 OR 5)");
+ params.add("bff", "field(test_ti)");
+ params.add("start", "0");
+ params.add("rows", "6");
+ params.add("df", "text");
+ params.add("debugQuery", "true");
+ assertQ(
+ req(params),
+ "*[count(//doc)=6]",
+ "//result/doc[1]/str[@name='id'][.='3']",
+ "//result/doc[1]/float[@name='score'][.='10019.105']",
+ "//result/doc[2]/str[@name='id'][.='4']",
+ "//result/doc[2]/float[@name='score'][.='1020.2315']",
+ "//result/doc[3]/str[@name='id'][.='6']",
+ "//result/doc[3]/float[@name='score'][.='41.400406']",
+ "//result/doc[4]/str[@name='id'][.='5']",
+ "//result/doc[4]/float[@name='score'][.='28.927517']",
+ "//result/doc[5]/str[@name='id'][.='2']",
+ "//result/doc[5]/float[@name='score'][.='15.5736']",
+ "//result/doc[6]/str[@name='id'][.='1']",
+ "//result/doc[6]/float[@name='score'][.='10.0']");
+
+ explainResponse = JQ(req(params));
+ assertTrue(
+ explainResponse.contains(
+ "10019.105 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains(
+ "1020.2315 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains(
+ "41.400406 = combined scaled first and unscaled second pass score"));
+ assertTrue(
+ explainResponse.contains(
+ "28.927517 = combined scaled first and unscaled second pass score"));
+
+ assertTrue(explainResponse.contains("15.5736 = scaled main query score between: 10-20"));
+
+ assertTrue(explainResponse.contains("10.0 = scaled main query score between: 10-20"));
+ }
}
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/query-re-ranking.adoc b/solr/solr-ref-guide/modules/query-guide/pages/query-re-ranking.adoc
index fe32836c458..d1eb28e302e 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/query-re-ranking.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/query-re-ranking.adoc
@@ -69,6 +69,26 @@ This number will be treated as a minimum, and may be increased internally automa
+
A multiplicative factor that will be applied to the score from the reRankQuery for each of the top matching documents, before that score is combined with the original score.
+`reRankScale`::
++
+[%autowidth,frame=none]
+|===
+|Optional |Default: `none`
+|===
++
+Scales the rerank scores between min and max values. The format of this parameter value is `min-max` where
+min and max are positive integers. Example `reRankScale=0-1` rescales the rerank scores between 0 and 1.
+
+`reRankMainScale`::
++
+[%autowidth,frame=none]
+|===
+|Optional |Default: `none`
+|===
++
+Scales the main query scores between min and max values. The format of this parameter value is `min-max` where
+min and max are positive integers. Example `reRankMainScale=0-1` rescales the main query scores between 0 and 1.
+
`reRankOperator`::
+
[%autowidth,frame=none]