1use std::cmp::Reverse;
9use std::collections::BinaryHeap;
10use std::sync::Arc;
11
12use crate::dsl::IvfRoutingMode;
13use crate::structures::vector::progress::PhaseProgress;
14use rand::prelude::*;
15use serde::{Deserialize, Serialize};
16
17pub const HNSW_AUTO_THRESHOLD: usize = 4_096;
25
26pub const BINARY_HNSW_AUTO_THRESHOLD: usize = 32_768;
49
50pub const HIERARCHICAL_TRAINING_THRESHOLD: usize = 4_096;
57
58const PARENT_BEAM_OVERSAMPLE: usize = 4;
62const MIN_BUILD_PARENT_BEAM: usize = 4;
65
66const HNSW_M: usize = 32;
67const HNSW_EF_CONSTRUCTION: usize = 200;
68const HNSW_QUERY_OVERSAMPLE: usize = 4;
69const HNSW_MIN_EF_SEARCH: usize = 128;
70const HNSW_BUILD_OVERSAMPLE: usize = 8;
75pub(crate) const HNSW_MIN_EF_BUILD: usize = 128;
85
86const NEIGHBOR_BLOCK: usize = HNSW_M * 4;
90
91pub trait QueryDistance {
98 fn distance(&self, node: u32) -> f32;
99
100 fn distances(&self, nodes: &[u32], out: &mut [f32]) {
102 debug_assert_eq!(nodes.len(), out.len());
103 for (slot, &node) in out.iter_mut().zip(nodes) {
104 *slot = self.distance(node);
105 }
106 }
107}
108
109impl<F: Fn(u32) -> f32> QueryDistance for F {
110 #[inline]
111 fn distance(&self, node: u32) -> f32 {
112 self(node)
113 }
114}
115
116pub trait PairDistance {
118 fn distance(&self, left: u32, right: u32) -> f32;
119
120 fn distances_from(&self, left: u32, rights: &[u32], out: &mut [f32]) {
122 debug_assert_eq!(rights.len(), out.len());
123 for (slot, &right) in out.iter_mut().zip(rights) {
124 *slot = self.distance(left, right);
125 }
126 }
127}
128
129impl<F: Fn(u32, u32) -> f32> PairDistance for F {
130 #[inline]
131 fn distance(&self, left: u32, right: u32) -> f32 {
132 self(left, right)
133 }
134}
135
136struct PairQueryDistance<'a, P: ?Sized> {
139 pair: &'a P,
140 left: u32,
141}
142
143impl<P: PairDistance + ?Sized> QueryDistance for PairQueryDistance<'_, P> {
144 #[inline]
145 fn distance(&self, node: u32) -> f32 {
146 self.pair.distance(self.left, node)
147 }
148
149 #[inline]
150 fn distances(&self, nodes: &[u32], out: &mut [f32]) {
151 self.pair.distances_from(self.left, nodes, out);
152 }
153}
154
155#[derive(Clone, Copy, Debug)]
156struct GraphCandidate {
157 node: u32,
158 distance: f32,
159}
160
161impl PartialEq for GraphCandidate {
162 fn eq(&self, other: &Self) -> bool {
163 self.node == other.node && self.distance.to_bits() == other.distance.to_bits()
164 }
165}
166
167impl Eq for GraphCandidate {}
168
169impl PartialOrd for GraphCandidate {
170 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
171 Some(self.cmp(other))
172 }
173}
174
175impl Ord for GraphCandidate {
176 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
177 self.distance
178 .total_cmp(&other.distance)
179 .then_with(|| self.node.cmp(&other.node))
180 }
181}
182
183struct VisitedNodes {
184 epochs: Vec<u32>,
185 current: u32,
186}
187
188impl VisitedNodes {
189 fn new(nodes: usize) -> Self {
190 Self {
191 epochs: vec![0; nodes],
192 current: 0,
193 }
194 }
195
196 fn reset(&mut self) {
197 self.current = self.current.wrapping_add(1);
198 if self.current == 0 {
199 self.epochs.fill(0);
200 self.current = 1;
201 }
202 }
203
204 fn ensure_nodes(&mut self, nodes: usize) {
205 if self.epochs.len() < nodes {
206 self.epochs.resize(nodes, 0);
207 }
208 }
209
210 fn insert(&mut self, node: u32) -> bool {
211 let slot = &mut self.epochs[node as usize];
212 if *slot == self.current {
213 false
214 } else {
215 *slot = self.current;
216 true
217 }
218 }
219}
220
221struct HnswQueryScratch {
222 visited: VisitedNodes,
223 candidates: BinaryHeap<Reverse<GraphCandidate>>,
224 best: BinaryHeap<GraphCandidate>,
225 ordered: Vec<GraphCandidate>,
226 pending: Vec<u32>,
229 pending_distances: Vec<f32>,
230}
231
232impl HnswQueryScratch {
233 fn new() -> Self {
234 Self {
235 visited: VisitedNodes::new(0),
236 candidates: BinaryHeap::new(),
237 best: BinaryHeap::new(),
238 ordered: Vec::new(),
239 pending: Vec::new(),
240 pending_distances: Vec::new(),
241 }
242 }
243}
244
245thread_local! {
246 static HNSW_QUERY_SCRATCH: std::cell::RefCell<HnswQueryScratch> =
250 std::cell::RefCell::new(HnswQueryScratch::new());
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct HnswRoutingGraph {
258 m: u16,
259 ef_construction: u32,
260 entry_point: u32,
261 max_level: u8,
262 node_levels: Vec<u8>,
263 node_offsets: Vec<u32>,
266 level_offsets: Vec<u32>,
267 neighbors: Vec<u32>,
268}
269
270impl HnswRoutingGraph {
271 pub fn build(
272 node_count: usize,
273 distance: impl PairDistance,
274 seed: u64,
275 index_label: &str,
276 ) -> Self {
277 assert!(node_count > 0 && node_count <= u32::MAX as usize);
278 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
279 let level_multiplier = 1.0 / (HNSW_M as f64).ln();
280 let node_levels: Vec<u8> = (0..node_count)
281 .map(|_| {
282 let uniform = rng.random::<f64>().clamp(f64::MIN_POSITIVE, 1.0);
283 (-uniform.ln() * level_multiplier).floor().min(31.0) as u8
284 })
285 .collect();
286 let mut insertion_order: Vec<u32> = (0..node_count as u32).collect();
287 insertion_order.shuffle(&mut rng);
288 let mut links: Vec<Vec<Vec<u32>>> = node_levels
289 .iter()
290 .map(|&level| vec![Vec::new(); level as usize + 1])
291 .collect();
292 let mut visited = VisitedNodes::new(node_count);
293 let mut entry_point = insertion_order[0];
294 let mut max_level = node_levels[entry_point as usize];
295
296 let mut progress = PhaseProgress::start(
297 index_label,
298 "hnsw graph build",
299 format!("{node_count} centroids, M={HNSW_M}, ef={HNSW_EF_CONSTRUCTION}"),
300 node_count,
301 );
302 for (inserted, &node) in insertion_order.iter().enumerate().skip(1) {
303 progress.advance(inserted);
304 let node_level = node_levels[node as usize];
305 let mut entry = entry_point;
306 let node_distance = PairQueryDistance {
307 pair: &distance,
308 left: node,
309 };
310
311 for level in ((node_level as usize + 1)..=max_level as usize).rev() {
312 entry = greedy_search_level(&links, entry, level, &node_distance);
313 }
314
315 for level in (0..=usize::min(node_level as usize, max_level as usize)).rev() {
316 let candidates = search_graph_layer(
317 &links,
318 entry,
319 level,
320 HNSW_EF_CONSTRUCTION,
321 &node_distance,
322 &mut visited,
323 );
324 if let Some(best) = candidates.first() {
325 entry = best.node;
326 }
327 let max_connections = if level == 0 { HNSW_M * 2 } else { HNSW_M };
328 let selected =
329 select_diverse_neighbors(node, candidates, max_connections, &distance);
330 links[node as usize][level] = selected.clone();
331 for neighbor in selected {
332 let adjacency = &mut links[neighbor as usize][level];
333 if !adjacency.contains(&node) {
334 adjacency.push(node);
335 }
336 if adjacency.len() > max_connections {
337 let candidates = adjacency
338 .iter()
339 .copied()
340 .map(|candidate| GraphCandidate {
341 node: candidate,
342 distance: distance.distance(neighbor, candidate),
343 })
344 .collect();
345 *adjacency = select_diverse_neighbors(
346 neighbor,
347 candidates,
348 max_connections,
349 &distance,
350 );
351 }
352 }
353 }
354
355 if node_level > max_level {
356 entry_point = node;
357 max_level = node_level;
358 }
359 }
360 progress.finish();
361
362 Self::compact(
363 HNSW_M,
364 HNSW_EF_CONSTRUCTION,
365 entry_point,
366 max_level,
367 node_levels,
368 links,
369 )
370 }
371
372 fn compact(
373 m: usize,
374 ef_construction: usize,
375 entry_point: u32,
376 max_level: u8,
377 node_levels: Vec<u8>,
378 links: Vec<Vec<Vec<u32>>>,
379 ) -> Self {
380 let mut node_offsets = Vec::with_capacity(links.len() + 1);
381 let level_count: usize = links.iter().map(|levels| levels.len() + 1).sum();
382 let neighbor_count: usize = links
383 .iter()
384 .flat_map(|levels| levels.iter())
385 .map(Vec::len)
386 .sum();
387 let mut level_offsets = Vec::with_capacity(level_count);
388 let mut neighbors = Vec::with_capacity(neighbor_count);
389 for levels in links {
390 node_offsets.push(level_offsets.len() as u32);
391 for mut adjacency in levels {
392 adjacency.sort_unstable();
393 adjacency.dedup();
394 level_offsets.push(neighbors.len() as u32);
395 neighbors.extend(adjacency);
396 }
397 level_offsets.push(neighbors.len() as u32);
398 }
399 node_offsets.push(level_offsets.len() as u32);
400 Self {
401 m: m as u16,
402 ef_construction: ef_construction as u32,
403 entry_point,
404 max_level,
405 node_levels,
406 node_offsets,
407 level_offsets,
408 neighbors,
409 }
410 }
411
412 #[inline]
413 pub fn neighbors(&self, node: u32, level: usize) -> &[u32] {
414 if (self.node_levels[node as usize] as usize) < level {
415 return &[];
416 }
417 let offset_index = self.node_offsets[node as usize] as usize + level;
418 let start = self.level_offsets[offset_index] as usize;
419 let end = self.level_offsets[offset_index + 1] as usize;
420 &self.neighbors[start..end]
421 }
422
423 pub fn search(&self, query_distance: impl QueryDistance, take: usize) -> Vec<u32> {
424 let take = take.min(self.node_levels.len());
425 if take == 0 {
426 return Vec::new();
427 }
428 let ef_search = take
429 .saturating_mul(HNSW_QUERY_OVERSAMPLE)
430 .max(HNSW_MIN_EF_SEARCH)
431 .min(self.node_levels.len());
432 self.search_with_budget(query_distance, take, ef_search)
433 }
434
435 pub(crate) fn search_for_build(
437 &self,
438 query_distance: impl QueryDistance,
439 take: usize,
440 ) -> Vec<u32> {
441 let take = take.min(self.node_levels.len());
442 if take == 0 {
443 return Vec::new();
444 }
445 self.search_with_budget(query_distance, take, self.build_budget(take))
446 }
447
448 pub(crate) fn search_best_for_build(&self, query_distance: impl QueryDistance) -> Option<u32> {
455 if self.node_levels.is_empty() {
456 return None;
457 }
458 let ef_search = self.build_budget(1);
459 let mut entry = self.entry_point;
460 for level in (1..=self.max_level as usize).rev() {
461 entry = greedy_search_compact(self, entry, level, &query_distance);
462 }
463 HNSW_QUERY_SCRATCH.with(|scratch| {
464 let mut scratch = scratch.borrow_mut();
465 search_compact_layer_reusing(self, entry, ef_search, &query_distance, &mut scratch);
466 Some(
467 scratch
468 .best
469 .iter()
470 .min()
471 .map_or(entry, |candidate| candidate.node),
472 )
473 })
474 }
475
476 #[inline]
477 fn build_budget(&self, take: usize) -> usize {
478 take.saturating_mul(HNSW_BUILD_OVERSAMPLE)
479 .max(HNSW_MIN_EF_BUILD)
480 .min(self.node_levels.len())
481 }
482
483 #[cfg(test)]
486 pub(crate) fn search_best_with_ef(
487 &self,
488 query_distance: impl QueryDistance,
489 ef: usize,
490 ) -> Option<u32> {
491 if self.node_levels.is_empty() {
492 return None;
493 }
494 let ef_search = ef.clamp(1, self.node_levels.len());
495 let mut entry = self.entry_point;
496 for level in (1..=self.max_level as usize).rev() {
497 entry = greedy_search_compact(self, entry, level, &query_distance);
498 }
499 HNSW_QUERY_SCRATCH.with(|scratch| {
500 let mut scratch = scratch.borrow_mut();
501 search_compact_layer_reusing(self, entry, ef_search, &query_distance, &mut scratch);
502 Some(
503 scratch
504 .best
505 .iter()
506 .min()
507 .map_or(entry, |candidate| candidate.node),
508 )
509 })
510 }
511
512 fn search_with_budget(
513 &self,
514 query_distance: impl QueryDistance,
515 take: usize,
516 ef_search: usize,
517 ) -> Vec<u32> {
518 let mut entry = self.entry_point;
519 for level in (1..=self.max_level as usize).rev() {
520 entry = greedy_search_compact(self, entry, level, &query_distance);
521 }
522 HNSW_QUERY_SCRATCH.with(|scratch| {
523 let mut scratch = scratch.borrow_mut();
524 search_compact_layer_reusing(self, entry, ef_search, &query_distance, &mut scratch);
525 order_scratch_candidates(&mut scratch);
526 scratch
527 .ordered
528 .iter()
529 .take(take)
530 .map(|candidate| candidate.node)
531 .collect()
532 })
533 }
534
535 pub fn validate(&self, expected_nodes: usize) -> bool {
536 if self.m as usize != HNSW_M
537 || self.ef_construction as usize != HNSW_EF_CONSTRUCTION
538 || expected_nodes == 0
539 || self.node_levels.len() != expected_nodes
540 || self.node_offsets.len() != expected_nodes + 1
541 || self.node_offsets.first() != Some(&0)
542 || self.node_offsets.last().copied() != Some(self.level_offsets.len() as u32)
543 || self.node_offsets.windows(2).any(|pair| pair[0] > pair[1])
544 || self
545 .node_offsets
546 .iter()
547 .any(|&offset| offset as usize > self.level_offsets.len())
548 || self.entry_point as usize >= expected_nodes
549 || self.node_levels[self.entry_point as usize] != self.max_level
550 || self.node_levels.iter().copied().max() != Some(self.max_level)
551 || self.level_offsets.last().copied() != Some(self.neighbors.len() as u32)
552 || self.level_offsets.windows(2).any(|pair| pair[0] > pair[1])
553 || self
554 .neighbors
555 .iter()
556 .any(|&node| node as usize >= expected_nodes)
557 {
558 return false;
559 }
560 for node in 0..expected_nodes {
561 let start = self.node_offsets[node] as usize;
562 let end = self.node_offsets[node + 1] as usize;
563 if end.saturating_sub(start) != self.node_levels[node] as usize + 2 {
564 return false;
565 }
566 for level in 0..=self.node_levels[node] as usize {
567 let adjacency = self.neighbors(node as u32, level);
568 let max_connections = if level == 0 { HNSW_M * 2 } else { HNSW_M };
569 if adjacency.len() > max_connections
570 || adjacency.contains(&(node as u32))
571 || adjacency.windows(2).any(|pair| pair[0] >= pair[1])
572 {
573 return false;
574 }
575 }
576 }
577 true
578 }
579
580 pub fn size_bytes(&self) -> usize {
581 self.node_levels.len()
582 + self.node_offsets.len() * size_of::<u32>()
583 + self.level_offsets.len() * size_of::<u32>()
584 + self.neighbors.len() * size_of::<u32>()
585 + 32
586 }
587
588 #[cfg(feature = "native")]
591 pub(crate) fn visit_resident_regions(&self, visit: &mut dyn FnMut(&'static str, &[u8])) {
592 visit("HNSW node levels", bytes_of_slice(&self.node_levels));
593 visit("HNSW node offsets", bytes_of_slice(&self.node_offsets));
594 visit("HNSW level offsets", bytes_of_slice(&self.level_offsets));
595 visit("HNSW neighbors", bytes_of_slice(&self.neighbors));
596 }
597}
598
599fn greedy_search_level(
600 links: &[Vec<Vec<u32>>],
601 mut current: u32,
602 level: usize,
603 query_distance: &impl QueryDistance,
604) -> u32 {
605 let mut current_distance = query_distance.distance(current);
606 let mut scores = [0f32; NEIGHBOR_BLOCK];
607 loop {
608 let mut changed = false;
609 for chunk in links[current as usize][level].chunks(NEIGHBOR_BLOCK) {
610 let scored = &mut scores[..chunk.len()];
611 query_distance.distances(chunk, scored);
612 for (&candidate, &distance) in chunk.iter().zip(scored.iter()) {
613 if distance < current_distance
614 || (distance == current_distance && candidate < current)
615 {
616 current = candidate;
617 current_distance = distance;
618 changed = true;
619 }
620 }
621 }
622 if !changed {
623 return current;
624 }
625 }
626}
627
628fn greedy_search_compact(
629 graph: &HnswRoutingGraph,
630 mut current: u32,
631 level: usize,
632 query_distance: &impl QueryDistance,
633) -> u32 {
634 let mut current_distance = query_distance.distance(current);
635 let mut scores = [0f32; NEIGHBOR_BLOCK];
636 loop {
637 let mut changed = false;
638 for chunk in graph.neighbors(current, level).chunks(NEIGHBOR_BLOCK) {
639 let scored = &mut scores[..chunk.len()];
640 query_distance.distances(chunk, scored);
641 for (&candidate, &distance) in chunk.iter().zip(scored.iter()) {
642 if distance < current_distance
643 || (distance == current_distance && candidate < current)
644 {
645 current = candidate;
646 current_distance = distance;
647 changed = true;
648 }
649 }
650 }
651 if !changed {
652 return current;
653 }
654 }
655}
656
657fn search_graph_layer(
658 links: &[Vec<Vec<u32>>],
659 entry: u32,
660 level: usize,
661 ef: usize,
662 query_distance: &impl QueryDistance,
663 visited: &mut VisitedNodes,
664) -> Vec<GraphCandidate> {
665 search_layer_impl(entry, ef, query_distance, visited, |node| {
666 &links[node as usize][level]
667 })
668}
669
670fn search_compact_layer_reusing(
675 graph: &HnswRoutingGraph,
676 entry: u32,
677 ef: usize,
678 query_distance: &impl QueryDistance,
679 scratch: &mut HnswQueryScratch,
680) {
681 scratch.visited.ensure_nodes(graph.node_levels.len());
682 scratch.visited.reset();
683 scratch.candidates.clear();
684 scratch.best.clear();
685 scratch.ordered.clear();
686 scratch.visited.insert(entry);
687 let first = GraphCandidate {
688 node: entry,
689 distance: query_distance.distance(entry),
690 };
691 scratch.candidates.push(Reverse(first));
692 scratch.best.push(first);
693
694 while let Some(Reverse(current)) = scratch.candidates.pop() {
695 if scratch.best.len() >= ef
696 && scratch
697 .best
698 .peek()
699 .is_some_and(|worst| current.distance > worst.distance)
700 {
701 break;
702 }
703 scratch.pending.clear();
707 for &neighbor in graph.neighbors(current.node, 0) {
708 if scratch.visited.insert(neighbor) {
709 scratch.pending.push(neighbor);
710 }
711 }
712 if scratch.pending.is_empty() {
713 continue;
714 }
715 scratch.pending_distances.clear();
716 scratch.pending_distances.resize(scratch.pending.len(), 0.0);
717 query_distance.distances(&scratch.pending, &mut scratch.pending_distances);
718
719 for (&node, &distance) in scratch.pending.iter().zip(scratch.pending_distances.iter()) {
720 let candidate = GraphCandidate { node, distance };
721 if scratch.best.len() < ef
722 || scratch.best.peek().is_some_and(|worst| candidate < *worst)
723 {
724 scratch.candidates.push(Reverse(candidate));
725 scratch.best.push(candidate);
726 if scratch.best.len() > ef {
727 scratch.best.pop();
728 }
729 }
730 }
731 }
732}
733
734fn order_scratch_candidates(scratch: &mut HnswQueryScratch) {
735 scratch.ordered.extend(scratch.best.drain());
736 scratch.ordered.sort_unstable();
737}
738
739fn search_layer_impl<'a>(
740 entry: u32,
741 ef: usize,
742 query_distance: &impl QueryDistance,
743 visited: &mut VisitedNodes,
744 neighbors: impl Fn(u32) -> &'a [u32],
745) -> Vec<GraphCandidate> {
746 visited.reset();
747 visited.insert(entry);
748 let first = GraphCandidate {
749 node: entry,
750 distance: query_distance.distance(entry),
751 };
752 let mut candidates = BinaryHeap::new();
753 let mut best = BinaryHeap::new();
754 candidates.push(Reverse(first));
755 best.push(first);
756 let mut pending: Vec<u32> = Vec::new();
757 let mut pending_distances: Vec<f32> = Vec::new();
758
759 while let Some(Reverse(current)) = candidates.pop() {
760 if best.len() >= ef
761 && best
762 .peek()
763 .is_some_and(|worst| current.distance > worst.distance)
764 {
765 break;
766 }
767 pending.clear();
768 for &neighbor in neighbors(current.node) {
769 if visited.insert(neighbor) {
770 pending.push(neighbor);
771 }
772 }
773 if pending.is_empty() {
774 continue;
775 }
776 pending_distances.clear();
777 pending_distances.resize(pending.len(), 0.0);
778 query_distance.distances(&pending, &mut pending_distances);
779
780 for (&node, &distance) in pending.iter().zip(pending_distances.iter()) {
781 let candidate = GraphCandidate { node, distance };
782 if best.len() < ef || best.peek().is_some_and(|worst| candidate < *worst) {
783 candidates.push(Reverse(candidate));
784 best.push(candidate);
785 if best.len() > ef {
786 best.pop();
787 }
788 }
789 }
790 }
791 best.into_sorted_vec()
792}
793
794fn select_diverse_neighbors(
795 query_node: u32,
796 mut candidates: Vec<GraphCandidate>,
797 limit: usize,
798 distance: &impl PairDistance,
799) -> Vec<u32> {
800 candidates.sort_unstable();
801 candidates.dedup_by_key(|candidate| candidate.node);
802 let mut selected = Vec::with_capacity(limit);
803 let mut deferred = Vec::new();
804 for candidate in candidates {
805 if candidate.node == query_node {
806 continue;
807 }
808 if selected
809 .iter()
810 .all(|&neighbor| distance.distance(candidate.node, neighbor) > candidate.distance)
811 {
812 selected.push(candidate.node);
813 if selected.len() == limit {
814 return selected;
815 }
816 } else {
817 deferred.push(candidate.node);
818 }
819 }
820 for candidate in deferred {
821 if selected.len() == limit {
822 break;
823 }
824 selected.push(candidate);
825 }
826 selected
827}
828
829fn contiguous_leaf_run(children: &[u32]) -> bool {
830 children
831 .windows(2)
832 .all(|pair| pair[1] == pair[0].saturating_add(1))
833}
834
835#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
839pub struct IvfRoutingTopology {
840 child_offsets: Vec<u32>,
841 leaf_ids: Vec<u32>,
842}
843
844impl IvfRoutingTopology {
845 pub fn from_children(children: &[Vec<u32>]) -> Self {
846 let mut child_offsets = Vec::with_capacity(children.len() + 1);
847 let mut leaf_ids = Vec::new();
848 child_offsets.push(0);
849 for child_list in children {
850 leaf_ids.extend_from_slice(child_list);
851 child_offsets.push(leaf_ids.len() as u32);
852 }
853 Self {
854 child_offsets,
855 leaf_ids,
856 }
857 }
858
859 pub fn parent_count(&self) -> usize {
860 self.child_offsets.len().saturating_sub(1)
861 }
862
863 pub fn children(&self, parent: usize) -> &[u32] {
864 let start = self.child_offsets[parent] as usize;
865 let end = self.child_offsets[parent + 1] as usize;
866 &self.leaf_ids[start..end]
867 }
868
869 pub fn children_run(&self, parent: usize) -> Option<(u32, usize)> {
877 let children = self.children(parent);
878 let first = *children.first()?;
879 contiguous_leaf_run(children).then_some((first, children.len()))
880 }
881
882 pub fn validate(&self, num_leaves: usize) -> bool {
883 if self.parent_count() == 0 {
884 return self.child_offsets.is_empty() && self.leaf_ids.is_empty();
885 }
886 self.child_offsets.first() == Some(&0)
887 && self.child_offsets.last().copied() == Some(self.leaf_ids.len() as u32)
888 && self.child_offsets.windows(2).all(|pair| pair[0] <= pair[1])
889 && self.leaf_ids.len() == num_leaves
890 && self.leaf_ids.iter().all(|&leaf| leaf < num_leaves as u32)
891 && (0..self.parent_count()).all(|parent| contiguous_leaf_run(self.children(parent)))
895 && {
896 let mut leaves = self.leaf_ids.clone();
897 leaves.sort_unstable();
898 leaves.iter().copied().eq(0..num_leaves as u32)
899 }
900 }
901
902 #[cfg(feature = "native")]
903 pub(crate) fn visit_resident_regions(&self, visit: &mut dyn FnMut(&'static str, &[u8])) {
904 visit(
905 "two-level child offsets",
906 bytes_of_slice(&self.child_offsets),
907 );
908 visit("two-level leaf IDs", bytes_of_slice(&self.leaf_ids));
909 }
910}
911
912#[cfg(feature = "native")]
915pub(crate) fn bytes_of_slice<T>(slice: &[T]) -> &[u8] {
916 let byte_len = std::mem::size_of_val(slice);
917 if byte_len == 0 {
918 return &[];
919 }
920 unsafe { std::slice::from_raw_parts(slice.as_ptr().cast::<u8>(), byte_len) }
923}
924
925pub fn routing_parent_count(num_leaves: usize) -> usize {
926 if num_leaves <= 1 {
927 return num_leaves;
928 }
929 ((num_leaves as f64).sqrt().ceil() as usize)
930 .clamp(2, 4_096)
931 .min(num_leaves)
932}
933
934pub fn allocate_child_clusters(group_sizes: &[usize], total_clusters: usize) -> Vec<usize> {
937 let total_points: u128 = group_sizes.iter().map(|&size| size as u128).sum();
938 let target = (total_clusters as u128).min(total_points) as usize;
939 if target == 0 {
940 return vec![0; group_sizes.len()];
941 }
942
943 let populated = group_sizes.iter().filter(|&&size| size > 0).count();
944 let guarantee_populated = target >= populated;
945 let mut allocated = vec![0usize; group_sizes.len()];
946 let mut fixed = vec![false; group_sizes.len()];
947 let mut fixed_cells = 0usize;
948
949 if guarantee_populated {
950 loop {
958 let active_weight: u128 = group_sizes
959 .iter()
960 .enumerate()
961 .filter(|(index, size)| **size > 0 && !fixed[*index])
962 .map(|(_, &size)| size as u128)
963 .sum();
964 let active_target = target - fixed_cells;
965 if active_weight == 0 || active_target == 0 {
966 break;
967 }
968 let newly_fixed = group_sizes
969 .iter()
970 .enumerate()
971 .filter_map(|(index, &size)| {
972 (size > 0
973 && !fixed[index]
974 && (size as u128) * (active_target as u128) <= active_weight)
975 .then_some(index)
976 })
977 .collect::<Vec<_>>();
978 if newly_fixed.is_empty() {
979 break;
980 }
981 for index in newly_fixed {
982 fixed[index] = true;
983 allocated[index] = 1;
984 fixed_cells += 1;
985 }
986 }
987 }
988
989 let remaining = target - fixed_cells;
990 if remaining == 0 {
991 return allocated;
992 }
993 let active_weight: u128 = group_sizes
994 .iter()
995 .enumerate()
996 .filter(|(index, size)| **size > 0 && !fixed[*index])
997 .map(|(_, &size)| size as u128)
998 .sum();
999 debug_assert!(active_weight > 0);
1000
1001 let mut remainders = Vec::with_capacity(group_sizes.len());
1002 for (index, &size) in group_sizes.iter().enumerate() {
1003 if size == 0 || fixed[index] {
1004 continue;
1005 }
1006 let numerator = (remaining as u128) * (size as u128);
1007 let whole = (numerator / active_weight) as usize;
1008 allocated[index] = whole;
1009 if whole < size {
1010 remainders.push((index, numerator % active_weight));
1011 }
1012 }
1013
1014 let remainder_cells = target - allocated.iter().sum::<usize>();
1015 remainders.sort_unstable_by(|(left_index, left), (right_index, right)| {
1016 right.cmp(left).then_with(|| left_index.cmp(right_index))
1017 });
1018 debug_assert!(remainder_cells <= remainders.len());
1019 for (index, _) in remainders.into_iter().take(remainder_cells) {
1020 allocated[index] += 1;
1021 }
1022 debug_assert_eq!(allocated.iter().sum::<usize>(), target);
1023 debug_assert!(
1024 allocated
1025 .iter()
1026 .zip(group_sizes)
1027 .all(|(&cells, &size)| cells <= size)
1028 );
1029 allocated
1030}
1031
1032#[derive(Debug, Clone, PartialEq, Eq)]
1034pub struct IvfProbePlan {
1035 pub quantizer_version: u64,
1036 pub request_fingerprint: u64,
1039 pub cluster_ids: Arc<[u32]>,
1040}
1041
1042impl IvfProbePlan {
1043 pub fn new(quantizer_version: u64, request_fingerprint: u64, cluster_ids: Vec<u32>) -> Self {
1044 Self {
1045 quantizer_version,
1046 request_fingerprint,
1047 cluster_ids: cluster_ids.into(),
1048 }
1049 }
1050}
1051
1052fn fingerprint_words(
1053 mode: IvfRoutingMode,
1054 nprobe: usize,
1055 words: impl IntoIterator<Item = u64>,
1056) -> u64 {
1057 let mut hash = 0xcbf2_9ce4_8422_2325u64;
1060 let mode_tag = match mode {
1061 IvfRoutingMode::Auto => 0u64,
1062 IvfRoutingMode::Flat => 1,
1063 IvfRoutingMode::TwoLevel => 2,
1064 IvfRoutingMode::Hnsw => 3,
1065 };
1066 for word in std::iter::once(mode_tag)
1067 .chain(std::iter::once(nprobe as u64))
1068 .chain(words)
1069 {
1070 hash ^= word;
1071 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
1072 }
1073 hash ^= hash >> 33;
1074 hash = hash.wrapping_mul(0xff51_afd7_ed55_8ccd);
1075 hash ^ (hash >> 33)
1076}
1077
1078pub fn float_probe_fingerprint(query: &[f32], nprobe: usize, mode: IvfRoutingMode) -> u64 {
1079 fingerprint_words(
1080 mode,
1081 nprobe,
1082 query.iter().map(|value| value.to_bits() as u64),
1083 )
1084}
1085
1086pub(crate) fn normalize_cosine_in_place(vector: &mut [f32]) {
1087 let norm = crate::structures::simd::dot_product_f32(vector, vector, vector.len()).sqrt();
1088 let inverse_norm = if norm.is_finite() && norm > 0.0 {
1089 1.0 / norm
1090 } else {
1091 0.0
1092 };
1093 vector.iter_mut().for_each(|value| *value *= inverse_norm);
1094}
1095
1096pub(crate) fn normalized_cosine_query(query: &[f32]) -> Vec<f32> {
1097 let mut normalized = query.to_vec();
1098 normalize_cosine_in_place(&mut normalized);
1099 normalized
1100}
1101
1102pub(crate) fn cosine_probe_fingerprint(query: &[f32], nprobe: usize, mode: IvfRoutingMode) -> u64 {
1103 let norm = crate::structures::simd::dot_product_f32(query, query, query.len()).sqrt();
1104 let inverse_norm = if norm.is_finite() && norm > 0.0 {
1105 1.0 / norm
1106 } else {
1107 0.0
1108 };
1109 fingerprint_words(
1110 mode,
1111 nprobe,
1112 query
1113 .iter()
1114 .map(|value| (value * inverse_norm).to_bits() as u64),
1115 )
1116}
1117
1118pub fn binary_probe_fingerprint(query: &[u8], nprobe: usize, mode: IvfRoutingMode) -> u64 {
1119 fingerprint_words(mode, nprobe, query.iter().map(|&value| value as u64))
1120}
1121
1122#[inline]
1124pub fn effective_routing_mode(mode: IvfRoutingMode, num_leaves: usize) -> IvfRoutingMode {
1125 resolve_auto_routing(mode, num_leaves, HNSW_AUTO_THRESHOLD)
1126}
1127
1128#[inline]
1135pub fn effective_binary_routing_mode(mode: IvfRoutingMode, num_leaves: usize) -> IvfRoutingMode {
1136 resolve_auto_routing(mode, num_leaves, BINARY_HNSW_AUTO_THRESHOLD)
1137}
1138
1139#[inline]
1140fn resolve_auto_routing(
1141 mode: IvfRoutingMode,
1142 num_leaves: usize,
1143 auto_threshold: usize,
1144) -> IvfRoutingMode {
1145 match mode {
1146 IvfRoutingMode::Auto if num_leaves >= auto_threshold => IvfRoutingMode::Hnsw,
1147 IvfRoutingMode::Auto => IvfRoutingMode::Flat,
1148 explicit => explicit,
1149 }
1150}
1151
1152pub fn parent_probe_count(nprobe: usize, num_leaves: usize, num_parents: usize) -> usize {
1154 if num_parents == 0 || num_leaves == 0 {
1155 return 0;
1156 }
1157 let leaves_per_parent = num_leaves.div_ceil(num_parents).max(1);
1158 nprobe
1159 .saturating_mul(PARENT_BEAM_OVERSAMPLE)
1160 .div_ceil(leaves_per_parent)
1161 .clamp(1, num_parents)
1162}
1163
1164pub fn select_parent_beam<const HIGHER_IS_BETTER: bool>(
1169 scores: &[f32],
1170 topology: &IvfRoutingTopology,
1171 requested_leaves: usize,
1172) -> Vec<u32> {
1173 let parent_count = scores.len().min(topology.parent_count());
1174 let leaf_count = topology.leaf_ids.len();
1175 let requested_leaves = requested_leaves.min(leaf_count);
1176 if parent_count == 0 || requested_leaves == 0 {
1177 return Vec::new();
1178 }
1179
1180 let initial_take =
1181 parent_probe_count(requested_leaves, leaf_count, parent_count).min(parent_count);
1182 let scores = &scores[..parent_count];
1183 let initial = select_best::<HIGHER_IS_BETTER>(scores, initial_take);
1184 let initial_coverage: usize = initial
1185 .iter()
1186 .map(|&parent| topology.children(parent as usize).len())
1187 .sum();
1188 if initial_coverage >= requested_leaves || initial_take == parent_count {
1189 return initial;
1190 }
1191
1192 let mut ranked = select_best::<HIGHER_IS_BETTER>(scores, parent_count);
1193 let mut coverage = 0usize;
1194 let mut take = parent_count;
1195 for (index, &parent) in ranked.iter().enumerate() {
1196 coverage = coverage.saturating_add(topology.children(parent as usize).len());
1197 if index + 1 >= initial_take && coverage >= requested_leaves {
1198 take = index + 1;
1199 break;
1200 }
1201 }
1202 ranked.truncate(take);
1203 ranked
1204}
1205
1206pub fn select_parent_beam_for_build<const HIGHER_IS_BETTER: bool>(
1212 scores: &[f32],
1213 topology: &IvfRoutingTopology,
1214 requested_leaves: usize,
1215) -> Vec<u32> {
1216 if requested_leaves == 0 {
1217 return Vec::new();
1218 }
1219 let parent_count = scores.len().min(topology.parent_count());
1220 if parent_count == 0 {
1221 return Vec::new();
1222 }
1223
1224 let query_parents = select_parent_beam::<HIGHER_IS_BETTER>(scores, topology, requested_leaves);
1225 let query_populated = query_parents
1226 .iter()
1227 .filter(|&&parent| !topology.children(parent as usize).is_empty())
1228 .count();
1229
1230 let scores = &scores[..parent_count];
1231 let mut ranked = select_best::<HIGHER_IS_BETTER>(scores, parent_count);
1232 ranked.retain(|&parent| !topology.children(parent as usize).is_empty());
1233 let take = query_populated
1234 .max(MIN_BUILD_PARENT_BEAM.min(ranked.len()))
1235 .min(ranked.len());
1236 ranked.truncate(take);
1237 ranked
1238}
1239
1240pub fn select_best<const HIGHER_IS_BETTER: bool>(scores: &[f32], take: usize) -> Vec<u32> {
1243 let take = take.min(scores.len());
1244 if take == 0 {
1245 return Vec::new();
1246 }
1247 let mut order: Vec<u32> = (0..scores.len() as u32).collect();
1248 let compare = |left: &u32, right: &u32| {
1249 let left_score = scores[*left as usize];
1250 let right_score = scores[*right as usize];
1251 let score_order = if HIGHER_IS_BETTER {
1252 right_score.total_cmp(&left_score)
1253 } else {
1254 left_score.total_cmp(&right_score)
1255 };
1256 score_order.then_with(|| left.cmp(right))
1257 };
1258 if take < order.len() {
1259 order.select_nth_unstable_by(take, compare);
1260 order.truncate(take);
1261 }
1262 order.sort_unstable_by(compare);
1263 order
1264}
1265
1266pub fn select_best_candidates<const HIGHER_IS_BETTER: bool>(
1270 candidates: &mut Vec<(u32, f32)>,
1271 take: usize,
1272) -> Vec<u32> {
1273 let take = take.min(candidates.len());
1274 if take == 0 {
1275 return Vec::new();
1276 }
1277 let compare = |left: &(u32, f32), right: &(u32, f32)| {
1278 let score_order = if HIGHER_IS_BETTER {
1279 right.1.total_cmp(&left.1)
1280 } else {
1281 left.1.total_cmp(&right.1)
1282 };
1283 score_order.then_with(|| left.0.cmp(&right.0))
1284 };
1285 if take < candidates.len() {
1286 candidates.select_nth_unstable_by(take, compare);
1287 candidates.truncate(take);
1288 }
1289 candidates.sort_unstable_by(compare);
1290 candidates
1291 .iter()
1292 .map(|(cluster_id, _)| *cluster_id)
1293 .collect()
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::*;
1299
1300 #[test]
1305 fn auto_routing_prefers_exact_flat_probing_below_the_measured_crossover() {
1306 for leaves in [1usize, 4_096, 16_384, BINARY_HNSW_AUTO_THRESHOLD - 1] {
1307 assert_eq!(
1308 effective_binary_routing_mode(IvfRoutingMode::Auto, leaves),
1309 IvfRoutingMode::Flat,
1310 "{leaves} binary leaves"
1311 );
1312 }
1313 for leaves in [BINARY_HNSW_AUTO_THRESHOLD, 114_309] {
1314 assert_eq!(
1315 effective_binary_routing_mode(IvfRoutingMode::Auto, leaves),
1316 IvfRoutingMode::Hnsw,
1317 "{leaves} binary leaves"
1318 );
1319 }
1320 assert_eq!(
1322 effective_routing_mode(IvfRoutingMode::Auto, HNSW_AUTO_THRESHOLD),
1323 IvfRoutingMode::Hnsw
1324 );
1325 assert_eq!(
1326 effective_routing_mode(IvfRoutingMode::Auto, HNSW_AUTO_THRESHOLD - 1),
1327 IvfRoutingMode::Flat
1328 );
1329 for leaves in [64usize, 1_000_000] {
1332 assert_eq!(
1333 effective_binary_routing_mode(IvfRoutingMode::Hnsw, leaves),
1334 IvfRoutingMode::Hnsw
1335 );
1336 assert_eq!(
1337 effective_binary_routing_mode(IvfRoutingMode::TwoLevel, leaves),
1338 IvfRoutingMode::TwoLevel
1339 );
1340 }
1341 const {
1342 assert!(HIERARCHICAL_TRAINING_THRESHOLD <= BINARY_HNSW_AUTO_THRESHOLD);
1343 }
1344 }
1345
1346 #[test]
1347 fn deterministic_selection_supports_both_metric_directions() {
1348 let scores = [0.5, 0.9, 0.1, 0.9];
1349 assert_eq!(select_best::<true>(&scores, 2), vec![1, 3]);
1350 assert_eq!(select_best::<false>(&scores, 2), vec![2, 0]);
1351 }
1352
1353 #[test]
1354 fn two_level_beam_is_oversubscribed_but_bounded() {
1355 assert_eq!(parent_probe_count(32, 65_536, 256), 1);
1356 assert_eq!(parent_probe_count(256, 65_536, 256), 4);
1357 assert_eq!(parent_probe_count(65_536, 65_536, 256), 256);
1358 }
1359
1360 #[test]
1361 fn two_level_beam_expands_until_skewed_parents_cover_leaf_budget() {
1362 let mut children: Vec<Vec<u32>> = (0..9).map(|leaf| vec![leaf]).collect();
1363 children.push((9..100).collect());
1364 let topology = IvfRoutingTopology::from_children(&children);
1365
1366 let lower_is_better: Vec<f32> = (0..10).map(|score| score as f32).collect();
1370 assert_eq!(
1371 select_parent_beam::<false>(&lower_is_better, &topology, 4),
1372 vec![0, 1, 2, 3]
1373 );
1374
1375 let higher_is_better: Vec<f32> = (0..10).rev().map(|score| score as f32).collect();
1376 assert_eq!(
1377 select_parent_beam::<true>(&higher_is_better, &topology, 4),
1378 vec![0, 1, 2, 3]
1379 );
1380 }
1381
1382 #[test]
1383 fn build_parent_beam_uses_four_populated_parents_when_query_uses_one() {
1384 let children: Vec<Vec<u32>> = (0..4)
1385 .map(|parent| {
1386 let first = parent * 512;
1387 (first..first + 512).map(|leaf| leaf as u32).collect()
1388 })
1389 .collect();
1390 let topology = IvfRoutingTopology::from_children(&children);
1391
1392 let lower_is_better = [0.0, 1.0, 2.0, 3.0];
1393 assert_eq!(
1394 select_parent_beam::<false>(&lower_is_better, &topology, 128),
1395 vec![0]
1396 );
1397 assert_eq!(
1398 select_parent_beam_for_build::<false>(&lower_is_better, &topology, 128),
1399 vec![0, 1, 2, 3]
1400 );
1401
1402 let higher_is_better = [4.0, 3.0, 2.0, 1.0];
1403 assert_eq!(
1404 select_parent_beam::<true>(&higher_is_better, &topology, 128),
1405 vec![0]
1406 );
1407 assert_eq!(
1408 select_parent_beam_for_build::<true>(&higher_is_better, &topology, 128),
1409 vec![0, 1, 2, 3]
1410 );
1411 }
1412
1413 #[test]
1414 fn build_parent_beam_uses_every_available_populated_parent() {
1415 let children = vec![vec![0], vec![], vec![1], vec![], vec![2]];
1416 let topology = IvfRoutingTopology::from_children(&children);
1417 let scores = [1.0, 0.0, 2.0, -1.0, 3.0];
1418
1419 assert_eq!(
1420 select_parent_beam_for_build::<false>(&scores, &topology, 1),
1421 vec![0, 2, 4]
1422 );
1423 }
1424
1425 #[test]
1426 fn child_allocation_uses_largest_remainders_instead_of_largest_parent() {
1427 assert_eq!(allocate_child_clusters(&[100, 90], 4), vec![2, 2]);
1428 assert_eq!(allocate_child_clusters(&[5, 5, 5], 5), vec![2, 2, 1]);
1429 assert_eq!(allocate_child_clusters(&[90, 10], 10), vec![9, 1]);
1430 }
1431
1432 #[test]
1433 fn child_allocation_is_exact_capacity_bounded_and_deterministic() {
1434 assert_eq!(
1435 allocate_child_clusters(&[1, 100, 7, 0], 100),
1436 vec![1, 93, 6, 0]
1437 );
1438 assert_eq!(allocate_child_clusters(&[1, 2, 0], 10), vec![1, 2, 0]);
1439 assert_eq!(allocate_child_clusters(&[10, 9, 8], 2), vec![1, 1, 0]);
1440
1441 for target in 0..=140 {
1442 let sizes = [100, 30, 0, 7];
1443 let allocation = allocate_child_clusters(&sizes, target);
1444 assert_eq!(
1445 allocation.iter().sum::<usize>(),
1446 target.min(sizes.iter().sum())
1447 );
1448 assert!(
1449 allocation
1450 .iter()
1451 .zip(sizes)
1452 .all(|(&cells, size)| cells <= size)
1453 );
1454 if target >= sizes.iter().filter(|&&size| size > 0).count() {
1455 assert!(
1456 allocation
1457 .iter()
1458 .zip(sizes)
1459 .all(|(&cells, size)| size == 0 || cells > 0)
1460 );
1461 }
1462 }
1463 }
1464
1465 #[test]
1466 fn compact_hnsw_routes_without_copying_points() {
1467 let points: Vec<[f32; 2]> = (0..512)
1468 .map(|index| {
1469 let angle = index as f32 * std::f32::consts::TAU / 512.0;
1470 [angle.cos(), angle.sin()]
1471 })
1472 .collect();
1473 let distance = |left: u32, right: u32| {
1474 let [lx, ly] = points[left as usize];
1475 let [rx, ry] = points[right as usize];
1476 (lx - rx).powi(2) + (ly - ry).powi(2)
1477 };
1478 let graph = HnswRoutingGraph::build(points.len(), distance, 42, "test");
1479 assert!(graph.validate(points.len()));
1480 assert!(graph.size_bytes() < points.len() * 512);
1481
1482 let query = [0.37f32, -0.91];
1483 let query_distance_calls = std::cell::Cell::new(0usize);
1484 let routed = graph.search(
1485 |node| {
1486 query_distance_calls.set(query_distance_calls.get() + 1);
1487 let [x, y] = points[node as usize];
1488 (x - query[0]).powi(2) + (y - query[1]).powi(2)
1489 },
1490 10,
1491 );
1492 let mut exact: Vec<u32> = (0..points.len() as u32).collect();
1493 exact.sort_unstable_by(|&left, &right| {
1494 let score = |node: u32| {
1495 let [x, y] = points[node as usize];
1496 (x - query[0]).powi(2) + (y - query[1]).powi(2)
1497 };
1498 score(left)
1499 .total_cmp(&score(right))
1500 .then_with(|| left.cmp(&right))
1501 });
1502 assert_eq!(routed, exact[..10]);
1503
1504 let build_distance_calls = std::cell::Cell::new(0usize);
1505 let build_routed = graph.search_for_build(
1506 |node| {
1507 build_distance_calls.set(build_distance_calls.get() + 1);
1508 let [x, y] = points[node as usize];
1509 (x - query[0]).powi(2) + (y - query[1]).powi(2)
1510 },
1511 10,
1512 );
1513 assert_eq!(build_routed, exact[..10]);
1514 assert_eq!(build_distance_calls.get(), query_distance_calls.get());
1520
1521 let wide_query_calls = std::cell::Cell::new(0usize);
1522 graph.search(
1523 |node| {
1524 wide_query_calls.set(wide_query_calls.get() + 1);
1525 let [x, y] = points[node as usize];
1526 (x - query[0]).powi(2) + (y - query[1]).powi(2)
1527 },
1528 64,
1529 );
1530 let wide_build_calls = std::cell::Cell::new(0usize);
1531 graph.search_for_build(
1532 |node| {
1533 wide_build_calls.set(wide_build_calls.get() + 1);
1534 let [x, y] = points[node as usize];
1535 (x - query[0]).powi(2) + (y - query[1]).powi(2)
1536 },
1537 64,
1538 );
1539 assert!(
1540 wide_build_calls.get() > wide_query_calls.get(),
1541 "multi-candidate construction should spend its wider search budget"
1542 );
1543
1544 let assigned = graph.search_best_for_build(|node| {
1547 let [x, y] = points[node as usize];
1548 (x - query[0]).powi(2) + (y - query[1]).powi(2)
1549 });
1550 assert_eq!(assigned, Some(exact[0]));
1551
1552 let bytes = bincode::serde::encode_to_vec(&graph, bincode::config::standard()).unwrap();
1553 let (decoded, consumed): (HnswRoutingGraph, usize) =
1554 bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
1555 assert_eq!(consumed, bytes.len());
1556 assert!(decoded.validate(points.len()));
1557
1558 let mut corrupted = decoded;
1559 corrupted.node_offsets[1] = u32::MAX;
1560 assert!(!corrupted.validate(points.len()));
1561 }
1562}