Skip to content

Commit a201eb1

Browse files
committed
Fix potential issue with search results fewer than k
1 parent 0e6e25f commit a201eb1

File tree

1 file changed

+24
-9
lines changed

1 file changed

+24
-9
lines changed

src/HNSW/Index.php

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -361,16 +361,31 @@ public function search(array $query, int $k = 10, ?int $ef = null): array
361361
[$epDist, $ep] = $this->searchLayerGreedy($qv, $ep, $epDist, $lc);
362362
}
363363

364-
// Full beam search at layer 0.
365-
$W = $this->searchLayer($qv, [[$epDist, $ep]], $ef, 0);
364+
// Full beam search at layer 0, retrying with a larger ef when soft-deleted
365+
// nodes shrink the active result set below $k. Doubling ef on each retry
366+
// costs at most O(log(totalNodes / ef)) extra passes in the worst case.
367+
$currentEf = $ef;
368+
$totalNodes = count($this->nodes);
369+
370+
do {
371+
$W = $this->searchLayer($qv, [[$epDist, $ep]], $currentEf, 0);
372+
373+
// Filter out soft-deleted nodes.
374+
if (!empty($this->deleted)) {
375+
$W = array_values(array_filter(
376+
$W,
377+
fn(array $pair) => !isset($this->deleted[$pair[1]])
378+
));
379+
}
366380

367-
// Filter out soft-deleted nodes and take the k nearest.
368-
if (!empty($this->deleted)) {
369-
$W = array_values(array_filter(
370-
$W,
371-
fn(array $pair) => !isset($this->deleted[$pair[1]])
372-
));
373-
}
381+
// Stop when we have enough active results, or ef already spans all nodes
382+
// (further expansion cannot surface new candidates).
383+
if (count($W) >= $k || $currentEf >= $totalNodes) {
384+
break;
385+
}
386+
387+
$currentEf = min($currentEf * 2, $totalNodes);
388+
} while (true);
374389

375390
$topK = array_slice($W, 0, $k);
376391
return $this->toSearchResults($topK);

0 commit comments

Comments
 (0)