/* * 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. */ using System; using IndexReader = Lucene.Net.Index.IndexReader; using ToStringUtils = Lucene.Net.Util.ToStringUtils; using ComplexExplanation = Lucene.Net.Search.ComplexExplanation; using Explanation = Lucene.Net.Search.Explanation; using Query = Lucene.Net.Search.Query; using Scorer = Lucene.Net.Search.Scorer; using Searcher = Lucene.Net.Search.Searcher; using Similarity = Lucene.Net.Search.Similarity; using Weight = Lucene.Net.Search.Weight; namespace Lucene.Net.Search.Function { /// Query that sets document score as a programmatic function of several (sub) scores: ///
    ///
  1. the score of its subQuery (any query)
  2. ///
  3. (optional) the score of its ValueSourceQuery (or queries). /// For most simple/convenient use cases this query is likely to be a /// {@link Lucene.Net.Search.Function.FieldScoreQuery FieldScoreQuery}
  4. ///
/// Subclasses can modify the computation by overriding {@link #getCustomScoreProvider}. /// ///

/// WARNING: The status of the Search.Function package is experimental. /// The APIs introduced here might change in the future and will not be /// supported anymore in such a case. ///

[Serializable] public class CustomScoreQuery:Query, System.ICloneable { private Query subQuery; private ValueSourceQuery[] valSrcQueries; // never null (empty array if there are no valSrcQueries). private bool strict = false; // if true, valueSource part of query does not take part in weights normalization. /// Create a CustomScoreQuery over input subQuery. /// the sub query whose scored is being customed. Must not be null. /// public CustomScoreQuery(Query subQuery):this(subQuery, new ValueSourceQuery[0]) { } /// Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}. /// the sub query whose score is being customed. Must not be null. /// /// a value source query whose scores are used in the custom score /// computation. For most simple/convineient use case this would be a /// {@link Lucene.Net.Search.Function.FieldScoreQuery FieldScoreQuery}. /// This parameter is optional - it can be null or even an empty array. /// public CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery):this(subQuery, valSrcQuery != null?new ValueSourceQuery[]{valSrcQuery}:new ValueSourceQuery[0]) { } /// Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}. /// the sub query whose score is being customized. Must not be null. /// /// value source queries whose scores are used in the custom score /// computation. For most simple/convenient use case these would be /// {@link Lucene.Net.Search.Function.FieldScoreQuery FieldScoreQueries}. /// This parameter is optional - it can be null or even an empty array. /// public CustomScoreQuery(Query subQuery, ValueSourceQuery[] valSrcQueries) { this.subQuery = subQuery; this.valSrcQueries = valSrcQueries != null?valSrcQueries:new ValueSourceQuery[0]; if (subQuery == null) throw new System.ArgumentException(" must not be null!"); } /*(non-Javadoc) @see Lucene.Net.Search.Query#rewrite(Lucene.Net.Index.IndexReader) */ public override Query Rewrite(IndexReader reader) { CustomScoreQuery clone = null; Query sq = subQuery.Rewrite(reader); if (sq != subQuery) { clone = (CustomScoreQuery)Clone(); clone.subQuery = sq; } for (int i = 0; i < valSrcQueries.Length; i++) { 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 Lucene.Net.Search.Query#extractTerms(java.util.Set) */ public override void ExtractTerms(System.Collections.Hashtable terms) { subQuery.ExtractTerms(terms); for (int i = 0; i < valSrcQueries.Length; i++) { valSrcQueries[i].ExtractTerms(terms); } } /*(non-Javadoc) @see Lucene.Net.Search.Query#clone() */ public override System.Object Clone() { CustomScoreQuery clone = (CustomScoreQuery) base.Clone(); clone.subQuery = (Query) subQuery.Clone(); clone.valSrcQueries = new ValueSourceQuery[valSrcQueries.Length]; for (int i = 0; i < valSrcQueries.Length; i++) { clone.valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].Clone(); } return clone; } /* (non-Javadoc) @see Lucene.Net.Search.Query#toString(java.lang.String) */ public override System.String ToString(System.String field) { System.Text.StringBuilder sb = new System.Text.StringBuilder(Name()).Append("("); sb.Append(subQuery.ToString(field)); for (int i = 0; i < valSrcQueries.Length; i++) { sb.Append(", ").Append(valSrcQueries[i].ToString(field)); } sb.Append(")"); sb.Append(strict?" STRICT":""); return sb.ToString() + ToStringUtils.Boost(GetBoost()); } /// Returns true if o is equal to this. public override bool Equals(System.Object o) { if (GetType() != o.GetType()) { return false; } 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; } for (int i = 0; i < valSrcQueries.Length; i++) { //TODO simplify with Arrays.deepEquals() once moving to Java 1.5 if (!valSrcQueries[i].Equals(other.valSrcQueries[i])) { return false; } } return true; } /// Returns a hash code value for this object. public override int GetHashCode() { int valSrcHash = 0; for (int i = 0; i < valSrcQueries.Length; i++) { //TODO simplify with Arrays.deepHashcode() once moving to Java 1.5 valSrcHash += valSrcQueries[i].GetHashCode(); } return (GetType().GetHashCode() + subQuery.GetHashCode() + valSrcHash) ^ BitConverter.ToInt32(BitConverter.GetBytes(GetBoost()), 0) ^ (strict ? 1234 : 4321); } /** * 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 */ protected virtual CustomScoreProvider GetCustomScoreProvider(IndexReader reader) { // when deprecated methods are removed, do not extend class here, just return new default CustomScoreProvider return new AnonymousCustomScoreProvider(this, reader); } class AnonymousCustomScoreProvider : CustomScoreProvider { CustomScoreQuery parent; public AnonymousCustomScoreProvider(CustomScoreQuery parent, IndexReader reader) : base(reader) { this.parent = parent; } public override float CustomScore(int doc, float subQueryScore, float[] valSrcScores) { return parent.CustomScore(doc, subQueryScore, valSrcScores); } public override float CustomScore(int doc, float subQueryScore, float valSrcScore) { return parent.CustomScore(doc, subQueryScore, valSrcScore); } public override Explanation CustomExplain(int doc, Explanation subQueryExpl, Explanation[] valSrcExpls) { return parent.CustomExplain(doc, subQueryExpl, valSrcExpls); } public override Explanation CustomExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) { return parent.CustomExplain(doc, subQueryExpl, valSrcExpl); } } /// /// Compute a custom score by the subQuery score and a number of /// ValueSourceQuery scores. /// /// The doc is relative to the current reader, which is /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9). /// Please override {@link #getCustomScoreProvider} and return a subclass /// of {@link CustomScoreProvider} for the given {@link IndexReader}. /// see CustomScoreProvider#customScore(int,float,float[]) /// [Obsolete("Will be removed in Lucene 3.1")] public virtual 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. /// /// The doc is relative to the current reader, which is /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9). /// Please override {@link #getCustomScoreProvider} and return a subclass /// of {@link CustomScoreProvider} for the given {@link IndexReader}. /// @see CustomScoreProvider#customScore(int,float,float) /// [Obsolete("Will be removed in Lucene 3.1")] public virtual float CustomScore(int doc, float subQueryScore, float valSrcScore) { return subQueryScore * valSrcScore; } /// Explain the custom score. /// /// The doc is relative to the current reader, which is /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9). /// Please override {@link #getCustomScoreProvider} and return a subclass /// of {@link CustomScoreProvider} for the given {@link IndexReader}. /// [Obsolete("Will be removed in Lucene 3.1.")] public virtual 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. /// The doc is relative to the current reader, which is /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9). /// Please override {@link #getCustomScoreProvider} and return a subclass /// of {@link CustomScoreProvider} for the given {@link IndexReader}. /// [Obsolete("Will be removed in Lucene 3.1")] public virtual 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; } //=========================== W E I G H T ============================ [Serializable] private class CustomWeight:Weight { private void InitBlock(CustomScoreQuery enclosingInstance) { this.enclosingInstance = enclosingInstance; } private CustomScoreQuery enclosingInstance; public CustomScoreQuery Enclosing_Instance { get { return enclosingInstance; } } internal Similarity similarity; internal Weight subQueryWeight; internal Weight[] valSrcWeights; internal bool qStrict; public CustomWeight(CustomScoreQuery enclosingInstance, Searcher searcher) { InitBlock(enclosingInstance); this.similarity = Enclosing_Instance.GetSimilarity(searcher); this.subQueryWeight = Enclosing_Instance.subQuery.Weight(searcher); this.valSrcWeights = new Weight[Enclosing_Instance.valSrcQueries.Length]; for (int i = 0; i < Enclosing_Instance.valSrcQueries.Length; i++) { this.valSrcWeights[i] = Enclosing_Instance.valSrcQueries[i].CreateWeight(searcher); } this.qStrict = Enclosing_Instance.strict; } /*(non-Javadoc) @see Lucene.Net.Search.Weight#getQuery() */ public override Query GetQuery() { return Enclosing_Instance; } /*(non-Javadoc) @see Lucene.Net.Search.Weight#getValue() */ public override float GetValue() { return Enclosing_Instance.GetBoost(); } /*(non-Javadoc) @see Lucene.Net.Search.Weight#sumOfSquaredWeights() */ public override float SumOfSquaredWeights() { float sum = subQueryWeight.SumOfSquaredWeights(); for (int i = 0; i < valSrcWeights.Length; i++) { if (qStrict) { valSrcWeights[i].SumOfSquaredWeights(); // do not include ValueSource part in the query normalization } else { sum += valSrcWeights[i].SumOfSquaredWeights(); } } sum *= Enclosing_Instance.GetBoost() * Enclosing_Instance.GetBoost(); // boost each sub-weight return sum; } /*(non-Javadoc) @see Lucene.Net.Search.Weight#normalize(float) */ public override void Normalize(float norm) { norm *= Enclosing_Instance.GetBoost(); // incorporate boost subQueryWeight.Normalize(norm); for (int i = 0; i < valSrcWeights.Length; i++) { if (qStrict) { valSrcWeights[i].Normalize(1); // do not normalize the ValueSource part } else { valSrcWeights[i].Normalize(norm); } } } public override Scorer Scorer(IndexReader reader, bool scoreDocsInOrder, bool topScorer) { // Pass true for "scoresDocsInOrder", because we // require in-order scoring, even if caller does not, // since we call advance on the valSrcScorers. Pass // false for "topScorer" because we will not invoke // score(Collector) on these scorers: Scorer subQueryScorer = subQueryWeight.Scorer(reader, true, false); if (subQueryScorer == null) { return null; } Scorer[] valSrcScorers = new Scorer[valSrcWeights.Length]; for (int i = 0; i < valSrcScorers.Length; i++) { valSrcScorers[i] = valSrcWeights[i].Scorer(reader, true, topScorer); } return new CustomScorer(enclosingInstance, similarity, reader, this, subQueryScorer, valSrcScorers); } public override Explanation Explain(IndexReader reader, int doc) { Explanation explain = DoExplain(reader, doc); return explain == null?new Explanation(0.0f, "no matching docs"):explain; } private Explanation DoExplain(IndexReader reader, int doc) { Scorer[] valSrcScorers = new Scorer[valSrcWeights.Length]; for (int i = 0; i < valSrcScorers.Length; i++) { valSrcScorers[i] = valSrcWeights[i].Scorer(reader, true, false); } Explanation subQueryExpl = subQueryWeight.Explain(reader, doc); if (!subQueryExpl.IsMatch()) { return subQueryExpl; } // match Explanation[] valSrcExpls = new Explanation[valSrcScorers.Length]; for (int i = 0; i < valSrcScorers.Length; i++) { valSrcExpls[i] = valSrcScorers[i].Explain(doc); } Explanation customExp = Enclosing_Instance.GetCustomScoreProvider(reader).CustomExplain(doc, subQueryExpl, valSrcExpls); float sc = GetValue() * customExp.GetValue(); Explanation res = new ComplexExplanation(true, sc, Enclosing_Instance.ToString() + ", product of:"); res.AddDetail(customExp); res.AddDetail(new Explanation(GetValue(), "queryBoost")); // actually using the q boost as q weight (== weight value) return res; } public override bool ScoresDocsOutOfOrder() { return false; } } //=========================== S C O R E R ============================ /// A scorer that applies a (callback) function on scores of the subQuery. private class CustomScorer:Scorer { private void InitBlock(CustomScoreQuery enclosingInstance) { this.enclosingInstance = enclosingInstance; } private CustomScoreQuery enclosingInstance; public CustomScoreQuery Enclosing_Instance { get { return enclosingInstance; } } private CustomWeight weight; private float qWeight; private Scorer subQueryScorer; private Scorer[] valSrcScorers; private IndexReader reader; private CustomScoreProvider provider; private float[] vScores; // reused in score() to avoid allocating this array for each doc // constructor internal CustomScorer(CustomScoreQuery enclosingInstance, Similarity similarity, IndexReader reader, CustomWeight w, Scorer subQueryScorer, Scorer[] valSrcScorers):base(similarity) { InitBlock(enclosingInstance); this.weight = w; this.qWeight = w.GetValue(); this.subQueryScorer = subQueryScorer; this.valSrcScorers = valSrcScorers; this.reader = reader; this.vScores = new float[valSrcScorers.Length]; this.provider = this.Enclosing_Instance.GetCustomScoreProvider(reader); } /// use {@link #NextDoc()} instead. /// [Obsolete("use NextDoc() instead.")] public override bool Next() { return NextDoc() != NO_MORE_DOCS; } public override int NextDoc() { int doc = subQueryScorer.NextDoc(); if (doc != NO_MORE_DOCS) { for (int i = 0; i < valSrcScorers.Length; i++) { valSrcScorers[i].Advance(doc); } } return doc; } /// use {@link #DocID()} instead. /// [Obsolete("use DocID() instead.")] public override int Doc() { return subQueryScorer.Doc(); } public override int DocID() { return subQueryScorer.DocID(); } /*(non-Javadoc) @see Lucene.Net.Search.Scorer#score() */ public override float Score() { for (int i = 0; i < valSrcScorers.Length; i++) { vScores[i] = valSrcScorers[i].Score(); } return qWeight * provider.CustomScore(subQueryScorer.DocID(), subQueryScorer.Score(), vScores); } /// use {@link #Advance(int)} instead. /// [Obsolete("use Advance(int) instead.")] public override bool SkipTo(int target) { return Advance(target) != NO_MORE_DOCS; } public override int Advance(int target) { int doc = subQueryScorer.Advance(target); if (doc != NO_MORE_DOCS) { for (int i = 0; i < valSrcScorers.Length; i++) { valSrcScorers[i].Advance(doc); } } return doc; } // TODO: remove in 3.0 /*(non-Javadoc) @see Lucene.Net.Search.Scorer#explain(int) */ public override Explanation Explain(int doc) { Explanation subQueryExpl = weight.subQueryWeight.Explain(reader, doc); if (!subQueryExpl.IsMatch()) { return subQueryExpl; } // match Explanation[] valSrcExpls = new Explanation[valSrcScorers.Length]; for (int i = 0; i < valSrcScorers.Length; i++) { valSrcExpls[i] = valSrcScorers[i].Explain(doc); } Explanation customExp = Enclosing_Instance.CustomExplain(doc, subQueryExpl, valSrcExpls); float sc = qWeight * customExp.GetValue(); Explanation res = new ComplexExplanation(true, sc, Enclosing_Instance.ToString() + ", product of:"); res.AddDetail(customExp); res.AddDetail(new Explanation(qWeight, "queryBoost")); // actually using the q boost as q weight (== weight value) return res; } } public override Weight CreateWeight(Searcher searcher) { return new CustomWeight(this, searcher); } /// Checks if this is strict custom scoring. /// In strict custom scoring, the ValueSource part does not participate in weight normalization. /// This may be useful when one wants full control over how scores are modified, and does /// not care about normalizing by the ValueSource part. /// One particular case where this is useful if for testing this query. ///

/// Note: only has effect when the ValueSource part is not null. ///

public virtual bool IsStrict() { return strict; } /// Set the strict mode of this query. /// The strict mode to set. /// /// /// public virtual void SetStrict(bool strict) { this.strict = strict; } /// A short name of this query, used in {@link #ToString(String)}. public virtual System.String Name() { return "custom"; } } }