1use std::cell::RefCell;
12use std::cmp::{Ordering, Reverse};
13use std::collections::BinaryHeap;
14use std::ops::Range;
15use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering};
16use std::sync::{Mutex, MutexGuard, PoisonError};
17
18use crate::error::{Error, Result};
19use crate::metric::Metric;
20use crate::rng::SplitMix64;
21
22pub(crate) const MAX_LEVEL: usize = 16;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27pub struct HnswParams {
28 pub m: usize,
30 pub ef_construction: usize,
33 pub ef_search: usize,
36}
37
38impl Default for HnswParams {
39 fn default() -> Self {
40 Self {
41 m: 16,
42 ef_construction: 200,
43 ef_search: 64,
44 }
45 }
46}
47
48impl HnswParams {
49 pub(crate) fn validate(&self) -> Result<()> {
50 if !(2..=256).contains(&self.m) {
51 return Err(Error::InvalidArgument("m must be between 2 and 256".into()));
52 }
53 if self.ef_construction == 0 || self.ef_search == 0 {
54 return Err(Error::InvalidArgument(
55 "ef values must be greater than zero".into(),
56 ));
57 }
58 Ok(())
59 }
60
61 pub(crate) fn max_links(&self, layer: usize) -> usize {
62 if layer == 0 { self.m * 2 } else { self.m }
63 }
64}
65
66pub(crate) struct Vectors {
68 pub(crate) dim: usize,
69 pub(crate) data: Vec<f32>,
70 pub(crate) codes: Vec<i8>,
71 pub(crate) scales: Vec<f32>,
72 pub(crate) encoding: crate::Quantization,
73 metric: Metric,
74}
75impl Vectors {
76 #[cfg(test)]
77 pub(crate) fn new(dim: usize) -> Self {
78 Self::with_encoding(dim, crate::Quantization::F32, Metric::L2)
79 }
80 pub(crate) fn with_encoding(dim: usize, encoding: crate::Quantization, metric: Metric) -> Self {
81 Self {
82 dim,
83 data: Vec::new(),
84 codes: Vec::new(),
85 scales: Vec::new(),
86 encoding,
87 metric,
88 }
89 }
90 pub(crate) fn len(&self) -> usize {
91 if self.encoding == crate::Quantization::F32 {
92 self.data.len() / self.dim.max(1)
93 } else {
94 self.scales.len()
95 }
96 }
97 pub(crate) fn bytes(&self) -> usize {
98 self.data.len() * 4 + self.codes.len() + self.scales.len() * 4
99 }
100 pub(crate) fn truncate(&mut self, nodes: usize) {
101 self.data.truncate(nodes * self.dim);
102 self.codes.truncate(nodes * self.dim);
103 self.scales.truncate(nodes);
104 }
105 #[inline]
106 pub(crate) fn get(&self, node: u32) -> std::borrow::Cow<'_, [f32]> {
107 let start = node as usize * self.dim;
108 if self.encoding == crate::Quantization::F32 {
109 return (&self.data[start..start + self.dim]).into();
110 }
111 self.codes[start..start + self.dim]
112 .iter()
113 .map(|&v| v as f32 * self.scales[node as usize])
114 .collect::<Vec<_>>()
115 .into()
116 }
117 #[inline]
118 pub(crate) fn distance(&self, query: &[f32], node: u32, metric: Metric) -> f32 {
119 if self.encoding == crate::Quantization::F32 {
120 return metric.distance(query, &self.get(node));
121 }
122 let start = node as usize * self.dim;
123 let scale = self.scales[node as usize];
124 let mut sum = 0.0;
125 for (&a, &b) in query.iter().zip(&self.codes[start..start + self.dim]) {
126 let b = b as f32 * scale;
127 sum += if metric == Metric::L2 {
128 (a - b) * (a - b)
129 } else {
130 a * b
131 };
132 }
133 match metric {
134 Metric::L2 => sum,
135 Metric::Cosine => 1.0 - sum,
136 Metric::Dot => -sum,
137 }
138 }
139 pub(crate) fn prefetch(&self, node: u32) {
140 if self.encoding == crate::Quantization::F32 {
141 prefetch(&self.get(node));
142 }
143 }
144 pub(crate) fn push(&mut self, vector: &[f32]) {
145 debug_assert_eq!(vector.len(), self.dim);
146 if self.encoding == crate::Quantization::F32 {
147 self.data.extend_from_slice(vector);
148 return;
149 }
150 let max = vector.iter().fold(0.0f32, |m, v| m.max(v.abs()));
151 let mut scale = if max == 0.0 {
152 1.0
153 } else {
154 (max / 127.0).max(f32::from_bits(1))
155 };
156 let start = self.codes.len();
157 self.codes.extend(
158 vector
159 .iter()
160 .map(|&v| (v / scale).round().clamp(-127.0, 127.0) as i8),
161 );
162 if self.metric == Metric::Cosine {
163 let norm = self.codes[start..]
164 .iter()
165 .map(|&v| (v as f32).powi(2))
166 .sum::<f32>()
167 .sqrt();
168 scale = 1.0 / norm;
169 }
170 self.scales.push(scale);
171 }
172}
173
174#[derive(Clone, Copy, Debug)]
175pub(crate) struct Candidate {
176 pub(crate) dist: f32,
177 pub(crate) id: u32,
178}
179
180impl PartialEq for Candidate {
181 fn eq(&self, other: &Self) -> bool {
182 self.cmp(other) == Ordering::Equal
183 }
184}
185
186impl Eq for Candidate {}
187
188impl PartialOrd for Candidate {
189 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
190 Some(self.cmp(other))
191 }
192}
193
194impl Ord for Candidate {
195 fn cmp(&self, other: &Self) -> Ordering {
196 self.dist
197 .total_cmp(&other.dist)
198 .then(self.id.cmp(&other.id))
199 }
200}
201
202#[derive(Clone, Copy, Debug, Default)]
204pub(crate) struct Counters {
205 pub(crate) visited: usize,
206 pub(crate) distance_computations: usize,
207}
208
209#[derive(Default)]
213pub(crate) struct Visited {
214 bits: Vec<u64>,
215 touched: Vec<u32>,
216 fresh: Vec<u32>,
218}
219
220impl Visited {
221 fn reset(&mut self, len: usize) {
222 for word in self.touched.drain(..) {
223 self.bits[word as usize] = 0;
224 }
225 let words = len.div_ceil(64);
226 if self.bits.len() < words {
227 self.bits.resize(words, 0);
228 }
229 }
230
231 #[inline]
233 fn insert(&mut self, node: u32) -> bool {
234 let (word, bit) = (node as usize / 64, 1u64 << (node % 64));
235 let bits = &mut self.bits[word];
236 if *bits & bit != 0 {
237 return false;
238 }
239 if *bits == 0 {
240 self.touched.push(word as u32);
241 }
242 *bits |= bit;
243 true
244 }
245}
246
247thread_local! {
248 static VISITED: RefCell<Visited> = RefCell::new(Visited::default());
249}
250
251pub(crate) trait ReadLinks {
253 fn with_neighbors<R>(&self, node: u32, layer: usize, f: impl FnOnce(&[u32]) -> R) -> R;
254
255 fn is_ready(&self, _node: u32) -> bool {
258 true
259 }
260}
261
262pub(crate) trait Links: ReadLinks {
264 fn set_neighbors(
267 &mut self,
268 node: u32,
269 layer: usize,
270 neighbors: Vec<u32>,
271 max: usize,
272 vectors: &Vectors,
273 metric: Metric,
274 );
275
276 fn add_link(
278 &mut self,
279 node: u32,
280 layer: usize,
281 new: u32,
282 max: usize,
283 vectors: &Vectors,
284 metric: Metric,
285 );
286}
287
288impl ReadLinks for [Vec<Vec<u32>>] {
289 #[inline]
290 fn with_neighbors<R>(&self, node: u32, layer: usize, f: impl FnOnce(&[u32]) -> R) -> R {
291 f(&self[node as usize][layer])
292 }
293}
294
295struct Exclusive<'a>(&'a mut [Vec<Vec<u32>>]);
297
298impl ReadLinks for Exclusive<'_> {
299 fn with_neighbors<R>(&self, node: u32, layer: usize, f: impl FnOnce(&[u32]) -> R) -> R {
300 self.0.with_neighbors(node, layer, f)
301 }
302}
303
304impl Links for Exclusive<'_> {
305 fn set_neighbors(
306 &mut self,
307 node: u32,
308 layer: usize,
309 neighbors: Vec<u32>,
310 _: usize,
311 _: &Vectors,
312 _: Metric,
313 ) {
314 self.0[node as usize][layer] = neighbors;
316 }
317
318 fn add_link(
319 &mut self,
320 node: u32,
321 layer: usize,
322 new: u32,
323 max: usize,
324 vectors: &Vectors,
325 metric: Metric,
326 ) {
327 push_and_prune(
328 &mut self.0[node as usize][layer],
329 node,
330 new,
331 max,
332 vectors,
333 metric,
334 );
335 }
336}
337
338#[derive(Clone, Copy)]
340struct Shared<'a> {
341 links: &'a [Mutex<Vec<Vec<u32>>>],
342 ready: &'a [AtomicBool],
343}
344
345impl Shared<'_> {
346 fn lock(&self, node: u32) -> MutexGuard<'_, Vec<Vec<u32>>> {
347 self.links[node as usize]
348 .lock()
349 .unwrap_or_else(PoisonError::into_inner)
350 }
351}
352
353impl ReadLinks for Shared<'_> {
354 fn with_neighbors<R>(&self, node: u32, layer: usize, f: impl FnOnce(&[u32]) -> R) -> R {
355 f(&self.lock(node)[layer])
356 }
357
358 fn is_ready(&self, node: u32) -> bool {
359 self.ready[node as usize].load(AtomicOrdering::Acquire)
360 }
361}
362
363impl Links for Shared<'_> {
364 fn set_neighbors(
365 &mut self,
366 node: u32,
367 layer: usize,
368 neighbors: Vec<u32>,
369 max: usize,
370 vectors: &Vectors,
371 metric: Metric,
372 ) {
373 let mut links = self.lock(node);
374 let list = &mut links[layer];
375 if list.is_empty() {
376 *list = neighbors;
377 return;
378 }
379 for neighbor in neighbors {
380 if !list.contains(&neighbor) {
381 list.push(neighbor);
382 }
383 }
384 prune(list, node, max, vectors, metric);
385 }
386
387 fn add_link(
388 &mut self,
389 node: u32,
390 layer: usize,
391 new: u32,
392 max: usize,
393 vectors: &Vectors,
394 metric: Metric,
395 ) {
396 push_and_prune(&mut self.lock(node)[layer], node, new, max, vectors, metric);
397 }
398}
399
400pub(crate) struct Graph {
401 pub(crate) params: HnswParams,
402 pub(crate) links: Vec<Vec<Vec<u32>>>,
404 pub(crate) entry: Option<u32>,
405 pub(crate) max_level: usize,
406 pub(crate) rng: SplitMix64,
407}
408
409impl Graph {
410 pub(crate) fn new(params: HnswParams, seed: u64) -> Self {
411 Self {
412 params,
413 links: Vec::new(),
414 entry: None,
415 max_level: 0,
416 rng: SplitMix64::new(seed),
417 }
418 }
419
420 pub(crate) fn len(&self) -> usize {
421 self.links.len()
422 }
423
424 fn random_level(&mut self) -> usize {
425 let ml = 1.0 / (self.params.m as f64).ln();
426 let level = (-self.rng.next_f64().ln() * ml).floor() as usize;
427 level.min(MAX_LEVEL)
428 }
429
430 pub(crate) fn insert(&mut self, id: u32, vectors: &Vectors, metric: Metric) {
433 debug_assert_eq!(id as usize, self.links.len());
434 self.insert_batch(id..id + 1, vectors, metric, 1);
435 }
436
437 pub(crate) fn insert_batch(
442 &mut self,
443 nodes: Range<u32>,
444 vectors: &Vectors,
445 metric: Metric,
446 threads: usize,
447 ) {
448 debug_assert_eq!(nodes.start as usize, self.links.len());
449 let levels: Vec<usize> = nodes.clone().map(|_| self.random_level()).collect();
450 for &level in &levels {
451 self.links.push(vec![Vec::new(); level + 1]);
452 }
453 let mut rest = nodes.clone();
454 if self.entry.is_none() {
455 let Some(first) = rest.next() else { return };
456 self.entry = Some(first);
457 self.max_level = levels[0];
458 }
459 let level_of = |node: u32| levels[(node - nodes.start) as usize];
460 let params = self.params;
461
462 if threads <= 1 || rest.len() < 1000 {
464 VISITED.with_borrow_mut(|visited| {
465 for node in rest {
466 let level = level_of(node);
467 let entry = self.entry.expect("entry is set above");
468 let mut links = Exclusive(&mut self.links);
469 link_node(
470 &mut links,
471 ¶ms,
472 node,
473 level,
474 entry,
475 self.max_level,
476 vectors,
477 metric,
478 visited,
479 );
480 if level > self.max_level {
481 self.max_level = level;
482 self.entry = Some(node);
483 }
484 }
485 });
486 return;
487 }
488
489 let locked: Vec<Mutex<Vec<Vec<u32>>>> = std::mem::take(&mut self.links)
490 .into_iter()
491 .map(Mutex::new)
492 .collect();
493 let top = Mutex::new((self.entry.expect("entry is set above"), self.max_level));
494 let next = AtomicUsize::new(rest.start as usize);
495 let ready: Vec<AtomicBool> = (0..locked.len())
496 .map(|node| AtomicBool::new(node < rest.start as usize))
497 .collect();
498 std::thread::scope(|scope| {
499 for _ in 0..threads {
500 scope.spawn(|| {
501 let mut links = Shared {
502 links: &locked,
503 ready: &ready,
504 };
505 let mut visited = Visited::default();
506 loop {
507 let node = next.fetch_add(1, AtomicOrdering::Relaxed);
508 if node >= rest.end as usize {
509 break;
510 }
511 let (node, level) = (node as u32, level_of(node as u32));
512 let mut top = top.lock().unwrap_or_else(PoisonError::into_inner);
513 let (entry, max_level) = *top;
514 if level > max_level {
515 link_node(
518 &mut links,
519 ¶ms,
520 node,
521 level,
522 entry,
523 max_level,
524 vectors,
525 metric,
526 &mut visited,
527 );
528 *top = (node, level);
529 } else {
530 drop(top);
531 link_node(
532 &mut links,
533 ¶ms,
534 node,
535 level,
536 entry,
537 max_level,
538 vectors,
539 metric,
540 &mut visited,
541 );
542 }
543 ready[node as usize].store(true, AtomicOrdering::Release);
544 }
545 });
546 }
547 });
548 self.links = locked
549 .into_iter()
550 .map(|m| m.into_inner().unwrap_or_else(PoisonError::into_inner))
551 .collect();
552 let (entry, max_level) = top.into_inner().unwrap_or_else(PoisonError::into_inner);
553 self.entry = Some(entry);
554 self.max_level = max_level;
555 self.repair_unreachable(vectors, metric);
556 }
557
558 pub(crate) fn repair_unreachable(&mut self, vectors: &Vectors, metric: Metric) -> usize {
562 let reachable = self.reachable();
563 let orphans: Vec<u32> = (0..self.links.len() as u32)
564 .filter(|&node| !reachable[node as usize])
565 .collect();
566 let Some(entry) = self.entry else { return 0 };
567 let max = self.params.max_links(0);
568 VISITED.with_borrow_mut(|visited| {
569 for &orphan in &orphans {
570 let query = vectors.get(orphan);
571 let found = {
572 let links: &[Vec<Vec<u32>>] = &self.links;
573 let mut counters = Counters::default();
574 let mut ep = Candidate {
575 dist: vectors.distance(&query, entry, metric),
576 id: entry,
577 };
578 for layer in (1..=self.max_level).rev() {
579 ep = greedy(links, &query, ep, layer, vectors, metric, &mut counters);
580 }
581 let ef = self.params.ef_construction;
582 let accept = |n: u32| n != orphan;
583 search_layer(
584 links,
585 &query,
586 &[ep],
587 ef,
588 0,
589 vectors,
590 metric,
591 &accept,
592 &mut counters,
593 visited,
594 )
595 };
596 let mut linked = false;
597 for candidate in found.iter().take(self.params.m) {
598 let list = &mut self.links[candidate.id as usize][0];
599 push_and_prune(list, candidate.id, orphan, max, vectors, metric);
600 linked |= list.contains(&orphan);
601 }
602 if !linked && let Some(nearest) = found.first() {
603 self.links[nearest.id as usize][0].push(orphan);
605 }
606 }
607 });
608 orphans.len()
609 }
610
611 #[allow(clippy::too_many_arguments)]
613 pub(crate) fn search(
614 &self,
615 query: &[f32],
616 k: usize,
617 ef: usize,
618 vectors: &Vectors,
619 metric: Metric,
620 accept: &dyn Fn(u32) -> bool,
621 counters: &mut Counters,
622 ) -> Vec<Candidate> {
623 let Some(entry) = self.entry else {
624 return Vec::new();
625 };
626 let links: &[Vec<Vec<u32>>] = &self.links;
627 counters.distance_computations += 1;
628 let mut ep = Candidate {
629 dist: vectors.distance(query, entry, metric),
630 id: entry,
631 };
632 for layer in (1..=self.max_level).rev() {
633 ep = greedy(links, query, ep, layer, vectors, metric, counters);
634 }
635 let mut found = VISITED.with_borrow_mut(|visited| {
636 search_layer(
637 links,
638 query,
639 &[ep],
640 ef.max(k),
641 0,
642 vectors,
643 metric,
644 accept,
645 counters,
646 visited,
647 )
648 });
649 found.truncate(k);
650 found
651 }
652
653 pub(crate) fn nodes_per_layer(&self) -> Vec<usize> {
655 if self.links.is_empty() {
656 return Vec::new();
657 }
658 let mut counts = vec![0; self.max_level + 1];
659 for layers in &self.links {
660 for count in counts.iter_mut().take(layers.len()) {
661 *count += 1;
662 }
663 }
664 counts
665 }
666
667 pub(crate) fn reachable(&self) -> Vec<bool> {
669 let mut seen = vec![false; self.links.len()];
670 let Some(entry) = self.entry else { return seen };
671 let mut stack = vec![entry];
672 seen[entry as usize] = true;
673 while let Some(node) = stack.pop() {
674 for &neighbor in &self.links[node as usize][0] {
675 if !seen[neighbor as usize] {
676 seen[neighbor as usize] = true;
677 stack.push(neighbor);
678 }
679 }
680 }
681 seen
682 }
683}
684
685#[allow(clippy::too_many_arguments)]
689fn link_node<L: Links>(
690 links: &mut L,
691 params: &HnswParams,
692 node: u32,
693 level: usize,
694 entry: u32,
695 max_level: usize,
696 vectors: &Vectors,
697 metric: Metric,
698 visited: &mut Visited,
699) {
700 let query = vectors.get(node);
701 let mut counters = Counters::default();
702 let mut ep = Candidate {
703 dist: vectors.distance(&query, entry, metric),
704 id: entry,
705 };
706 for layer in (level + 1..=max_level).rev() {
707 ep = greedy(&*links, &query, ep, layer, vectors, metric, &mut counters);
708 }
709
710 let mut entry_points = vec![ep];
711 for layer in (0..=level.min(max_level)).rev() {
712 let found = search_layer(
713 &*links,
714 &query,
715 &entry_points,
716 params.ef_construction,
717 layer,
718 vectors,
719 metric,
720 &|n| n != node,
721 &mut counters,
722 visited,
723 );
724 let max = params.max_links(layer);
725 let neighbors = select_neighbors(&found, max, vectors, metric);
726 links.set_neighbors(node, layer, neighbors.clone(), max, vectors, metric);
727 for neighbor in neighbors {
728 links.add_link(neighbor, layer, node, max, vectors, metric);
729 }
730 entry_points = found;
731 }
732}
733
734#[inline(always)]
737fn prefetch(vector: &[f32]) {
738 for line in vector.chunks(16) {
739 let ptr = line.as_ptr();
740 #[cfg(target_arch = "aarch64")]
741 unsafe {
744 std::arch::asm!("prfm pldl1keep, [{0}]", in(reg) ptr, options(nostack, preserves_flags, readonly));
745 }
746 #[cfg(target_arch = "x86_64")]
747 unsafe {
749 std::arch::x86_64::_mm_prefetch(ptr.cast::<i8>(), std::arch::x86_64::_MM_HINT_T0);
750 }
751 #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
752 let _ = ptr;
753 }
754}
755
756fn greedy<R: ReadLinks + ?Sized>(
757 links: &R,
758 query: &[f32],
759 mut best: Candidate,
760 layer: usize,
761 vectors: &Vectors,
762 metric: Metric,
763 counters: &mut Counters,
764) -> Candidate {
765 loop {
766 let current = best.id;
767 links.with_neighbors(current, layer, |neighbors| {
768 for &neighbor in neighbors {
769 vectors.prefetch(neighbor);
770 }
771 for &neighbor in neighbors {
772 counters.distance_computations += 1;
773 let dist = vectors.distance(query, neighbor, metric);
774 if dist < best.dist && links.is_ready(neighbor) {
777 best = Candidate { dist, id: neighbor };
778 }
779 }
780 });
781 if best.id == current {
782 return best;
783 }
784 }
785}
786
787#[allow(clippy::too_many_arguments)]
791fn search_layer<R: ReadLinks + ?Sized>(
792 links: &R,
793 query: &[f32],
794 entry_points: &[Candidate],
795 ef: usize,
796 layer: usize,
797 vectors: &Vectors,
798 metric: Metric,
799 accept: &dyn Fn(u32) -> bool,
800 counters: &mut Counters,
801 visited: &mut Visited,
802) -> Vec<Candidate> {
803 visited.reset(vectors.len());
804 let mut candidates = BinaryHeap::new();
805 let mut results: BinaryHeap<Candidate> = BinaryHeap::new();
806
807 for &ep in entry_points {
808 if !visited.insert(ep.id) {
809 continue;
810 }
811 counters.visited += 1;
812 candidates.push(Reverse(ep));
813 if accept(ep.id) {
814 results.push(ep);
815 if results.len() > ef {
816 results.pop();
817 }
818 }
819 }
820
821 while let Some(Reverse(current)) = candidates.pop() {
822 if results.len() >= ef && results.peek().is_some_and(|w| current.dist > w.dist) {
823 break;
824 }
825 let mut fresh = std::mem::take(&mut visited.fresh);
828 fresh.clear();
829 links.with_neighbors(current.id, layer, |neighbors| {
830 for &neighbor in neighbors {
831 if visited.insert(neighbor) {
832 fresh.push(neighbor);
833 }
834 }
835 });
836 for &neighbor in &fresh {
837 vectors.prefetch(neighbor);
838 }
839 for &neighbor in &fresh {
840 counters.visited += 1;
841 counters.distance_computations += 1;
842 let dist = vectors.distance(query, neighbor, metric);
843 let worst = results.peek().map_or(f32::INFINITY, |w| w.dist);
844 if results.len() < ef || dist < worst {
845 let candidate = Candidate { dist, id: neighbor };
846 candidates.push(Reverse(candidate));
847 if accept(neighbor) {
848 results.push(candidate);
849 if results.len() > ef {
850 results.pop();
851 }
852 }
853 }
854 }
855 visited.fresh = fresh;
856 }
857
858 let mut out = results.into_vec();
859 out.sort();
860 out
861}
862
863fn select_neighbors(
868 candidates: &[Candidate],
869 max: usize,
870 vectors: &Vectors,
871 metric: Metric,
872) -> Vec<u32> {
873 let mut selected: Vec<Candidate> = Vec::with_capacity(max);
874 let mut pruned = Vec::new();
875 for &candidate in candidates {
876 if selected.len() >= max {
877 break;
878 }
879 let vector = vectors.get(candidate.id);
880 let diverse = selected
881 .iter()
882 .all(|s| vectors.distance(&vector, s.id, metric) > candidate.dist);
883 if diverse {
884 selected.push(candidate);
885 } else {
886 pruned.push(candidate);
887 }
888 }
889 for candidate in pruned {
890 if selected.len() >= max {
891 break;
892 }
893 selected.push(candidate);
894 }
895 selected.into_iter().map(|c| c.id).collect()
896}
897
898fn push_and_prune(
899 list: &mut Vec<u32>,
900 node: u32,
901 new: u32,
902 max: usize,
903 vectors: &Vectors,
904 metric: Metric,
905) {
906 list.push(new);
907 prune(list, node, max, vectors, metric);
908}
909
910fn prune(list: &mut Vec<u32>, node: u32, max: usize, vectors: &Vectors, metric: Metric) {
912 if list.len() <= max {
913 return;
914 }
915 let base = vectors.get(node);
916 let mut candidates: Vec<Candidate> = list
917 .iter()
918 .map(|&n| Candidate {
919 dist: vectors.distance(&base, n, metric),
920 id: n,
921 })
922 .collect();
923 candidates.sort();
924 *list = select_neighbors(&candidates, max, vectors, metric);
925}
926
927#[cfg(test)]
928mod tests {
929 use super::*;
930
931 fn random_graph(n: usize, dim: usize) -> (Graph, Vectors) {
932 let mut rng = SplitMix64::new(9);
933 let mut vectors = Vectors::new(dim);
934 for _ in 0..n {
935 let v: Vec<f32> = (0..dim).map(|_| rng.next_f64() as f32).collect();
936 vectors.push(&v);
937 }
938 let mut graph = Graph::new(HnswParams::default(), 1);
939 graph.insert_batch(0..n as u32, &vectors, Metric::L2, 1);
940 (graph, vectors)
941 }
942
943 #[test]
944 fn repair_relinks_orphaned_nodes() {
945 let (mut graph, vectors) = random_graph(500, 8);
946 let orphan = (0..500u32).find(|&n| Some(n) != graph.entry).unwrap();
947 for layers in &mut graph.links {
948 for list in layers.iter_mut() {
949 list.retain(|&n| n != orphan);
950 }
951 }
952 assert!(!graph.reachable()[orphan as usize]);
953 assert_eq!(graph.repair_unreachable(&vectors, Metric::L2), 1);
954 assert!(graph.reachable().iter().all(|&r| r));
955 assert_eq!(graph.repair_unreachable(&vectors, Metric::L2), 0);
956 }
957
958 #[test]
959 fn visited_set_resets_between_searches() {
960 let mut visited = Visited::default();
961 visited.reset(200);
962 assert!(visited.insert(3) && visited.insert(130));
963 assert!(!visited.insert(3));
964 visited.reset(200);
965 assert!(visited.insert(3) && visited.insert(130));
966 }
967}