Index: lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java (revision 1574538) +++ lucene/core/src/java/org/apache/lucene/search/BooleanScorer2.java (working copy) @@ -171,7 +171,7 @@ @Override public float score() throws IOException { coordinator.nrMatchers += super.nrMatchers; - return (float) super.score; + return super.score(); } }; } Index: lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java =================================================================== --- lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java (revision 1574538) +++ lucene/core/src/java/org/apache/lucene/search/DisjunctionSumScorer.java (working copy) @@ -27,8 +27,8 @@ /** The number of subscorers that provide the current match. */ protected int nrMatchers = -1; - protected double score = Float.NaN; private final float[] coord; + private final int[] matches; /** Construct a DisjunctionScorer. * @param weight The weight to be used. @@ -41,6 +41,7 @@ if (numScorers <= 1) { throw new IllegalArgumentException("There must be at least 2 subScorers"); } + matches = new int[numScorers]; this.coord = coord; } @@ -49,23 +50,19 @@ final Scorer sub = subScorers[0]; doc = sub.docID(); if (doc != NO_MORE_DOCS) { - score = sub.score(); + matches[0] = 0; nrMatchers = 1; - countMatches(1); - countMatches(2); + gatherMatches(1); + gatherMatches(2); } } - // TODO: this currently scores, but so did the previous impl // TODO: remove recursion. - // TODO: if we separate scoring, out of here, - // then change freq() to just always compute it from scratch - private void countMatches(int root) throws IOException { + private void gatherMatches(int root) throws IOException { if (root < numScorers && subScorers[root].docID() == doc) { - nrMatchers++; - score += subScorers[root].score(); - countMatches((root<<1)+1); - countMatches((root<<1)+2); + matches[nrMatchers++] = root; + gatherMatches((root<<1)+1); + gatherMatches((root<<1)+2); } } @@ -74,7 +71,15 @@ */ @Override public float score() throws IOException { - return (float)score * coord[nrMatchers]; + double score = 0.0; + for(int i=0;isims. */ + public SwitchingSimilarity(Similarity... sims) { + this.sims = sims; + } + + @Override + public long computeNorm(FieldInvertState state) { + return sims[0].computeNorm(state); + } + + @Override + public SimWeight computeWeight(float queryBoost, CollectionStatistics collectionStats, TermStatistics... termStats) { + SimWeight subStats[] = new SimWeight[sims.length]; + for (int i = 0; i < subStats.length; i++) { + subStats[i] = sims[i].computeWeight(queryBoost, collectionStats, termStats); + } + return new SwitchingStats(subStats); + } + + @Override + public SimScorer simScorer(SimWeight stats, AtomicReaderContext context) throws IOException { + SimScorer subScorers[] = new SimScorer[sims.length]; + for (int i = 0; i < subScorers.length; i++) { + subScorers[i] = sims[i].simScorer(((SwitchingStats)stats).subStats[i], context); + } + return new SwitchingSimScorer(subScorers); + } + + /** Moves to the next sub-scorer. */ + public void switchScorer() { + scorerIndex++; + if (scorerIndex == sims.length) { + scorerIndex = 0; + } + } + + public class SwitchingSimScorer extends SimScorer { + private final SimScorer subScorers[]; + + SwitchingSimScorer(SimScorer subScorers[]) { + this.subScorers = subScorers; + } + + @Override + public float score(int doc, float freq) { + return subScorers[scorerIndex].score(doc, freq); + } + + @Override + public Explanation explain(int doc, Explanation freq) { + return subScorers[scorerIndex].explain(doc, freq); + } + + @Override + public float computeSlopFactor(int distance) { + return subScorers[0].computeSlopFactor(distance); + } + + @Override + public float computePayloadFactor(int doc, int start, int end, BytesRef payload) { + return subScorers[0].computePayloadFactor(doc, start, end, payload); + } + } + + static class SwitchingStats extends SimWeight { + final SimWeight subStats[]; + + SwitchingStats(SimWeight subStats[]) { + this.subStats = subStats; + } + + @Override + public float getValueForNormalization() { + float sum = 0.0f; + for (SimWeight stat : subStats) { + sum += stat.getValueForNormalization(); + } + return sum / subStats.length; + } + + @Override + public void normalize(float queryNorm, float topLevelBoost) { + for (SimWeight stat : subStats) { + stat.normalize(queryNorm, topLevelBoost); + } + } + } +} Property changes on: lucene/misc/src/java/org/apache/lucene/search/SwitchingSimilarity.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/misc/src/test/org/apache/lucene/search/TestSwitchingSimilarityCollector.java =================================================================== --- lucene/misc/src/test/org/apache/lucene/search/TestSwitchingSimilarityCollector.java (revision 0) +++ lucene/misc/src/test/org/apache/lucene/search/TestSwitchingSimilarityCollector.java (working copy) @@ -0,0 +1,114 @@ +package org.apache.lucene.search; + +/* + * 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 org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.similarities.BM25Similarity; +import org.apache.lucene.search.similarities.DefaultSimilarity; +import org.apache.lucene.search.similarities.LMJelinekMercerSimilarity; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.LuceneTestCase; + +public class TestSwitchingSimilarityCollector extends LuceneTestCase { + + public void testBasic() throws Exception { + Directory dir = newDirectory(); + + IndexWriterConfig iwc = newIndexWriterConfig(TEST_VERSION_CURRENT, + new MockAnalyzer(random())); + iwc.setSimilarity(new SwitchingSimilarity(new DefaultSimilarity(), + new BM25Similarity())); + RandomIndexWriter w = new RandomIndexWriter(random(), dir, iwc); + + Document doc = new Document(); + doc.add(newTextField("field", "a x", Field.Store.NO)); + w.addDocument(doc); + + doc.add(newTextField("field", "a y", Field.Store.NO)); + w.addDocument(doc); + + doc.add(newTextField("field", "b x", Field.Store.NO)); + w.addDocument(doc); + + IndexReader r = w.getReader(); + w.close(); + + // TermQuery + runQuery(r, new TermQuery(new Term("field", "a"))); + + // BooleanQuery + BooleanQuery bq = new BooleanQuery(); + bq.add(new TermQuery(new Term("field", "a")), BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("field", "y")), BooleanClause.Occur.SHOULD); + runQuery(r, bq); + + r.close(); + dir.close(); + } + + private void runQuery(IndexReader r, Query q) throws Exception { + if (VERBOSE) { + System.out.println("\nTEST: run query=" + q); + } + + TopScoreDocCollector c1 = TopScoreDocCollector.create(10, true); + TopScoreDocCollector c2 = TopScoreDocCollector.create(10, true); + TopScoreDocCollector c3 = TopScoreDocCollector.create(10, true); + SwitchingSimilarity sim = new SwitchingSimilarity(new DefaultSimilarity(), + new BM25Similarity(), + new LMJelinekMercerSimilarity(0.3f)); + + // NOTE: we must make a new searcher for every query, + // because SwitchingSimilarity has per-hit state. This + // is fine (it's cheap: basically just wraps the + // reader): + IndexSearcher s = newSearcher(r); + s.setSimilarity(sim); + s.search(q, + new SwitchingSimilarityCollector(sim, c1, c2, c3)); + + TopDocs hits1 = c1.topDocs(); + TopDocs hits2 = c2.topDocs(); + TopDocs hits3 = c3.topDocs(); + + assertEquals(hits1.totalHits, hits2.totalHits); + assertEquals(hits1.totalHits, hits3.totalHits); + assertEquals(hits1.scoreDocs.length, hits2.scoreDocs.length); + assertEquals(hits1.scoreDocs.length, hits3.scoreDocs.length); + boolean sawDifference = false; + for(int i=0;i