@@ -410,19 +410,44 @@ struct VamanaSingleSearchType {
410410 typename SearchBuffer,
411411 typename Scratch,
412412 typename Query,
413- typename Search>
413+ typename Search,
414+ typename Index>
414415 void operator ()(
415416 const Data& data,
416417 SearchBuffer& search_buffer,
417418 Scratch& scratch,
418419 const Query& query,
419420 const Search& search,
421+ const Index& index,
420422 const lib::DefaultPredicate& cancel = lib::Returns(lib::Const<false >())
421423 ) const {
422- svs::svs_invoke (*this , data, search_buffer, scratch, query, search, cancel);
424+ svs::svs_invoke (*this , data, search_buffer, scratch, query, search, index, cancel);
423425 }
424426};
425427
428+ // / In rare cases, the search buffer may not be filled with enough results.
429+ // / This can occur in dynamic indexes when many vectors have been deleted
430+ // / and the graph becomes sparsely connected. It's a corner case and should
431+ // / not happen frequently, but when it does, we may need to supplement the buffer
432+ // / with additional results.
433+ template <typename Index, typename SearchBuffer, typename Query>
434+ void check_and_supplement_search_buffer (
435+ const Index& index, SearchBuffer& search_buffer, const Query& query
436+ ) {
437+ if (search_buffer.valid () < search_buffer.target_window () &&
438+ search_buffer.valid () < index.size ()) {
439+ for (auto external_id : index.external_ids ()) {
440+ auto internal_id = index.translate_external_id (external_id);
441+ auto dist = index.get_distance (external_id, query);
442+ auto builder = index.internal_search_builder ();
443+ search_buffer.insert (builder (internal_id, dist));
444+ if (search_buffer.valid () >= search_buffer.target_window ()) {
445+ break ;
446+ }
447+ }
448+ }
449+ }
450+
426451// / Customization point object for processing single queries.
427452inline constexpr VamanaSingleSearchType single_search{};
428453
@@ -434,14 +459,16 @@ template <
434459 typename SearchBuffer,
435460 typename Distance,
436461 typename Query,
437- typename Search>
462+ typename Search,
463+ typename Index>
438464SVS_FORCE_INLINE void svs_invoke (
439465 svs::tag_t <single_search>,
440466 const Data& SVS_UNUSED (dataset),
441467 SearchBuffer& search_buffer,
442468 Distance& distance,
443469 const Query& query,
444470 const Search& search,
471+ const Index& index,
445472 const lib::DefaultPredicate& cancel = lib::Returns(lib::Const<false >())
446473) {
447474 // Check if request to cancel the search
@@ -451,6 +478,10 @@ SVS_FORCE_INLINE void svs_invoke(
451478 // Perform graph search.
452479 auto accessor = data::GetDatumAccessor ();
453480 search (query, accessor, distance, search_buffer);
481+
482+ if constexpr (Index::needs_id_translation) {
483+ check_and_supplement_search_buffer (index, search_buffer, query);
484+ }
454485}
455486
456487// /
@@ -488,7 +519,8 @@ struct VamanaPerThreadBatchSearchType {
488519 typename Scratch,
489520 data::ImmutableMemoryDataset Queries,
490521 std::integral I,
491- typename Search>
522+ typename Search,
523+ typename Index>
492524 SVS_FORCE_INLINE void operator ()(
493525 const Data& data,
494526 SearchBuffer& search_buffer,
@@ -497,6 +529,7 @@ struct VamanaPerThreadBatchSearchType {
497529 QueryResultView<I>& result,
498530 threads::UnitRange<size_t > thread_indices,
499531 const Search& search,
532+ const Index& index,
500533 const lib::DefaultPredicate& cancel = lib::Returns(lib::Const<false >())
501534 ) const {
502535 svs::svs_invoke (
@@ -508,6 +541,7 @@ struct VamanaPerThreadBatchSearchType {
508541 result,
509542 thread_indices,
510543 search,
544+ index,
511545 cancel
512546 );
513547 }
@@ -523,7 +557,8 @@ template <
523557 typename Distance,
524558 typename Queries,
525559 std::integral I,
526- typename Search>
560+ typename Search,
561+ typename Index>
527562void svs_invoke (
528563 svs::tag_t <per_thread_batch_search>,
529564 const Data& dataset,
@@ -533,6 +568,7 @@ void svs_invoke(
533568 QueryResultView<I>& result,
534569 threads::UnitRange<size_t > thread_indices,
535570 const Search& search,
571+ const Index& index,
536572 const lib::DefaultPredicate& cancel = lib::Returns(lib::Const<false >())
537573) {
538574 // Fallback implementation
@@ -544,7 +580,7 @@ void svs_invoke(
544580 }
545581 // Perform search - results will be queued in the search buffer.
546582 single_search (
547- dataset, search_buffer, distance, queries.get_datum (i), search, cancel
583+ dataset, search_buffer, distance, queries.get_datum (i), search, index, cancel
548584 );
549585
550586 // Copy back results.
0 commit comments