weavatrix_search_vector/hnsw/
index_search.rs1use super::graph_helpers::from_node;
2use super::{FilterSearchPolicy, SearchPolicy, SearchScratch, VectorIndex};
3use crate::error::SearchError;
4use crate::hit::SearchHit;
5use crate::vector::Candidate;
6
7impl VectorIndex {
8 pub fn search(&self, query: &[f32], count: usize) -> Result<Vec<SearchHit>, SearchError> {
14 self.search_with_policy(query, count, SearchPolicy::new(self.config.expansion_query))
15 }
16
17 pub fn search_with_policy(
27 &self,
28 query: &[f32],
29 count: usize,
30 policy: SearchPolicy,
31 ) -> Result<Vec<SearchHit>, SearchError> {
32 let policy = policy.validate()?;
33 let mut scratch = SearchScratch::new(self.len())?;
34 self.search_with_scratch(query, count, policy, &mut scratch)
35 }
36
37 pub fn search_exact(&self, query: &[f32], count: usize) -> Result<Vec<SearchHit>, SearchError> {
45 self.vectors.exact(query, count)
46 }
47
48 pub fn search_exact_filtered<F>(
54 &self,
55 query: &[f32],
56 count: usize,
57 filter: F,
58 ) -> Result<Vec<SearchHit>, SearchError>
59 where
60 F: Fn(u64) -> bool,
61 {
62 self.vectors.exact_filtered(query, count, filter)
63 }
64
65 pub fn search_filtered<F>(
72 &self,
73 query: &[f32],
74 count: usize,
75 filter: F,
76 ) -> Result<Vec<SearchHit>, SearchError>
77 where
78 F: Fn(u64) -> bool,
79 {
80 self.search_filtered_with_policy(query, count, filter, FilterSearchPolicy::ExactFallback)
81 }
82
83 pub fn search_filtered_with_policy<F>(
93 &self,
94 query: &[f32],
95 count: usize,
96 filter: F,
97 policy: FilterSearchPolicy,
98 ) -> Result<Vec<SearchHit>, SearchError>
99 where
100 F: Fn(u64) -> bool,
101 {
102 let requested = count.min(self.len());
103 if requested == 0 {
104 self.vectors.query_squared_norm(query)?;
105 return Ok(Vec::new());
106 }
107 let mut scratch = SearchScratch::new(self.len())?;
108 let hits = self.search_with_scratch_where(
109 query,
110 requested,
111 SearchPolicy::new(self.config.expansion_query),
112 &mut scratch,
113 &filter,
114 )?;
115 if hits.len() == requested || policy == FilterSearchPolicy::Traversal {
116 return Ok(hits);
117 }
118 self.vectors.exact_filtered(query, requested, filter)
119 }
120
121 pub(super) fn search_with_scratch(
122 &self,
123 query: &[f32],
124 count: usize,
125 policy: SearchPolicy,
126 scratch: &mut SearchScratch,
127 ) -> Result<Vec<SearchHit>, SearchError> {
128 self.search_with_scratch_where(query, count, policy, scratch, &|_| true)
129 }
130
131 fn search_with_scratch_where<F>(
132 &self,
133 query: &[f32],
134 count: usize,
135 policy: SearchPolicy,
136 scratch: &mut SearchScratch,
137 accepts: &F,
138 ) -> Result<Vec<SearchHit>, SearchError>
139 where
140 F: Fn(u64) -> bool,
141 {
142 let query_norm = self.vectors.query_squared_norm(query)?;
143 let limit = count.min(self.len());
144 if limit == 0 {
145 return Ok(Vec::new());
146 }
147 let expansion = policy.expansion.max(limit);
148 let mut merged = std::mem::take(&mut scratch.merged);
149 merged.clear();
150 merged
151 .try_reserve(self.graphs.len().saturating_mul(expansion))
152 .map_err(|_| SearchError::AllocationFailed)?;
153 for graph in &self.graphs {
154 graph.search_into(
155 &self.vectors,
156 query,
157 query_norm,
158 expansion,
159 scratch,
160 &mut merged,
161 accepts,
162 );
163 }
164 self.append_routing_candidates(query, query_norm, policy, scratch, accepts, &mut merged)?;
165 merged.sort_unstable();
166 let hits = collect_unique_hits(&self.vectors, &merged, limit)?;
167 merged.clear();
168 scratch.merged = merged;
169 Ok(hits)
170 }
171
172 fn append_routing_candidates<F>(
173 &self,
174 query: &[f32],
175 query_norm: f32,
176 policy: SearchPolicy,
177 scratch: &mut SearchScratch,
178 accepts: &F,
179 merged: &mut Vec<Candidate>,
180 ) -> Result<(), SearchError>
181 where
182 F: Fn(u64) -> bool,
183 {
184 let mut routing_nodes = std::mem::take(&mut scratch.routing_nodes);
185 routing_nodes.clear();
186 self.vectors.routing_probes_into(
187 query,
188 policy.routing_probes,
189 &mut scratch.routing_probes,
190 &mut scratch.routing_probe_heap,
191 )?;
192 self.routing
193 .append_candidates(&scratch.routing_probes, &mut routing_nodes);
194 merged
195 .try_reserve(routing_nodes.len())
196 .map_err(|_| SearchError::AllocationFailed)?;
197 merged.extend(
198 routing_nodes
199 .iter()
200 .copied()
201 .map(from_node)
202 .filter(|index| accepts(self.vectors.key(*index)))
203 .map(|index| {
204 Candidate::new(self.vectors.distance_query(index, query, query_norm), index)
205 }),
206 );
207 routing_nodes.clear();
208 scratch.routing_nodes = routing_nodes;
209 Ok(())
210 }
211}
212
213fn collect_unique_hits(
214 vectors: &crate::vector::VectorStore,
215 candidates: &[Candidate],
216 limit: usize,
217) -> Result<Vec<SearchHit>, SearchError> {
218 let mut hits = Vec::new();
219 hits.try_reserve_exact(limit)
220 .map_err(|_| SearchError::AllocationFailed)?;
221 for candidate in candidates.iter().copied() {
222 let key = vectors.key(candidate.index());
223 if hits.iter().any(|hit: &SearchHit| hit.key == key) {
224 continue;
225 }
226 hits.push(SearchHit {
227 key,
228 distance: candidate.distance,
229 });
230 if hits.len() == limit {
231 break;
232 }
233 }
234 Ok(hits)
235}