1use std::cmp::Ordering;
16use std::collections::BinaryHeap;
17use std::sync::Arc;
18
19const BRUTE_FORCE_THRESHOLD: usize = 1000;
20const M: usize = 16; const EF_CONSTRUCTION: usize = 200; const EF_SEARCH: usize = 64; const ML: f64 = 0.360_674_0;
25
26#[derive(Clone)]
31pub struct FlatEmbeddings {
32 pub data: Arc<[f32]>,
33 pub dim: usize,
34}
35
36impl FlatEmbeddings {
37 #[inline]
39 pub fn n_vectors(&self) -> usize {
40 if self.dim == 0 {
41 return 0;
42 }
43 self.data.len() / self.dim
44 }
45
46 #[inline]
48 pub fn get(&self, i: usize) -> &[f32] {
49 let start = i * self.dim;
50 &self.data[start..][..self.dim]
51 }
52
53 #[inline]
55 pub fn get_vec(&self, i: usize) -> Vec<f32> {
56 self.get(i).to_vec()
57 }
58
59 pub fn from_vecs(vecs: Vec<Vec<f32>>) -> Self {
61 let dim = vecs.first().map_or(0, std::vec::Vec::len);
62 let n = vecs.len();
63 let mut data = Vec::with_capacity(n * dim);
64 for v in vecs {
65 debug_assert_eq!(v.len(), dim);
66 data.extend_from_slice(&v);
67 }
68 Self {
69 data: Arc::from(data),
70 dim,
71 }
72 }
73}
74
75impl std::fmt::Debug for FlatEmbeddings {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("FlatEmbeddings")
78 .field("n_vectors", &self.n_vectors())
79 .field("dim", &self.dim)
80 .field("total_bytes", &(self.data.len() * 4))
81 .finish()
82 }
83}
84
85#[derive(Clone, PartialEq)]
87struct Candidate {
88 idx: usize,
89 sim: f32,
90}
91
92impl Eq for Candidate {}
93
94impl PartialOrd for Candidate {
95 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
96 Some(self.cmp(other))
97 }
98}
99
100impl Ord for Candidate {
101 fn cmp(&self, other: &Self) -> Ordering {
102 other.sim.partial_cmp(&self.sim).unwrap_or(Ordering::Equal)
104 }
105}
106
107#[derive(Clone, PartialEq)]
109struct MaxCandidate {
110 idx: usize,
111 sim: f32,
112}
113
114impl Eq for MaxCandidate {}
115
116impl PartialOrd for MaxCandidate {
117 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
118 Some(self.cmp(other))
119 }
120}
121
122impl Ord for MaxCandidate {
123 fn cmp(&self, other: &Self) -> Ordering {
124 self.sim.partial_cmp(&other.sim).unwrap_or(Ordering::Equal)
125 }
126}
127
128struct Node {
130 connections: Vec<Vec<usize>>, }
132
133pub struct AnnIndex {
139 embeddings: FlatEmbeddings,
140 nodes: Vec<Node>,
141 entry_point: usize,
142 max_level: usize,
143}
144
145impl AnnIndex {
146 pub fn build(embeddings: FlatEmbeddings) -> Self {
152 let n = embeddings.n_vectors();
153 if n == 0 {
154 return Self {
155 embeddings,
156 nodes: Vec::new(),
157 entry_point: 0,
158 max_level: 0,
159 };
160 }
161
162 if n < BRUTE_FORCE_THRESHOLD {
163 return Self {
164 embeddings,
165 nodes: Vec::new(),
166 entry_point: 0,
167 max_level: 0,
168 };
169 }
170
171 let mut index = Self {
172 embeddings,
173 nodes: Vec::with_capacity(n),
174 entry_point: 0,
175 max_level: 0,
176 };
177
178 for i in 0..n {
179 index.insert(i);
180 }
181
182 index
183 }
184
185 fn insert(&mut self, new_id: usize) {
186 let level = Self::level_for(new_id);
187
188 self.nodes.push(Node {
189 connections: vec![Vec::new(); level + 1],
190 });
191
192 if self.nodes.len() == 1 {
193 self.entry_point = 0;
194 self.max_level = level;
195 return;
196 }
197
198 let mut ep = self.entry_point;
199 let new_vec = self.embeddings.get(new_id);
200
201 for lc in (level + 1..=self.max_level).rev() {
203 ep = self.search_layer_single(new_vec, ep, lc);
204 }
205
206 let insert_levels = level.min(self.max_level);
208 for lc in (0..=insert_levels).rev() {
209 let neighbors = self.search_layer(new_vec, ep, EF_CONSTRUCTION, lc);
210 let selected = Self::select_neighbors(&neighbors, M);
211
212 if lc < self.nodes[new_id].connections.len() {
213 self.nodes[new_id].connections[lc].clone_from(&selected);
214 }
215
216 for &neighbor in &selected {
217 if lc < self.nodes[neighbor].connections.len() {
218 self.nodes[neighbor].connections[lc].push(new_id);
219 if self.nodes[neighbor].connections[lc].len() > M * 2 {
220 let nv = self.embeddings.get(neighbor);
221 let mut scored: Vec<(usize, f32)> = self.nodes[neighbor].connections[lc]
222 .iter()
223 .map(|&n| (n, cosine_sim(nv, self.embeddings.get(n))))
224 .collect();
225 scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
226 scored.truncate(M);
227 self.nodes[neighbor].connections[lc] =
228 scored.into_iter().map(|(id, _)| id).collect();
229 }
230 }
231 }
232
233 if !neighbors.is_empty() {
234 ep = neighbors[0].0;
235 }
236 }
237
238 if level > self.max_level {
239 self.max_level = level;
240 self.entry_point = new_id;
241 }
242 }
243
244 fn search_layer_single(&self, query: &[f32], ep: usize, _layer: usize) -> usize {
245 let mut current = ep;
246 let mut best_sim = cosine_sim(query, self.embeddings.get(ep));
247
248 loop {
249 let mut improved = false;
250 let conns = &self.nodes[current].connections;
251 let layer_conns = if _layer < conns.len() {
252 &conns[_layer]
253 } else {
254 break;
255 };
256
257 for &neighbor in layer_conns {
258 let sim = cosine_sim(query, self.embeddings.get(neighbor));
259 if sim > best_sim {
260 best_sim = sim;
261 current = neighbor;
262 improved = true;
263 }
264 }
265 if !improved {
266 break;
267 }
268 }
269 current
270 }
271
272 fn search_layer(&self, query: &[f32], ep: usize, ef: usize, layer: usize) -> Vec<(usize, f32)> {
273 let n = self.embeddings.n_vectors();
274 let mut visited = vec![false; n];
275 let mut candidates = BinaryHeap::<MaxCandidate>::new();
276 let mut results = BinaryHeap::<Candidate>::new();
277
278 let sim = cosine_sim(query, self.embeddings.get(ep));
279 visited[ep] = true;
280 candidates.push(MaxCandidate { idx: ep, sim });
281 results.push(Candidate { idx: ep, sim });
282
283 while let Some(MaxCandidate { idx: c, sim: _ }) = candidates.pop() {
284 let worst_result = results.peek().map_or(f32::MIN, |r| r.sim);
285 if cosine_sim(query, self.embeddings.get(c)) < worst_result && results.len() >= ef {
286 break;
287 }
288
289 let conns = &self.nodes[c].connections;
290 let layer_conns = if layer < conns.len() {
291 &conns[layer]
292 } else {
293 continue;
294 };
295
296 for &neighbor in layer_conns {
297 if visited[neighbor] {
298 continue;
299 }
300 visited[neighbor] = true;
301
302 let n_sim = cosine_sim(query, self.embeddings.get(neighbor));
303 let worst = results.peek().map_or(f32::MIN, |r| r.sim);
304
305 if results.len() < ef || n_sim > worst {
306 candidates.push(MaxCandidate {
307 idx: neighbor,
308 sim: n_sim,
309 });
310 results.push(Candidate {
311 idx: neighbor,
312 sim: n_sim,
313 });
314 if results.len() > ef {
315 results.pop();
316 }
317 }
318 }
319 }
320
321 let mut out: Vec<(usize, f32)> = results.into_iter().map(|c| (c.idx, c.sim)).collect();
322 out.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
323 out
324 }
325
326 fn select_neighbors(candidates: &[(usize, f32)], max_count: usize) -> Vec<usize> {
327 candidates
328 .iter()
329 .take(max_count)
330 .map(|&(idx, _)| idx)
331 .collect()
332 }
333
334 fn level_for(node_id: usize) -> usize {
336 let mut z = (node_id as u64).wrapping_add(0x9E37_79B9_7F4A_7C15);
337 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
338 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
339 z ^= z >> 31;
340 let r = (((z >> 11) as f64) + 1.0) / ((1u64 << 53) as f64 + 1.0);
341 (-r.ln() * ML).floor() as usize
342 }
343
344 pub fn search(&self, query: &[f32], top_k: usize) -> Vec<(usize, f32)> {
347 if self.embeddings.n_vectors() == 0 {
348 return Vec::new();
349 }
350
351 if self.nodes.is_empty() || self.embeddings.n_vectors() < BRUTE_FORCE_THRESHOLD {
353 return brute_force_topk(&self.embeddings, query, top_k);
354 }
355
356 let mut ep = self.entry_point;
358 for lc in (1..=self.max_level).rev() {
359 ep = self.search_layer_single(query, ep, lc);
360 }
361
362 let mut results = self.search_layer(query, ep, EF_SEARCH.max(top_k), 0);
363 results.truncate(top_k);
364 results
365 }
366}
367
368pub fn brute_force_topk(
370 embeddings: &FlatEmbeddings,
371 query: &[f32],
372 top_k: usize,
373) -> Vec<(usize, f32)> {
374 let n = embeddings.n_vectors();
375 let mut heap = BinaryHeap::<Candidate>::with_capacity(top_k + 1);
376
377 for i in 0..n {
378 let sim = cosine_sim(query, embeddings.get(i));
379 if heap.len() < top_k {
380 heap.push(Candidate { idx: i, sim });
381 } else if let Some(worst) = heap.peek()
382 && sim > worst.sim
383 {
384 heap.pop();
385 heap.push(Candidate { idx: i, sim });
386 }
387 }
388
389 let mut results: Vec<(usize, f32)> = heap.into_iter().map(|c| (c.idx, c.sim)).collect();
390 results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
391 results
392}
393
394#[inline]
400fn cosine_sim(a: &[f32], b: &[f32]) -> f32 {
401 if a.len() != b.len() {
402 return 0.0;
403 }
404 crate::core::embeddings::cosine_similarity_raw(a, b)
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 fn random_vec(dim: usize, seed: u64) -> Vec<f32> {
412 let mut v = Vec::with_capacity(dim);
413 let mut s = seed;
414 for _ in 0..dim {
415 s = s.wrapping_mul(6364136223846793005).wrapping_add(1);
416 v.push((s as f32 / u64::MAX as f32) * 2.0 - 1.0);
417 }
418 v
419 }
420
421 fn flat_from(vecs: Vec<Vec<f32>>) -> FlatEmbeddings {
422 FlatEmbeddings::from_vecs(vecs)
423 }
424
425 #[test]
426 fn brute_force_topk_correctness() {
427 let vectors = (0..100).map(|i| random_vec(16, i)).collect();
428 let flat = flat_from(vectors);
429 let query = random_vec(16, 999);
430
431 let results = brute_force_topk(&flat, &query, 5);
432 assert_eq!(results.len(), 5);
433
434 for w in results.windows(2) {
435 assert!(w[0].1 >= w[1].1);
436 }
437 }
438
439 #[test]
440 fn brute_force_topk_matches_exhaustive() {
441 let vectors: Vec<Vec<f32>> = (0..50).map(|i| random_vec(8, i + 42)).collect();
442 let flat = flat_from(vectors);
443 let query = random_vec(8, 123);
444
445 let top5 = brute_force_topk(&flat, &query, 5);
446
447 let mut all: Vec<(usize, f32)> = (0..flat.n_vectors())
449 .map(|i| (i, cosine_sim(&query, flat.get(i))))
450 .collect();
451 all.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
452 all.truncate(5);
453
454 for (heap_r, exact_r) in top5.iter().zip(all.iter()) {
455 assert_eq!(heap_r.0, exact_r.0);
456 assert!((heap_r.1 - exact_r.1).abs() < 1e-6);
457 }
458 }
459
460 #[test]
461 fn empty_index_returns_empty() {
462 let flat = FlatEmbeddings {
463 data: Arc::from(Vec::new()),
464 dim: 2,
465 };
466 let index = AnnIndex::build(flat);
467 assert!(index.search(&[1.0, 0.0], 5).is_empty());
468 }
469
470 #[test]
471 fn small_index_uses_brute_force() {
472 let vectors: Vec<Vec<f32>> = (0..50).map(|i| random_vec(4, i)).collect();
473 let flat = flat_from(vectors);
474 let index = AnnIndex::build(flat);
475 assert!(index.nodes.is_empty()); let results = index.search(&random_vec(4, 999), 3);
477 assert_eq!(results.len(), 3);
478 }
479
480 #[test]
481 fn flat_embeddings_from_vecs_shape() {
482 let vecs = vec![
483 vec![1.0, 2.0, 3.0],
484 vec![4.0, 5.0, 6.0],
485 vec![7.0, 8.0, 9.0],
486 ];
487 let flat = FlatEmbeddings::from_vecs(vecs);
488 assert_eq!(flat.n_vectors(), 3);
489 assert_eq!(flat.dim, 3);
490 assert_eq!(flat.get(0), &[1.0, 2.0, 3.0]);
491 assert_eq!(flat.get(2), &[7.0, 8.0, 9.0]);
492 }
493
494 #[test]
495 fn flat_embeddings_clone_is_shallow() {
496 let vecs = vec![vec![1.0, 2.0]];
497 let a = FlatEmbeddings::from_vecs(vecs);
498 let b = a.clone();
499 assert!(Arc::ptr_eq(&a.data, &b.data));
501 }
502}