|
28 | 28 | #include <faiss/impl/AuxIndexStructures.h> |
29 | 29 | #include <faiss/impl/FaissException.h> |
30 | 30 | #include <faiss/impl/IDSelector.h> |
| 31 | +#include <faiss/impl/ResultHandler.h> |
31 | 32 | #include <faiss/index_factory.h> |
32 | 33 |
|
33 | 34 | namespace { |
@@ -504,6 +505,78 @@ TEST(IVFEarlyTermination, FastscanNheapUpdatesIsZeroToday) { |
504 | 505 | "test and verify the value is correct."; |
505 | 506 | } |
506 | 507 |
|
| 508 | +// RangeResultHandler::add_result must return true when a result is added |
| 509 | +// (distance passes the threshold) so that callers in expanded_scanners.h |
| 510 | +// correctly track nheap_updates. |
| 511 | +TEST(IVFEarlyTermination, RangeResultHandlerAddResultReturnValue) { |
| 512 | + using C = faiss::CMax<float, faiss::idx_t>; |
| 513 | + |
| 514 | + faiss::RangeSearchResult rsr(1); |
| 515 | + faiss::RangeSearchPartialResult pres(&rsr); |
| 516 | + auto& qr = pres.new_result(0); |
| 517 | + |
| 518 | + faiss::RangeResultHandler<C> handler(&qr, /*threshold=*/5.0f); |
| 519 | + |
| 520 | + // CMax::cmp(5.0, 3.0) is true: distance within radius, should add |
| 521 | + EXPECT_TRUE(handler.add_result(3.0f, 42)); |
| 522 | + EXPECT_EQ(qr.nres, size_t(1)); |
| 523 | + |
| 524 | + // CMax::cmp(5.0, 5.0) is false: at boundary, should not add |
| 525 | + EXPECT_FALSE(handler.add_result(5.0f, 43)); |
| 526 | + EXPECT_EQ(qr.nres, size_t(1)); |
| 527 | + |
| 528 | + // CMax::cmp(5.0, 10.0) is false: distance exceeds radius |
| 529 | + EXPECT_FALSE(handler.add_result(10.0f, 44)); |
| 530 | + EXPECT_EQ(qr.nres, size_t(1)); |
| 531 | + |
| 532 | + // Second valid result |
| 533 | + EXPECT_TRUE(handler.add_result(1.0f, 45)); |
| 534 | + EXPECT_EQ(qr.nres, size_t(2)); |
| 535 | + |
| 536 | + // Finalize and verify stored (dis, id) pairs |
| 537 | + pres.finalize(); |
| 538 | + ASSERT_EQ(rsr.lims[0], size_t(0)); |
| 539 | + ASSERT_EQ(rsr.lims[1], size_t(2)); |
| 540 | + std::set<faiss::idx_t> ids(rsr.labels, rsr.labels + 2); |
| 541 | + EXPECT_EQ(ids, (std::set<faiss::idx_t>{42, 45})); |
| 542 | + std::set<float> dists(rsr.distances, rsr.distances + 2); |
| 543 | + EXPECT_EQ(dists, (std::set<float>{1.0f, 3.0f})); |
| 544 | +} |
| 545 | + |
| 546 | +// Same test with CMin (inner product semantics): adds when threshold < dis. |
| 547 | +TEST(IVFEarlyTermination, RangeResultHandlerAddResultCMin) { |
| 548 | + using C = faiss::CMin<float, faiss::idx_t>; |
| 549 | + |
| 550 | + faiss::RangeSearchResult rsr(1); |
| 551 | + faiss::RangeSearchPartialResult pres(&rsr); |
| 552 | + auto& qr = pres.new_result(0); |
| 553 | + |
| 554 | + faiss::RangeResultHandler<C> handler(&qr, /*threshold=*/5.0f); |
| 555 | + |
| 556 | + // CMin::cmp(5.0, 10.0) is true: dis above threshold, should add |
| 557 | + EXPECT_TRUE(handler.add_result(10.0f, 100)); |
| 558 | + EXPECT_EQ(qr.nres, size_t(1)); |
| 559 | + |
| 560 | + // CMin::cmp(5.0, 5.0) is false: at boundary, should not add |
| 561 | + EXPECT_FALSE(handler.add_result(5.0f, 101)); |
| 562 | + EXPECT_EQ(qr.nres, size_t(1)); |
| 563 | + |
| 564 | + // CMin::cmp(5.0, 3.0) is false: dis below threshold |
| 565 | + EXPECT_FALSE(handler.add_result(3.0f, 102)); |
| 566 | + EXPECT_EQ(qr.nres, size_t(1)); |
| 567 | + |
| 568 | + // Second valid result |
| 569 | + EXPECT_TRUE(handler.add_result(7.0f, 103)); |
| 570 | + EXPECT_EQ(qr.nres, size_t(2)); |
| 571 | + |
| 572 | + pres.finalize(); |
| 573 | + ASSERT_EQ(rsr.lims[1], size_t(2)); |
| 574 | + std::set<faiss::idx_t> ids(rsr.labels, rsr.labels + 2); |
| 575 | + EXPECT_EQ(ids, (std::set<faiss::idx_t>{100, 103})); |
| 576 | + std::set<float> dists(rsr.distances, rsr.distances + 2); |
| 577 | + EXPECT_EQ(dists, (std::set<float>{7.0f, 10.0f})); |
| 578 | +} |
| 579 | + |
507 | 580 | // FastScan early-stop options use the per-query implementations. |
508 | 581 | TEST(IVFEarlyTermination, FastscanDefaultsWork) { |
509 | 582 | auto xb = make_data(nb, 0xcafebabe); |
|
0 commit comments