LUCENE-2190: Added a new class CustomScoreProvider to function package that can be subclassed to provide custom scoring to CustomScoreQuery

git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@912386 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Uwe Schindler 2010-02-21 18:27:05 +00:00
parent 8b165b40b5
commit 30b45bf592
6 changed files with 347 additions and 250 deletions

View File

@ -20,6 +20,9 @@ Changes in backwards compatibility policy
toString. These are advanced APIs and subject to change suddenly.
(Tim Smith via Mike McCandless)
* LUCENE-2190: Removed deprecated customScore() and customExplain()
methods from experimental CustomScoreQuery. (Uwe Schindler)
Changes in runtime behavior
* LUCENE-1923: Made IndexReader.toString() produce something
@ -259,10 +262,16 @@ API Changes
* LUCENE-1972 (3.0.1 only): Restore SortField.getComparatorSource
(it was accidentally removed in 3.0.0) (John Wang via Uwe Schindler)
* LUCENE-2190: Added setNextReader method to CustomScoreQuery, which
is necessary with per-segment searching to notify the subclass
which reader the int doc, passed to customScore, refers to. (Paul
chez Jamespot via Mike McCandless)
* LUCENE-2190: Added a new class CustomScoreProvider to function package
that can be subclassed to provide custom scoring to CustomScoreQuery.
The methods in CustomScoreQuery that did this before were deprecated
and replaced by a method getCustomScoreProvider(IndexReader) that
returns a custom score implementation using the above class. The change
is necessary with per-segment searching, as CustomScoreQuery is
a stateless class (like all other Queries) and does not know about
the currently searched segment. This API works similar to Filter's
getDocIdSet(IndexReader). (Paul chez Jamespot via Mike McCandless,
Uwe Schindler)
* LUCENE-2080: Deprecate Version.LUCENE_CURRENT, as using this constant
will cause backwards compatibility problems when upgrading Lucene. See

View File

@ -43,7 +43,7 @@
<property name="dev.version" value="3.1-dev"/>
<property name="version" value="${dev.version}"/>
<property name="backwards.branch" value="lucene_3_0_back_compat_tests"/>
<property name="backwards.rev" value="912375"/>
<property name="backwards.rev" value="912385"/>
<property name="spec.version" value="${version}"/>
<property name="year" value="2000-${current.year}"/>
<property name="final.name" value="lucene-${name}-${version}"/>

View File

@ -27,6 +27,7 @@ import org.apache.lucene.analysis.WhitespaceAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
@ -36,6 +37,7 @@ import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.function.CustomScoreQuery;
import org.apache.lucene.search.function.CustomScoreProvider;
import org.apache.lucene.search.function.FieldScoreQuery;
import org.apache.lucene.search.function.FieldScoreQuery.Type;
import org.apache.lucene.spatial.geohash.GeoHashUtils;
@ -189,6 +191,9 @@ public class TestCartesian extends TestCase{
CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override // TODO: broken, as reader is not used!
public float customScore(int doc, float subQueryScore, float valSrcScore){
System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
@ -204,6 +209,9 @@ public class TestCartesian extends TestCase{
return score * subQueryScore;
}
};
}
};
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
@ -276,6 +284,9 @@ public class TestCartesian extends TestCase{
CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override // TODO: broken, as reader is not used!
public float customScore(int doc, float subQueryScore, float valSrcScore){
System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
@ -291,6 +302,9 @@ public class TestCartesian extends TestCase{
return score * subQueryScore;
}
};
}
};
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
@ -363,8 +377,10 @@ public class TestCartesian extends TestCase{
FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
CustomScoreQuery customScore = new CustomScoreQuery(dq.getQuery(tq),fsQuery){
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override // TODO: broken, as reader is not used!
public float customScore(int doc, float subQueryScore, float valSrcScore){
//System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
@ -380,6 +396,8 @@ public class TestCartesian extends TestCase{
return score * subQueryScore;
}
};
}
};
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.
@ -452,8 +470,10 @@ public class TestCartesian extends TestCase{
FieldScoreQuery fsQuery = new FieldScoreQuery("geo_distance", Type.FLOAT);
CustomScoreQuery customScore = new CustomScoreQuery(tq,fsQuery){
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override // TODO: broken, as reader is not used!
public float customScore(int doc, float subQueryScore, float valSrcScore){
//System.out.println(doc);
if (dq.distanceFilter.getDistance(doc) == null)
@ -469,6 +489,8 @@ public class TestCartesian extends TestCase{
return score * subQueryScore;
}
};
}
};
// Create a distance sort
// As the radius filter has performed the distance calculations
// already, pass in the filter to reuse the results.

View File

@ -0,0 +1,163 @@
package org.apache.lucene.search.function;
/**
* 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.
*/
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.ComplexExplanation;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.FieldCache; // for javadocs
/**
* An instance of this subclass should be returned by
* {@link CustomScoreQuery#getCustomScoreProvider}, if you want
* to modify the custom score calculation of a {@link CustomScoreQuery}.
* <p>Since Lucene 2.9, queries operate on each segment of an index separately,
* so the protected {@link #reader} field can be used to resolve doc IDs,
* as the supplied <code>doc</code> ID is per-segment and without knowledge
* of the IndexReader you cannot access the document or {@link FieldCache}.
*
* @lucene.experimental
* @since 2.9.2
*/
public class CustomScoreProvider {
protected final IndexReader reader;
/**
* Creates a new instance of the provider class for the given {@link IndexReader}.
*/
public CustomScoreProvider(IndexReader reader) {
this.reader = reader;
}
/**
* Compute a custom score by the subQuery score and a number of
* {@link ValueSourceQuery} scores.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override the other
* {@link #customScore(int, float, float) customScore()}
* method, which is simpler.
* <p>
* The default computation herein is a multiplication of given scores:
* <pre>
* ModifiedScore = valSrcScore * valSrcScores[0] * valSrcScores[1] * ...
* </pre>
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScores scores of that doc by the ValueSourceQuery.
* @return custom score.
*/
public float customScore(int doc, float subQueryScore, float valSrcScores[]) throws IOException {
if (valSrcScores.length == 1) {
return customScore(doc, subQueryScore, valSrcScores[0]);
}
if (valSrcScores.length == 0) {
return customScore(doc, subQueryScore, 1);
}
float score = subQueryScore;
for(int i = 0; i < valSrcScores.length; i++) {
score *= valSrcScores[i];
}
return score;
}
/**
* Compute a custom score by the subQuery score and the ValueSourceQuery score.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override this customScore() method, which is simpler.
* <p>
* The default computation herein is a multiplication of the two scores:
* <pre>
* ModifiedScore = subQueryScore * valSrcScore
* </pre>
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScore score of that doc by the ValueSourceQuery.
* @return custom score.
*/
public float customScore(int doc, float subQueryScore, float valSrcScore) throws IOException {
return subQueryScore * valSrcScore;
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float[])},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpls explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) throws IOException {
if (valSrcExpls.length == 1) {
return customExplain(doc, subQueryExpl, valSrcExpls[0]);
}
if (valSrcExpls.length == 0) {
return subQueryExpl;
}
float valSrcScore = 1;
for (int i = 0; i < valSrcExpls.length; i++) {
valSrcScore *= valSrcExpls[i].getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
for (int i = 0; i < valSrcExpls.length; i++) {
exp.addDetail(valSrcExpls[i]);
}
return exp;
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float)},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpl explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) throws IOException {
float valSrcScore = 1;
if (valSrcExpl != null) {
valSrcScore *= valSrcExpl.getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
exp.addDetail(valSrcExpl);
return exp;
}
}

View File

@ -40,7 +40,7 @@ import org.apache.lucene.util.ToStringUtils;
* For most simple/convenient use cases this query is likely to be a
* {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}</li>
* </ol>
* Subclasses can modify the computation by overriding {@link #customScore(int, float, float)}.
* Subclasses can modify the computation by overriding {@link #getCustomScoreProvider}.
*
* @lucene.experimental
*/
@ -80,7 +80,6 @@ public class CustomScoreQuery extends Query {
* This parameter is optional - it can be null or even an empty array.
*/
public CustomScoreQuery(Query subQuery, ValueSourceQuery... valSrcQueries) {
super();
this.subQuery = subQuery;
this.valSrcQueries = valSrcQueries!=null?
valSrcQueries : new ValueSourceQuery[0];
@ -90,11 +89,23 @@ public class CustomScoreQuery extends Query {
/*(non-Javadoc) @see org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader) */
@Override
public Query rewrite(IndexReader reader) throws IOException {
subQuery = subQuery.rewrite(reader);
for(int i = 0; i < valSrcQueries.length; i++) {
valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].rewrite(reader);
CustomScoreQuery clone = null;
final Query sq = subQuery.rewrite(reader);
if (sq != subQuery) {
clone = (CustomScoreQuery) clone();
clone.subQuery = sq;
}
return this;
for(int i = 0; i < valSrcQueries.length; i++) {
final ValueSourceQuery v = (ValueSourceQuery) valSrcQueries[i].rewrite(reader);
if (v != valSrcQueries[i]) {
if (clone == null) clone = (CustomScoreQuery) clone();
clone.valSrcQueries[i] = v;
}
}
return (clone == null) ? this : clone;
}
/*(non-Javadoc) @see org.apache.lucene.search.Query#extractTerms(java.util.Set) */
@ -140,6 +151,7 @@ public class CustomScoreQuery extends Query {
CustomScoreQuery other = (CustomScoreQuery)o;
if (this.getBoost() != other.getBoost() ||
!this.subQuery.equals(other.subQuery) ||
this.strict != other.strict ||
this.valSrcQueries.length != other.valSrcQueries.length) {
return false;
}
@ -150,132 +162,17 @@ public class CustomScoreQuery extends Query {
@Override
public int hashCode() {
return (getClass().hashCode() + subQuery.hashCode() + Arrays.hashCode(valSrcQueries))
^ Float.floatToIntBits(getBoost());
^ Float.floatToIntBits(getBoost()) ^ (strict ? 1234 : 4321);
}
/**
* Compute a custom score by the subQuery score and a number of
* ValueSourceQuery scores.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override the other
* {@link #customScore(int, float, float) customScore()}
* method, which is simpler.
* <p>
* The default computation herein is a multiplication of given scores:
* <pre>
* ModifiedScore = valSrcScore * valSrcScores[0] * valSrcScores[1] * ...
* </pre>
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScores scores of that doc by the ValueSourceQuery.
* @return custom score.
* Returns a {@link CustomScoreProvider} that calculates the custom scores
* for the given {@link IndexReader}. The default implementation returns a default
* implementation as specified in the docs of {@link CustomScoreProvider}.
* @since 2.9.2
*/
public float customScore(int doc, float subQueryScore, float valSrcScores[]) {
if (valSrcScores.length == 1) {
return customScore(doc, subQueryScore, valSrcScores[0]);
}
if (valSrcScores.length == 0) {
return customScore(doc, subQueryScore, 1);
}
float score = subQueryScore;
for(int i = 0; i < valSrcScores.length; i++) {
score *= valSrcScores[i];
}
return score;
}
/**
* Compute a custom score by the subQuery score and the ValueSourceQuery score.
* <p>
* Subclasses can override this method to modify the custom score.
* <p>
* If your custom scoring is different than the default herein you
* should override at least one of the two customScore() methods.
* If the number of ValueSourceQueries is always &lt; 2 it is
* sufficient to override this customScore() method, which is simpler.
* <p>
* The default computation herein is a multiplication of the two scores:
* <pre>
* ModifiedScore = subQueryScore * valSrcScore
* </pre>
*
* <p><b>NOTE</b>: The doc is relative to the current
* reader, last passed to {@link #setNextReader}.
*
* @param doc id of scored doc.
* @param subQueryScore score of that doc by the subQuery.
* @param valSrcScore score of that doc by the ValueSourceQuery.
* @return custom score.
*/
public float customScore(int doc, float subQueryScore, float valSrcScore) {
return subQueryScore * valSrcScore;
}
/**
* Called when the scoring switches to another reader.
*
* @param reader
* next IndexReader
*/
public void setNextReader(IndexReader reader) throws IOException {
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float[])},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpls explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) {
if (valSrcExpls.length == 1) {
return customExplain(doc, subQueryExpl, valSrcExpls[0]);
}
if (valSrcExpls.length == 0) {
return subQueryExpl;
}
float valSrcScore = 1;
for (int i = 0; i < valSrcExpls.length; i++) {
valSrcScore *= valSrcExpls[i].getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
for (int i = 0; i < valSrcExpls.length; i++) {
exp.addDetail(valSrcExpls[i]);
}
return exp;
}
/**
* Explain the custom score.
* Whenever overriding {@link #customScore(int, float, float)},
* this method should also be overridden to provide the correct explanation
* for the part of the custom scoring.
*
* @param doc doc being explained.
* @param subQueryExpl explanation for the sub-query part.
* @param valSrcExpl explanation for the value source part.
* @return an explanation for the custom score
*/
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) {
float valSrcScore = 1;
if (valSrcExpl != null) {
valSrcScore *= valSrcExpl.getValue();
}
Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
exp.addDetail(subQueryExpl);
exp.addDetail(valSrcExpl);
return exp;
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
return new CustomScoreProvider(reader);
}
//=========================== W E I G H T ============================
@ -371,7 +268,7 @@ public class CustomScoreQuery extends Query {
for(int i = 0; i < valSrcWeights.length; i++) {
valSrcExpls[i] = valSrcWeights[i].explain(reader, doc);
}
Explanation customExp = customExplain(doc,subQueryExpl,valSrcExpls);
Explanation customExp = CustomScoreQuery.this.getCustomScoreProvider(reader).customExplain(doc,subQueryExpl,valSrcExpls);
float sc = getValue() * customExp.getValue();
Explanation res = new ComplexExplanation(
true, sc, CustomScoreQuery.this.toString() + ", product of:");
@ -398,6 +295,7 @@ public class CustomScoreQuery extends Query {
private Scorer subQueryScorer;
private Scorer[] valSrcScorers;
private IndexReader reader;
private final CustomScoreProvider provider;
private float vScores[]; // reused in score() to avoid allocating this array for each doc
// constructor
@ -409,7 +307,7 @@ public class CustomScoreQuery extends Query {
this.valSrcScorers = valSrcScorers;
this.reader = reader;
this.vScores = new float[valSrcScorers.length];
setNextReader(reader);
this.provider = CustomScoreQuery.this.getCustomScoreProvider(reader);
}
@Override
@ -434,7 +332,7 @@ public class CustomScoreQuery extends Query {
for (int i = 0; i < valSrcScorers.length; i++) {
vScores[i] = valSrcScorers[i].score();
}
return qWeight * customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);
return qWeight * provider.customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);
}
@Override

View File

@ -33,7 +33,6 @@ import org.apache.lucene.index.IndexReader;
/**
* Test CustomScoreQuery search.
*/
@SuppressWarnings({"MagicNumber"})
public class TestCustomScoreQuery extends FunctionTestSetup {
/* @override constructor */
@ -97,13 +96,14 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
return "customAdd";
}
/*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#customScore(int, float, float) */
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override
public float customScore(int doc, float subQueryScore, float valSrcScore) {
return subQueryScore + valSrcScore;
}
/* (non-Javadoc)@see org.apache.lucene.search.function.CustomScoreQuery#customExplain(int, org.apache.lucene.search.Explanation, org.apache.lucene.search.Explanation)*/
@Override
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) {
float valSrcScore = valSrcExpl == null ? 0 : valSrcExpl.getValue();
@ -114,6 +114,8 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
}
return exp;
}
};
}
}
// must have static class otherwise serialization tests fail
@ -130,7 +132,9 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
return "customMulAdd";
}
/*(non-Javadoc) @see org.apache.lucene.search.function.CustomScoreQuery#customScore(int, float, float) */
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) {
return new CustomScoreProvider(reader) {
@Override
public float customScore(int doc, float subQueryScore, float valSrcScores[]) {
if (valSrcScores.length == 0) {
@ -144,7 +148,6 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
return (subQueryScore + valSrcScores[0]) * valSrcScores[1]; // we know there are two
}
/* (non-Javadoc)@see org.apache.lucene.search.function.CustomScoreQuery#customExplain(int, org.apache.lucene.search.Explanation, org.apache.lucene.search.Explanation)*/
@Override
public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) {
if (valSrcExpls.length == 0) {
@ -162,20 +165,22 @@ public class TestCustomScoreQuery extends FunctionTestSetup {
exp2.addDetail(exp);
return exp2;
}
};
}
}
private final class CustomExternalQuery extends CustomScoreQuery {
private IndexReader reader;
private int[] values;
public float customScore(int doc, float subScore, float valSrcScore) {
@Override
protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
final int[] values = FieldCache.DEFAULT.getInts(reader, INT_FIELD);
return new CustomScoreProvider(reader) {
@Override
public float customScore(int doc, float subScore, float valSrcScore) throws IOException {
assertTrue(doc <= reader.maxDoc());
return (float) values[doc];
}
public void setNextReader(IndexReader r) throws IOException {
reader = r;
values = FieldCache.DEFAULT.getInts(r, INT_FIELD);
};
}
public CustomExternalQuery(Query q) {