Fix inconsistent usage of ScriptScoreFunction in FiltersFunctionScoreQuery

This commit fixes inconsistencies in `function_score` and `filters_function_score`
using scripts, see issue #3464

The method 'ScoreFunction.factor(docId)' is removed completely, since the name
suggests that this method actually computes a factor which was not the case.
Multiplying the computed score is now handled by 'FiltersFunctionScoreQuery'
and 'FunctionScoreQuery' and not implicitely performed in
'ScoreFunction.factor(docId, subQueryScore)' as was the case for 'BoostScoreFunction'
and 'DecayScoreFunctions'.

This commit also fixes the explain function for FiltersFunctionScoreQuery. Here,
the influence of the maxBoost was never printed. Furthermore, the queryBoost was
printed as beeing multiplied to the filter score.

Closes #3464
This commit is contained in:
Britta Weber 2013-08-07 17:39:51 +02:00
parent 2b03bc83a4
commit b007af1f46
9 changed files with 257 additions and 223 deletions

View File

@ -25,11 +25,12 @@ import org.apache.lucene.search.Explanation;
/** /**
* *
*/ */
public class BoostScoreFunction implements ScoreFunction { public class BoostScoreFunction extends ScoreFunction {
private final float boost; private final float boost;
public BoostScoreFunction(float boost) { public BoostScoreFunction(float boost) {
super(CombineFunction.MULT);
this.boost = boost; this.boost = boost;
} }
@ -44,27 +45,16 @@ public class BoostScoreFunction implements ScoreFunction {
@Override @Override
public double score(int docId, float subQueryScore) { public double score(int docId, float subQueryScore) {
return subQueryScore * boost;
}
@Override
public double factor(int docId) {
return boost; return boost;
} }
@Override @Override
public Explanation explainScore(int docId, Explanation subQueryExpl) { public Explanation explainScore(int docId, Explanation subQueryExpl) {
Explanation exp = new Explanation(boost * subQueryExpl.getValue(), "static boost function: product of:"); Explanation exp = new Explanation(boost, "static boost factor");
exp.addDetail(subQueryExpl);
exp.addDetail(new Explanation(boost, "boostFactor")); exp.addDetail(new Explanation(boost, "boostFactor"));
return exp; return exp;
} }
@Override
public Explanation explainFactor(int docId) {
return new Explanation(boost, "boostFactor");
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) if (this == o)

View File

@ -0,0 +1,90 @@
/*
* Licensed to ElasticSearch and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. ElasticSearch 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.elasticsearch.common.lucene.search.function;
import org.apache.lucene.search.ComplexExplanation;
import org.apache.lucene.search.Explanation;
public enum CombineFunction {
MULT {
@Override
public float combine(double queryBoost, double queryScore, double funcScore, double maxBoost) {
return toFloat(queryBoost * queryScore * Math.min(funcScore, maxBoost));
}
@Override
public String getName() {
return "mult";
}
@Override
public ComplexExplanation explain(float queryBoost, Explanation queryExpl, Explanation funcExpl, float maxBoost) {
float score = queryBoost * Math.min(funcExpl.getValue(), maxBoost) * queryExpl.getValue();
ComplexExplanation res = new ComplexExplanation(true, score, "function score, product of:");
res.addDetail(queryExpl);
ComplexExplanation minExpl = new ComplexExplanation(true, Math.min(funcExpl.getValue(), maxBoost), "Math.min of");
minExpl.addDetail(funcExpl);
minExpl.addDetail(new Explanation(maxBoost, "maxBoost"));
res.addDetail(minExpl);
res.addDetail(new Explanation(queryBoost, "queryBoost"));
return res;
}
},
PLAIN {
@Override
public float combine(double queryBoost, double queryScore, double funcScore, double maxBoost) {
return toFloat(queryBoost * Math.min(funcScore, maxBoost));
}
@Override
public String getName() {
return "plain";
}
@Override
public ComplexExplanation explain(float queryBoost, Explanation queryExpl, Explanation funcExpl, float maxBoost) {
float score = queryBoost * Math.min(funcExpl.getValue(), maxBoost);
ComplexExplanation res = new ComplexExplanation(true, score, "function score, product of:");
ComplexExplanation minExpl = new ComplexExplanation(true, Math.min(funcExpl.getValue(), maxBoost), "Math.min of");
minExpl.addDetail(funcExpl);
minExpl.addDetail(new Explanation(maxBoost, "maxBoost"));
res.addDetail(minExpl);
res.addDetail(new Explanation(queryBoost, "queryBoost"));
return res;
}
};
public abstract float combine(double queryBoost, double queryScore, double funcScore, double maxBoost);
public abstract String getName();
public static float toFloat(double input) {
assert deviation(input) <= 0.001 : "input " + input + " out of float scope for function score deviation: " + deviation(input);
return (float) input;
}
private static double deviation(double input) { // only with assert!
float floatVersion = (float)input;
return Double.compare(floatVersion, input) == 0 || input == 0.0d ? 0 : 1.d-(floatVersion) / input;
}
public abstract ComplexExplanation explain(float queryBoost, Explanation queryExpl, Explanation funcExpl, float maxBoost);
}

View File

@ -28,14 +28,11 @@ import org.apache.lucene.util.ToStringUtils;
import org.elasticsearch.common.lucene.docset.DocIdSets; import org.elasticsearch.common.lucene.docset.DocIdSets;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Locale;
import java.util.Set;
/** /**
* A query that allows for a pluggable boost function / filter. If it matches the filter, it will * A query that allows for a pluggable boost function / filter. If it matches
* be boosted by the formula. * the filter, it will be boosted by the formula.
*/ */
public class FiltersFunctionScoreQuery extends Query { public class FiltersFunctionScoreQuery extends Query {
@ -50,13 +47,17 @@ public class FiltersFunctionScoreQuery extends Query {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o)
if (o == null || getClass() != o.getClass()) return false; return true;
if (o == null || getClass() != o.getClass())
return false;
FilterFunction that = (FilterFunction) o; FilterFunction that = (FilterFunction) o;
if (filter != null ? !filter.equals(that.filter) : that.filter != null) return false; if (filter != null ? !filter.equals(that.filter) : that.filter != null)
if (function != null ? !function.equals(that.function) : that.function != null) return false; return false;
if (function != null ? !function.equals(that.function) : that.function != null)
return false;
return true; return true;
} }
@ -69,20 +70,29 @@ public class FiltersFunctionScoreQuery extends Query {
} }
} }
public static enum ScoreMode {First, Avg, Max, Total, Min, Multiply} public static enum ScoreMode {
First, Avg, Max, Total, Min, Multiply
}
Query subQuery; Query subQuery;
final FilterFunction[] filterFunctions; final FilterFunction[] filterFunctions;
final ScoreMode scoreMode; final ScoreMode scoreMode;
final float maxBoost; final float maxBoost;
protected CombineFunction combineFunction;
public FiltersFunctionScoreQuery(Query subQuery, ScoreMode scoreMode, FilterFunction[] filterFunctions, float maxBoost) { public FiltersFunctionScoreQuery(Query subQuery, ScoreMode scoreMode, FilterFunction[] filterFunctions, float maxBoost) {
this.subQuery = subQuery; this.subQuery = subQuery;
this.scoreMode = scoreMode; this.scoreMode = scoreMode;
this.filterFunctions = filterFunctions; this.filterFunctions = filterFunctions;
this.maxBoost = maxBoost; this.maxBoost = maxBoost;
combineFunction = CombineFunction.MULT;
} }
public FiltersFunctionScoreQuery setCombineFunction(CombineFunction combineFunction){
this.combineFunction = combineFunction;
return this;
}
public Query getSubQuery() { public Query getSubQuery() {
return subQuery; return subQuery;
} }
@ -94,7 +104,8 @@ public class FiltersFunctionScoreQuery extends Query {
@Override @Override
public Query rewrite(IndexReader reader) throws IOException { public Query rewrite(IndexReader reader) throws IOException {
Query newQ = subQuery.rewrite(reader); Query newQ = subQuery.rewrite(reader);
if (newQ == subQuery) return this; if (newQ == subQuery)
return this;
FiltersFunctionScoreQuery bq = (FiltersFunctionScoreQuery) this.clone(); FiltersFunctionScoreQuery bq = (FiltersFunctionScoreQuery) this.clone();
bq.subQuery = newQ; bq.subQuery = newQ;
return bq; return bq;
@ -148,107 +159,88 @@ public class FiltersFunctionScoreQuery extends Query {
filterFunction.function.setNextReader(context); filterFunction.function.setNextReader(context);
docSets[i] = DocIdSets.toSafeBits(context.reader(), filterFunction.filter.getDocIdSet(context, acceptDocs)); docSets[i] = DocIdSets.toSafeBits(context.reader(), filterFunction.filter.getDocIdSet(context, acceptDocs));
} }
return new CustomBoostFactorScorer(this, subQueryScorer, scoreMode, filterFunctions, maxBoost, docSets); return new CustomBoostFactorScorer(this, subQueryScorer, scoreMode, filterFunctions, maxBoost, docSets, combineFunction);
} }
@Override @Override
public Explanation explain(AtomicReaderContext context, int doc) throws IOException { public Explanation explain(AtomicReaderContext context, int doc) throws IOException {
Explanation subQueryExpl = subQueryWeight.explain(context, doc); Explanation subQueryExpl = subQueryWeight.explain(context, doc);
if (!subQueryExpl.isMatch()) { if (!subQueryExpl.isMatch()) {
return subQueryExpl; return subQueryExpl;
} }
// First: Gather explanations for all filters
if (scoreMode == ScoreMode.First) { List<ComplexExplanation> filterExplanations = new ArrayList<ComplexExplanation>();
for (FilterFunction filterFunction : filterFunctions) { for (FilterFunction filterFunction : filterFunctions) {
Bits docSet = DocIdSets.toSafeBits(context.reader(), filterFunction.filter.getDocIdSet(context, context.reader().getLiveDocs())); Bits docSet = DocIdSets.toSafeBits(context.reader(),
filterFunction.filter.getDocIdSet(context, context.reader().getLiveDocs()));
if (docSet.get(doc)) { if (docSet.get(doc)) {
filterFunction.function.setNextReader(context); filterFunction.function.setNextReader(context);
Explanation functionExplanation = filterFunction.function.explainFactor(doc); Explanation functionExplanation = filterFunction.function.explainScore(doc, subQueryExpl);
double factor = functionExplanation.getValue(); double factor = functionExplanation.getValue();
if (factor > maxBoost) { float sc = CombineFunction.toFloat(factor);
factor = maxBoost; ComplexExplanation filterExplanation = new ComplexExplanation(true, sc, "function score, product of:");
}
float sc = FunctionScoreQuery.toFloat(getBoost() * factor);
Explanation filterExplanation = new ComplexExplanation(true, sc, "function score, product of:");
filterExplanation.addDetail(new Explanation(1.0f, "match filter: " + filterFunction.filter.toString())); filterExplanation.addDetail(new Explanation(1.0f, "match filter: " + filterFunction.filter.toString()));
filterExplanation.addDetail(functionExplanation); filterExplanation.addDetail(functionExplanation);
filterExplanation.addDetail(new Explanation(getBoost(), "queryBoost")); filterExplanations.add(filterExplanation);
// top level score = subquery.score * filter.score (this already has the query boost)
float topLevelScore = subQueryExpl.getValue() * sc;
Explanation topLevel = new ComplexExplanation(true, topLevelScore, "function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]");
topLevel.addDetail(subQueryExpl);
topLevel.addDetail(filterExplanation);
return topLevel;
} }
} }
} else { if (filterExplanations.size() == 0) {
int count = 0; float sc = getBoost() * subQueryExpl.getValue();
float total = 0; Explanation res = new ComplexExplanation(true, sc, "function score, no filter match, product of:");
float multiply = 1; res.addDetail(subQueryExpl);
double max = Double.NEGATIVE_INFINITY;
double min = Double.POSITIVE_INFINITY;
ArrayList<Explanation> filtersExplanations = new ArrayList<Explanation>();
for (FilterFunction filterFunction : filterFunctions) {
Bits docSet = DocIdSets.toSafeBits(context.reader(), filterFunction.filter.getDocIdSet(context, context.reader().getLiveDocs()));
if (docSet.get(doc)) {
filterFunction.function.setNextReader(context);
Explanation functionExplanation = filterFunction.function.explainFactor(doc);
double factor = functionExplanation.getValue();
count++;
total += factor;
multiply *= factor;
max = Math.max(factor, max);
min = Math.min(factor, min);
Explanation res = new ComplexExplanation(true, FunctionScoreQuery.toFloat(factor), "function score, product of:");
res.addDetail(new Explanation(1.0f, "match filter: " + filterFunction.filter.toString()));
res.addDetail(functionExplanation);
res.addDetail(new Explanation(getBoost(), "queryBoost")); res.addDetail(new Explanation(getBoost(), "queryBoost"));
filtersExplanations.add(res); return res;
} }
}
if (count > 0) { // Second: Compute the factor that would have been computed by the
double factor = 0; // filters
double factor = 1.0;
switch (scoreMode) { switch (scoreMode) {
case Avg: case First:
factor = total / count;
factor = filterExplanations.get(0).getValue();
break; break;
case Max: case Max:
factor = max; double maxFactor = Double.NEGATIVE_INFINITY;
for (int i = 0; i < filterExplanations.size(); i++) {
factor = Math.max(filterExplanations.get(i).getValue(), maxFactor);
}
break; break;
case Min: case Min:
factor = min; double minFactor = Double.POSITIVE_INFINITY;
break; for (int i = 0; i < filterExplanations.size(); i++) {
case Total: factor = Math.min(filterExplanations.get(i).getValue(), minFactor);
factor = total; }
break; break;
case Multiply: case Multiply:
factor = multiply; for (int i = 0; i < filterExplanations.size(); i++) {
factor *= filterExplanations.get(i).getValue();
}
break; break;
default: // Avg / Total
double totalFactor = 0.0f;
int count = 0;
for (int i = 0; i < filterExplanations.size(); i++) {
totalFactor += filterExplanations.get(i).getValue();
count++;
} }
if (count != 0) {
if (factor > maxBoost) { factor = totalFactor;
factor = maxBoost; if (scoreMode == ScoreMode.Avg) {
} factor /= count;
float sc = FunctionScoreQuery.toFloat(factor * subQueryExpl.getValue() * getBoost());
Explanation res = new ComplexExplanation(true, sc, "function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]");
res.addDetail(subQueryExpl);
for (Explanation explanation : filtersExplanations) {
res.addDetail(explanation);
}
return res;
} }
} }
}
float sc = getBoost() * subQueryExpl.getValue(); ComplexExplanation factorExplanaition = new ComplexExplanation(true, CombineFunction.toFloat(factor),
Explanation res = new ComplexExplanation(true, sc, "custom score, no filter match, product of:"); "function score, score mode [" + scoreMode.toString().toLowerCase(Locale.ROOT) + "]");
res.addDetail(subQueryExpl); for (int i = 0; i < filterExplanations.size(); i++) {
res.addDetail(new Explanation(getBoost(), "queryBoost")); factorExplanaition.addDetail(filterExplanations.get(i));
return res; }
return combineFunction.explain(getBoost(), subQueryExpl, factorExplanaition, maxBoost);
} }
} }
static class CustomBoostFactorScorer extends Scorer { static class CustomBoostFactorScorer extends Scorer {
private final float subQueryBoost; private final float subQueryBoost;
@ -257,9 +249,10 @@ public class FiltersFunctionScoreQuery extends Query {
private final ScoreMode scoreMode; private final ScoreMode scoreMode;
private final float maxBoost; private final float maxBoost;
private final Bits[] docSets; private final Bits[] docSets;
private final CombineFunction scoreCombiner;
private CustomBoostFactorScorer(CustomBoostFactorWeight w, Scorer scorer, ScoreMode scoreMode, private CustomBoostFactorScorer(CustomBoostFactorWeight w, Scorer scorer, ScoreMode scoreMode, FilterFunction[] filterFunctions,
FilterFunction[] filterFunctions, float maxBoost, Bits[] docSets) throws IOException { float maxBoost, Bits[] docSets, CombineFunction scoreCombiner) throws IOException {
super(w); super(w);
this.subQueryBoost = w.getQuery().getBoost(); this.subQueryBoost = w.getQuery().getBoost();
this.scorer = scorer; this.scorer = scorer;
@ -267,6 +260,7 @@ public class FiltersFunctionScoreQuery extends Query {
this.filterFunctions = filterFunctions; this.filterFunctions = filterFunctions;
this.maxBoost = maxBoost; this.maxBoost = maxBoost;
this.docSets = docSets; this.docSets = docSets;
this.scoreCombiner = scoreCombiner;
} }
@Override @Override
@ -288,10 +282,11 @@ public class FiltersFunctionScoreQuery extends Query {
public float score() throws IOException { public float score() throws IOException {
int docId = scorer.docID(); int docId = scorer.docID();
double factor = 1.0f; double factor = 1.0f;
float subQueryScore = scorer.score();
if (scoreMode == ScoreMode.First) { if (scoreMode == ScoreMode.First) {
for (int i = 0; i < filterFunctions.length; i++) { for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) { if (docSets[i].get(docId)) {
factor = filterFunctions[i].function.factor(docId); factor = filterFunctions[i].function.score(docId, subQueryScore);
break; break;
} }
} }
@ -299,7 +294,7 @@ public class FiltersFunctionScoreQuery extends Query {
double maxFactor = Double.NEGATIVE_INFINITY; double maxFactor = Double.NEGATIVE_INFINITY;
for (int i = 0; i < filterFunctions.length; i++) { for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) { if (docSets[i].get(docId)) {
maxFactor = Math.max(filterFunctions[i].function.factor(docId), maxFactor); maxFactor = Math.max(filterFunctions[i].function.score(docId, subQueryScore), maxFactor);
} }
} }
if (maxFactor != Float.NEGATIVE_INFINITY) { if (maxFactor != Float.NEGATIVE_INFINITY) {
@ -309,7 +304,7 @@ public class FiltersFunctionScoreQuery extends Query {
double minFactor = Double.POSITIVE_INFINITY; double minFactor = Double.POSITIVE_INFINITY;
for (int i = 0; i < filterFunctions.length; i++) { for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) { if (docSets[i].get(docId)) {
minFactor = Math.min(filterFunctions[i].function.factor(docId), minFactor); minFactor = Math.min(filterFunctions[i].function.score(docId, subQueryScore), minFactor);
} }
} }
if (minFactor != Float.POSITIVE_INFINITY) { if (minFactor != Float.POSITIVE_INFINITY) {
@ -318,7 +313,7 @@ public class FiltersFunctionScoreQuery extends Query {
} else if (scoreMode == ScoreMode.Multiply) { } else if (scoreMode == ScoreMode.Multiply) {
for (int i = 0; i < filterFunctions.length; i++) { for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) { if (docSets[i].get(docId)) {
factor *= filterFunctions[i].function.factor(docId); factor *= filterFunctions[i].function.score(docId, subQueryScore);
} }
} }
} else { // Avg / Total } else { // Avg / Total
@ -326,7 +321,7 @@ public class FiltersFunctionScoreQuery extends Query {
int count = 0; int count = 0;
for (int i = 0; i < filterFunctions.length; i++) { for (int i = 0; i < filterFunctions.length; i++) {
if (docSets[i].get(docId)) { if (docSets[i].get(docId)) {
totalFactor += filterFunctions[i].function.factor(docId); totalFactor += filterFunctions[i].function.score(docId, subQueryScore);
count++; count++;
} }
} }
@ -337,11 +332,7 @@ public class FiltersFunctionScoreQuery extends Query {
} }
} }
} }
if (factor > maxBoost) { return scoreCombiner.combine(subQueryBoost, subQueryScore, factor, maxBoost);
factor = maxBoost;
}
float score = scorer.score();
return FunctionScoreQuery.toFloat(subQueryBoost * score * factor);
} }
@Override @Override
@ -355,10 +346,9 @@ public class FiltersFunctionScoreQuery extends Query {
} }
} }
public String toString(String field) { public String toString(String field) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("custom score (").append(subQuery.toString(field)).append(", functions: ["); sb.append("function score (").append(subQuery.toString(field)).append(", functions: [");
for (FilterFunction filterFunction : filterFunctions) { for (FilterFunction filterFunction : filterFunctions) {
sb.append("{filter(").append(filterFunction.filter).append("), function [").append(filterFunction.function).append("]}"); sb.append("{filter(").append(filterFunction.filter).append("), function [").append(filterFunction.function).append("]}");
} }
@ -368,7 +358,8 @@ public class FiltersFunctionScoreQuery extends Query {
} }
public boolean equals(Object o) { public boolean equals(Object o) {
if (getClass() != o.getClass()) return false; if (getClass() != o.getClass())
return false;
FiltersFunctionScoreQuery other = (FiltersFunctionScoreQuery) o; FiltersFunctionScoreQuery other = (FiltersFunctionScoreQuery) o;
if (this.getBoost() != other.getBoost()) if (this.getBoost() != other.getBoost())
return false; return false;
@ -382,4 +373,3 @@ public class FiltersFunctionScoreQuery extends Query {
return subQuery.hashCode() + 31 * Arrays.hashCode(filterFunctions) ^ Float.floatToIntBits(getBoost()); return subQuery.hashCode() + 31 * Arrays.hashCode(filterFunctions) ^ Float.floatToIntBits(getBoost());
} }
} }

View File

@ -37,10 +37,16 @@ public class FunctionScoreQuery extends Query {
Query subQuery; Query subQuery;
final ScoreFunction function; final ScoreFunction function;
float maxBoost = Float.MAX_VALUE; float maxBoost = Float.MAX_VALUE;
CombineFunction combineFunction;
public FunctionScoreQuery(Query subQuery, ScoreFunction function) { public FunctionScoreQuery(Query subQuery, ScoreFunction function) {
this.subQuery = subQuery; this.subQuery = subQuery;
this.function = function; this.function = function;
this.combineFunction = function.getDefaultScoreCombiner();
}
public void setCombineFunction(CombineFunction combineFunction) {
this.combineFunction = combineFunction;
} }
public void setMaxBoost(float maxBoost) { public void setMaxBoost(float maxBoost) {
@ -112,7 +118,7 @@ public class FunctionScoreQuery extends Query {
return null; return null;
} }
function.setNextReader(context); function.setNextReader(context);
return new CustomBoostFactorScorer(this, subQueryScorer, function, maxBoost); return new CustomBoostFactorScorer(this, subQueryScorer, function, maxBoost, combineFunction);
} }
@Override @Override
@ -121,14 +127,9 @@ public class FunctionScoreQuery extends Query {
if (!subQueryExpl.isMatch()) { if (!subQueryExpl.isMatch()) {
return subQueryExpl; return subQueryExpl;
} }
function.setNextReader(context); function.setNextReader(context);
Explanation functionExplanation = function.explainScore(doc, subQueryExpl); Explanation functionExplanation = function.explainScore(doc, subQueryExpl);
float sc = getBoost() * functionExplanation.getValue(); return combineFunction.explain(getBoost(), subQueryExpl, functionExplanation, maxBoost);
Explanation res = new ComplexExplanation(true, sc, "function score, product of:");
res.addDetail(functionExplanation);
res.addDetail(new Explanation(getBoost(), "queryBoost"));
return res;
} }
} }
@ -138,14 +139,16 @@ public class FunctionScoreQuery extends Query {
private final Scorer scorer; private final Scorer scorer;
private final ScoreFunction function; private final ScoreFunction function;
private final float maxBoost; private final float maxBoost;
private final CombineFunction scoreCombiner;
private CustomBoostFactorScorer(CustomBoostFactorWeight w, Scorer scorer, ScoreFunction function, float maxBoost) private CustomBoostFactorScorer(CustomBoostFactorWeight w, Scorer scorer, ScoreFunction function, float maxBoost, CombineFunction scoreCombiner)
throws IOException { throws IOException {
super(w); super(w);
this.subQueryBoost = w.getQuery().getBoost(); this.subQueryBoost = w.getQuery().getBoost();
this.scorer = scorer; this.scorer = scorer;
this.function = function; this.function = function;
this.maxBoost = maxBoost; this.maxBoost = maxBoost;
this.scoreCombiner = scoreCombiner;
} }
@Override @Override
@ -165,8 +168,8 @@ public class FunctionScoreQuery extends Query {
@Override @Override
public float score() throws IOException { public float score() throws IOException {
double factor = function.score(scorer.docID(), scorer.score()); return scoreCombiner.combine(subQueryBoost, scorer.score(),
return toFloat(subQueryBoost * Math.min(maxBoost, factor)); function.score(scorer.docID(), scorer.score()), maxBoost);
} }
@Override @Override
@ -182,7 +185,7 @@ public class FunctionScoreQuery extends Query {
public String toString(String field) { public String toString(String field) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("custom score (").append(subQuery.toString(field)).append(",function=").append(function).append(')'); sb.append("function score (").append(subQuery.toString(field)).append(",function=").append(function).append(')');
sb.append(ToStringUtils.boost(getBoost())); sb.append(ToStringUtils.boost(getBoost()));
return sb.toString(); return sb.toString();
} }
@ -198,15 +201,4 @@ public class FunctionScoreQuery extends Query {
public int hashCode() { public int hashCode() {
return subQuery.hashCode() + 31 * function.hashCode() ^ Float.floatToIntBits(getBoost()); return subQuery.hashCode() + 31 * function.hashCode() ^ Float.floatToIntBits(getBoost());
} }
public static float toFloat(double input) {
assert deviation(input) <= 0.001 : "input " + input + " out of float scope for function score deviation: " + deviation(input);
return (float) input;
}
private static double deviation(double input) { // only with assert!
float floatVersion = (float)input;
return Double.compare(floatVersion, input) == 0 || input == 0.0d ? 0 : 1.d-(floatVersion) / input;
}
} }

View File

@ -25,12 +25,13 @@ import org.apache.lucene.search.Explanation;
/** /**
* *
*/ */
public class RandomScoreFunction implements ScoreFunction { public class RandomScoreFunction extends ScoreFunction {
private final PRNG prng; private final PRNG prng;
private int docBase; private int docBase;
public RandomScoreFunction(long seed) { public RandomScoreFunction(long seed) {
super(CombineFunction.MULT);
this.prng = new PRNG(seed); this.prng = new PRNG(seed);
} }
@ -44,11 +45,6 @@ public class RandomScoreFunction implements ScoreFunction {
return prng.random(docBase + docId); return prng.random(docBase + docId);
} }
@Override
public double factor(int docId) {
return prng.seed;
}
@Override @Override
public Explanation explainScore(int docId, Explanation subQueryExpl) { public Explanation explainScore(int docId, Explanation subQueryExpl) {
Explanation exp = new Explanation(); Explanation exp = new Explanation();
@ -57,14 +53,6 @@ public class RandomScoreFunction implements ScoreFunction {
return exp; return exp;
} }
@Override
public Explanation explainFactor(int docId) {
Explanation exp = new Explanation();
exp.setDescription("seed: " + prng.originalSeed + ")");
return exp;
}
/** /**
* Algorithm largely based on {@link java.util.Random} except this one is not * Algorithm largely based on {@link java.util.Random} except this one is not
* thread safe and it incorporates the doc id on next(); * thread safe and it incorporates the doc id on next();

View File

@ -25,15 +25,22 @@ import org.apache.lucene.search.Explanation;
/** /**
* *
*/ */
public interface ScoreFunction { public abstract class ScoreFunction {
void setNextReader(AtomicReaderContext context); private final CombineFunction scoreCombiner;
double score(int docId, float subQueryScore); public abstract void setNextReader(AtomicReaderContext context);
double factor(int docId); public abstract double score(int docId, float subQueryScore);
Explanation explainScore(int docId, Explanation subQueryExpl); public abstract Explanation explainScore(int docId, Explanation subQueryExpl);
public CombineFunction getDefaultScoreCombiner() {
return scoreCombiner;
}
protected ScoreFunction(CombineFunction scoreCombiner) {
this.scoreCombiner = scoreCombiner;
}
Explanation explainFactor(int docId);
} }

View File

@ -26,7 +26,7 @@ import org.elasticsearch.script.SearchScript;
import java.util.Map; import java.util.Map;
public class ScriptScoreFunction implements ScoreFunction { public class ScriptScoreFunction extends ScoreFunction {
private final String sScript; private final String sScript;
@ -34,7 +34,9 @@ public class ScriptScoreFunction implements ScoreFunction {
private final SearchScript script; private final SearchScript script;
public ScriptScoreFunction(String sScript, Map<String, Object> params, SearchScript script) { public ScriptScoreFunction(String sScript, Map<String, Object> params, SearchScript script) {
super(CombineFunction.PLAIN);
this.sScript = sScript; this.sScript = sScript;
this.params = params; this.params = params;
this.script = script; this.script = script;
@ -52,13 +54,6 @@ public class ScriptScoreFunction implements ScoreFunction {
return script.runAsDouble(); return script.runAsDouble();
} }
@Override
public double factor(int docId) {
// just the factor, so don't provide _score
script.setNextDocId(docId);
return script.runAsFloat();
}
@Override @Override
public Explanation explainScore(int docId, Explanation subQueryExpl) { public Explanation explainScore(int docId, Explanation subQueryExpl) {
Explanation exp; Explanation exp;
@ -68,19 +63,15 @@ public class ScriptScoreFunction implements ScoreFunction {
exp = ((ExplainableSearchScript) script).explain(subQueryExpl); exp = ((ExplainableSearchScript) script).explain(subQueryExpl);
} else { } else {
double score = score(docId, subQueryExpl.getValue()); double score = score(docId, subQueryExpl.getValue());
exp = new Explanation((float)score, "script score function: composed of:"); exp = new Explanation(CombineFunction.toFloat(score), "script score function: composed of:");
exp.addDetail(subQueryExpl); exp.addDetail(subQueryExpl);
} }
return exp; return exp;
} }
@Override
public Explanation explainFactor(int docId) {
return new Explanation((float)factor(docId), "script_factor");
}
@Override @Override
public String toString() { public String toString() {
return "script[" + sScript + "], params [" + params + "]"; return "script[" + sScript + "], params [" + params + "]";
} }
} }

View File

@ -26,6 +26,7 @@ import org.elasticsearch.ElasticSearchIllegalArgumentException;
import org.elasticsearch.ElasticSearchParseException; import org.elasticsearch.ElasticSearchParseException;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.ScoreFunction; import org.elasticsearch.common.lucene.search.function.ScoreFunction;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
@ -329,12 +330,13 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
* This is the base class for scoring a single field. * This is the base class for scoring a single field.
* *
* */ * */
public static abstract class AbstractDistanceScoreFunction implements ScoreFunction { public static abstract class AbstractDistanceScoreFunction extends ScoreFunction {
private final double scale; private final double scale;
private final DecayFunction func; private final DecayFunction func;
public AbstractDistanceScoreFunction(double userSuppiedScale, double userSuppliedScaleWeight, DecayFunction func) { public AbstractDistanceScoreFunction(double userSuppiedScale, double userSuppliedScaleWeight, DecayFunction func) {
super(CombineFunction.MULT);
if (userSuppiedScale <= 0.0) { if (userSuppiedScale <= 0.0) {
throw new ElasticSearchIllegalArgumentException(FunctionScoreQueryParser.NAME + " : scale must be > 0.0."); throw new ElasticSearchIllegalArgumentException(FunctionScoreQueryParser.NAME + " : scale must be > 0.0.");
} }
@ -348,11 +350,6 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
@Override @Override
public double score(int docId, float subQueryScore) { public double score(int docId, float subQueryScore) {
return (subQueryScore * factor(docId));
}
@Override
public double factor(int docId) {
double value = distance(docId); double value = distance(docId);
return func.evaluate(value, scale); return func.evaluate(value, scale);
} }
@ -372,19 +369,9 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
@Override @Override
public Explanation explainScore(int docId, Explanation subQueryExpl) { public Explanation explainScore(int docId, Explanation subQueryExpl) {
ComplexExplanation ce = new ComplexExplanation(); ComplexExplanation ce = new ComplexExplanation();
ce.setValue((float) score(docId, subQueryExpl.getValue())); ce.setValue(CombineFunction.toFloat(score(docId, subQueryExpl.getValue())));
ce.setMatch(true); ce.setMatch(true);
ce.setDescription("subQueryScore * Function for field " + getFieldName() + ":"); ce.setDescription("Function for field " + getFieldName() + ":");
ce.addDetail(func.explainFunction(getDistanceString(docId), distance(docId), scale));
return ce;
}
@Override
public Explanation explainFactor(int docId) {
ComplexExplanation ce = new ComplexExplanation();
ce.setValue((float) factor(docId));
ce.setMatch(true);
ce.setDescription("subQueryScore * Function for field " + getFieldName() + ":");
ce.addDetail(func.explainFunction(getDistanceString(docId), distance(docId), scale)); ce.addDetail(func.explainFunction(getDistanceString(docId), distance(docId), scale));
return ce; return ce;
} }

View File

@ -84,15 +84,15 @@ public class CustomScoreSearchTests extends AbstractSharedClusterTest {
assertNotNull(explanation); assertNotNull(explanation);
assertThat(explanation.isMatch(), equalTo(true)); assertThat(explanation.isMatch(), equalTo(true));
assertThat(explanation.getValue(), equalTo(3f)); assertThat(explanation.getValue(), equalTo(3f));
assertThat(explanation.getDescription(), equalTo("function score, score mode [first]")); assertThat(explanation.getDescription(), equalTo("function score, product of:"));
assertThat(explanation.getDetails().length, equalTo(2)); assertThat(explanation.getDetails().length, equalTo(3));
assertThat(explanation.getDetails()[0].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[0].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[0].getValue(), equalTo(1f)); assertThat(explanation.getDetails()[0].getValue(), equalTo(1f));
assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2)); assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[1].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[1].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[1].getValue(), equalTo(3f)); assertThat(explanation.getDetails()[1].getValue(), equalTo(3f));
assertThat(explanation.getDetails()[1].getDetails().length, equalTo(3)); assertThat(explanation.getDetails()[1].getDetails().length, equalTo(2));
// Same query but with boost // Same query but with boost
searchResponse = client().prepareSearch("test") searchResponse = client().prepareSearch("test")
@ -114,17 +114,17 @@ public class CustomScoreSearchTests extends AbstractSharedClusterTest {
assertNotNull(explanation); assertNotNull(explanation);
assertThat(explanation.isMatch(), equalTo(true)); assertThat(explanation.isMatch(), equalTo(true));
assertThat(explanation.getValue(), equalTo(6f)); assertThat(explanation.getValue(), equalTo(6f));
assertThat(explanation.getDescription(), equalTo("function score, score mode [first]")); assertThat(explanation.getDescription(), equalTo("function score, product of:"));
assertThat(explanation.getDetails().length, equalTo(2)); assertThat(explanation.getDetails().length, equalTo(3));
assertThat(explanation.getDetails()[0].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[0].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[0].getValue(), equalTo(1f)); assertThat(explanation.getDetails()[0].getValue(), equalTo(1f));
assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2)); assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[1].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[1].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[1].getValue(), equalTo(6f)); assertThat(explanation.getDetails()[1].getValue(), equalTo(3f));
assertThat(explanation.getDetails()[1].getDetails().length, equalTo(3)); assertThat(explanation.getDetails()[1].getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[1].getDetails()[2].getDescription(), equalTo("queryBoost")); assertThat(explanation.getDetails()[2].getDescription(), equalTo("queryBoost"));
assertThat(explanation.getDetails()[1].getDetails()[2].getValue(), equalTo(2f)); assertThat(explanation.getDetails()[2].getValue(), equalTo(2f));
} }
@ -157,15 +157,14 @@ public class CustomScoreSearchTests extends AbstractSharedClusterTest {
assertNotNull(explanation); assertNotNull(explanation);
assertThat(explanation.isMatch(), equalTo(true)); assertThat(explanation.isMatch(), equalTo(true));
assertThat(explanation.getValue(), equalTo(3f)); assertThat(explanation.getValue(), equalTo(3f));
assertThat(explanation.getDescription(), equalTo("function score, score mode [first]")); assertThat(explanation.getDescription(), equalTo("function score, product of:"));
assertThat(explanation.getDetails().length, equalTo(3));
assertThat(explanation.getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[0].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[0].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[0].getValue(), equalTo(1f)); assertThat(explanation.getDetails()[0].getValue(), equalTo(1f));
assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2)); assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[1].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[1].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[1].getValue(), equalTo(3f)); assertThat(explanation.getDetails()[1].getValue(), equalTo(3f));
assertThat(explanation.getDetails()[1].getDetails().length, equalTo(3)); assertThat(explanation.getDetails()[1].getDetails().length, equalTo(2));
// Same query but with boost // Same query but with boost
searchResponse = client().prepareSearch("test") searchResponse = client().prepareSearch("test")
@ -183,17 +182,17 @@ public class CustomScoreSearchTests extends AbstractSharedClusterTest {
assertNotNull(explanation); assertNotNull(explanation);
assertThat(explanation.isMatch(), equalTo(true)); assertThat(explanation.isMatch(), equalTo(true));
assertThat(explanation.getValue(), equalTo(6f)); assertThat(explanation.getValue(), equalTo(6f));
assertThat(explanation.getDescription(), equalTo("function score, score mode [first]")); assertThat(explanation.getDescription(), equalTo("function score, product of:"));
assertThat(explanation.getDetails().length, equalTo(2)); assertThat(explanation.getDetails().length, equalTo(3));
assertThat(explanation.getDetails()[0].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[0].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[0].getValue(), equalTo(1f)); assertThat(explanation.getDetails()[0].getValue(), equalTo(1f));
assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2)); assertThat(explanation.getDetails()[0].getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[1].isMatch(), equalTo(true)); assertThat(explanation.getDetails()[1].isMatch(), equalTo(true));
assertThat(explanation.getDetails()[1].getValue(), equalTo(6f)); assertThat(explanation.getDetails()[1].getValue(), equalTo(3f));
assertThat(explanation.getDetails()[1].getDetails().length, equalTo(3)); assertThat(explanation.getDetails()[1].getDetails().length, equalTo(2));
assertThat(explanation.getDetails()[1].getDetails()[2].getDescription(), equalTo("queryBoost")); assertThat(explanation.getDetails()[2].getDescription(), equalTo("queryBoost"));
assertThat(explanation.getDetails()[1].getDetails()[2].getValue(), equalTo(2f)); assertThat(explanation.getDetails()[2].getValue(), equalTo(2f));
} }
@Test @Test