You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by at...@apache.org on 2025/04/18 10:09:16 UTC

(lucene) 02/09: More stuff without wiring

This is an automated email from the ASF dual-hosted git repository.

atri pushed a commit to branch index_binning_changes
in repository https://gitbox.apache.org/repos/asf/lucene.git

commit 47237db09a3b23e6ab0062d22a4df9cdf31f53c8
Author: Atri Sharma <at...@192.168.1.25>
AuthorDate: Fri Apr 18 01:10:56 2025 +0530

    More stuff without wiring
---
 .../lucene/codecs/TestBinMapReaderWriter.java      | 144 ++++++++++++
 .../lucene/codecs/TestBinQualityMetrics.java       |  51 +++++
 .../lucene/codecs/TestDocBinningGraphBuilder.java  | 103 +++++++++
 .../apache/lucene/codecs/TestDocGraphBuilder.java  |  94 ++++++++
 .../lucene103/TestLucene103PostingsFormat.java     | 247 +++++++++++++++++++++
 .../org/apache/lucene/index/TestBinScoreUtil.java  | 123 ++++++++++
 .../lucene/search/TestAnytimeRankingNDCG.java      | 116 ++++++++++
 .../search/TestAnytimeRankingRelevanceMatch.java   | 121 ++++++++++
 .../search/TestAnytimeRankingRelevanceSearch.java  | 214 ++++++++++++++++++
 .../lucene/search/TestAnytimeRankingSearch.java    | 216 ++++++++++++++++++
 .../lucene/search/TestBinningRelevanceBoost.java   | 136 ++++++++++++
 .../lucene/util/TestHeuristicSLAEstimator.java     | 130 +++++++++++
 12 files changed, 1695 insertions(+)

diff --git a/lucene/core/src/test/org/apache/lucene/codecs/TestBinMapReaderWriter.java b/lucene/core/src/test/org/apache/lucene/codecs/TestBinMapReaderWriter.java
new file mode 100644
index 00000000000..15d208d884f
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/codecs/TestBinMapReaderWriter.java
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.codecs;
+
+import org.apache.lucene.index.SegmentInfo;
+import org.apache.lucene.index.SegmentReadState;
+import org.apache.lucene.index.SegmentWriteState;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.IOContext;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.apache.lucene.util.StringHelper;
+import org.apache.lucene.util.Version;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collections;
+
+/**
+ * Unit tests for {@link BinMapWriter} and {@link BinMapReader} using {@link
+ * DocBinningGraphBuilder}.
+ */
+public class TestBinMapReaderWriter extends LuceneTestCase {
+
+  private Directory dir;
+  private SegmentWriteState writeState;
+  private SegmentReadState readState;
+
+  @Before
+  public void setUpTest() throws Exception {
+
+    super.setUp();
+    Path tempDir = createTempDir("binmap-test");
+    dir = FSDirectory.open(tempDir);
+    SegmentInfo info =
+        new SegmentInfo(
+            dir,
+            Version.LATEST,
+            Version.LATEST,
+            "_1",
+            16,
+            false,
+            false,
+            Codec.getDefault(),
+            Collections.emptyMap(),
+            StringHelper.randomId(),
+            Collections.emptyMap(),
+            null);
+    readState = new SegmentReadState(dir, info, null, newIOContext(random()));
+    writeState = new SegmentWriteState(null, dir, info, null, null, IOContext.DEFAULT);
+  }
+
+  @After
+  public void tearDownTest() throws Exception {
+    super.tearDown();
+    dir.close();
+  }
+
+  @Test
+  public void testBinMapWriteAndRead() throws IOException {
+    final int maxDoc = writeState.segmentInfo.maxDoc();
+    final int numBins = 4;
+
+    InMemorySparseEdgeGraph graph = new InMemorySparseEdgeGraph();
+    for (int i = 0; i < maxDoc; i++) {
+      if (i < maxDoc - 1) {
+        graph.addEdge(i, i + 1, 1.0f);
+      }
+      // ensure all nodes are initialized in the graph
+      graph.ensureVertex(i);
+    }
+
+    int[] docToBin = DocBinningGraphBuilder.computeBins(graph, maxDoc, numBins);
+    BinMapWriter writer = new BinMapWriter(writeState.directory, writeState, docToBin, numBins);
+
+    writer.close();
+
+    try (BinMapReader reader = new BinMapReader(dir, readState)) {
+      assertEquals("bin count mismatch", numBins, reader.getBinCount());
+      for (int i = 0; i < maxDoc; i++) {
+        assertEquals("bin mismatch at docID " + i, docToBin[i], reader.getBin(i));
+      }
+    }
+  }
+
+  @Test
+  public void testEmptySegment() throws IOException {
+    Path emptyDir = createTempDir("binmap-empty");
+    Directory directory = FSDirectory.open(emptyDir);
+
+    SegmentInfo emptyInfo =
+        new SegmentInfo(
+            directory,
+            Version.LATEST,
+            Version.LATEST,
+            "_empty",
+            0,
+            false,
+            false,
+            Codec.getDefault(),
+            Collections.emptyMap(),
+            StringHelper.randomId(),
+            Collections.emptyMap(),
+            null);
+
+    SegmentWriteState emptyWriteState =
+        new SegmentWriteState(null, directory, emptyInfo, null, null, IOContext.DEFAULT);
+    SegmentReadState emptyReadState =
+        new SegmentReadState(directory, emptyInfo, null, IOContext.DEFAULT);
+
+    int[] emptyBinArray = {
+      /*nothing in here = array with no slots*/
+    };
+
+    // Nothing to write since maxDoc is 0, but simulate writing header
+    BinMapWriter writer =
+        new BinMapWriter(emptyWriteState.directory, emptyWriteState, emptyBinArray, 0);
+    writer.close();
+
+    // Reader should read back bin count as 0
+    try (BinMapReader reader = new BinMapReader(directory, emptyReadState)) {
+      assertEquals(0, reader.getBinCount());
+    }
+
+    directory.close();
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/TestBinQualityMetrics.java b/lucene/core/src/test/org/apache/lucene/codecs/TestBinQualityMetrics.java
new file mode 100644
index 00000000000..f9715e0a018
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/codecs/TestBinQualityMetrics.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.codecs;
+
+import org.apache.lucene.tests.util.LuceneTestCase;
+
+import java.util.Random;
+
+/** Unit tests for verifying bin balance and quality. */
+public class TestBinQualityMetrics extends LuceneTestCase {
+
+  public void testStandardDeviationAndGini() {
+    int[] counts = new int[] {100, 90, 110, 95, 105}; // reasonably balanced
+    double stddev = BinQualityMetrics.computeStdDev(counts);
+    double gini = BinQualityMetrics.computeGiniIndex(counts);
+    assertTrue("Expected low stddev", stddev < 10.0);
+    assertTrue("Expected low Gini", gini < 0.05);
+  }
+
+  public void testUnbalancedBins() {
+    int[] counts = new int[] {300, 0, 0, 0, 0}; // extremely skewed
+    double stddev = BinQualityMetrics.computeStdDev(counts);
+    double gini = BinQualityMetrics.computeGiniIndex(counts);
+    assertTrue("Expected high stddev", stddev > 100.0);
+    assertTrue("Expected high Gini", gini >= 0.8);
+  }
+
+  public void testRandomBins() {
+    Random random = new Random(42);
+    int[] counts = new int[16];
+    for (int i = 0; i < counts.length; i++) {
+      counts[i] = random.nextInt(100);
+    }
+    double gini = BinQualityMetrics.computeGiniIndex(counts);
+    assertTrue("Gini index must be in range [0,1]", gini >= 0.0 && gini <= 1.0);
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/TestDocBinningGraphBuilder.java b/lucene/core/src/test/org/apache/lucene/codecs/TestDocBinningGraphBuilder.java
new file mode 100644
index 00000000000..39902bb8682
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/codecs/TestDocBinningGraphBuilder.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.codecs;
+
+import org.apache.lucene.tests.util.LuceneTestCase;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+public class TestDocBinningGraphBuilder extends LuceneTestCase {
+
+  public void testSimpleLinearGraph() throws IOException {
+    final int maxDoc = 8;
+    final int numBins = 4;
+
+    SparseEdgeGraph graph = new InMemorySparseEdgeGraph();
+    for (int i = 0; i < maxDoc - 1; i++) {
+      graph.addEdge(i, i + 1, 1.0f);
+    }
+
+    int[] docToBin = DocBinningGraphBuilder.computeBins(graph, maxDoc, numBins);
+    assertEquals(maxDoc, docToBin.length);
+
+    Set<Integer> seenBins = new HashSet<>();
+    for (int bin : docToBin) {
+      assertTrue("Bin must be non-negative", bin >= 0);
+      assertTrue("Bin must be less than numBins", bin < numBins);
+      seenBins.add(bin);
+    }
+
+    assertTrue("Expected to use multiple bins", seenBins.size() > 1);
+  }
+
+  public void testSingleBinAssignment() throws IOException {
+    final int maxDoc = 5;
+    final int numBins = 1;
+
+    SparseEdgeGraph graph = new InMemorySparseEdgeGraph();
+    for (int i = 0; i < maxDoc - 1; i++) {
+      graph.addEdge(i, i + 1, 1.0f);
+    }
+
+    int[] docToBin = DocBinningGraphBuilder.computeBins(graph, maxDoc, numBins);
+    for (int bin : docToBin) {
+      assertEquals(0, bin);
+    }
+  }
+
+  public void testDisconnectedGraph() throws IOException {
+    final int maxDoc = 6;
+    final int numBins = 2;
+
+    SparseEdgeGraph graph = new InMemorySparseEdgeGraph(); // no edges
+    for (int i = 0; i < maxDoc; i++) {
+      graph.ensureVertex(i);
+    }
+
+    int[] docToBin = DocBinningGraphBuilder.computeBins(graph, maxDoc, numBins);
+    assertEquals(maxDoc, docToBin.length);
+    for (int bin : docToBin) {
+      assertTrue(bin >= 0 && bin < numBins);
+    }
+  }
+
+  public void testMinimalDocsSplit() throws IOException {
+    final int maxDoc = 2;
+    final int numBins = 2;
+
+    SparseEdgeGraph graph = new InMemorySparseEdgeGraph();
+    graph.addEdge(0, 1, 1.0f);
+
+    int[] docToBin = DocBinningGraphBuilder.computeBins(graph, maxDoc, numBins);
+    assertEquals(2, docToBin.length);
+    assertNotEquals(docToBin[0], docToBin[1]);
+  }
+
+  public void testInvalidBinCount() {
+    SparseEdgeGraph graph = new InMemorySparseEdgeGraph();
+    expectThrows(
+        IllegalArgumentException.class, () -> DocBinningGraphBuilder.computeBins(graph, 10, 3));
+  }
+
+  public void testZeroDocsFails() {
+    SparseEdgeGraph graph = new InMemorySparseEdgeGraph();
+    expectThrows(
+        IllegalArgumentException.class, () -> DocBinningGraphBuilder.computeBins(graph, 0, 2));
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/TestDocGraphBuilder.java b/lucene/core/src/test/org/apache/lucene/codecs/TestDocGraphBuilder.java
new file mode 100644
index 00000000000..69c665b64bd
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/codecs/TestDocGraphBuilder.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.codecs;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.tests.analysis.MockAnalyzer;
+import org.apache.lucene.tests.analysis.MockTokenizer;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/** Unit test for {@link DocGraphBuilder} without external analyzer dependencies. */
+public class TestDocGraphBuilder extends LuceneTestCase {
+
+  @Test
+  public void testSparseEdgeGraphConstruction() throws Exception {
+    Path indexPath = createTempDir("doc-graph-builder");
+    try (Directory dir = FSDirectory.open(indexPath)) {
+      IndexWriterConfig iwc =
+          new IndexWriterConfig(new MockAnalyzer(random(), MockTokenizer.SIMPLE, true));
+      iwc.setUseCompoundFile(false);
+      iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        addDoc(writer, "alpha beta gamma");
+        addDoc(writer, "beta gamma delta");
+        addDoc(writer, "alpha delta epsilon");
+        addDoc(writer, "zeta eta theta");
+        writer.commit();
+      }
+
+      try (IndexReader reader = DirectoryReader.open(dir)) {
+        LeafReader leaf = reader.leaves().get(0).reader();
+
+        DocGraphBuilder builder = new DocGraphBuilder("content", 2);
+        SparseEdgeGraph graph = builder.build(leaf);
+
+        assertEquals("Graph should contain one node per doc", leaf.maxDoc(), graph.size());
+
+        for (int docID = 0; docID < leaf.maxDoc(); docID++) {
+          int[] neighbors = graph.getNeighbors(docID);
+          float[] weights = graph.getWeights(docID);
+
+          assertEquals(neighbors.length, weights.length);
+          for (int i = 0; i < neighbors.length; i++) {
+            assertNotEquals("Self-loops are not expected", docID, neighbors[i]);
+            assertTrue("Edge weight should be positive", weights[i] > 0);
+          }
+        }
+      }
+    }
+  }
+
+  private void addDoc(IndexWriter writer, String content) throws IOException {
+    Document doc = new Document();
+
+    FieldType tvType = new FieldType(TextField.TYPE_STORED);
+    tvType.setStoreTermVectors(true);
+    tvType.setStoreTermVectorPositions(true);
+    tvType.setStoreTermVectorOffsets(true);
+    tvType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+
+    Field field = new Field("content", content, tvType);
+    doc.add(field);
+    writer.addDocument(doc);
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/codecs/lucene103/TestLucene103PostingsFormat.java b/lucene/core/src/test/org/apache/lucene/codecs/lucene103/TestLucene103PostingsFormat.java
index 210fde9fa23..bca6e565bf7 100644
--- a/lucene/core/src/test/org/apache/lucene/codecs/lucene103/TestLucene103PostingsFormat.java
+++ b/lucene/core/src/test/org/apache/lucene/codecs/lucene103/TestLucene103PostingsFormat.java
@@ -20,6 +20,8 @@ import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+
+import org.apache.lucene.codecs.BinMapReader;
 import org.apache.lucene.codecs.Codec;
 import org.apache.lucene.codecs.CompetitiveImpactAccumulator;
 import org.apache.lucene.codecs.lucene103.Lucene103PostingsReader.MutableImpactList;
@@ -27,10 +29,18 @@ import org.apache.lucene.codecs.lucene103.blocktree.FieldReader;
 import org.apache.lucene.codecs.lucene103.blocktree.Stats;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.FilterLeafReader;
 import org.apache.lucene.index.Impact;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReader;
+import org.apache.lucene.index.SegmentCommitInfo;
+import org.apache.lucene.index.SegmentInfos;
+import org.apache.lucene.index.SegmentReadState;
+import org.apache.lucene.index.SegmentReader;
 import org.apache.lucene.store.ByteArrayDataInput;
 import org.apache.lucene.store.ByteArrayDataOutput;
 import org.apache.lucene.store.Directory;
@@ -134,6 +144,243 @@ public class TestLucene103PostingsFormat extends BasePostingsFormatTestCase {
             new Impact(1005, -3L)));
   }
 
+    public void testBinMapIsWrittenOnFlush() throws Exception {
+    try (Directory dir = newDirectory()) {
+      IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+      iwc.setCodec(getCodec());
+      iwc.setUseCompoundFile(false);
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        for (int i = 0; i < 3; i++) {
+          Document doc = new Document();
+          FieldType ft = new FieldType(TextField.TYPE_STORED);
+          ft.setStoreTermVectors(true);
+          ft.putAttribute("doBinning", "true");
+          Field field = new Field("field", "value" + i, ft);
+          doc.add(field);
+          writer.addDocument(doc);
+        }
+        writer.commit();
+      }
+
+      SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
+      SegmentCommitInfo info = infos.info(0);
+      String segmentName = info.info.name;
+      String segmentSuffix = null;
+      String binmapFile = null;
+
+      for (String file : dir.listAll()) {
+        if (file.startsWith(segmentName + "_") && file.endsWith(".binmap")) {
+          binmapFile = file;
+          segmentSuffix =
+              file.substring(segmentName.length() + 1, file.length() - ".binmap".length());
+          break;
+        }
+      }
+
+      assertNotNull("binmap file should exist", binmapFile);
+
+      SegmentReadState readState =
+          new SegmentReadState(dir, info.info, null, newIOContext(random()), segmentSuffix);
+
+      try (BinMapReader binMap = new BinMapReader(dir, readState)) {
+        assertEquals(3, binMap.getBinArrayCopy().length);
+        for (int i = 0; i < 3; i++) {
+          int bin = binMap.getBin(i);
+          assertTrue("Bin should be non-negative", bin >= 0);
+        }
+      }
+    }
+  }
+
+  public void testBinMapWithCompoundFileFormat() throws Exception {
+    try (Directory dir = newDirectory()) {
+      IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+      iwc.setCodec(getCodec());
+      iwc.setUseCompoundFile(true);
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        for (int i = 0; i < 4; i++) {
+          Document doc = new Document();
+          FieldType ft = new FieldType(TextField.TYPE_STORED);
+          ft.setStoreTermVectors(true);
+          ft.putAttribute("doBinning", "true");
+          Field field = new Field("field", "term" + i, ft);
+          doc.add(field);
+          writer.addDocument(doc);
+        }
+        writer.commit();
+      }
+
+      SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
+      SegmentCommitInfo info = infos.info(0);
+      String segmentName = info.info.name;
+
+      try (Directory cfsDir =
+          info.info.getCodec().compoundFormat().getCompoundReader(dir, info.info)) {
+        String binmapFile = null;
+        String segmentSuffix = null;
+        for (String file : cfsDir.listAll()) {
+          if (file.startsWith(segmentName + "_") && file.endsWith(".binmap")) {
+            binmapFile = file;
+            segmentSuffix =
+                file.substring(segmentName.length() + 1, file.length() - ".binmap".length());
+            break;
+          }
+        }
+
+        assertNotNull("compound file should contain binmap", binmapFile);
+
+        SegmentReadState readState =
+            new SegmentReadState(cfsDir, info.info, null, newIOContext(random()), segmentSuffix);
+
+        try (BinMapReader binMap = new BinMapReader(cfsDir, readState)) {
+          assertEquals(4, binMap.getBinArrayCopy().length);
+          for (int i = 0; i < 4; i++) {
+            int bin = binMap.getBin(i);
+            assertTrue("Bin should be non-negative", bin >= 0);
+          }
+        }
+      }
+    }
+  }
+
+  public void testBinMapBinningDisabled() throws Exception {
+    try (Directory dir = newDirectory()) {
+      IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+      iwc.setCodec(getCodec());
+      iwc.setUseCompoundFile(false); // standalone files
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        Document doc = new Document();
+        doc.add(newTextField("field", "term value relevance", Field.Store.NO)); // no binning
+        writer.addDocument(doc);
+        writer.commit();
+      }
+
+      for (String file : dir.listAll()) {
+        assertFalse(
+            "binmap should not exist when binning is not enabled", file.endsWith(".binmap"));
+      }
+    }
+  }
+
+  public void testExplicitBinCountIsHonored() throws Exception {
+    try (Directory dir = newDirectory()) {
+      IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+      iwc.setCodec(getCodec());
+      iwc.setUseCompoundFile(false);
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        for (int i = 0; i < 12; i++) {
+          Document doc = new Document();
+          FieldType ft = new FieldType(TextField.TYPE_STORED);
+          ft.setStoreTermVectors(true);
+          ft.putAttribute("doBinning", "true");
+          ft.putAttribute("bin.count", "4");
+          Field field = new Field("field", "lucene term doc", ft);
+          doc.add(field);
+          writer.addDocument(doc);
+        }
+        writer.commit();
+      }
+
+      SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
+      SegmentCommitInfo info = infos.info(0);
+
+      String segmentName = info.info.name;
+      String segmentSuffix = null;
+      String binmapFile = null;
+
+      for (String file : dir.listAll()) {
+        if (file.startsWith(segmentName + "_") && file.endsWith(".binmap")) {
+          binmapFile = file;
+          segmentSuffix =
+              file.substring(segmentName.length() + 1, file.length() - ".binmap".length());
+          break;
+        }
+      }
+
+      assertNotNull("binmap file should exist", binmapFile);
+
+      SegmentReadState readState =
+          new SegmentReadState(dir, info.info, null, newIOContext(random()), segmentSuffix);
+
+      try (BinMapReader binMap = new BinMapReader(dir, readState)) {
+        assertEquals("Expected 4 bins", 4, binMap.getBinCount());
+        int[] bins = binMap.getBinArrayCopy();
+        assertEquals(12, bins.length);
+        for (int bin : bins) {
+          assertTrue("bin id should be in [0, 3]", bin >= 0 && bin < 4);
+        }
+      }
+    }
+  }
+
+  public void testBinMapIsPreservedAndUsableAfterMerge() throws Exception {
+    try (Directory dir = newDirectory()) {
+      IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+      iwc.setCodec(getCodec());
+      iwc.setUseCompoundFile(random().nextBoolean());
+      iwc.setMaxBufferedDocs(2);
+
+      FieldType ft = new FieldType(TextField.TYPE_NOT_STORED);
+      ft.setStoreTermVectors(false);
+      ft.putAttribute("postingsFormat", "Lucene103");
+      ft.putAttribute("doBinning", "true");
+      ft.putAttribute("bin.count", "4");
+      ft.freeze();
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        for (int i = 0; i < 24; i++) {
+          Document doc = new Document();
+          doc.add(new Field("field", "lucene merge binning test", ft));
+          writer.addDocument(doc);
+        }
+        writer.commit();
+        writer.forceMerge(1);
+      }
+
+      // Confirm single merged segment exists
+      try (DirectoryReader reader = DirectoryReader.open(dir)) {
+        assertEquals("Expected single merged segment", 1, reader.leaves().size());
+
+        LeafReader leaf = reader.leaves().get(0).reader();
+        SegmentReader segmentReader = (SegmentReader) FilterLeafReader.unwrap(leaf);
+        SegmentCommitInfo info = segmentReader.getSegmentInfo();
+        String segmentName = info.info.name;
+
+        // Extract actual binmap file from Directory
+        String binmapFile = null;
+        for (String file : dir.listAll()) {
+          if (file.startsWith(segmentName + "_") && file.endsWith(".binmap")) {
+            binmapFile = file;
+            break;
+          }
+        }
+
+        assertNotNull("Expected .binmap file after merge", binmapFile);
+
+        String suffix =
+            binmapFile.substring(
+                segmentName.length() + 1, binmapFile.length() - ".binmap".length());
+
+        SegmentReadState readState =
+            new SegmentReadState(
+                dir, info.info, segmentReader.getFieldInfos(), newIOContext(random()), suffix);
+
+        try (BinMapReader binMap = new BinMapReader(dir, readState)) {
+          assertEquals("Expected 4 bins", 4, binMap.getBinCount());
+          int[] bins = binMap.getBinArrayCopy();
+          assertEquals("Unexpected number of docs", 24, bins.length);
+          for (int bin : bins) {
+            assertTrue("Invalid bin ID", bin >= 0 && bin < 4);
+          }
+        }
+      }
+    }
+  }
+
   private void doTestImpactSerialization(List<Impact> impacts) throws IOException {
     CompetitiveImpactAccumulator acc = new CompetitiveImpactAccumulator();
     for (Impact impact : impacts) {
diff --git a/lucene/core/src/test/org/apache/lucene/index/TestBinScoreUtil.java b/lucene/core/src/test/org/apache/lucene/index/TestBinScoreUtil.java
new file mode 100644
index 00000000000..4b5d7c1b1b1
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/index/TestBinScoreUtil.java
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.index;
+
+import org.apache.lucene.codecs.lucene103.Lucene103PostingsFormat;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MMapDirectory;
+import org.apache.lucene.tests.analysis.MockAnalyzer;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.apache.lucene.tests.util.TestUtil;
+
+import java.nio.file.Path;
+
+public class TestBinScoreUtil extends LuceneTestCase {
+
+  private static final String FIELD = "field";
+
+  private Directory dir;
+  private IndexWriter writer;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    Path path = createTempDir("binutil");
+    dir = new MMapDirectory(path);
+    IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+    iwc.setCodec(TestUtil.alwaysPostingsFormat(new Lucene103PostingsFormat()));
+    iwc.setUseCompoundFile(false);
+    writer = new IndexWriter(dir, iwc);
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    super.tearDown();
+
+    writer.close();
+    dir.close();
+  }
+
+  public void testSingleSegmentWrapping() throws Exception {
+    for (int i = 0; i < 5; i++) {
+      Document doc = new Document();
+      doc.add(newBinningField(FIELD, "term " + i));
+      writer.addDocument(doc);
+    }
+    writer.commit();
+
+    try (DirectoryReader reader = DirectoryReader.open(dir)) {
+      IndexReader wrapped = BinScoreUtil.wrap(reader);
+      for (LeafReaderContext ctx : wrapped.leaves()) {
+        LeafReader leaf = ctx.reader();
+        assertTrue(leaf instanceof BinScoreLeafReader);
+        BinScoreReader bin = ((BinScoreLeafReader) leaf).getBinScoreReader();
+        assertTrue(bin.getBinCount() > 0);
+        for (int docID = 0; docID < leaf.maxDoc(); docID++) {
+          int b = bin.getBinForDoc(docID);
+          assertTrue("bin must be non-negative", b >= 0);
+        }
+      }
+    }
+  }
+
+  public void testMultiSegmentWrapping() throws Exception {
+    for (int i = 0; i < 3; i++) {
+      Document doc = new Document();
+      doc.add(newBinningField(FIELD, "segment " + i));
+      writer.addDocument(doc);
+      writer.commit();
+    }
+
+    try (DirectoryReader reader = DirectoryReader.open(dir)) {
+      IndexReader wrapped = BinScoreUtil.wrap(reader);
+      for (LeafReaderContext ctx : wrapped.leaves()) {
+        LeafReader leaf = ctx.reader();
+        assertTrue(leaf instanceof BinScoreLeafReader);
+      }
+    }
+  }
+
+  public void testCompoundFileWrapping() throws Exception {
+    writer.getConfig().setUseCompoundFile(true);
+    for (int i = 0; i < 4; i++) {
+      Document doc = new Document();
+      doc.add(newBinningField(FIELD, "compound " + i));
+      writer.addDocument(doc);
+    }
+    writer.commit();
+
+    try (DirectoryReader reader = DirectoryReader.open(dir)) {
+      IndexReader wrapped = BinScoreUtil.wrap(reader);
+      for (LeafReaderContext ctx : wrapped.leaves()) {
+        LeafReader leaf = ctx.reader();
+        assertTrue(leaf instanceof BinScoreLeafReader);
+      }
+    }
+  }
+
+  private static Field newBinningField(String name, String value) {
+    FieldType type = new FieldType(TextField.TYPE_NOT_STORED);
+    type.setStoreTermVectors(true);
+    type.putAttribute("doBinning", "true");
+    type.freeze();
+    return new Field(name, value, type);
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingNDCG.java b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingNDCG.java
new file mode 100644
index 00000000000..d7198c1f58d
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingNDCG.java
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.search;
+
+import org.apache.lucene.codecs.lucene101.Lucene101Codec;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.similarities.BM25Similarity;
+import org.apache.lucene.store.ByteBuffersDirectory;
+import org.apache.lucene.tests.analysis.MockAnalyzer;
+import org.apache.lucene.tests.store.MockDirectoryWrapper;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.apache.lucene.tests.util.TestUtil;
+
+import java.util.Arrays;
+
+public class TestAnytimeRankingNDCG extends LuceneTestCase {
+
+  public void testNDCGDoesNotDiverge() throws Exception {
+    MockDirectoryWrapper dir = new MockDirectoryWrapper(random(), new ByteBuffersDirectory());
+    IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()));
+    iwc.setCodec(new Lucene101Codec());
+
+    FieldType ft = new FieldType(TextField.TYPE_NOT_STORED);
+    ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+    ft.setTokenized(true);
+    ft.putAttribute("postingsFormat", "Lucene101");
+    ft.putAttribute("doBinning", "true");
+    ft.putAttribute("bin.count", "4");
+    ft.freeze();
+
+    try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+      for (int i = 0; i < 500; i++) {
+        Document doc = new Document();
+        StringBuilder content = new StringBuilder();
+
+        if (i % 50 == 0) {
+          content.append("lucene high quality scoring search");
+        } else {
+          content.append("random filler ").append(TestUtil.randomSimpleString(random()));
+        }
+
+        doc.add(new Field("field", content.toString(), ft));
+        writer.addDocument(doc);
+      }
+      writer.commit();
+    }
+
+    DirectoryReader reader = DirectoryReader.open(dir);
+    IndexSearcher baseline = new IndexSearcher(reader);
+    baseline.setSimilarity(new BM25Similarity());
+
+    AnytimeRankingSearcher anytime = new AnytimeRankingSearcher(baseline, 10, 5, "field");
+
+    TopDocs baselineDocs = baseline.search(new TermQuery(new Term("field", "lucene")), 10);
+    TopDocs anytimeDocs = anytime.search(new TermQuery(new Term("field", "lucene")));
+
+    double baselineNDCG = computeNDCG(baselineDocs);
+    double anytimeNDCG = computeNDCG(anytimeDocs);
+
+    // Ensure that NDCG does not diverge by more than 0.1
+    assertEquals("NDCG values should be close", baselineNDCG, anytimeNDCG, 0.1);
+
+    reader.close();
+    dir.close();
+  }
+
+  private double computeNDCG(TopDocs topDocs) {
+    int[] gains = new int[topDocs.scoreDocs.length];
+    for (int i = 0; i < topDocs.scoreDocs.length; i++) {
+      gains[i] = (topDocs.scoreDocs[i].doc % 50 == 0) ? 3 : 0;
+    }
+
+    double dcg = 0.0;
+    for (int i = 0; i < gains.length; i++) {
+      int rel = gains[i];
+      if (rel > 0) {
+        dcg += (Math.pow(2, rel) - 1) / (Math.log(i + 2) / Math.log(2));
+      }
+    }
+
+    Arrays.sort(gains);
+    double idcg = 0.0;
+    for (int i = gains.length - 1; i >= 0; i--) {
+      int rel = gains[i];
+      if (rel > 0) {
+        int rank = gains.length - i;
+        idcg += (Math.pow(2, rel) - 1) / (Math.log(rank + 1) / Math.log(2));
+      }
+    }
+
+    return idcg == 0.0 ? 0.0 : dcg / idcg;
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingRelevanceMatch.java b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingRelevanceMatch.java
new file mode 100644
index 00000000000..80d17887f3d
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingRelevanceMatch.java
@@ -0,0 +1,121 @@
+/*
+ * 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
+ */
+package org.apache.lucene.search;
+
+import org.apache.lucene.codecs.lucene101.Lucene101Codec;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.similarities.BM25Similarity;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.MMapDirectory;
+import org.apache.lucene.tests.analysis.MockAnalyzer;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.junit.Test;
+
+public class TestAnytimeRankingRelevanceMatch extends LuceneTestCase {
+
+  private static final int DOC_COUNT = 20000;
+  private static final int RELEVANT_FREQ = 200;
+
+  @Test
+  public void testRelevanceMetricsComparison() throws Exception {
+    Directory dir = new MMapDirectory(createTempDir("relevance-test"));
+    IndexWriterConfig config = new IndexWriterConfig(new MockAnalyzer(random()));
+    config.setCodec(new Lucene101Codec());
+    config.setUseCompoundFile(false);
+    config.setMaxBufferedDocs(100);
+
+    FieldType ft = new FieldType();
+    ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+    ft.setTokenized(true);
+    ft.setStored(false);
+    ft.putAttribute("postingsFormat", "Lucene101");
+    ft.putAttribute("doBinning", "true");
+    ft.putAttribute("bin.count", "4");
+    ft.freeze();
+
+    try (IndexWriter writer = new IndexWriter(dir, config)) {
+      for (int i = 0; i < DOC_COUNT; i++) {
+        Document doc = new Document();
+        String content =
+            (i % RELEVANT_FREQ == 0) ? "lucene precision recall document" : "random filler content";
+        doc.add(new Field("field", content, ft));
+        writer.addDocument(doc);
+      }
+      writer.commit();
+    }
+
+    IndexReader reader = DirectoryReader.open(dir);
+    IndexSearcher baselineSearcher = new IndexSearcher(reader);
+    baselineSearcher.setSimilarity(new BM25Similarity());
+
+    AnytimeRankingSearcher anytime = new AnytimeRankingSearcher(baselineSearcher, 10, 3, "field");
+
+    TermQuery query = new TermQuery(new Term("field", "lucene"));
+    TopDocs baseline = baselineSearcher.search(query, 10);
+    TopDocs anytimeResults = anytime.search(query);
+
+    float baselineRecall = countRelevant(baseline) / 10.0f;
+    float anytimeRecall = countRelevant(anytimeResults) / 10.0f;
+
+    float baselineMRR = computeMRR(baseline);
+    float anytimeMRR = computeMRR(anytimeResults);
+
+    float baselineNDCG = computeNDCG(baseline);
+    float anytimeNDCG = computeNDCG(anytimeResults);
+
+    assertTrue("Anytime recall should be >= baseline", anytimeRecall >= baselineRecall);
+    assertTrue("Anytime MRR should be >= baseline", anytimeMRR >= baselineMRR);
+    assertTrue("Anytime NDCG should be >= baseline", anytimeNDCG >= baselineNDCG);
+
+    reader.close();
+    dir.close();
+  }
+
+  private int countRelevant(TopDocs topDocs) {
+    int relevant = 0;
+    for (ScoreDoc doc : topDocs.scoreDocs) {
+      if (doc.doc % RELEVANT_FREQ == 0) {
+        relevant++;
+      }
+    }
+    return relevant;
+  }
+
+  private float computeMRR(TopDocs topDocs) {
+    for (int i = 0; i < topDocs.scoreDocs.length; i++) {
+      if (topDocs.scoreDocs[i].doc % RELEVANT_FREQ == 0) {
+        return 1.0f / (i + 1);
+      }
+    }
+    return 0f;
+  }
+
+  private float computeNDCG(TopDocs topDocs) {
+    float dcg = 0f;
+    float idcg = 0f;
+    int count = 0;
+    for (int i = 0; i < topDocs.scoreDocs.length; i++) {
+      int rank = i + 1;
+      if (topDocs.scoreDocs[i].doc % RELEVANT_FREQ == 0) {
+        dcg += (float) (1.0 / (Math.log(rank + 1) / Math.log(2)));
+        count++;
+      }
+    }
+    for (int j = 1; j <= count; j++) {
+      idcg += (float) (1.0 / (Math.log(j + 1) / Math.log(2)));
+    }
+    return idcg > 0 ? dcg / idcg : 0f;
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingRelevanceSearch.java b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingRelevanceSearch.java
new file mode 100644
index 00000000000..024e97eb2f2
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingRelevanceSearch.java
@@ -0,0 +1,214 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.search;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.BinScoreUtil;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.similarities.BM25Similarity;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class TestAnytimeRankingRelevanceSearch extends LuceneTestCase {
+
+  private String indexPath;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    indexPath = Files.createTempDirectory("testindex_relevance").toAbsolutePath().toString();
+
+    try (Directory directory = FSDirectory.open(Paths.get(indexPath));
+        IndexWriter writer =
+            new IndexWriter(directory, new IndexWriterConfig(new StandardAnalyzer()))) {
+
+      for (int i = 1; i <= 50; i++) {
+        addDocument(
+            writer,
+            "Lucene test document "
+                + i
+                + " containing ranking relevance efficiency performance scoring",
+            i);
+        if (i % 5 == 0) {
+          addDocument(writer, "Lucene boosted search efficiency query expansion", i + 50);
+        }
+        if (i % 3 == 0) {
+          addDocument(writer, "Lucene document test ranking optimization BM25 tuning", i + 100);
+        }
+        if (i % 7 == 0) {
+          addDocument(writer, "Lucene scalable indexing techniques for optimal retrieval", i + 150);
+        }
+      }
+
+      writer.commit();
+    }
+  }
+
+  @Test
+  public void testRelevanceAcrossDiverseDocuments() throws Exception {
+    try (IndexReader reader =
+        BinScoreUtil.wrap(DirectoryReader.open(FSDirectory.open(Paths.get(indexPath))))) {
+      IndexSearcher searcher = newSearcher(reader);
+      searcher.setSimilarity(new BM25Similarity());
+
+      Query query = new TermQuery(new Term("content", "lucene"));
+      AnytimeRankingSearcher anytimeSearcher =
+          new AnytimeRankingSearcher(searcher, 10, 20, "content");
+      TopDocs results = anytimeSearcher.search(query);
+
+      assertNotNull("Results should not be null", results);
+      assertTrue("Results should be greater than zero", results.scoreDocs.length > 0);
+    }
+  }
+
+  public void testPerformanceQueries() throws Exception {
+    try (IndexReader reader =
+        BinScoreUtil.wrap(DirectoryReader.open(FSDirectory.open(Paths.get(indexPath))))) {
+      IndexSearcher searcher = newSearcher(reader);
+      searcher.setSimilarity(new BM25Similarity());
+
+      AnytimeRankingSearcher anytimeSearcher =
+          new AnytimeRankingSearcher(searcher, 15, 30, "content");
+      Query query = new TermQuery(new Term("content", "performance"));
+      TopDocs results = anytimeSearcher.search(query);
+
+      assertNotNull("Results should not be null", results);
+      assertTrue("Results should be greater than zero", results.scoreDocs.length > 0);
+    }
+  }
+
+  @Test
+  public void testBM25ScoringDiversity() throws Exception {
+    try (IndexReader reader =
+        BinScoreUtil.wrap(DirectoryReader.open(FSDirectory.open(Paths.get(indexPath))))) {
+      IndexSearcher searcher = newSearcher(reader);
+      searcher.setSimilarity(new BM25Similarity());
+
+      AnytimeRankingSearcher anytimeSearcher =
+          new AnytimeRankingSearcher(searcher, 10, 50, "content");
+      Query query = new TermQuery(new Term("content", "scoring"));
+      TopDocs results = anytimeSearcher.search(query);
+
+      assertNotNull("Results should not be null", results);
+      assertTrue("Results should be greater than zero", results.scoreDocs.length > 0);
+    }
+  }
+
+  @Test
+  public void testEmptyQuery() throws Exception {
+    try (IndexReader reader =
+        BinScoreUtil.wrap(DirectoryReader.open(FSDirectory.open(Paths.get(indexPath))))) {
+      IndexSearcher searcher = newSearcher(reader);
+      searcher.setSimilarity(new BM25Similarity());
+
+      AnytimeRankingSearcher anytimeSearcher =
+          new AnytimeRankingSearcher(searcher, 5, 20, "content");
+      Query query = new TermQuery(new Term("content", " "));
+      TopDocs results = anytimeSearcher.search(query);
+
+      assertNotNull("Empty queries should return zero results", results);
+      assertEquals("Results should be zero for an empty query", 0, results.scoreDocs.length);
+    }
+  }
+
+  @Test
+  public void testBinAwareScoringImpact() throws Exception {
+    String path = Files.createTempDirectory("test-bin-aware").toString();
+    try (Directory dir = FSDirectory.open(Paths.get(path));
+        IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new StandardAnalyzer()))) {
+      for (int i = 0; i < 100; i++) {
+        Document doc = new Document();
+        doc.add(new TextField("content", "lucene bin test", Field.Store.NO));
+        doc.add(new IntPoint("docID", i));
+        writer.addDocument(doc);
+      }
+      writer.commit();
+    }
+
+    try (IndexReader reader =
+        BinScoreUtil.wrap(DirectoryReader.open(FSDirectory.open(Paths.get(indexPath))))) {
+      IndexSearcher searcher = newSearcher(reader);
+      AnytimeRankingSearcher ars = new AnytimeRankingSearcher(searcher, 5, 20, "content");
+      TopDocs results = ars.search(new TermQuery(new Term("content", "lucene")));
+      assertTrue(results.scoreDocs.length > 0);
+    }
+  }
+
+  @Test
+  public void testMultiSegmentIndexScoring() throws Exception {
+    Directory dir = newDirectory();
+    IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer());
+    config.setMaxBufferedDocs(10); // force small segments
+    try (IndexWriter writer = new IndexWriter(dir, config)) {
+      for (int i = 0; i < 100; i++) {
+        Document doc = new Document();
+        doc.add(new TextField("content", "lucene segment scoring", Field.Store.NO));
+        doc.add(new IntPoint("docID", i));
+        writer.addDocument(doc);
+      }
+      writer.commit(); // multiple segments
+    }
+
+    try (IndexReader reader = DirectoryReader.open(dir)) {
+      assertTrue("Should have multiple segments", reader.leaves().size() > 1);
+
+      IndexSearcher searcher = newSearcher(reader);
+      AnytimeRankingSearcher anytimeSearcher =
+          new AnytimeRankingSearcher(searcher, 10, 50, "content");
+      TopDocs topDocs = anytimeSearcher.search(new TermQuery(new Term("content", "lucene")));
+
+      assertNotNull("TopDocs should not be null", topDocs);
+      assertTrue("Should return some results", topDocs.scoreDocs.length > 0);
+    }
+
+    dir.close();
+  }
+
+  private void addDocument(IndexWriter writer, String content, int docID) throws IOException {
+    FieldType type = new FieldType();
+    type.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+    type.setTokenized(true);
+    type.setStored(true);
+    type.setStoreTermVectors(true);
+    type.putAttribute("doBinning", "true");
+    type.freeze();
+
+    Document doc = new Document();
+    doc.add(new IntPoint("docID", docID));
+    doc.add(new StoredField("docID", docID));
+    doc.add(new Field("content", content, type));
+
+    writer.addDocument(doc);
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingSearch.java b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingSearch.java
new file mode 100644
index 00000000000..13aaba0a593
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestAnytimeRankingSearch.java
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+package org.apache.lucene.search;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.codecs.Codec;
+import org.apache.lucene.codecs.lucene101.Lucene101Codec;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.index.BinScoreReader;
+import org.apache.lucene.index.BinScoreUtil;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.NoMergePolicy;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.similarities.BM25Similarity;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.apache.lucene.tests.util.TestUtil;
+import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.NamedThreadFactory;
+import org.junit.Test;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class TestAnytimeRankingSearch extends LuceneTestCase {
+
+  private FieldType newBinningFieldType() {
+    FieldType ft = new FieldType();
+    ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+    ft.setTokenized(true);
+    ft.setStored(false);
+    ft.setStoreTermVectors(true);
+    ft.setStoreTermVectorPositions(true);
+    ft.setStoreTermVectorOffsets(true);
+    ft.setStoreTermVectorPayloads(true);
+    ft.putAttribute("postingsFormat", "Lucene101");
+    ft.putAttribute("doBinning", "true");
+    ft.putAttribute("bin.count", "4");
+    ft.freeze();
+    return ft;
+  }
+
+  private Document newDoc(int docID, String content, FieldType ft) {
+    Document doc = new Document();
+    doc.add(new IntPoint("docID", docID));
+    doc.add(new StoredField("docID", docID));
+    doc.add(new Field("content", content, ft));
+    return doc;
+  }
+
+  @Test
+  public void testSparseBinDistribution() throws Exception {
+    try (Directory dir = newDirectory()) {
+      IndexWriterConfig iwc = new IndexWriterConfig(new StandardAnalyzer());
+      iwc.setMaxBufferedDocs(5);
+      iwc.setMergePolicy(NoMergePolicy.INSTANCE);
+
+      FieldType ft = newBinningFieldType();
+
+      try (IndexWriter writer = new IndexWriter(dir, iwc)) {
+        for (int i = 0; i < 64; i++) {
+          String content = (i % 8 == 0) ? "lucene boost" : "noise filler content";
+          writer.addDocument(newDoc(i, content, ft));
+        }
+        writer.commit();
+      }
+
+      IndexReader base = DirectoryReader.open(dir);
+      IndexReader wrapped = BinScoreUtil.wrap(base);
+
+      try {
+        IndexSearcher searcher = newSearcher(wrapped);
+        searcher.setSimilarity(new BM25Similarity());
+
+        AnytimeRankingSearcher rankingSearcher =
+            new AnytimeRankingSearcher(searcher, 10, 100, "content");
+
+        TopDocs topDocs = rankingSearcher.search(new TermQuery(new Term("content", "lucene")));
+
+        assertNotNull(topDocs);
+        assertTrue("Expected some results", topDocs.scoreDocs.length > 0);
+
+        int bin0Hits = 0;
+        int total = 0;
+
+        for (ScoreDoc sd : topDocs.scoreDocs) {
+          int docID = sd.doc;
+          for (LeafReaderContext ctx : wrapped.leaves()) {
+            if (docID >= ctx.docBase && docID < ctx.docBase + ctx.reader().maxDoc()) {
+              int segDoc = docID - ctx.docBase;
+              BinScoreReader binReader = BinScoreUtil.getBinScoreReader(ctx.reader());
+              if (binReader != null) {
+                int bin = binReader.getBinForDoc(segDoc);
+                System.out.println("doc" + segDoc + " bin " + bin);
+                if (bin == 0) {
+                  bin0Hits++;
+                }
+                total++;
+              }
+              break;
+            }
+          }
+        }
+
+        assertTrue(
+            "Bin 0 should dominate in sparse distribution, got " + bin0Hits + " of " + total,
+            total > 0 && bin0Hits >= total / 2);
+      } finally {
+        IOUtils.close(wrapped, base);
+      }
+    }
+  }
+
+  @Test
+  public void testSlaCutoffTriggersEarlyTermination() throws Exception {
+    Codec.setDefault(new Lucene101Codec());
+
+    Path indexPath = Files.createTempDirectory("testindex_sla_cutoff");
+    try (Directory dir = FSDirectory.open(indexPath);
+        IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new StandardAnalyzer()))) {
+
+      FieldType ft = newBinningFieldType();
+      for (int i = 0; i < 500; i++) {
+        writer.addDocument(newDoc(i, "lucene boost fast search scoring ranking", ft));
+      }
+      writer.commit();
+
+      IndexReader base = DirectoryReader.open(dir);
+      IndexReader wrapped = BinScoreUtil.wrap(base);
+      try {
+        IndexSearcher searcher = newSearcher(wrapped);
+        searcher.setSimilarity(new BM25Similarity());
+        AnytimeRankingSearcher s = new AnytimeRankingSearcher(searcher, 10, 1, "content");
+
+        TopDocs docs = s.search(new TermQuery(new Term("content", "lucene")));
+        assertNotNull(docs);
+        assertTrue("Expect partial results under tight SLA", docs.scoreDocs.length > 0);
+      } finally {
+        IOUtils.close(wrapped, base);
+      }
+    }
+  }
+
+  @Test
+  public void testMultiThreadedQueries() throws Exception {
+    Path path = Files.createTempDirectory("multiThreadedQuery");
+    try (Directory dir = FSDirectory.open(path);
+        IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(new StandardAnalyzer()))) {
+
+      FieldType ft = newBinningFieldType();
+      for (int i = 0; i < 1000; i++) {
+        String content =
+            "lucene relevance scoring performance content " + TestUtil.randomSimpleString(random());
+        writer.addDocument(newDoc(i, content, ft));
+      }
+      writer.commit();
+
+      IndexReader base = DirectoryReader.open(dir);
+      IndexReader wrapped = BinScoreUtil.wrap(base);
+      try {
+        IndexSearcher searcher = newSearcher(wrapped);
+        searcher.setSimilarity(new BM25Similarity(2.0f, 0.2f));
+        AnytimeRankingSearcher anytimeSearcher =
+            new AnytimeRankingSearcher(searcher, 10, 100, "content");
+
+        ExecutorService exec =
+            Executors.newFixedThreadPool(4, new NamedThreadFactory("test-search"));
+        List<Future<TopDocs>> futures = new ArrayList<>();
+
+        Query query = new TermQuery(new Term("content", "lucene"));
+        for (int i = 0; i < 4; i++) {
+          futures.add(exec.submit(() -> anytimeSearcher.search(query)));
+        }
+
+        for (Future<TopDocs> f : futures) {
+          TopDocs d = f.get();
+          assertNotNull(d);
+          assertTrue(d.scoreDocs.length > 0);
+        }
+        exec.shutdown();
+      } finally {
+        IOUtils.close(wrapped, base);
+      }
+    }
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestBinningRelevanceBoost.java b/lucene/core/src/test/org/apache/lucene/search/TestBinningRelevanceBoost.java
new file mode 100644
index 00000000000..801e6715fbc
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/search/TestBinningRelevanceBoost.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.search;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.FieldType;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexOptions;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.similarities.BM25Similarity;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.tests.index.RandomIndexWriter;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.apache.lucene.tests.util.TestUtil;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class TestBinningRelevanceBoost extends LuceneTestCase {
+
+  public void testBinningImprovesRecallUnderTruncation() throws Exception {
+    final int totalDocs = 5000;
+    final int relevantEvery = 200;
+    final int truncationLimit = 20;
+
+    Directory dir = newDirectory();
+    IndexWriterConfig iwc = newIndexWriterConfig();
+    iwc.setCodec(TestUtil.getDefaultCodec());
+    iwc.setMaxBufferedDocs(100);
+    iwc.setUseCompoundFile(false);
+
+    FieldType ft = new FieldType(TextField.TYPE_NOT_STORED);
+    ft.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS);
+    ft.setTokenized(true);
+    ft.putAttribute("postingsFormat", "Lucene101");
+    ft.putAttribute("doBinning", "true");
+    ft.putAttribute("bin.count", "4");
+    ft.freeze();
+
+    try (RandomIndexWriter writer = new RandomIndexWriter(random(), dir, iwc)) {
+      for (int i = 0; i < totalDocs; i++) {
+        Document doc = new Document();
+        String content =
+            (i % relevantEvery == 0) ? "lucene highly relevant text" : "noise filler irrelevant";
+        doc.add(new StoredField("docID", i));
+        doc.add(new Field("content", content, ft));
+        writer.addDocument(doc);
+      }
+      writer.commit();
+    }
+
+    IndexReader reader = DirectoryReader.open(dir);
+    IndexSearcher baseline = newSearcher(reader);
+    baseline.setSimilarity(new BM25Similarity());
+
+    AnytimeRankingSearcher anytimeRankingSearcher =
+        new AnytimeRankingSearcher(baseline, 10, truncationLimit, "content");
+
+    TermQuery query = new TermQuery(new Term("content", "lucene"));
+
+    AtomicInteger baselineHits = new AtomicInteger();
+    baseline.search(
+        query,
+        new CollectorManager<SimpleCollector, Void>() {
+          @Override
+          public SimpleCollector newCollector() {
+            return new SimpleCollector() {
+              private int seen = 0;
+
+              @Override
+              public void collect(int doc) throws IOException {
+                if (doc % relevantEvery == 0) {
+                  baselineHits.incrementAndGet();
+                }
+                if (++seen >= truncationLimit) {
+                  throw new CollectionTerminatedException();
+                }
+              }
+
+              @Override
+              public void doSetNextReader(LeafReaderContext context) {}
+
+              @Override
+              public ScoreMode scoreMode() {
+                return ScoreMode.COMPLETE_NO_SCORES;
+              }
+            };
+          }
+
+          @Override
+          public Void reduce(Collection<SimpleCollector> collectors) {
+            return null;
+          }
+        });
+
+    TopDocs anytimeRankingResults = anytimeRankingSearcher.search(query);
+    int anytimeRankingHits = 0;
+    for (ScoreDoc sd : anytimeRankingResults.scoreDocs) {
+      if (sd.doc % relevantEvery == 0) {
+        anytimeRankingHits++;
+      }
+    }
+
+    assertTrue(
+        "AnytimeRankingSearch should retrieve more relevant results under truncation. "
+            + "Baseline hits="
+            + baselineHits.get()
+            + ", AnytimeRankingSearch hits="
+            + anytimeRankingHits,
+        anytimeRankingHits > baselineHits.get());
+
+    reader.close();
+    dir.close();
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/util/TestHeuristicSLAEstimator.java b/lucene/core/src/test/org/apache/lucene/util/TestHeuristicSLAEstimator.java
new file mode 100644
index 00000000000..5251d4e7ea0
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/util/TestHeuristicSLAEstimator.java
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+package org.apache.lucene.util;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.DisjunctionMaxQuery;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+public class TestHeuristicSLAEstimator extends LuceneTestCase {
+
+  private static final String FIELD = "content";
+  private IndexReader reader;
+  private HeuristicSLAEstimator estimator;
+
+  @Before
+  public void setup() throws Exception {
+    String indexPath = Files.createTempDirectory("testindex1").toAbsolutePath().toString();
+    Directory dir = FSDirectory.open(Paths.get(indexPath));
+    IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer());
+    IndexWriter writer = new IndexWriter(dir, config);
+
+    for (int i = 0; i < 100; i++) {
+      Document doc = new Document();
+      doc.add(
+          new TextField(
+              FIELD, "lucene search relevance ranking performance document", Field.Store.NO));
+      writer.addDocument(doc);
+    }
+
+    writer.commit();
+    writer.close();
+
+    reader = DirectoryReader.open(dir);
+    estimator = new HeuristicSLAEstimator(FIELD);
+  }
+
+  @Test
+  public void testTermQueryEstimate() throws IOException {
+    Query query = new TermQuery(new Term(FIELD, "lucene"));
+    double estimate = estimator.estimate(query, reader, 100);
+    assertTrue("Estimate should be >= base SLA", estimate >= 100);
+  }
+
+  @Test
+  public void testPhraseQueryEstimate() throws IOException {
+    PhraseQuery query = new PhraseQuery(FIELD, "lucene", "search", "ranking");
+    double estimate = estimator.estimate(query, reader, 100);
+    assertTrue("Phrase query should increase SLA", estimate > 100);
+  }
+
+  @Test
+  public void testBooleanQueryEstimate() throws IOException {
+    BooleanQuery.Builder builder = new BooleanQuery.Builder();
+    builder.add(new TermQuery(new Term(FIELD, "lucene")), BooleanClause.Occur.MUST);
+    builder.add(new TermQuery(new Term(FIELD, "search")), BooleanClause.Occur.SHOULD);
+    Query query = builder.build();
+    double estimate = estimator.estimate(query, reader, 100);
+    assertTrue("Boolean query should increase SLA", estimate > 100);
+  }
+
+  @Test
+  public void testDisjunctionMaxQueryEstimate() throws IOException {
+    DisjunctionMaxQuery query =
+        new DisjunctionMaxQuery(
+            java.util.Arrays.asList(
+                new TermQuery(new Term(FIELD, "lucene")),
+                new TermQuery(new Term(FIELD, "ranking"))),
+            0.1f);
+    double estimate = estimator.estimate(query, reader, 100);
+    assertTrue("DisjunctionMaxQuery should increase SLA", estimate > 100);
+  }
+
+  @Test
+  public void testNestedBooleanQueryEstimate() throws IOException {
+    BooleanQuery.Builder inner = new BooleanQuery.Builder();
+    inner.add(new TermQuery(new Term(FIELD, "lucene")), BooleanClause.Occur.MUST);
+    inner.add(new TermQuery(new Term(FIELD, "search")), BooleanClause.Occur.MUST);
+
+    BooleanQuery.Builder outer = new BooleanQuery.Builder();
+    outer.add(inner.build(), BooleanClause.Occur.MUST);
+    outer.add(new TermQuery(new Term(FIELD, "ranking")), BooleanClause.Occur.SHOULD);
+
+    Query query = outer.build();
+    double estimate = estimator.estimate(query, reader, 100);
+    assertTrue("Nested Boolean query should increase SLA", estimate > 100);
+  }
+
+  @Test
+  public void testMatchAllDocsQueryEstimate() throws IOException {
+    Query query = new MatchAllDocsQuery();
+    double estimate = estimator.estimate(query, reader, 100);
+    assertTrue("MatchAllDocsQuery should be close to base SLA", estimate >= 100);
+  }
+}