1use std::cmp::Reverse;
9use std::collections::BinaryHeap;
10use std::sync::Arc;
11
12use crate::dsl::IvfRoutingMode;
13use rand::prelude::*;
14use serde::{Deserialize, Serialize};
15
16pub const HNSW_AUTO_THRESHOLD: usize = 4_096;
20
21const PARENT_BEAM_OVERSAMPLE: usize = 4;
25
26const HNSW_M: usize = 32;
27const HNSW_EF_CONSTRUCTION: usize = 200;
28const HNSW_QUERY_OVERSAMPLE: usize = 4;
29const HNSW_MIN_EF_SEARCH: usize = 128;
30
31#[derive(Clone, Copy, Debug)]
32struct GraphCandidate {
33 node: u32,
34 distance: f32,
35}
36
37impl PartialEq for GraphCandidate {
38 fn eq(&self, other: &Self) -> bool {
39 self.node == other.node && self.distance.to_bits() == other.distance.to_bits()
40 }
41}
42
43impl Eq for GraphCandidate {}
44
45impl PartialOrd for GraphCandidate {
46 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
47 Some(self.cmp(other))
48 }
49}
50
51impl Ord for GraphCandidate {
52 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
53 self.distance
54 .total_cmp(&other.distance)
55 .then_with(|| self.node.cmp(&other.node))
56 }
57}
58
59struct VisitedNodes {
60 epochs: Vec<u32>,
61 current: u32,
62}
63
64impl VisitedNodes {
65 fn new(nodes: usize) -> Self {
66 Self {
67 epochs: vec![0; nodes],
68 current: 0,
69 }
70 }
71
72 fn reset(&mut self) {
73 self.current = self.current.wrapping_add(1);
74 if self.current == 0 {
75 self.epochs.fill(0);
76 self.current = 1;
77 }
78 }
79
80 fn ensure_nodes(&mut self, nodes: usize) {
81 if self.epochs.len() < nodes {
82 self.epochs.resize(nodes, 0);
83 }
84 }
85
86 fn insert(&mut self, node: u32) -> bool {
87 let slot = &mut self.epochs[node as usize];
88 if *slot == self.current {
89 false
90 } else {
91 *slot = self.current;
92 true
93 }
94 }
95}
96
97struct HnswQueryScratch {
98 visited: VisitedNodes,
99 candidates: BinaryHeap<Reverse<GraphCandidate>>,
100 best: BinaryHeap<GraphCandidate>,
101 ordered: Vec<GraphCandidate>,
102}
103
104impl HnswQueryScratch {
105 fn new() -> Self {
106 Self {
107 visited: VisitedNodes::new(0),
108 candidates: BinaryHeap::new(),
109 best: BinaryHeap::new(),
110 ordered: Vec::new(),
111 }
112 }
113}
114
115thread_local! {
116 static HNSW_QUERY_SCRATCH: std::cell::RefCell<HnswQueryScratch> =
120 std::cell::RefCell::new(HnswQueryScratch::new());
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct HnswRoutingGraph {
128 m: u16,
129 ef_construction: u32,
130 entry_point: u32,
131 max_level: u8,
132 node_levels: Vec<u8>,
133 node_offsets: Vec<u32>,
136 level_offsets: Vec<u32>,
137 neighbors: Vec<u32>,
138}
139
140impl HnswRoutingGraph {
141 pub fn build(node_count: usize, distance: impl Fn(u32, u32) -> f32, seed: u64) -> Self {
142 assert!(node_count > 0 && node_count <= u32::MAX as usize);
143 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
144 let level_multiplier = 1.0 / (HNSW_M as f64).ln();
145 let node_levels: Vec<u8> = (0..node_count)
146 .map(|_| {
147 let uniform = rng.random::<f64>().clamp(f64::MIN_POSITIVE, 1.0);
148 (-uniform.ln() * level_multiplier).floor().min(31.0) as u8
149 })
150 .collect();
151 let mut insertion_order: Vec<u32> = (0..node_count as u32).collect();
152 insertion_order.shuffle(&mut rng);
153 let mut links: Vec<Vec<Vec<u32>>> = node_levels
154 .iter()
155 .map(|&level| vec![Vec::new(); level as usize + 1])
156 .collect();
157 let mut visited = VisitedNodes::new(node_count);
158 let mut entry_point = insertion_order[0];
159 let mut max_level = node_levels[entry_point as usize];
160
161 for &node in insertion_order.iter().skip(1) {
162 let node_level = node_levels[node as usize];
163 let mut entry = entry_point;
164 let node_distance = |candidate| distance(node, candidate);
165
166 for level in ((node_level as usize + 1)..=max_level as usize).rev() {
167 entry = greedy_search_level(&links, entry, level, &node_distance);
168 }
169
170 for level in (0..=usize::min(node_level as usize, max_level as usize)).rev() {
171 let candidates = search_graph_layer(
172 &links,
173 entry,
174 level,
175 HNSW_EF_CONSTRUCTION,
176 &node_distance,
177 &mut visited,
178 );
179 if let Some(best) = candidates.first() {
180 entry = best.node;
181 }
182 let max_connections = if level == 0 { HNSW_M * 2 } else { HNSW_M };
183 let selected =
184 select_diverse_neighbors(node, candidates, max_connections, &distance);
185 links[node as usize][level] = selected.clone();
186 for neighbor in selected {
187 let adjacency = &mut links[neighbor as usize][level];
188 if !adjacency.contains(&node) {
189 adjacency.push(node);
190 }
191 if adjacency.len() > max_connections {
192 let candidates = adjacency
193 .iter()
194 .copied()
195 .map(|candidate| GraphCandidate {
196 node: candidate,
197 distance: distance(neighbor, candidate),
198 })
199 .collect();
200 *adjacency = select_diverse_neighbors(
201 neighbor,
202 candidates,
203 max_connections,
204 &distance,
205 );
206 }
207 }
208 }
209
210 if node_level > max_level {
211 entry_point = node;
212 max_level = node_level;
213 }
214 }
215
216 Self::compact(
217 HNSW_M,
218 HNSW_EF_CONSTRUCTION,
219 entry_point,
220 max_level,
221 node_levels,
222 links,
223 )
224 }
225
226 fn compact(
227 m: usize,
228 ef_construction: usize,
229 entry_point: u32,
230 max_level: u8,
231 node_levels: Vec<u8>,
232 links: Vec<Vec<Vec<u32>>>,
233 ) -> Self {
234 let mut node_offsets = Vec::with_capacity(links.len() + 1);
235 let level_count: usize = links.iter().map(|levels| levels.len() + 1).sum();
236 let neighbor_count: usize = links
237 .iter()
238 .flat_map(|levels| levels.iter())
239 .map(Vec::len)
240 .sum();
241 let mut level_offsets = Vec::with_capacity(level_count);
242 let mut neighbors = Vec::with_capacity(neighbor_count);
243 for levels in links {
244 node_offsets.push(level_offsets.len() as u32);
245 for mut adjacency in levels {
246 adjacency.sort_unstable();
247 adjacency.dedup();
248 level_offsets.push(neighbors.len() as u32);
249 neighbors.extend(adjacency);
250 }
251 level_offsets.push(neighbors.len() as u32);
252 }
253 node_offsets.push(level_offsets.len() as u32);
254 Self {
255 m: m as u16,
256 ef_construction: ef_construction as u32,
257 entry_point,
258 max_level,
259 node_levels,
260 node_offsets,
261 level_offsets,
262 neighbors,
263 }
264 }
265
266 #[inline]
267 pub fn neighbors(&self, node: u32, level: usize) -> &[u32] {
268 if (self.node_levels[node as usize] as usize) < level {
269 return &[];
270 }
271 let offset_index = self.node_offsets[node as usize] as usize + level;
272 let start = self.level_offsets[offset_index] as usize;
273 let end = self.level_offsets[offset_index + 1] as usize;
274 &self.neighbors[start..end]
275 }
276
277 pub fn search(&self, query_distance: impl Fn(u32) -> f32, take: usize) -> Vec<u32> {
278 let take = take.min(self.node_levels.len());
279 if take == 0 {
280 return Vec::new();
281 }
282 let mut entry = self.entry_point;
283 for level in (1..=self.max_level as usize).rev() {
284 entry = greedy_search_compact(self, entry, level, &query_distance);
285 }
286 let ef_search = take
287 .saturating_mul(HNSW_QUERY_OVERSAMPLE)
288 .max(HNSW_MIN_EF_SEARCH)
289 .min(self.node_levels.len());
290 HNSW_QUERY_SCRATCH.with(|scratch| {
291 let mut scratch = scratch.borrow_mut();
292 search_compact_layer_reusing(self, entry, ef_search, &query_distance, &mut scratch);
293 scratch
294 .ordered
295 .iter()
296 .take(take)
297 .map(|candidate| candidate.node)
298 .collect()
299 })
300 }
301
302 pub fn search_one(&self, query_distance: impl Fn(u32) -> f32) -> u32 {
303 let mut entry = self.entry_point;
304 for level in (1..=self.max_level as usize).rev() {
305 entry = greedy_search_compact(self, entry, level, &query_distance);
306 }
307 let ef_search = HNSW_MIN_EF_SEARCH.min(self.node_levels.len());
308 HNSW_QUERY_SCRATCH.with(|scratch| {
309 let mut scratch = scratch.borrow_mut();
310 search_compact_layer_reusing(self, entry, ef_search, &query_distance, &mut scratch);
311 scratch
312 .ordered
313 .first()
314 .map_or(entry, |candidate| candidate.node)
315 })
316 }
317
318 pub fn validate(&self, expected_nodes: usize) -> bool {
319 if self.m as usize != HNSW_M
320 || self.ef_construction as usize != HNSW_EF_CONSTRUCTION
321 || expected_nodes == 0
322 || self.node_levels.len() != expected_nodes
323 || self.node_offsets.len() != expected_nodes + 1
324 || self.node_offsets.first() != Some(&0)
325 || self.node_offsets.last().copied() != Some(self.level_offsets.len() as u32)
326 || self.node_offsets.windows(2).any(|pair| pair[0] > pair[1])
327 || self
328 .node_offsets
329 .iter()
330 .any(|&offset| offset as usize > self.level_offsets.len())
331 || self.entry_point as usize >= expected_nodes
332 || self.node_levels[self.entry_point as usize] != self.max_level
333 || self.node_levels.iter().copied().max() != Some(self.max_level)
334 || self.level_offsets.last().copied() != Some(self.neighbors.len() as u32)
335 || self.level_offsets.windows(2).any(|pair| pair[0] > pair[1])
336 || self
337 .neighbors
338 .iter()
339 .any(|&node| node as usize >= expected_nodes)
340 {
341 return false;
342 }
343 for node in 0..expected_nodes {
344 let start = self.node_offsets[node] as usize;
345 let end = self.node_offsets[node + 1] as usize;
346 if end.saturating_sub(start) != self.node_levels[node] as usize + 2 {
347 return false;
348 }
349 for level in 0..=self.node_levels[node] as usize {
350 let adjacency = self.neighbors(node as u32, level);
351 let max_connections = if level == 0 { HNSW_M * 2 } else { HNSW_M };
352 if adjacency.len() > max_connections
353 || adjacency.contains(&(node as u32))
354 || adjacency.windows(2).any(|pair| pair[0] >= pair[1])
355 {
356 return false;
357 }
358 }
359 }
360 true
361 }
362
363 pub fn size_bytes(&self) -> usize {
364 self.node_levels.len()
365 + self.node_offsets.len() * size_of::<u32>()
366 + self.level_offsets.len() * size_of::<u32>()
367 + self.neighbors.len() * size_of::<u32>()
368 + 32
369 }
370
371 #[cfg(feature = "native")]
374 pub(crate) fn visit_resident_regions(&self, visit: &mut dyn FnMut(&'static str, &[u8])) {
375 visit("HNSW node levels", bytes_of_slice(&self.node_levels));
376 visit("HNSW node offsets", bytes_of_slice(&self.node_offsets));
377 visit("HNSW level offsets", bytes_of_slice(&self.level_offsets));
378 visit("HNSW neighbors", bytes_of_slice(&self.neighbors));
379 }
380}
381
382fn greedy_search_level(
383 links: &[Vec<Vec<u32>>],
384 mut current: u32,
385 level: usize,
386 query_distance: &impl Fn(u32) -> f32,
387) -> u32 {
388 let mut current_distance = query_distance(current);
389 loop {
390 let mut changed = false;
391 for &candidate in &links[current as usize][level] {
392 let distance = query_distance(candidate);
393 if distance < current_distance || (distance == current_distance && candidate < current)
394 {
395 current = candidate;
396 current_distance = distance;
397 changed = true;
398 }
399 }
400 if !changed {
401 return current;
402 }
403 }
404}
405
406fn greedy_search_compact(
407 graph: &HnswRoutingGraph,
408 mut current: u32,
409 level: usize,
410 query_distance: &impl Fn(u32) -> f32,
411) -> u32 {
412 let mut current_distance = query_distance(current);
413 loop {
414 let mut changed = false;
415 for &candidate in graph.neighbors(current, level) {
416 let distance = query_distance(candidate);
417 if distance < current_distance || (distance == current_distance && candidate < current)
418 {
419 current = candidate;
420 current_distance = distance;
421 changed = true;
422 }
423 }
424 if !changed {
425 return current;
426 }
427 }
428}
429
430fn search_graph_layer(
431 links: &[Vec<Vec<u32>>],
432 entry: u32,
433 level: usize,
434 ef: usize,
435 query_distance: &impl Fn(u32) -> f32,
436 visited: &mut VisitedNodes,
437) -> Vec<GraphCandidate> {
438 search_layer_impl(entry, ef, query_distance, visited, |node| {
439 &links[node as usize][level]
440 })
441}
442
443fn search_compact_layer_reusing(
444 graph: &HnswRoutingGraph,
445 entry: u32,
446 ef: usize,
447 query_distance: &impl Fn(u32) -> f32,
448 scratch: &mut HnswQueryScratch,
449) {
450 scratch.visited.ensure_nodes(graph.node_levels.len());
451 scratch.visited.reset();
452 scratch.candidates.clear();
453 scratch.best.clear();
454 scratch.ordered.clear();
455 scratch.visited.insert(entry);
456 let first = GraphCandidate {
457 node: entry,
458 distance: query_distance(entry),
459 };
460 scratch.candidates.push(Reverse(first));
461 scratch.best.push(first);
462
463 while let Some(Reverse(current)) = scratch.candidates.pop() {
464 if scratch.best.len() >= ef
465 && scratch
466 .best
467 .peek()
468 .is_some_and(|worst| current.distance > worst.distance)
469 {
470 break;
471 }
472 for &neighbor in graph.neighbors(current.node, 0) {
473 if !scratch.visited.insert(neighbor) {
474 continue;
475 }
476 let candidate = GraphCandidate {
477 node: neighbor,
478 distance: query_distance(neighbor),
479 };
480 if scratch.best.len() < ef
481 || scratch.best.peek().is_some_and(|worst| candidate < *worst)
482 {
483 scratch.candidates.push(Reverse(candidate));
484 scratch.best.push(candidate);
485 if scratch.best.len() > ef {
486 scratch.best.pop();
487 }
488 }
489 }
490 }
491 scratch.ordered.extend(scratch.best.drain());
492 scratch.ordered.sort_unstable();
493}
494
495fn search_layer_impl<'a>(
496 entry: u32,
497 ef: usize,
498 query_distance: &impl Fn(u32) -> f32,
499 visited: &mut VisitedNodes,
500 neighbors: impl Fn(u32) -> &'a [u32],
501) -> Vec<GraphCandidate> {
502 visited.reset();
503 visited.insert(entry);
504 let first = GraphCandidate {
505 node: entry,
506 distance: query_distance(entry),
507 };
508 let mut candidates = BinaryHeap::new();
509 let mut best = BinaryHeap::new();
510 candidates.push(Reverse(first));
511 best.push(first);
512
513 while let Some(Reverse(current)) = candidates.pop() {
514 if best.len() >= ef
515 && best
516 .peek()
517 .is_some_and(|worst| current.distance > worst.distance)
518 {
519 break;
520 }
521 for &neighbor in neighbors(current.node) {
522 if !visited.insert(neighbor) {
523 continue;
524 }
525 let candidate = GraphCandidate {
526 node: neighbor,
527 distance: query_distance(neighbor),
528 };
529 if best.len() < ef || best.peek().is_some_and(|worst| candidate < *worst) {
530 candidates.push(Reverse(candidate));
531 best.push(candidate);
532 if best.len() > ef {
533 best.pop();
534 }
535 }
536 }
537 }
538 best.into_sorted_vec()
539}
540
541fn select_diverse_neighbors(
542 query_node: u32,
543 mut candidates: Vec<GraphCandidate>,
544 limit: usize,
545 distance: &impl Fn(u32, u32) -> f32,
546) -> Vec<u32> {
547 candidates.sort_unstable();
548 candidates.dedup_by_key(|candidate| candidate.node);
549 let mut selected = Vec::with_capacity(limit);
550 let mut deferred = Vec::new();
551 for candidate in candidates {
552 if candidate.node == query_node {
553 continue;
554 }
555 if selected
556 .iter()
557 .all(|&neighbor| distance(candidate.node, neighbor) > candidate.distance)
558 {
559 selected.push(candidate.node);
560 if selected.len() == limit {
561 return selected;
562 }
563 } else {
564 deferred.push(candidate.node);
565 }
566 }
567 for candidate in deferred {
568 if selected.len() == limit {
569 break;
570 }
571 selected.push(candidate);
572 }
573 selected
574}
575
576#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
580pub struct IvfRoutingTopology {
581 child_offsets: Vec<u32>,
582 leaf_ids: Vec<u32>,
583}
584
585impl IvfRoutingTopology {
586 pub fn from_children(children: &[Vec<u32>]) -> Self {
587 let mut child_offsets = Vec::with_capacity(children.len() + 1);
588 let mut leaf_ids = Vec::new();
589 child_offsets.push(0);
590 for child_list in children {
591 leaf_ids.extend_from_slice(child_list);
592 child_offsets.push(leaf_ids.len() as u32);
593 }
594 Self {
595 child_offsets,
596 leaf_ids,
597 }
598 }
599
600 pub fn parent_count(&self) -> usize {
601 self.child_offsets.len().saturating_sub(1)
602 }
603
604 pub fn children(&self, parent: usize) -> &[u32] {
605 let start = self.child_offsets[parent] as usize;
606 let end = self.child_offsets[parent + 1] as usize;
607 &self.leaf_ids[start..end]
608 }
609
610 pub fn validate(&self, num_leaves: usize) -> bool {
611 if self.parent_count() == 0 {
612 return self.child_offsets.is_empty() && self.leaf_ids.is_empty();
613 }
614 self.child_offsets.first() == Some(&0)
615 && self.child_offsets.last().copied() == Some(self.leaf_ids.len() as u32)
616 && self.child_offsets.windows(2).all(|pair| pair[0] <= pair[1])
617 && self.leaf_ids.len() == num_leaves
618 && self.leaf_ids.iter().all(|&leaf| leaf < num_leaves as u32)
619 && {
620 let mut leaves = self.leaf_ids.clone();
621 leaves.sort_unstable();
622 leaves.iter().copied().eq(0..num_leaves as u32)
623 }
624 }
625
626 #[cfg(feature = "native")]
627 pub(crate) fn visit_resident_regions(&self, visit: &mut dyn FnMut(&'static str, &[u8])) {
628 visit(
629 "two-level child offsets",
630 bytes_of_slice(&self.child_offsets),
631 );
632 visit("two-level leaf IDs", bytes_of_slice(&self.leaf_ids));
633 }
634}
635
636#[cfg(feature = "native")]
639pub(crate) fn bytes_of_slice<T>(slice: &[T]) -> &[u8] {
640 let byte_len = std::mem::size_of_val(slice);
641 if byte_len == 0 {
642 return &[];
643 }
644 unsafe { std::slice::from_raw_parts(slice.as_ptr().cast::<u8>(), byte_len) }
647}
648
649pub fn routing_parent_count(num_leaves: usize) -> usize {
650 if num_leaves <= 1 {
651 return num_leaves;
652 }
653 ((num_leaves as f64).sqrt().ceil() as usize)
654 .clamp(2, 4_096)
655 .min(num_leaves)
656}
657
658pub fn allocate_child_clusters(group_sizes: &[usize], total_clusters: usize) -> Vec<usize> {
661 let mut allocated: Vec<usize> = group_sizes
662 .iter()
663 .map(|&size| usize::from(size > 0))
664 .collect();
665 let mut remaining = total_clusters.saturating_sub(allocated.iter().sum());
666 let total_points: usize = group_sizes.iter().sum();
667 if remaining == 0 || total_points == 0 {
668 return allocated;
669 }
670 for (allocation, &size) in allocated.iter_mut().zip(group_sizes) {
671 let capacity = size.saturating_sub(*allocation);
672 let share = remaining
673 .saturating_mul(size)
674 .checked_div(total_points)
675 .unwrap_or(0)
676 .min(capacity);
677 *allocation += share;
678 }
679 remaining = total_clusters.saturating_sub(allocated.iter().sum());
680 while remaining > 0 {
681 let Some((index, _)) = group_sizes
682 .iter()
683 .enumerate()
684 .filter(|(index, size)| allocated[*index] < **size)
685 .max_by_key(|(index, size)| (**size, std::cmp::Reverse(allocated[*index])))
686 else {
687 break;
688 };
689 allocated[index] += 1;
690 remaining -= 1;
691 }
692 allocated
693}
694
695#[derive(Debug, Clone, PartialEq, Eq)]
697pub struct IvfProbePlan {
698 pub quantizer_version: u64,
699 pub request_fingerprint: u64,
702 pub cluster_ids: Arc<[u32]>,
703}
704
705impl IvfProbePlan {
706 pub fn new(quantizer_version: u64, request_fingerprint: u64, cluster_ids: Vec<u32>) -> Self {
707 Self {
708 quantizer_version,
709 request_fingerprint,
710 cluster_ids: cluster_ids.into(),
711 }
712 }
713}
714
715fn fingerprint_words(
716 mode: IvfRoutingMode,
717 nprobe: usize,
718 words: impl IntoIterator<Item = u64>,
719) -> u64 {
720 let mut hash = 0xcbf2_9ce4_8422_2325u64;
723 let mode_tag = match mode {
724 IvfRoutingMode::Auto => 0u64,
725 IvfRoutingMode::Flat => 1,
726 IvfRoutingMode::TwoLevel => 2,
727 IvfRoutingMode::Hnsw => 3,
728 };
729 for word in std::iter::once(mode_tag)
730 .chain(std::iter::once(nprobe as u64))
731 .chain(words)
732 {
733 hash ^= word;
734 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
735 }
736 hash ^= hash >> 33;
737 hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
738 hash ^ (hash >> 33)
739}
740
741pub fn float_probe_fingerprint(query: &[f32], nprobe: usize, mode: IvfRoutingMode) -> u64 {
742 fingerprint_words(
743 mode,
744 nprobe,
745 query.iter().map(|value| value.to_bits() as u64),
746 )
747}
748
749pub fn binary_probe_fingerprint(query: &[u8], nprobe: usize, mode: IvfRoutingMode) -> u64 {
750 fingerprint_words(mode, nprobe, query.iter().map(|&value| value as u64))
751}
752
753#[inline]
754pub fn effective_routing_mode(mode: IvfRoutingMode, num_leaves: usize) -> IvfRoutingMode {
755 match mode {
756 IvfRoutingMode::Auto if num_leaves >= HNSW_AUTO_THRESHOLD => IvfRoutingMode::Hnsw,
757 IvfRoutingMode::Auto => IvfRoutingMode::Flat,
758 explicit => explicit,
759 }
760}
761
762pub fn parent_probe_count(nprobe: usize, num_leaves: usize, num_parents: usize) -> usize {
764 if num_parents == 0 || num_leaves == 0 {
765 return 0;
766 }
767 let leaves_per_parent = num_leaves.div_ceil(num_parents).max(1);
768 nprobe
769 .saturating_mul(PARENT_BEAM_OVERSAMPLE)
770 .div_ceil(leaves_per_parent)
771 .clamp(1, num_parents)
772}
773
774pub fn select_best<const HIGHER_IS_BETTER: bool>(scores: &[f32], take: usize) -> Vec<u32> {
777 let take = take.min(scores.len());
778 if take == 0 {
779 return Vec::new();
780 }
781 let mut order: Vec<u32> = (0..scores.len() as u32).collect();
782 let compare = |left: &u32, right: &u32| {
783 let left_score = scores[*left as usize];
784 let right_score = scores[*right as usize];
785 let score_order = if HIGHER_IS_BETTER {
786 right_score.total_cmp(&left_score)
787 } else {
788 left_score.total_cmp(&right_score)
789 };
790 score_order.then_with(|| left.cmp(right))
791 };
792 if take < order.len() {
793 order.select_nth_unstable_by(take, compare);
794 order.truncate(take);
795 }
796 order.sort_unstable_by(compare);
797 order
798}
799
800pub fn select_best_candidates<const HIGHER_IS_BETTER: bool>(
804 candidates: &mut Vec<(u32, f32)>,
805 take: usize,
806) -> Vec<u32> {
807 let take = take.min(candidates.len());
808 if take == 0 {
809 return Vec::new();
810 }
811 let compare = |left: &(u32, f32), right: &(u32, f32)| {
812 let score_order = if HIGHER_IS_BETTER {
813 right.1.total_cmp(&left.1)
814 } else {
815 left.1.total_cmp(&right.1)
816 };
817 score_order.then_with(|| left.0.cmp(&right.0))
818 };
819 if take < candidates.len() {
820 candidates.select_nth_unstable_by(take, compare);
821 candidates.truncate(take);
822 }
823 candidates.sort_unstable_by(compare);
824 candidates
825 .iter()
826 .map(|(cluster_id, _)| *cluster_id)
827 .collect()
828}
829
830#[cfg(test)]
831mod tests {
832 use super::*;
833
834 #[test]
835 fn deterministic_selection_supports_both_metric_directions() {
836 let scores = [0.5, 0.9, 0.1, 0.9];
837 assert_eq!(select_best::<true>(&scores, 2), vec![1, 3]);
838 assert_eq!(select_best::<false>(&scores, 2), vec![2, 0]);
839 }
840
841 #[test]
842 fn two_level_beam_is_oversubscribed_but_bounded() {
843 assert_eq!(parent_probe_count(32, 65_536, 256), 1);
844 assert_eq!(parent_probe_count(256, 65_536, 256), 4);
845 assert_eq!(parent_probe_count(65_536, 65_536, 256), 256);
846 }
847
848 #[test]
849 fn compact_hnsw_routes_without_copying_points() {
850 let points: Vec<[f32; 2]> = (0..512)
851 .map(|index| {
852 let angle = index as f32 * std::f32::consts::TAU / 512.0;
853 [angle.cos(), angle.sin()]
854 })
855 .collect();
856 let distance = |left: u32, right: u32| {
857 let [lx, ly] = points[left as usize];
858 let [rx, ry] = points[right as usize];
859 (lx - rx).powi(2) + (ly - ry).powi(2)
860 };
861 let graph = HnswRoutingGraph::build(points.len(), distance, 42);
862 assert!(graph.validate(points.len()));
863 assert!(graph.size_bytes() < points.len() * 512);
864
865 let query = [0.37f32, -0.91];
866 let routed = graph.search(
867 |node| {
868 let [x, y] = points[node as usize];
869 (x - query[0]).powi(2) + (y - query[1]).powi(2)
870 },
871 10,
872 );
873 let mut exact: Vec<u32> = (0..points.len() as u32).collect();
874 exact.sort_unstable_by(|&left, &right| {
875 let score = |node: u32| {
876 let [x, y] = points[node as usize];
877 (x - query[0]).powi(2) + (y - query[1]).powi(2)
878 };
879 score(left)
880 .total_cmp(&score(right))
881 .then_with(|| left.cmp(&right))
882 });
883 assert_eq!(routed, exact[..10]);
884
885 let bytes = bincode::serde::encode_to_vec(&graph, bincode::config::standard()).unwrap();
886 let (decoded, consumed): (HnswRoutingGraph, usize) =
887 bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
888 assert_eq!(consumed, bytes.len());
889 assert!(decoded.validate(points.len()));
890
891 let mut corrupted = decoded;
892 corrupted.node_offsets[1] = u32::MAX;
893 assert!(!corrupted.validate(points.len()));
894 }
895}