@@ -1310,18 +1310,30 @@ void reservePriorityQueue(
13101310// / Templated body of `search_from_candidate_unbounded`. The choice of
13111311// / max-heap vs min-heap for both `top_candidates` and `candidates` is
13121312// / derived from C via `TopCandidatesQueue` / `CandidatesQueue`.
1313- template <typename VTType, class C >
1313+ template <typename VTType, class C , bool use_selector >
13141314TopCandidatesQueue<C> search_from_candidate_unbounded_fixVT (
13151315 const HNSW & hnsw,
13161316 const HNSW ::Node& node,
13171317 DistanceComputer& qdis,
13181318 int ef,
13191319 VTType& vt,
1320- HNSWStats& stats) {
1320+ HNSWStats& stats,
1321+ const IDSelector* sel) {
13211322 int ndis = 0 ;
13221323 TopCandidatesQueue<C> top_candidates;
13231324 reservePriorityQueue (top_candidates, ef);
13241325
1326+ TopCandidatesQueue<C> result_candidates;
1327+ if constexpr (use_selector) {
1328+ FAISS_ASSERT (sel);
1329+ // Keep rejected nodes in top_candidates so they can still be used
1330+ // for graph traversal, but never return them as search results.
1331+ reservePriorityQueue (result_candidates, ef);
1332+ if (sel->is_member (node.second )) {
1333+ result_candidates.push (node);
1334+ }
1335+ }
1336+
13251337 CandidatesQueue<C> candidates;
13261338 reservePriorityQueue (candidates, ef);
13271339
@@ -1361,6 +1373,17 @@ TopCandidatesQueue<C> search_from_candidate_unbounded_fixVT(
13611373 size_t saved_j[4 ];
13621374
13631375 auto add_to_heap = [&](const size_t idx, const float dis) {
1376+ if constexpr (use_selector) {
1377+ if (sel->is_member (idx) &&
1378+ (result_candidates.size () < static_cast <size_t >(ef) ||
1379+ C::cmp (result_candidates.top ().first , dis))) {
1380+ result_candidates.emplace (dis, idx);
1381+ if (result_candidates.size () > static_cast <size_t >(ef)) {
1382+ result_candidates.pop ();
1383+ }
1384+ }
1385+ }
1386+
13641387 if (C::cmp (top_candidates.top ().first , dis) ||
13651388 top_candidates.size () < static_cast <size_t >(ef)) {
13661389 candidates.emplace (dis, idx);
@@ -1416,7 +1439,11 @@ TopCandidatesQueue<C> search_from_candidate_unbounded_fixVT(
14161439 }
14171440 stats.ndis += ndis;
14181441
1419- return top_candidates;
1442+ if constexpr (use_selector) {
1443+ return result_candidates;
1444+ } else {
1445+ return top_candidates;
1446+ }
14201447}
14211448
14221449} // namespace
@@ -1434,8 +1461,8 @@ std::priority_queue<HNSW::Node> hnsw_detail::search_from_candidate_unbounded(
14341461 HNSWStats& stats) {
14351462 using C = HNSW ::C_distance;
14361463 auto call = [&]<typename VTType>(VTType& vt_concrete) {
1437- return search_from_candidate_unbounded_fixVT<VTType, C>(
1438- hnsw, node, qdis, ef, vt_concrete, stats);
1464+ return search_from_candidate_unbounded_fixVT<VTType, C, false >(
1465+ hnsw, node, qdis, ef, vt_concrete, stats, nullptr );
14391466 };
14401467 if (VisitedTableVector* vtv = dynamic_cast <VisitedTableVector*>(vt)) {
14411468 return call (*vtv);
@@ -1522,14 +1549,26 @@ HNSWStats search_impl(
15221549 }
15231550 }
15241551 } else {
1552+ const IDSelector* sel = params ? params->sel : nullptr ;
15251553 auto call = [&]<typename VTType>(VTType& vt_concrete) {
1526- return search_from_candidate_unbounded_fixVT<VTType, C>(
1554+ if (sel) {
1555+ return search_from_candidate_unbounded_fixVT<VTType, C, true >(
1556+ hnsw,
1557+ HNSW::Node (d_nearest, nearest),
1558+ qdis,
1559+ ef,
1560+ vt_concrete,
1561+ stats,
1562+ sel);
1563+ }
1564+ return search_from_candidate_unbounded_fixVT<VTType, C, false >(
15271565 hnsw,
15281566 HNSW::Node (d_nearest, nearest),
15291567 qdis,
15301568 ef,
15311569 vt_concrete,
1532- stats);
1570+ stats,
1571+ nullptr );
15331572 };
15341573 TopCandidatesQueue<C> top_candidates;
15351574 if (VisitedTableVector* vtv = dynamic_cast <VisitedTableVector*>(&vt)) {
0 commit comments