Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/algorithm/hgraph/hgraph_parameter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ HGraphSearchParameters::FromJson(const std::string& json_string) {
params[INDEX_TYPE_HGRAPH][HNSW_PARAMETER_SKIP_STRATEGY].GetString());
}

if (params[INDEX_TYPE_HGRAPH].Contains("min_distance")) {
obj.min_distance = params[INDEX_TYPE_HGRAPH]["min_distance"].GetFloat();
}

return obj;
}
} // namespace vsag
1 change: 1 addition & 0 deletions src/algorithm/hgraph/hgraph_parameter.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class HGraphSearchParameters : public IndexSearchParameter {
float skip_ratio{0.2F};
FilterSearchSkipStrategyType skip_strategy_type{
FilterSearchSkipStrategyType::DETERMINISTIC_ACCUMULATIVE};
float min_distance{std::numeric_limits<float>::lowest()};

private:
HGraphSearchParameters() = default;
Expand Down
3 changes: 3 additions & 0 deletions src/algorithm/hgraph/hgraph_search.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ HGraph::KnnSearch(const DatasetPtr& query,
search_param.enable_rabitq_one_bit_search = params.rabitq_one_bit_search;
search_param.skip_ratio = params.skip_ratio;
search_param.skip_strategy_type = params.skip_strategy_type;
search_param.min_distance = params.min_distance;

DistanceRecordVector rabitq_lower_bound_candidates(ctx.alloc);
auto* rabitq_lower_bound_candidates_ptr =
Expand Down Expand Up @@ -479,6 +480,7 @@ HGraph::SearchWithRequest(const SearchRequest& request) const {
search_param.parallel_search_thread_count = params.parallel_search_thread_count;
search_param.enable_reorder = params.enable_reorder;
search_param.enable_rabitq_one_bit_search = params.rabitq_one_bit_search;
search_param.min_distance = params.min_distance;
} else {
search_param.ef = std::max(params.ef_search, k);
search_param.is_inner_id_allowed = ft;
Expand All @@ -497,6 +499,7 @@ HGraph::SearchWithRequest(const SearchRequest& request) const {
stats.is_timeout.store(false, std::memory_order_relaxed);
}
search_param.parallel_search_thread_count = params.parallel_search_thread_count;
search_param.min_distance = params.min_distance;

if (params.hops_limit <= static_cast<uint32_t>(params.ef_search)) {
search_param.hops_limit = std::numeric_limits<uint32_t>::max();
Expand Down
4 changes: 3 additions & 1 deletion src/algorithm/hnswlib/algorithm_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <cstdint>
#include <functional>
#include <limits>
#include <queue>
#include <string>

Expand Down Expand Up @@ -58,7 +59,8 @@ class AlgorithmInterface {
vsag::FilterSearchSkipStrategyType::DETERMINISTIC_ACCUMULATIVE,
vsag::Allocator* allocator = nullptr,
vsag::IteratorFilterContext* iter_ctx = nullptr,
bool is_last_filter = false) const = 0;
bool is_last_filter = false,
float min_distance = std::numeric_limits<float>::lowest()) const = 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[note] The min_distance parameter is plumbed through the searchKnn virtual interface in algorithm_interface.h with a default value of std::numeric_limits<float>::lowest(). This is backward-compatible for existing callers. However, there is no corresponding min_distance parameter added to searchRange in the same interface. If range search also needs min_distance support in the future, it would need a separate change.

Also, the bruteForce method does not receive min_distance. If brute-force fallback is used (e.g., via brute_force_threshold in HGraph), results below min_distance will not be filtered. Consider whether this is intentional or if bruteForce should also respect min_distance.


virtual std::priority_queue<std::pair<dist_t, LabelType>>
searchRange(const void* query_data,
Expand Down
43 changes: 29 additions & 14 deletions src/algorithm/hnswlib/hnswalg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,8 @@ HierarchicalNSW::searchBaseLayerST(InnerIdType ep_id,
const float skip_ratio,
vsag::FilterSearchSkipStrategyType skip_strategy_type,
vsag::Allocator* allocator,
vsag::IteratorFilterContext* iter_ctx) const {
vsag::IteratorFilterContext* iter_ctx,
float min_distance) const {
VisitedListPtr vl = visited_list_pool_->getFreeVisitedList();
vl_type* visited_array = vl->mass;
vl_type visited_array_tag = vl->curV;
Expand Down Expand Up @@ -562,14 +563,16 @@ HierarchicalNSW::searchBaseLayerST(InnerIdType ep_id,
iter_ctx->PopDiscard();
}
} else {
lower_bound = std::numeric_limits<float>::max();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] When iter_ctx != nullptr && !iter_ctx->IsFirstUsed(), the discard nodes from the previous iteration are replayed into top_candidates without checking min_distance. This is inconsistent with the entry-point path (the else branch) and the neighbor-visit path, both of which apply the min_distance filter. If a discard node has a distance <= min_distance, it should not be added to top_candidates.

Consider adding a min_distance check here:

if (iter_ctx->CheckPoint(cur_inner_id) && cur_dist > min_distance + vsag::THRESHOLD_ERROR) {
    top_candidates.emplace(cur_dist, cur_inner_id);
    ...
}

if ((!has_deletions || !isMarkedDeleted(ep_id)) &&
((!is_id_allowed) || is_id_allowed->CheckValid(getExternalLabel(ep_id)))) {
float dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_);
lower_bound = dist;
top_candidates.emplace(dist, ep_id);
candidate_set.emplace(-dist, ep_id);
if (dist > min_distance + vsag::THRESHOLD_ERROR) {
top_candidates.emplace(dist, ep_id);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] In the else branch, when the entry point is valid but its distance is <= min_distance + THRESHOLD_ERROR, lower_bound remains std::numeric_limits<float>::max() (set at line 566). This means the search termination condition (-current_node_pair.first) > lower_bound will never trigger until top_candidates reaches ef size, potentially causing the search to explore more nodes than necessary.

In the original code, lower_bound was unconditionally set to dist when the entry point was valid. Consider whether lower_bound should still be set to dist even when the result is filtered out by min_distance, since lower_bound is used for search pruning, not result filtering.

lower_bound = dist;
}
} else {
lower_bound = std::numeric_limits<float>::max();
candidate_set.emplace(-lower_bound, ep_id);
}
visited_array[ep_id] = visited_array_tag;
Expand Down Expand Up @@ -639,6 +642,9 @@ HierarchicalNSW::searchBaseLayerST(InnerIdType ep_id,
if (iter_ctx != nullptr && !iter_ctx->CheckPoint(candidate_id)) {
continue;
}
if (dist <= min_distance + vsag::THRESHOLD_ERROR) {
continue;
}
top_candidates.emplace(dist, candidate_id);
}

Expand Down Expand Up @@ -667,7 +673,8 @@ HierarchicalNSW::searchBaseLayerST(InnerIdType ep_id,
const void* data_point,
float radius,
int64_t ef,
const vsag::FilterPtr is_id_allowed) const {
const vsag::FilterPtr is_id_allowed,
float min_distance) const {
VisitedListPtr vl = visited_list_pool_->getFreeVisitedList();
vl_type* visited_array = vl->mass;
vl_type visited_array_tag = vl->curV;
Expand All @@ -680,7 +687,7 @@ HierarchicalNSW::searchBaseLayerST(InnerIdType ep_id,
((!is_id_allowed) || is_id_allowed->CheckValid(getExternalLabel(ep_id)))) {
float dist = fstdistfunc_(data_point, getDataByInternalId(ep_id), dist_func_param_);
lower_bound = dist;
if (dist <= radius + vsag::THRESHOLD_ERROR)
if (dist <= radius + vsag::THRESHOLD_ERROR && dist > min_distance + vsag::THRESHOLD_ERROR)
top_candidates.emplace(dist, ep_id);
candidate_set.emplace(-dist, ep_id);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] In the searchBaseLayerST range-search overload (the second template), the entry point check at the top of the function also uses min_distance for filtering top_candidates but does not adjust lower_bound when the entry point is filtered out. The same lower_bound concern applies here — lower_bound is set to dist regardless, which is correct for this overload since lower_bound is set before the min_distance check. However, the else branch (invalid entry point) sets lower_bound = std::numeric_limits<float>::max() which is fine.

This is just a note for consistency — the first overload (with iter_ctx) should follow a similar pattern where lower_bound reflects the actual search frontier, not the filtered frontier.

} else {
Expand Down Expand Up @@ -740,8 +747,10 @@ HierarchicalNSW::searchBaseLayerST(InnerIdType ep_id,

if ((!has_deletions || !isMarkedDeleted(candidate_id)) &&
((!is_id_allowed) ||
is_id_allowed->CheckValid(getExternalLabel(candidate_id))))
top_candidates.emplace(dist, candidate_id);
is_id_allowed->CheckValid(getExternalLabel(candidate_id)))) {
if (dist > min_distance + vsag::THRESHOLD_ERROR)
top_candidates.emplace(dist, candidate_id);
}

if (not top_candidates.empty())
lower_bound = top_candidates.top().first;
Expand Down Expand Up @@ -1689,7 +1698,8 @@ HierarchicalNSW::searchKnn(const void* query_data,
vsag::FilterSearchSkipStrategyType skip_strategy_type,
vsag::Allocator* allocator,
vsag::IteratorFilterContext* iter_ctx,
bool is_last_filter) const {
bool is_last_filter,
float min_distance) const {
std::shared_lock resize_lock(resize_mutex_);
std::priority_queue<std::pair<float, LabelType>> result;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The searchKnn method now accepts min_distance and passes it to searchBaseLayerST, but the is_last_filter fast-path (lines 1715-1721, unchanged in this diff) collects discard nodes from iter_ctx and returns them directly without applying the min_distance filter:

if (is_last_filter) {
    while (!iter_ctx->Empty()) {
        uint32_t cur_inner_id = iter_ctx->GetTopID();
        float cur_dist = iter_ctx->GetTopDist();
        result.emplace(cur_dist, getExternalLabel(cur_inner_id));  // no min_distance check
        iter_ctx->PopDiscard();
    }
    return result;
}

This means that in the last filter iteration of a multi-filter search, results with distance <= min_distance will be returned to the user, while all previous iterations correctly filter them out. Consider adding a min_distance check here:

if (cur_dist > min_distance + vsag::THRESHOLD_ERROR) {
    result.emplace(cur_dist, getExternalLabel(cur_inner_id));
}

if (cur_element_count_ == 0)
Expand Down Expand Up @@ -1718,7 +1728,8 @@ HierarchicalNSW::searchKnn(const void* query_data,
skip_ratio,
skip_strategy_type,
allocator,
iter_ctx);
iter_ctx,
min_distance);
} else {
int64_t currObj;
int max_level_copy;
Expand Down Expand Up @@ -1767,7 +1778,8 @@ HierarchicalNSW::searchKnn(const void* query_data,
skip_ratio,
skip_strategy_type,
allocator,
iter_ctx);
iter_ctx,
min_distance);
} else {
top_candidates = searchBaseLayerST<true, true>(currObj,
query_data,
Expand All @@ -1776,7 +1788,8 @@ HierarchicalNSW::searchKnn(const void* query_data,
skip_ratio,
skip_strategy_type,
allocator,
iter_ctx);
iter_ctx,
min_distance);
}
}

Expand Down Expand Up @@ -1901,14 +1914,16 @@ HierarchicalNSW::searchBaseLayerST<false, false>(
const float skip_ratio,
vsag::FilterSearchSkipStrategyType skip_strategy_type,
vsag::Allocator* allocator,
vsag::IteratorFilterContext* iter_ctx) const;
vsag::IteratorFilterContext* iter_ctx,
float min_distance) const;

template MaxHeap
HierarchicalNSW::searchBaseLayerST<false, false>(InnerIdType ep_id,
const void* data_point,
float radius,
int64_t ef,
const vsag::FilterPtr is_id_allowed) const;
const vsag::FilterPtr is_id_allowed,
float min_distance) const;

void
HierarchicalNSW::setImmutable() {
Expand Down
10 changes: 7 additions & 3 deletions src/algorithm/hnswlib/hnswalg.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <functional>
#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
#include <mutex>
#include <random>
Expand Down Expand Up @@ -291,15 +292,17 @@ class HierarchicalNSW : public AlgorithmInterface<float> {
vsag::FilterSearchSkipStrategyType skip_strategy_type =
vsag::FilterSearchSkipStrategyType::DETERMINISTIC_ACCUMULATIVE,
vsag::Allocator* allocator = nullptr,
vsag::IteratorFilterContext* iter_ctx = nullptr) const;
vsag::IteratorFilterContext* iter_ctx = nullptr,
float min_distance = std::numeric_limits<float>::lowest()) const;

template <bool has_deletions, bool collect_metrics = false>
MaxHeap
searchBaseLayerST(InnerIdType ep_id,
const void* data_point,
float radius,
int64_t ef,
const vsag::FilterPtr is_id_allowed = nullptr) const;
const vsag::FilterPtr is_id_allowed = nullptr,
float min_distance = std::numeric_limits<float>::lowest()) const;

void
getNeighborsByHeuristic2(MaxHeap& top_candidates, uint64_t M);
Expand Down Expand Up @@ -472,7 +475,8 @@ class HierarchicalNSW : public AlgorithmInterface<float> {
vsag::FilterSearchSkipStrategyType::DETERMINISTIC_ACCUMULATIVE,
vsag::Allocator* allocator = nullptr,
vsag::IteratorFilterContext* iter_ctx = nullptr,
bool is_last_filter = false) const override;
bool is_last_filter = false,
float min_distance = std::numeric_limits<float>::lowest()) const override;

std::priority_queue<std::pair<float, LabelType>>
searchRange(const void* query_data,
Expand Down
3 changes: 3 additions & 0 deletions src/impl/inner_search_param.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class InnerSearchParam {
// use in search process with duplicate ids
bool consider_duplicate{false};

// skip results with dist <= min_distance (for search iterator)
float min_distance{std::numeric_limits<float>::lowest()};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[note] The min_distance parameter defaults to std::numeric_limits<float>::lowest() (approximately -3.4e38). The check dist > min_distance + THRESHOLD_ERROR will therefore always pass when min_distance is at its default value, since THRESHOLD_ERROR is 2e-6 and the sum is still effectively -3.4e38. This means the default behavior is a no-op, which is correct.

However, if a user sets min_distance to a very large positive value (e.g., 1e38), min_distance + THRESHOLD_ERROR could overflow to +inf due to floating-point precision limits, causing the check to always fail and returning zero results. This is an edge case, but worth being aware of.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[note] This PR adds a new min_distance search parameter across 12 files but does not include any test changes. The feature filters search results based on a distance threshold, which is a correctness-sensitive operation. Consider adding tests that verify:

  1. Results with distance <= min_distance are correctly excluded from search output
  2. The default value (std::numeric_limits<float>::lowest()) is a no-op (all results returned)
  3. Edge cases: min_distance set to a very large value returns empty results
  4. Interaction with filters and iterator-based search
  5. Interaction with range search (where both radius and min_distance constraints apply)

Since min_distance is plumbed through both the HNSW native path and the HGraph searcher path (basic_searcher + parallel_searcher), both paths should be tested.


// time record
std::shared_ptr<Timer> time_cost{nullptr};

Expand Down
17 changes: 11 additions & 6 deletions src/impl/searcher/basic_searcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ BasicSearcher::search_impl(const GraphInterfacePtr& graph,
flatten->Query(&cur_dist, computer, &cur_inner_id, 1, ctx);
// Sign convention: top_candidates stores positive distances (nearest = smallest);
// candidate_set is a max-heap, so distances are negated (nearest = largest, popped first).
top_candidates->Push(cur_dist, cur_inner_id);
if (cur_dist > inner_search_param.min_distance + THRESHOLD_ERROR) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] In the basic_searcher.cpp iterator overload search_impl, when replaying discard nodes from iter_ctx (the !iter_ctx->IsFirstUsed() path), the min_distance check is applied to top_candidates->Push but NOT to candidate_set->Push. The candidate_set always gets the node pushed regardless of min_distance. This is correct for graph traversal — candidate_set should include all nodes for neighborhood expansion. However, the hnswalg.cpp searchBaseLayerST has the same pattern (candidate_set always gets the entry point), so this is consistent.

No action needed, just confirming the pattern is intentional.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] In the basic_searcher.cpp iterator overload of search_impl (the first template, around line 168), when replaying discard nodes from iter_ctx, if all discard nodes are filtered out by min_distance (i.e., cur_dist <= inner_search_param.min_distance + THRESHOLD_ERROR), top_candidates remains empty and lower_bound stays at its initial value std::numeric_limits<float>::max(). This causes the search termination condition (-current_node_pair.first) > lower_bound to never trigger until top_candidates reaches ef size, leading to unnecessary graph exploration.

This is the same class of issue as the lower_bound concern previously flagged in hnswalg.cpp:572 and basic_searcher.cpp:211lower_bound is used for search pruning, not result filtering, so it should reflect the actual search frontier regardless of min_distance filtering.

Consider setting lower_bound based on the discard nodes' distances even when they are filtered out of top_candidates, or alternatively, track a separate search_lower_bound for pruning purposes.

top_candidates->Push(cur_dist, cur_inner_id);
}
candidate_set->Push(-cur_dist, cur_inner_id);
if constexpr (mode == InnerSearchMode::RANGE_SEARCH) {
if (cur_dist > inner_search_param.radius and not top_candidates->Empty()) {
Expand Down Expand Up @@ -205,7 +207,8 @@ BasicSearcher::search_impl(const GraphInterfacePtr& graph,
} else {
flatten->Query(&dist, computer, &ep, 1, ctx);
}
if (not is_id_allowed || is_id_allowed->CheckValid(ep)) {
if ((not is_id_allowed || is_id_allowed->CheckValid(ep)) and
dist > inner_search_param.min_distance + THRESHOLD_ERROR) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] When the entry point is valid (passes check_func) but its distance is <= min_distance + THRESHOLD_ERROR, the entry point is filtered out of top_candidates and lower_bound remains at its initial value std::numeric_limits<float>::max(). This means the search termination condition (-current_node_pair.first) > lower_bound will never trigger until top_candidates reaches ef size, potentially causing the search to explore more nodes than necessary.

In the original code, lower_bound was unconditionally set to dist when the entry point was valid. Consider whether lower_bound should still be set to dist even when the result is filtered out by min_distance, since lower_bound is used for search pruning, not result filtering.

This is the same concern as in hnswalg.cpp line 572 (previously flagged).

top_candidates->Push(dist, ep);
lower_bound = top_candidates->Top().first;
}
Expand Down Expand Up @@ -283,7 +286,7 @@ BasicSearcher::search_impl(const GraphInterfacePtr& graph,
}
candidate_set->Push(-dist, cur_id);
flatten->Prefetch(candidate_set->Top().second);
if (id_allowed) {
if (id_allowed && dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
top_candidates->Push(dist, cur_id);
}

Expand Down Expand Up @@ -381,7 +384,7 @@ BasicSearcher::search_impl(const GraphInterfacePtr& graph,
flatten->Query(&dist, computer, &ep, 1, ctx);
}
++dist_cmp;
if (check_func(ep)) {
if (check_func(ep) && dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
top_candidates->Push(dist, ep);
lower_bound = top_candidates->Top().first;
}
Expand Down Expand Up @@ -478,15 +481,17 @@ BasicSearcher::search_impl(const GraphInterfacePtr& graph,
(mode == RANGE_SEARCH && dist <= inner_search_param.radius)) {
candidate_set->Push(-dist, cur_id);
// flatten->Prefetch(candidate_set->Top().second);
if (check_func(cur_id)) {
if (check_func(cur_id) &&
dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
top_candidates->Push(dist, cur_id);
} else if (reasoning != nullptr) {
reasoning->RecordFilterReject(cur_id);
}
if (inner_search_param.consider_duplicate) {
const auto duplicate_ids = graph->GetDuplicateIds(cur_id);
for (const auto& item : duplicate_ids) {
if (check_func(item)) {
if (check_func(item) &&
dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
top_candidates->Push(dist, item);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/impl/searcher/parallel_searcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,15 @@ ParallelSearcher::search_impl(const GraphInterfacePtr& graph,
if (top_candidates->Size() < ef || lower_bound > dist ||

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The entry point in parallel_searcher.cpp is pushed to top_candidates without checking min_distance. This is inconsistent with basic_searcher.cpp (both templates, lines 210-211 and 387) where the entry point is guarded by dist > inner_search_param.min_distance + THRESHOLD_ERROR.

Current code (line 179-182):

if (check_func(ep)) {
    top_candidates->Push(dist, ep);
    lower_bound = top_candidates->Top().first;
}

Suggested fix:

if (check_func(ep) && dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
    top_candidates->Push(dist, ep);
    lower_bound = top_candidates->Top().first;
}

Without this check, the parallel searcher path will include results with distance <= min_distance, while the basic searcher path correctly filters them out. This leads to inconsistent behavior depending on whether parallel search is enabled.

(mode == RANGE_SEARCH && dist <= inner_search_param.radius)) {
candidate_set->Push(-dist, cur_id);
if (check_func(cur_id)) {
if (check_func(cur_id) &&
dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
top_candidates->Push(dist, cur_id);
}
if (inner_search_param.consider_duplicate) {
const auto duplicate_ids = graph->GetDuplicateIds(cur_id);
for (const auto& item : duplicate_ids) {
if (check_func(item)) {
if (check_func(item) &&
dist > inner_search_param.min_distance + THRESHOLD_ERROR) {
top_candidates->Push(dist, item);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/index/hnsw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ HNSW::knn_search(const DatasetPtr& query,
params.skip_strategy_type,
allocator,
iter_filter_ctx,
is_last_filter);
is_last_filter,
params.min_distance);
} catch (const std::runtime_error& e) {
LOG_ERROR_AND_RETURNS(ErrorType::INTERNAL_ERROR,
"failed to perofrm knn_search(internalError): ",
Expand Down
3 changes: 3 additions & 0 deletions src/index/hnsw_zparameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ HnswSearchParameters::FromJson(const std::string& json_string) {
params[index_name][HNSW_PARAMETER_SKIP_STRATEGY].GetString());
}

if (params[index_name].Contains("min_distance")) {
obj.min_distance = params[index_name]["min_distance"].GetFloat();
}
return obj;
}

Expand Down
2 changes: 2 additions & 0 deletions src/index/hnsw_zparameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#pragma once

#include <limits>
#include <memory>
#include <string>

Expand Down Expand Up @@ -65,6 +66,7 @@ struct HnswSearchParameters {
FilterSearchSkipStrategyType skip_strategy_type{
FilterSearchSkipStrategyType::DETERMINISTIC_ACCUMULATIVE};
bool use_conjugate_graph_search;
float min_distance{std::numeric_limits<float>::lowest()};

private:
HnswSearchParameters() = default;
Expand Down
Loading