Add sub query explanations in DisjunctionMaxQuery#explain on no-match (#13362)

This commit is contained in:
Tim Grein 2024-05-14 17:28:14 +02:00 committed by GitHub
parent dae7181a97
commit 8e5409c9b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 61 additions and 5 deletions

View File

@ -293,6 +293,8 @@ Improvements
* GITHUB#12966: Move most of the responsibility from TaxonomyFacets implementations to TaxonomyFacets itself. * GITHUB#12966: Move most of the responsibility from TaxonomyFacets implementations to TaxonomyFacets itself.
This reduces code duplication and enables future development. (Stefan Vodita) This reduces code duplication and enables future development. (Stefan Vodita)
* GITHUB#13362: Add sub query explanations to DisjunctionMaxQuery, if the overall query didn't match. (Tim Grein)
Optimizations Optimizations
--------------------- ---------------------

View File

@ -213,12 +213,13 @@ public final class DisjunctionMaxQuery extends Query implements Iterable<Query>
boolean match = false; boolean match = false;
double max = 0; double max = 0;
double otherSum = 0; double otherSum = 0;
List<Explanation> subs = new ArrayList<>(); List<Explanation> subsOnMatch = new ArrayList<>();
List<Explanation> subsOnNoMatch = new ArrayList<>();
for (Weight wt : weights) { for (Weight wt : weights) {
Explanation e = wt.explain(context, doc); Explanation e = wt.explain(context, doc);
if (e.isMatch()) { if (e.isMatch()) {
match = true; match = true;
subs.add(e); subsOnMatch.add(e);
double score = e.getValue().doubleValue(); double score = e.getValue().doubleValue();
if (score >= max) { if (score >= max) {
otherSum += max; otherSum += max;
@ -226,6 +227,8 @@ public final class DisjunctionMaxQuery extends Query implements Iterable<Query>
} else { } else {
otherSum += score; otherSum += score;
} }
} else if (match == false) {
subsOnNoMatch.add(e);
} }
} }
if (match) { if (match) {
@ -234,9 +237,9 @@ public final class DisjunctionMaxQuery extends Query implements Iterable<Query>
tieBreakerMultiplier == 0.0f tieBreakerMultiplier == 0.0f
? "max of:" ? "max of:"
: "max plus " + tieBreakerMultiplier + " times others of:"; : "max plus " + tieBreakerMultiplier + " times others of:";
return Explanation.match(score, desc, subs); return Explanation.match(score, desc, subsOnMatch);
} else { } else {
return Explanation.noMatch("No matching clause"); return Explanation.noMatch("No matching clause", subsOnNoMatch);
} }
} }
} // end of DisjunctionMaxWeight inner class } // end of DisjunctionMaxWeight inner class

View File

@ -497,6 +497,57 @@ public class TestDisjunctionMaxQuery extends LuceneTestCase {
doTestRandomTopDocs(4, 1.0f, 0.5f, 0.05f, 0f); doTestRandomTopDocs(4, 1.0f, 0.5f, 0.05f, 0f);
} }
public void testExplainMatch() throws IOException {
// Both match
Query sub1 = tq("hed", "elephant");
Query sub2 = tq("dek", "elephant");
final DisjunctionMaxQuery dq = new DisjunctionMaxQuery(Arrays.asList(sub1, sub2), 0.0f);
final Weight dw = s.createWeight(s.rewrite(dq), ScoreMode.COMPLETE, 1);
LeafReaderContext context = (LeafReaderContext) s.getTopReaderContext();
Explanation explanation = dw.explain(context, 1);
assertEquals("max of:", explanation.getDescription());
// Two matching sub queries should be included in the explanation details
assertEquals(2, explanation.getDetails().length);
}
public void testExplainNoMatch() throws IOException {
// No match
Query sub1 = tq("abc", "elephant");
Query sub2 = tq("def", "elephant");
final DisjunctionMaxQuery dq = new DisjunctionMaxQuery(Arrays.asList(sub1, sub2), 0.0f);
final Weight dw = s.createWeight(s.rewrite(dq), ScoreMode.COMPLETE, 1);
LeafReaderContext context = (LeafReaderContext) s.getTopReaderContext();
Explanation explanation = dw.explain(context, 1);
assertEquals("No matching clause", explanation.getDescription());
// Two non-matching sub queries should be included in the explanation details
assertEquals(2, explanation.getDetails().length);
}
public void testExplainMatch_OneNonMatchingSubQuery_NotIncludedInExplanation()
throws IOException {
// Matches
Query sub1 = tq("hed", "elephant");
// Doesn't match
Query sub2 = tq("def", "elephant");
final DisjunctionMaxQuery dq = new DisjunctionMaxQuery(Arrays.asList(sub1, sub2), 0.0f);
final Weight dw = s.createWeight(s.rewrite(dq), ScoreMode.COMPLETE, 1);
LeafReaderContext context = (LeafReaderContext) s.getTopReaderContext();
Explanation explanation = dw.explain(context, 1);
assertEquals("max of:", explanation.getDescription());
// Only the matching sub query (sub1) should be included in the explanation details
assertEquals(1, explanation.getDetails().length);
}
private void doTestRandomTopDocs(int numFields, double... freqs) throws IOException { private void doTestRandomTopDocs(int numFields, double... freqs) throws IOException {
assert numFields == freqs.length; assert numFields == freqs.length;
Directory dir = newDirectory(); Directory dir = newDirectory();

View File

@ -417,7 +417,7 @@ public class CheckHits {
if (descr.startsWith("score based on ") && descr.contains("child docs in range")) { if (descr.startsWith("score based on ") && descr.contains("child docs in range")) {
assertTrue("Child doc explanations are missing", detail.length > 0); assertTrue("Child doc explanations are missing", detail.length > 0);
} }
if (detail.length > 0) { if (detail.length > 0 && expl.isMatch()) {
if (detail.length == 1 && COMPUTED_FROM_PATTERN.matcher(descr).matches() == false) { if (detail.length == 1 && COMPUTED_FROM_PATTERN.matcher(descr).matches() == false) {
// simple containment, unless it's a freq of: (which lets a query explain how the freq is // simple containment, unless it's a freq of: (which lets a query explain how the freq is
// calculated), // calculated),