Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
CustomScoreQuery.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using System.Linq;
20 using Lucene.Net.Index;
21 using IndexReader = Lucene.Net.Index.IndexReader;
22 using ToStringUtils = Lucene.Net.Util.ToStringUtils;
23 using ComplexExplanation = Lucene.Net.Search.ComplexExplanation;
24 using Explanation = Lucene.Net.Search.Explanation;
25 using Query = Lucene.Net.Search.Query;
26 using Scorer = Lucene.Net.Search.Scorer;
27 using Searcher = Lucene.Net.Search.Searcher;
28 using Similarity = Lucene.Net.Search.Similarity;
29 using Weight = Lucene.Net.Search.Weight;
30 
31 namespace Lucene.Net.Search.Function
32 {
33 
34  /// <summary> Query that sets document score as a programmatic function of several (sub) scores:
35  /// <list type="bullet">
36  /// <item>the score of its subQuery (any query)</item>
37  /// <item>(optional) the score of its ValueSourceQuery (or queries).
38  /// For most simple/convenient use cases this query is likely to be a
39  /// <see cref="Lucene.Net.Search.Function.FieldScoreQuery">FieldScoreQuery</see></item>
40  /// </list>
41  /// Subclasses can modify the computation by overriding <see cref="GetCustomScoreProvider" />.
42  ///
43  /// <p/><font color="#FF0000">
44  /// WARNING: The status of the <b>Search.Function</b> package is experimental.
45  /// The APIs introduced here might change in the future and will not be
46  /// supported anymore in such a case.</font>
47  /// </summary>
48  [Serializable]
49  public class CustomScoreQuery:Query, System.ICloneable
50  {
51 
52  private Query subQuery;
53  private ValueSourceQuery[] valSrcQueries; // never null (empty array if there are no valSrcQueries).
54  private bool strict = false; // if true, valueSource part of query does not take part in weights normalization.
55 
56  /// <summary> Create a CustomScoreQuery over input subQuery.</summary>
57  /// <param name="subQuery">the sub query whose scored is being customed. Must not be null.
58  /// </param>
59  public CustomScoreQuery(Query subQuery):this(subQuery, new ValueSourceQuery[0])
60  {
61  }
62 
63  /// <summary> Create a CustomScoreQuery over input subQuery and a <see cref="ValueSourceQuery" />.</summary>
64  /// <param name="subQuery">the sub query whose score is being customed. Must not be null.
65  /// </param>
66  /// <param name="valSrcQuery">a value source query whose scores are used in the custom score
67  /// computation. For most simple/convineient use case this would be a
68  /// <see cref="Lucene.Net.Search.Function.FieldScoreQuery">FieldScoreQuery</see>.
69  /// This parameter is optional - it can be null or even an empty array.
70  /// </param>
71  public CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery):this(subQuery, valSrcQuery != null?new ValueSourceQuery[]{valSrcQuery}:new ValueSourceQuery[0])
72  {
73  }
74 
75  /// <summary> Create a CustomScoreQuery over input subQuery and a <see cref="ValueSourceQuery" />.</summary>
76  /// <param name="subQuery">the sub query whose score is being customized. Must not be null.
77  /// </param>
78  /// <param name="valSrcQueries">value source queries whose scores are used in the custom score
79  /// computation. For most simple/convenient use case these would be
80  /// <see cref="Lucene.Net.Search.Function.FieldScoreQuery">FieldScoreQueries</see>.
81  /// This parameter is optional - it can be null or even an empty array.
82  /// </param>
83  public CustomScoreQuery(Query subQuery, params ValueSourceQuery[] valSrcQueries)
84  {
85  this.subQuery = subQuery;
86  this.valSrcQueries = valSrcQueries != null?valSrcQueries:new ValueSourceQuery[0];
87  if (subQuery == null)
88  throw new System.ArgumentException("<subquery> must not be null!");
89  }
90 
91  /*(non-Javadoc) <see cref="Lucene.Net.Search.Query.rewrite(Lucene.Net.Index.IndexReader) */
92  public override Query Rewrite(IndexReader reader)
93  {
94  CustomScoreQuery clone = null;
95 
96  Query sq = subQuery.Rewrite(reader);
97  if (sq != subQuery)
98  {
99  clone = (CustomScoreQuery)Clone();
100  clone.subQuery = sq;
101  }
102 
103  for (int i = 0; i < valSrcQueries.Length; i++)
104  {
105  ValueSourceQuery v = (ValueSourceQuery)valSrcQueries[i].Rewrite(reader);
106  if (v != valSrcQueries[i])
107  {
108  if (clone == null) clone = (CustomScoreQuery)Clone();
109  clone.valSrcQueries[i] = v;
110  }
111  }
112 
113  return (clone == null) ? this : clone;
114  }
115 
116  /*(non-Javadoc) <see cref="Lucene.Net.Search.Query.extractTerms(java.util.Set) */
117  public override void ExtractTerms(System.Collections.Generic.ISet<Term> terms)
118  {
119  subQuery.ExtractTerms(terms);
120  for (int i = 0; i < valSrcQueries.Length; i++)
121  {
122  valSrcQueries[i].ExtractTerms(terms);
123  }
124  }
125 
126  /*(non-Javadoc) <see cref="Lucene.Net.Search.Query.clone() */
127  public override System.Object Clone()
128  {
129  CustomScoreQuery clone = (CustomScoreQuery) base.Clone();
130  clone.subQuery = (Query) subQuery.Clone();
131  clone.valSrcQueries = new ValueSourceQuery[valSrcQueries.Length];
132  for (int i = 0; i < valSrcQueries.Length; i++)
133  {
134  clone.valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].Clone();
135  }
136  return clone;
137  }
138 
139  /* (non-Javadoc) <see cref="Lucene.Net.Search.Query.toString(java.lang.String) */
140  public override System.String ToString(System.String field)
141  {
142  System.Text.StringBuilder sb = new System.Text.StringBuilder(Name()).Append("(");
143  sb.Append(subQuery.ToString(field));
144  for (int i = 0; i < valSrcQueries.Length; i++)
145  {
146  sb.Append(", ").Append(valSrcQueries[i].ToString(field));
147  }
148  sb.Append(")");
149  sb.Append(strict?" STRICT":"");
150  return sb.ToString() + ToStringUtils.Boost(Boost);
151  }
152 
153  /// <summary>Returns true if <c>o</c> is equal to this. </summary>
154  public override bool Equals(System.Object o)
155  {
156  if (GetType() != o.GetType())
157  {
158  return false;
159  }
161  if (this.Boost != other.Boost ||
162  !this.subQuery.Equals(other.subQuery) ||
163  this.strict != other.strict ||
164  this.valSrcQueries.Length != other.valSrcQueries.Length)
165  {
166  return false;
167  }
168 
169  // SequenceEqual should properly mimic java's Array.equals()
170  return valSrcQueries.SequenceEqual(other.valSrcQueries);
171  }
172 
173  /// <summary>Returns a hash code value for this object. </summary>
174  public override int GetHashCode()
175  {
176  int valSrcHash = 0;
177  for (int i = 0; i < valSrcQueries.Length; i++)
178  {
179  // TODO: Simplify this hash code generation
180  valSrcHash += valSrcQueries[i].GetHashCode();
181  }
182  return (GetType().GetHashCode() + subQuery.GetHashCode() + valSrcHash) ^
183  BitConverter.ToInt32(BitConverter.GetBytes(Boost), 0) ^ (strict ? 1234 : 4321);
184 
185  }
186 
187  /// <summary>
188  /// Returns a <see cref="CustomScoreProvider" /> that calculates the custom scores
189  /// for the given <see cref="IndexReader" />. The default implementation returns a default
190  /// implementation as specified in the docs of <see cref="CustomScoreProvider" />.
191  /// </summary>
192  protected virtual CustomScoreProvider GetCustomScoreProvider(IndexReader reader)
193  {
194  // when deprecated methods are removed, do not extend class here, just return new default CustomScoreProvider
195  return new AnonymousCustomScoreProvider(this, reader);
196  }
197 
198  class AnonymousCustomScoreProvider : CustomScoreProvider
199  {
200  CustomScoreQuery parent;
201  public AnonymousCustomScoreProvider(CustomScoreQuery parent, IndexReader reader) : base(reader)
202  {
203  this.parent = parent;
204  }
205  public override float CustomScore(int doc, float subQueryScore, float[] valSrcScores)
206  {
207  return parent.CustomScore(doc, subQueryScore, valSrcScores);
208  }
209 
210  public override float CustomScore(int doc, float subQueryScore, float valSrcScore)
211  {
212  return parent.CustomScore(doc, subQueryScore, valSrcScore);
213  }
214 
215  public override Explanation CustomExplain(int doc, Explanation subQueryExpl, Explanation[] valSrcExpls)
216  {
217  return parent.CustomExplain(doc, subQueryExpl, valSrcExpls);
218  }
219 
220  public override Explanation CustomExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl)
221  {
222  return parent.CustomExplain(doc, subQueryExpl, valSrcExpl);
223  }
224  }
225 
226  /// <summary>
227  /// Compute a custom score by the subQuery score and a number of
228  /// ValueSourceQuery scores.
229  ///
230  /// The doc is relative to the current reader, which is
231  /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
232  /// Please override <see cref="GetCustomScoreProvider" /> and return a subclass
233  /// of <see cref="CustomScoreProvider" /> for the given <see cref="IndexReader" />.
234  /// see CustomScoreProvider#customScore(int,float,float[])
235  /// </summary>
236  [Obsolete("Will be removed in Lucene 3.1")]
237  public virtual float CustomScore(int doc, float subQueryScore, float[] valSrcScores)
238  {
239  if (valSrcScores.Length == 1)
240  {
241  return CustomScore(doc, subQueryScore, valSrcScores[0]);
242  }
243  if (valSrcScores.Length == 0)
244  {
245  return CustomScore(doc, subQueryScore, 1);
246  }
247  float score = subQueryScore;
248  for (int i = 0; i < valSrcScores.Length; i++)
249  {
250  score *= valSrcScores[i];
251  }
252  return score;
253  }
254 
255  /// <summary> Compute a custom score by the subQuery score and the ValueSourceQuery score.
256  ///
257  /// The doc is relative to the current reader, which is
258  /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
259  /// Please override <see cref="GetCustomScoreProvider" /> and return a subclass
260  /// of <see cref="CustomScoreProvider" /> for the given <see cref="IndexReader" />.
261  /// </summary>
262  /// <seealso cref="CustomScoreProvider.CustomScore(int,float,float)" />
263  [Obsolete("Will be removed in Lucene 3.1")]
264  public virtual float CustomScore(int doc, float subQueryScore, float valSrcScore)
265  {
266  return subQueryScore * valSrcScore;
267  }
268 
269 
270 
271  /// <summary> Explain the custom score.
272  ///
273  /// The doc is relative to the current reader, which is
274  /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
275  /// Please override <see cref="GetCustomScoreProvider(IndexReader)" /> and return a subclass
276  /// of <see cref="CustomScoreProvider" /> for the given <see cref="IndexReader" />.
277  /// </summary>
278  [Obsolete("Will be removed in Lucene 3.1")]
279  public virtual Explanation CustomExplain(int doc, Explanation subQueryExpl, Explanation[] valSrcExpls)
280  {
281  if (valSrcExpls.Length == 1)
282  {
283  return CustomExplain(doc, subQueryExpl, valSrcExpls[0]);
284  }
285  if (valSrcExpls.Length == 0)
286  {
287  return subQueryExpl;
288  }
289  float valSrcScore = 1;
290  for (int i = 0; i < valSrcExpls.Length; i++)
291  {
292  valSrcScore *= valSrcExpls[i].Value;
293  }
294  Explanation exp = new Explanation(valSrcScore * subQueryExpl.Value, "custom score: product of:");
295  exp.AddDetail(subQueryExpl);
296  for (int i = 0; i < valSrcExpls.Length; i++)
297  {
298  exp.AddDetail(valSrcExpls[i]);
299  }
300  return exp;
301  }
302 
303  /// <summary> Explain the custom score.
304  /// The doc is relative to the current reader, which is
305  /// unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
306  /// Please override <see cref="GetCustomScoreProvider" /> and return a subclass
307  /// of <see cref="CustomScoreProvider" /> for the given <see cref="IndexReader" />.
308  /// </summary>
309  [Obsolete("Will be removed in Lucene 3.1")]
310  public virtual Explanation CustomExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl)
311  {
312  float valSrcScore = 1;
313  if (valSrcExpl != null)
314  {
315  valSrcScore *= valSrcExpl.Value;
316  }
317  Explanation exp = new Explanation(valSrcScore * subQueryExpl.Value, "custom score: product of:");
318  exp.AddDetail(subQueryExpl);
319  exp.AddDetail(valSrcExpl);
320  return exp;
321  }
322 
323  //=========================== W E I G H T ============================
324 
325  [Serializable]
326  private class CustomWeight:Weight
327  {
328  private void InitBlock(CustomScoreQuery enclosingInstance)
329  {
330  this.enclosingInstance = enclosingInstance;
331  }
332  private CustomScoreQuery enclosingInstance;
333  public CustomScoreQuery Enclosing_Instance
334  {
335  get
336  {
337  return enclosingInstance;
338  }
339 
340  }
341  internal Similarity similarity;
342  internal Weight subQueryWeight;
343  internal Weight[] valSrcWeights;
344  internal bool qStrict;
345 
346  public CustomWeight(CustomScoreQuery enclosingInstance, Searcher searcher)
347  {
348  InitBlock(enclosingInstance);
349  this.similarity = Enclosing_Instance.GetSimilarity(searcher);
350  this.subQueryWeight = Enclosing_Instance.subQuery.Weight(searcher);
351  this.valSrcWeights = new Weight[Enclosing_Instance.valSrcQueries.Length];
352  for (int i = 0; i < Enclosing_Instance.valSrcQueries.Length; i++)
353  {
354  this.valSrcWeights[i] = Enclosing_Instance.valSrcQueries[i].CreateWeight(searcher);
355  }
356  this.qStrict = Enclosing_Instance.strict;
357  }
358 
359  /*(non-Javadoc) <see cref="Lucene.Net.Search.Weight.getQuery() */
360 
361  public override Query Query
362  {
363  get { return Enclosing_Instance; }
364  }
365 
366  /*(non-Javadoc) <see cref="Lucene.Net.Search.Weight.getValue() */
367 
368  public override float Value
369  {
370  get { return Enclosing_Instance.Boost; }
371  }
372 
373  /*(non-Javadoc) <see cref="Lucene.Net.Search.Weight.sumOfSquaredWeights() */
374 
375  public override float GetSumOfSquaredWeights()
376  {
377  float sum = subQueryWeight.GetSumOfSquaredWeights();
378  for (int i = 0; i < valSrcWeights.Length; i++)
379  {
380  if (qStrict)
381  {
382  var sumsq = valSrcWeights[i].GetSumOfSquaredWeights();
383  // do not include ValueSource part in the query normalization
384  }
385  else
386  {
387  sum += valSrcWeights[i].GetSumOfSquaredWeights();
388  }
389  }
390  sum *= Enclosing_Instance.Boost*Enclosing_Instance.Boost; // boost each sub-weight
391  return sum;
392  }
393 
394  /*(non-Javadoc) <see cref="Lucene.Net.Search.Weight.normalize(float) */
395  public override void Normalize(float norm)
396  {
397  norm *= Enclosing_Instance.Boost; // incorporate boost
398  subQueryWeight.Normalize(norm);
399  for (int i = 0; i < valSrcWeights.Length; i++)
400  {
401  if (qStrict)
402  {
403  valSrcWeights[i].Normalize(1); // do not normalize the ValueSource part
404  }
405  else
406  {
407  valSrcWeights[i].Normalize(norm);
408  }
409  }
410  }
411 
412  public override Scorer Scorer(IndexReader reader, bool scoreDocsInOrder, bool topScorer)
413  {
414  // Pass true for "scoresDocsInOrder", because we
415  // require in-order scoring, even if caller does not,
416  // since we call advance on the valSrcScorers. Pass
417  // false for "topScorer" because we will not invoke
418  // score(Collector) on these scorers:
419  Scorer subQueryScorer = subQueryWeight.Scorer(reader, true, false);
420  if (subQueryScorer == null)
421  {
422  return null;
423  }
424  Scorer[] valSrcScorers = new Scorer[valSrcWeights.Length];
425  for (int i = 0; i < valSrcScorers.Length; i++)
426  {
427  valSrcScorers[i] = valSrcWeights[i].Scorer(reader, true, topScorer);
428  }
429  return new CustomScorer(enclosingInstance, similarity, reader, this, subQueryScorer, valSrcScorers);
430  }
431 
432  public override Explanation Explain(IndexReader reader, int doc)
433  {
434  Explanation explain = DoExplain(reader, doc);
435  return explain == null?new Explanation(0.0f, "no matching docs"):explain;
436  }
437 
438  private Explanation DoExplain(IndexReader reader, int doc)
439  {
440  Explanation subQueryExpl = subQueryWeight.Explain(reader, doc);
441  if (!subQueryExpl.IsMatch)
442  {
443  return subQueryExpl;
444  }
445  // match
446  Explanation[] valSrcExpls = new Explanation[valSrcWeights.Length];
447  for (int i = 0; i < valSrcWeights.Length; i++)
448  {
449  valSrcExpls[i] = valSrcWeights[i].Explain(reader, doc);
450  }
451  Explanation customExp = Enclosing_Instance.GetCustomScoreProvider(reader).CustomExplain(doc, subQueryExpl, valSrcExpls);
452  float sc = Value * customExp.Value;
453  Explanation res = new ComplexExplanation(true, sc, Enclosing_Instance.ToString() + ", product of:");
454  res.AddDetail(customExp);
455  res.AddDetail(new Explanation(Value, "queryBoost")); // actually using the q boost as q weight (== weight value)
456  return res;
457  }
458 
459  public override bool GetScoresDocsOutOfOrder()
460  {
461  return false;
462  }
463  }
464 
465 
466  //=========================== S C O R E R ============================
467 
468  /// <summary> A scorer that applies a (callback) function on scores of the subQuery.</summary>
469  private class CustomScorer:Scorer
470  {
471  private void InitBlock(CustomScoreQuery enclosingInstance)
472  {
473  this.enclosingInstance = enclosingInstance;
474  }
475  private CustomScoreQuery enclosingInstance;
476  public CustomScoreQuery Enclosing_Instance
477  {
478  get
479  {
480  return enclosingInstance;
481  }
482 
483  }
484  private float qWeight;
485  private Scorer subQueryScorer;
486  private Scorer[] valSrcScorers;
487  private IndexReader reader;
488  private CustomScoreProvider provider;
489  private float[] vScores; // reused in score() to avoid allocating this array for each doc
490 
491  // constructor
492  internal CustomScorer(CustomScoreQuery enclosingInstance, Similarity similarity, IndexReader reader, CustomWeight w, Scorer subQueryScorer, Scorer[] valSrcScorers):base(similarity)
493  {
494  InitBlock(enclosingInstance);
495  this.qWeight = w.Value;
496  this.subQueryScorer = subQueryScorer;
497  this.valSrcScorers = valSrcScorers;
498  this.reader = reader;
499  this.vScores = new float[valSrcScorers.Length];
500  this.provider = this.Enclosing_Instance.GetCustomScoreProvider(reader);
501  }
502 
503  public override int NextDoc()
504  {
505  int doc = subQueryScorer.NextDoc();
506  if (doc != NO_MORE_DOCS)
507  {
508  for (int i = 0; i < valSrcScorers.Length; i++)
509  {
510  valSrcScorers[i].Advance(doc);
511  }
512  }
513  return doc;
514  }
515 
516  public override int DocID()
517  {
518  return subQueryScorer.DocID();
519  }
520 
521  /*(non-Javadoc) <see cref="Lucene.Net.Search.Scorer.score() */
522  public override float Score()
523  {
524  for (int i = 0; i < valSrcScorers.Length; i++)
525  {
526  vScores[i] = valSrcScorers[i].Score();
527  }
528  return qWeight * provider.CustomScore(subQueryScorer.DocID(), subQueryScorer.Score(), vScores);
529  }
530 
531  public override int Advance(int target)
532  {
533  int doc = subQueryScorer.Advance(target);
534  if (doc != NO_MORE_DOCS)
535  {
536  for (int i = 0; i < valSrcScorers.Length; i++)
537  {
538  valSrcScorers[i].Advance(doc);
539  }
540  }
541  return doc;
542  }
543  }
544 
545  public override Weight CreateWeight(Searcher searcher)
546  {
547  return new CustomWeight(this, searcher);
548  }
549 
550  /// <summary> Checks if this is strict custom scoring.
551  /// In strict custom scoring, the ValueSource part does not participate in weight normalization.
552  /// This may be useful when one wants full control over how scores are modified, and does
553  /// not care about normalizing by the ValueSource part.
554  /// One particular case where this is useful if for testing this query.
555  /// <p/>
556  /// Note: only has effect when the ValueSource part is not null.
557  /// </summary>
558  public virtual bool IsStrict()
559  {
560  return strict;
561  }
562 
563  /// <summary> Set the strict mode of this query. </summary>
564  /// <param name="strict">The strict mode to set.
565  /// </param>
566  /// <seealso cref="IsStrict()">
567  /// </seealso>
568  public virtual void SetStrict(bool strict)
569  {
570  this.strict = strict;
571  }
572 
573  /// <summary> A short name of this query, used in <see cref="ToString(String)" />.</summary>
574  public virtual System.String Name()
575  {
576  return "custom";
577  }
578  }
579 }