From 0a733a1ea81167df8ed31d946bd8b7b432e89086 Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 13:21:07 +0000 Subject: [PATCH 01/10] acorn-1 --- include/usearch/index.hpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index 73c70755b..d5399ef76 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4239,6 +4239,34 @@ class index_gt { radius = top.top().distance; } } + } + + // predicate exists and explore 2-hop neighbors (ACORN-1) + if (!is_dummy()) { + for (compressed_slot_t successor_slot : candidate_neighbors) { + neighbors_ref_t hop2_candidate_neighbors = neighbors_base_(node_at_(successor_slot)); + + // Assume the worst-case when reserving memory + if (!visits.reserve(visits.size() + hop2_candidate_neighbors.size())) + return false; + + for (compressed_slot_t hop2_successor_slot : hop2_candidate_neighbors) { + + if (visits.set(hop2_successor_slot)) + continue; + + distance_t hop2_successor_dist = context.measure(query, citerator_at(hop2_successor_slot), metric); + if (top.size() < top_limit || hop2_successor_dist < radius) { + // This can substantially grow our priority queue: + next.insert({-hop2_successor_dist, hop2_successor_slot}); + if (is_dummy() || + predicate(member_cref_t{node_at_(hop2_successor_slot).ckey(), hop2_successor_slot})) { + top.insert({hop2_successor_dist, hop2_successor_slot}, top_limit); + radius = top.top().distance; + } + } + } + } } } From 27e8554e8ffe8961dbed12e6978063b98f68e48e Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 13:23:41 +0000 Subject: [PATCH 02/10] update --- include/usearch/index.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index d5399ef76..2b57535b9 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4239,7 +4239,7 @@ class index_gt { radius = top.top().distance; } } - } + } // predicate exists and explore 2-hop neighbors (ACORN-1) if (!is_dummy()) { @@ -4266,7 +4266,7 @@ class index_gt { } } } - } + } } } From 9b317d947bde68992401079ea2ec5c63425fd1da Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 13:24:33 +0000 Subject: [PATCH 03/10] update --- include/usearch/index.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index 2b57535b9..31ca0a643 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4265,7 +4265,7 @@ class index_gt { radius = top.top().distance; } } - } + } } } } From 92d5e78e3e2ccec14667847f82ede640b413e340 Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 13:40:02 +0000 Subject: [PATCH 04/10] fmt --- include/usearch/index.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index 31ca0a643..274187496 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4253,7 +4253,7 @@ class index_gt { for (compressed_slot_t hop2_successor_slot : hop2_candidate_neighbors) { if (visits.set(hop2_successor_slot)) - continue; + continue; distance_t hop2_successor_dist = context.measure(query, citerator_at(hop2_successor_slot), metric); if (top.size() < top_limit || hop2_successor_dist < radius) { From 4226ecb12bd8bc73a201617eccb0cc2b2c4619fb Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 13:54:26 +0000 Subject: [PATCH 05/10] prefetch --- include/usearch/index.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index 274187496..0c75e9296 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4246,6 +4246,12 @@ class index_gt { for (compressed_slot_t successor_slot : candidate_neighbors) { neighbors_ref_t hop2_candidate_neighbors = neighbors_base_(node_at_(successor_slot)); + // Optional prefetching + if (!is_dummy()) { + candidates_range_t missing_candidates{*this, hop2_candidate_neighbors, visits}; + prefetch(missing_candidates.begin(), missing_candidates.end()); + } + // Assume the worst-case when reserving memory if (!visits.reserve(visits.size() + hop2_candidate_neighbors.size())) return false; From 5afdae1b64b2d8b170a93b3ab8ccfd38685e46c8 Mon Sep 17 00:00:00 2001 From: eric Date: Wed, 29 Oct 2025 16:35:57 +0000 Subject: [PATCH 06/10] update --- include/usearch/index.hpp | 100 ++++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 32 deletions(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index 0c75e9296..74568a585 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4213,36 +4213,70 @@ class index_gt { next.pop(); context.iteration_cycles++; - neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); + if (is_dummy()) { - // Optional prefetching - if (!is_dummy()) { - candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; - prefetch(missing_candidates.begin(), missing_candidates.end()); - } + neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); - // Assume the worst-case when reserving memory - if (!visits.reserve(visits.size() + candidate_neighbors.size())) - return false; + // Optional prefetching + if (!is_dummy()) { + candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; + prefetch(missing_candidates.begin(), missing_candidates.end()); + } - for (compressed_slot_t successor_slot : candidate_neighbors) { - if (visits.set(successor_slot)) - continue; + // Assume the worst-case when reserving memory + if (!visits.reserve(visits.size() + candidate_neighbors.size())) + return false; - distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); - if (top.size() < top_limit || successor_dist < radius) { - // This can substantially grow our priority queue: - next.insert({-successor_dist, successor_slot}); - if (is_dummy() || - predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) { - top.insert({successor_dist, successor_slot}, top_limit); - radius = top.top().distance; + for (compressed_slot_t successor_slot : candidate_neighbors) { + if (visits.set(successor_slot)) + continue; + + distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); + if (top.size() < top_limit || successor_dist < radius) { + // This can substantially grow our priority queue: + next.insert({-successor_dist, successor_slot}); + if (is_dummy() || + predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) { + top.insert({successor_dist, successor_slot}, top_limit); + radius = top.top().distance; + } } } - } - // predicate exists and explore 2-hop neighbors (ACORN-1) - if (!is_dummy()) { + } else { + // ACORN-1 + + // explore 1-hop neighbors + neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); + + // Optional prefetching + if (!is_dummy()) { + candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; + prefetch(missing_candidates.begin(), missing_candidates.end()); + } + + // Assume the worst-case when reserving memory + if (!visits.reserve(visits.size() + candidate_neighbors.size())) + return false; + + for (compressed_slot_t successor_slot : candidate_neighbors) { + if (visits.set(successor_slot)) + continue; + + if (predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) { + distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); + if (top.size() < top_limit || successor_dist < radius) { + // This can substantially grow our priority queue: + next.insert({-successor_dist, successor_slot}); + + top.insert({successor_dist, successor_slot}, top_limit); + radius = top.top().distance; + } + } + } + + + // explore 2-hop neighbors for (compressed_slot_t successor_slot : candidate_neighbors) { neighbors_ref_t hop2_candidate_neighbors = neighbors_base_(node_at_(successor_slot)); @@ -4261,19 +4295,21 @@ class index_gt { if (visits.set(hop2_successor_slot)) continue; - distance_t hop2_successor_dist = context.measure(query, citerator_at(hop2_successor_slot), metric); - if (top.size() < top_limit || hop2_successor_dist < radius) { - // This can substantially grow our priority queue: - next.insert({-hop2_successor_dist, hop2_successor_slot}); - if (is_dummy() || - predicate(member_cref_t{node_at_(hop2_successor_slot).ckey(), hop2_successor_slot})) { - top.insert({hop2_successor_dist, hop2_successor_slot}, top_limit); - radius = top.top().distance; + if (predicate(member_cref_t{node_at_(hop2_successor_slot).ckey(), hop2_successor_slot})) { + distance_t hop2_successor_dist = + context.measure(query, citerator_at(hop2_successor_slot), metric); + if (top.size() < top_limit || hop2_successor_dist < radius) { + // This can substantially grow our priority queue: + next.insert({-hop2_successor_dist, hop2_successor_slot}); + + top.insert({hop2_successor_dist, hop2_successor_slot}, top_limit); + radius = top.top().distance; } } } } - } + } + } return true; From 1eaff9056b97acdc2341d8227a25f91ad7862ba3 Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 16:44:57 +0000 Subject: [PATCH 07/10] fmt --- include/usearch/index.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index 74568a585..ffd3c6c0b 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4308,8 +4308,7 @@ class index_gt { } } } - } - + } } return true; From 55c7961eccfb8724f6e37fe4da7c2e092679acee Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 23:36:26 +0000 Subject: [PATCH 08/10] Fix: remove 2-hop-neighbour visit --- include/usearch/index.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index ffd3c6c0b..f15a53970 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4292,9 +4292,6 @@ class index_gt { for (compressed_slot_t hop2_successor_slot : hop2_candidate_neighbors) { - if (visits.set(hop2_successor_slot)) - continue; - if (predicate(member_cref_t{node_at_(hop2_successor_slot).ckey(), hop2_successor_slot})) { distance_t hop2_successor_dist = context.measure(query, citerator_at(hop2_successor_slot), metric); From 12cde8b2dfa4b57471631db4b5c3c8babcab04a1 Mon Sep 17 00:00:00 2001 From: cpegeric Date: Wed, 29 Oct 2025 23:43:58 +0000 Subject: [PATCH 09/10] Revert "Fix: remove 2-hop-neighbour visit" This reverts commit 55c7961eccfb8724f6e37fe4da7c2e092679acee. --- include/usearch/index.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index f15a53970..ffd3c6c0b 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -4292,6 +4292,9 @@ class index_gt { for (compressed_slot_t hop2_successor_slot : hop2_candidate_neighbors) { + if (visits.set(hop2_successor_slot)) + continue; + if (predicate(member_cref_t{node_at_(hop2_successor_slot).ckey(), hop2_successor_slot})) { distance_t hop2_successor_dist = context.measure(query, citerator_at(hop2_successor_slot), metric); From d4a130c33a9d09f576e67c76227053af866a3085 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 25 Jun 2026 19:37:35 +0100 Subject: [PATCH 10/10] ACORN-1: regime-adaptive filtered search with starvation fallback (#672) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrites search_to_find_in_base_ so the 2-hop "bridge" expansion is used where it helps and avoided where it hurts, fixing the prior implementation's slowness and its recall/correctness problems. The 2-hop bridge (visit a filtered-out member's neighbors so the filter-passing subgraph stays connected) is a large win for SELECTIVE filters — on a 50k/64-d set it matches the exhaustive baseline's recall at ~13-15x the QPS — but it is pure M^2 overhead for PERMISSIVE filters, where a plain traverse-through-all is both faster and higher recall. The old code ran the bridge unconditionally, so it was slow at permissive filters and, on a very selective filter (e.g. a predicate matching a single key), could not reach the isolated passing member at all and returned nothing. This version: - Estimates the filter pass-rate from the entry point's base-layer neighborhood (a graph-LOCAL sample near the query, not a uniform global one) and dispatches: permissive/unfiltered -> plain HNSW base expansion; selective -> ACORN (filter members first, only passing ones enter the frontier, then one extra hop through the 1-hop neighbors). - Falls back once to an exhaustive baseline walk when an ACORN pass returns fewer than `wanted` (k) results, so pathologically selective / isolated passing sets are never silently dropped. Gated on `wanted` rather than `expansion`, since a selective filter legitimately has fewer than `ef` passing members and gating on `ef` would fall back on every query and erase the speedup at large `ef`. - Collapses the duplicated dummy/ACORN branches into one loop; threads `wanted` into search_to_find_in_base_ for the fallback gate. Validated: full cpp/test.cpp suite passes (incl. test_filtered_search), single-key predicates return the matching member, and recall/QPS hold across 1%/10%/50% filter selectivities (selective uses ACORN, permissive falls back to baseline). Co-Authored-By: Claude Opus 4.8 (1M context) --- include/usearch/index.hpp | 174 ++++++++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 62 deletions(-) diff --git a/include/usearch/index.hpp b/include/usearch/index.hpp index d54b4833f..79527ca1e 100644 --- a/include/usearch/index.hpp +++ b/include/usearch/index.hpp @@ -3443,7 +3443,8 @@ class index_gt { query, metric, prefetch, static_cast(entry_slot_), max_level_, 0, context); // For bottom layer we need a more optimized procedure - if (!search_to_find_in_base_(query, metric, predicate, prefetch, closest_slot, expansion, context)) + if (!search_to_find_in_base_(query, metric, predicate, prefetch, closest_slot, expansion, wanted, + context)) return result.failed("Out of memory!"); } @@ -4628,7 +4629,8 @@ class index_gt { template bool search_to_find_in_base_( // value_at&& query, metric_at&& metric, predicate_at&& predicate, prefetch_at&& prefetch, // - compressed_slot_t start_slot, std::size_t expansion, context_t& context) const usearch_noexcept_m { + compressed_slot_t start_slot, std::size_t expansion, std::size_t wanted, + context_t& context) const usearch_noexcept_m { visits_hash_set_t& visits = context.visits; next_candidates_t& next = context.next_candidates; // pop min, push @@ -4657,6 +4659,26 @@ class index_gt { top.insert_reserved({radius, start_slot}); } + // ACORN-1 dispatch. The 2-hop "bridge" expansion below lets a filtered search hop over + // filtered-out members — a large win when the filter is SELECTIVE, but pure overhead when + // it is PERMISSIVE (there a plain traverse-through-all is both faster and higher recall). + // So estimate the filter's pass-rate from the entry point's base-layer neighborhood: a + // graph-LOCAL sample that reflects the filter density near the query (better than a uniform + // global sample for filters correlated with the vector space) and only re-checks members + // we are about to examine. Unfiltered searches always take the plain path. The `wanted`- + // gated fallback below is the safety net when this estimate is wrong or too noisy. + constexpr std::size_t acorn_selective_percent_k = 25; // ACORN when < 25% of local members pass + bool use_acorn = false; + if (!is_dummy()) { + std::size_t passed = 0, sampled = 0; + neighbors_ref_t probe = neighbors_base_(node_at_(start_slot)); + for (compressed_slot_t s : probe) { + passed += predicate(member_cref_t{node_at_(s).ckey(), s}) ? 1u : 0u; + ++sampled; + } + use_acorn = sampled != 0 && passed * 100u < acorn_selective_percent_k * sampled; + } + while (!next.empty()) { candidate_t candidate = next.top(); @@ -4666,27 +4688,28 @@ class index_gt { next.pop(); context.iteration_cycles++; - if (is_dummy()) { - - neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); + neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); - // Optional prefetching - if (!is_dummy()) { - candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; - prefetch(missing_candidates.begin(), missing_candidates.end()); - } + // Optional prefetching + if (!is_dummy()) { + candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; + prefetch(missing_candidates.begin(), missing_candidates.end()); + } - // Assume the worst-case when reserving memory - if (!visits.reserve(visits.size() + candidate_neighbors.size())) - return false; + // Assume the worst-case when reserving memory + if (!visits.reserve(visits.size() + candidate_neighbors.size())) + return false; + if (!use_acorn) { + // Plain HNSW base-layer expansion: traverse through every neighbor (filtered or + // not), keeping only filter-passing members in the result list. Best when the + // filter is permissive or absent. for (compressed_slot_t successor_slot : candidate_neighbors) { if (visits.set(successor_slot)) continue; distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); if (top.size() < top_limit || successor_dist < radius) { - // This can substantially grow our priority queue: next.insert({-successor_dist, successor_slot}); if (is_dummy() || predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) { @@ -4695,69 +4718,96 @@ class index_gt { } } } + continue; + } - } else { - // ACORN-1 + // ACORN-1 (selective filter). Only filter-passing members enter the frontier — we + // never traverse through (or even measure distances to) filtered-out members. To + // keep the filter-passing subgraph connected we then expand ONE extra hop through the + // (mostly filtered-out) 1-hop neighbors, which also pre-explores the neighborhoods of + // the good members so recall survives early termination. + for (compressed_slot_t successor_slot : candidate_neighbors) { + if (visits.set(successor_slot)) + continue; + if (!predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) + continue; - // explore 1-hop neighbors - neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); + distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); + if (top.size() < top_limit || successor_dist < radius) { + next.insert({-successor_dist, successor_slot}); + top.insert({successor_dist, successor_slot}, top_limit); + radius = top.top().distance; + } + } + + for (compressed_slot_t successor_slot : candidate_neighbors) { + neighbors_ref_t hop2_neighbors = neighbors_base_(node_at_(successor_slot)); - // Optional prefetching if (!is_dummy()) { - candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; + candidates_range_t missing_candidates{*this, hop2_neighbors, visits}; prefetch(missing_candidates.begin(), missing_candidates.end()); } - - // Assume the worst-case when reserving memory - if (!visits.reserve(visits.size() + candidate_neighbors.size())) + if (!visits.reserve(visits.size() + hop2_neighbors.size())) return false; - for (compressed_slot_t successor_slot : candidate_neighbors) { - if (visits.set(successor_slot)) + for (compressed_slot_t hop2_slot : hop2_neighbors) { + if (visits.set(hop2_slot)) continue; - - if (predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) { - distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); - if (top.size() < top_limit || successor_dist < radius) { - // This can substantially grow our priority queue: - next.insert({-successor_dist, successor_slot}); - - top.insert({successor_dist, successor_slot}, top_limit); - radius = top.top().distance; - } + if (!predicate(member_cref_t{node_at_(hop2_slot).ckey(), hop2_slot})) + continue; // filtered-out 2-hop member: ACORN-1 expands exactly one extra hop + + distance_t hop2_dist = context.measure(query, citerator_at(hop2_slot), metric); + if (top.size() < top_limit || hop2_dist < radius) { + next.insert({-hop2_dist, hop2_slot}); + top.insert({hop2_dist, hop2_slot}, top_limit); + radius = top.top().distance; } } + } + } + // Fallback: a STARVED ACORN pass — one that returned fewer than the `wanted` results + // because the filter is so selective that 2-hop bridging cannot reach the passing members + // (e.g. a predicate matching a single key) — is redone once with an exhaustive baseline + // walk, so we never silently drop reachable filter-passing members. Gated on `wanted` + // (not `expansion`): a selective filter legitimately has fewer than `ef` passing nodes, so + // gating on `ef` would fall back on every query and erase ACORN's speedup at large `ef`. + if (use_acorn && top.size() < wanted) { + visits.clear(); + next.clear(); + top.clear(); + + radius = context.measure(query, citerator_at(start_slot), metric); + next.insert_reserved({-radius, start_slot}); + visits.set(start_slot); + if (predicate(member_cref_t{node_at_(start_slot).ckey(), start_slot})) + top.insert_reserved({radius, start_slot}); + + while (!next.empty()) { + candidate_t candidate = next.top(); + if ((-candidate.distance) > radius && top.size() == top_limit) + break; - // explore 2-hop neighbors - for (compressed_slot_t successor_slot : candidate_neighbors) { - neighbors_ref_t hop2_candidate_neighbors = neighbors_base_(node_at_(successor_slot)); - - // Optional prefetching - if (!is_dummy()) { - candidates_range_t missing_candidates{*this, hop2_candidate_neighbors, visits}; - prefetch(missing_candidates.begin(), missing_candidates.end()); - } - - // Assume the worst-case when reserving memory - if (!visits.reserve(visits.size() + hop2_candidate_neighbors.size())) - return false; - - for (compressed_slot_t hop2_successor_slot : hop2_candidate_neighbors) { - - if (visits.set(hop2_successor_slot)) - continue; + next.pop(); + context.iteration_cycles++; - if (predicate(member_cref_t{node_at_(hop2_successor_slot).ckey(), hop2_successor_slot})) { - distance_t hop2_successor_dist = - context.measure(query, citerator_at(hop2_successor_slot), metric); - if (top.size() < top_limit || hop2_successor_dist < radius) { - // This can substantially grow our priority queue: - next.insert({-hop2_successor_dist, hop2_successor_slot}); + neighbors_ref_t candidate_neighbors = neighbors_base_(node_at_(candidate.slot)); + if (!is_dummy()) { + candidates_range_t missing_candidates{*this, candidate_neighbors, visits}; + prefetch(missing_candidates.begin(), missing_candidates.end()); + } + if (!visits.reserve(visits.size() + candidate_neighbors.size())) + return false; - top.insert({hop2_successor_dist, hop2_successor_slot}, top_limit); - radius = top.top().distance; - } + for (compressed_slot_t successor_slot : candidate_neighbors) { + if (visits.set(successor_slot)) + continue; + distance_t successor_dist = context.measure(query, citerator_at(successor_slot), metric); + if (top.size() < top_limit || successor_dist < radius) { + next.insert({-successor_dist, successor_slot}); + if (predicate(member_cref_t{node_at_(successor_slot).ckey(), successor_slot})) { + top.insert({successor_dist, successor_slot}, top_limit); + radius = top.top().distance; } } }