1use serde::{Deserialize, Serialize};
7
8use super::routing::{
9 HIERARCHICAL_TRAINING_THRESHOLD, HnswRoutingGraph, IvfProbePlan, IvfRoutingTopology,
10 allocate_child_clusters, effective_routing_mode, float_probe_fingerprint, routing_parent_count,
11 select_best_candidates, select_parent_beam, select_parent_beam_for_build,
12};
13use super::soar::{MultiAssignment, SoarConfig};
14use crate::dsl::IvfRoutingMode;
15
16const SOAR_LAMBDA: f32 = 1.0;
20const MAX_SELECTIVE_SECONDARY_TO_PRIMARY_DISTANCE_RATIO_SQ: f32 = 4.0;
28const BUILD_ASSIGNMENT_CANDIDATES: usize = 128;
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct CoarseConfig {
36 pub num_clusters: usize,
38 pub dim: usize,
40 pub max_iters: usize,
42 pub seed: u64,
44 pub soar: Option<SoarConfig>,
46 pub routing: IvfRoutingMode,
48}
49
50impl CoarseConfig {
51 pub fn new(dim: usize, num_clusters: usize) -> Self {
52 Self {
53 num_clusters,
54 dim,
55 max_iters: 25,
56 seed: 42,
57 soar: None,
58 routing: IvfRoutingMode::Auto,
59 }
60 }
61
62 pub fn with_soar(mut self, config: SoarConfig) -> Self {
63 self.soar = Some(config);
64 self
65 }
66
67 pub fn with_seed(mut self, seed: u64) -> Self {
68 self.seed = seed;
69 self
70 }
71
72 pub fn with_max_iters(mut self, iters: usize) -> Self {
73 self.max_iters = iters;
74 self
75 }
76
77 pub fn with_routing(mut self, routing: IvfRoutingMode) -> Self {
78 self.routing = routing;
79 self
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct CoarseCentroids {
86 pub num_clusters: u32,
88 pub dim: usize,
90 pub centroids: Vec<f32>,
92 pub version: u64,
94 pub soar_config: Option<SoarConfig>,
96 pub(crate) routing_index: Option<FloatCentroidRouter>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub(crate) enum FloatCentroidRouter {
102 TwoLevel {
103 parent_centroids: Vec<f32>,
104 topology: IvfRoutingTopology,
105 },
106 Hnsw(HnswRoutingGraph),
107}
108
109struct FlatClusterMemberships {
110 offsets: Vec<usize>,
111 members: Vec<usize>,
112}
113
114impl FlatClusterMemberships {
115 fn cluster(&self, cluster: usize) -> &[usize] {
116 &self.members[self.offsets[cluster]..self.offsets[cluster + 1]]
117 }
118}
119
120impl CoarseCentroids {
121 pub fn train(config: &CoarseConfig, vectors: &[Vec<f32>], index_label: &str) -> Self {
125 assert!(!vectors.is_empty(), "Cannot train on empty vector set");
126 assert!(config.num_clusters > 0, "Need at least 1 cluster");
127 assert!(vectors.iter().all(|vector| vector.len() == config.dim));
128
129 let flat = vectors
133 .iter()
134 .flat_map(|vector| vector.iter().copied())
135 .collect::<Vec<_>>();
136 Self::train_contiguous(config, &flat, vectors.len(), index_label)
137 }
138
139 pub(crate) fn train_contiguous(
141 config: &CoarseConfig,
142 vectors: &[f32],
143 vector_count: usize,
144 index_label: &str,
145 ) -> Self {
146 assert!(vector_count > 0, "Cannot train on empty vector set");
147 assert!(config.num_clusters > 0, "Need at least 1 cluster");
148 assert_eq!(vectors.len(), vector_count.saturating_mul(config.dim));
149
150 let actual_clusters = config.num_clusters.min(vector_count);
151 let (centroids, routing_index) =
152 match effective_routing_mode(config.routing, actual_clusters) {
153 IvfRoutingMode::TwoLevel => {
154 let (leaves, router) =
155 Self::train_hierarchical(config, vectors, vector_count, actual_clusters);
156 (leaves, Some(router))
157 }
158 IvfRoutingMode::Hnsw => {
159 let leaves = if actual_clusters >= HIERARCHICAL_TRAINING_THRESHOLD {
160 Self::train_hierarchical(config, vectors, vector_count, actual_clusters).0
161 } else {
162 Self::train_flat(config, vectors, vector_count, actual_clusters)
163 };
164 let graph = HnswRoutingGraph::build(
165 actual_clusters,
166 |left, right| {
167 let left = left as usize * config.dim;
168 let right = right as usize * config.dim;
169 squared_l2(
170 &leaves[left..left + config.dim],
171 &leaves[right..right + config.dim],
172 )
173 },
174 config.seed,
175 index_label,
176 );
177 (leaves, Some(FloatCentroidRouter::Hnsw(graph)))
178 }
179 IvfRoutingMode::Flat | IvfRoutingMode::Auto => (
180 Self::train_flat(config, vectors, vector_count, actual_clusters),
181 None,
182 ),
183 };
184
185 let version = std::time::SystemTime::now()
186 .duration_since(std::time::UNIX_EPOCH)
187 .unwrap_or_default()
188 .as_nanos() as u64;
189
190 let mut soar_config = config.soar.clone();
191 if let Some(soar) = &mut soar_config
192 && soar.num_secondary > 1
193 {
194 log::warn!(
195 "SOAR currently implements the published primary + one-secondary objective; \
196 clamping {} requested secondary assignments to one",
197 soar.num_secondary,
198 );
199 soar.num_secondary = 1;
200 }
201 let calibration_target = soar_config
202 .as_ref()
203 .and_then(SoarConfig::calibration_target);
204 let mut trained = Self {
205 num_clusters: actual_clusters as u32,
206 dim: config.dim,
207 centroids,
208 version,
209 soar_config: None,
212 routing_index,
213 };
214 if let Some(target) = calibration_target {
215 let threshold = trained.calibrate_selective_spill_threshold(
216 vectors,
217 vector_count,
218 config.routing,
219 target,
220 );
221 if let Some(soar) = &mut soar_config {
222 soar.spill_threshold = threshold;
223 }
224 log::info!(
225 "Calibrated SOAR selective spilling to at most {:.1}% of the training sample \
226 (residual threshold {:.6})",
227 target * 100.0,
228 threshold,
229 );
230 }
231 trained.soar_config = soar_config;
232 trained
233 }
234
235 fn calibrate_selective_spill_threshold(
236 &self,
237 vectors: &[f32],
238 vector_count: usize,
239 routing: IvfRoutingMode,
240 target_fraction: f32,
241 ) -> f32 {
242 let routing = effective_routing_mode(routing, self.num_clusters as usize);
246 let sample_limit = match routing {
247 IvfRoutingMode::Flat | IvfRoutingMode::Auto => {
248 let work_per_vector = (self.num_clusters as usize).saturating_mul(self.dim).max(1);
249 (64_000_000usize / work_per_vector).clamp(32, 4_096)
250 }
251 IvfRoutingMode::TwoLevel | IvfRoutingMode::Hnsw => 8_192,
252 }
253 .min(vector_count);
254 let mut residual_norms = Vec::with_capacity(sample_limit);
255 for sample in 0..sample_limit {
256 let vector_index = sample.saturating_mul(vector_count) / sample_limit;
257 let offset = vector_index * self.dim;
258 let vector = &vectors[offset..offset + self.dim];
259 let primary = self.assign_with_routing(vector, routing).primary_cluster;
260 residual_norms.push(squared_l2(vector, self.get_centroid(primary)).sqrt());
261 }
262 residual_norms.sort_unstable_by(f32::total_cmp);
263 if residual_norms.is_empty() {
264 return 0.0;
265 }
266 let spill_count = ((residual_norms.len() as f32 * target_fraction.clamp(0.0, 1.0)).round()
267 as usize)
268 .min(residual_norms.len());
269 if spill_count == 0 {
270 let largest = residual_norms.last().copied().unwrap_or(0.0);
271 return threshold_strictly_above(largest);
272 }
273 let boundary = residual_norms[residual_norms.len() - spill_count];
274 let at_or_above =
275 residual_norms.len() - residual_norms.partition_point(|&norm| norm < boundary);
276 if at_or_above > spill_count {
277 threshold_strictly_above(boundary)
282 } else {
283 boundary
284 }
285 }
286
287 fn train_flat(
288 config: &CoarseConfig,
289 vectors: &[f32],
290 vector_count: usize,
291 clusters: usize,
292 ) -> Vec<f32> {
293 Self::run_kmeans(config, vectors, vector_count, clusters).centroids
294 }
295
296 fn train_flat_with_memberships(
297 config: &CoarseConfig,
298 vectors: &[f32],
299 vector_count: usize,
300 clusters: usize,
301 ) -> (Vec<f32>, FlatClusterMemberships) {
302 let trained = Self::run_kmeans(config, vectors, vector_count, clusters);
303 (
304 trained.centroids,
305 FlatClusterMemberships {
306 offsets: trained.member_offsets,
307 members: trained.members,
308 },
309 )
310 }
311
312 fn run_kmeans(
313 config: &CoarseConfig,
314 vectors: &[f32],
315 vector_count: usize,
316 clusters: usize,
317 ) -> crate::structures::vector::kmeans::EuclideanKMeans {
318 crate::structures::vector::kmeans::train_euclidean_kmeans(
319 vectors,
320 vector_count,
321 config.dim,
322 clusters,
323 config.max_iters,
324 config.seed,
325 )
326 }
327
328 fn train_hierarchical(
329 config: &CoarseConfig,
330 vectors: &[f32],
331 vector_count: usize,
332 leaf_count: usize,
333 ) -> (Vec<f32>, FloatCentroidRouter) {
334 let parent_count = routing_parent_count(leaf_count).min(vector_count);
335 let mut parent_config = config.clone();
336 parent_config.routing = IvfRoutingMode::Flat;
337 parent_config.num_clusters = parent_count;
338 let (parent_centroids, groups) =
339 Self::train_flat_with_memberships(&parent_config, vectors, vector_count, parent_count);
340 let group_sizes: Vec<usize> = (0..parent_count)
341 .map(|parent| groups.cluster(parent).len())
342 .collect();
343 let child_counts = allocate_child_clusters(&group_sizes, leaf_count);
344 let mut leaves = Vec::with_capacity(leaf_count.saturating_mul(config.dim));
345 let mut children = vec![Vec::new(); parent_count];
346 let mut group_vectors = Vec::new();
347
348 for (parent, &child_count) in child_counts.iter().enumerate() {
349 if child_count == 0 {
350 continue;
351 }
352 let indices = groups.cluster(parent);
353 group_vectors.clear();
354 group_vectors.reserve(indices.len().saturating_mul(config.dim));
355 for &index in indices {
356 let offset = index * config.dim;
357 group_vectors.extend_from_slice(&vectors[offset..offset + config.dim]);
358 }
359 let mut child_config = config.clone();
360 child_config.routing = IvfRoutingMode::Flat;
361 child_config.num_clusters = child_count;
362 child_config.seed = config.seed ^ (parent as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
363 let first_leaf = leaves.len() / config.dim;
364 leaves.extend_from_slice(&Self::train_flat(
365 &child_config,
366 &group_vectors,
367 indices.len(),
368 child_count,
369 ));
370 children[parent].extend((first_leaf..first_leaf + child_count).map(|leaf| leaf as u32));
371 }
372 debug_assert_eq!(leaves.len(), leaf_count * config.dim);
373
374 (
375 leaves,
376 FloatCentroidRouter::TwoLevel {
377 parent_centroids,
378 topology: IvfRoutingTopology::from_children(&children),
379 },
380 )
381 }
382
383 fn find_nearest_idx_static(vector: &[f32], centroids: &[f32], dim: usize) -> usize {
385 let num_clusters = centroids.len() / dim;
386 let mut best_idx = 0;
387 let mut best_dist = f32::MAX;
388
389 for c in 0..num_clusters {
390 let offset = c * dim;
391 let dist: f32 = vector
392 .iter()
393 .zip(¢roids[offset..offset + dim])
394 .map(|(&a, &b)| (a - b) * (a - b))
395 .sum();
396
397 if dist < best_dist {
398 best_dist = dist;
399 best_idx = c;
400 }
401 }
402
403 best_idx
404 }
405
406 pub fn find_nearest(&self, vector: &[f32]) -> u32 {
408 Self::find_nearest_idx_static(vector, &self.centroids, self.dim) as u32
409 }
410
411 pub fn find_k_nearest(&self, vector: &[f32], k: usize) -> Vec<u32> {
413 let mut distances: Vec<(u32, f32)> = (0..self.num_clusters)
414 .map(|c| {
415 let offset = c as usize * self.dim;
416 let dist: f32 = vector
417 .iter()
418 .zip(&self.centroids[offset..offset + self.dim])
419 .map(|(&a, &b)| (a - b) * (a - b))
420 .sum();
421 (c, dist)
422 })
423 .collect();
424
425 if distances.len() > k {
427 distances.select_nth_unstable_by(k, |a, b| a.1.total_cmp(&b.1));
428 distances.truncate(k);
429 }
430 distances.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
431 distances.into_iter().map(|(c, _)| c).collect()
432 }
433
434 pub fn probe(&self, vector: &[f32], k: usize, mode: IvfRoutingMode) -> IvfProbePlan {
439 let take = k.clamp(1, self.num_clusters as usize);
440 let clusters = match effective_routing_mode(mode, self.num_clusters as usize) {
441 IvfRoutingMode::Flat | IvfRoutingMode::Auto => self.find_k_nearest(vector, take),
442 IvfRoutingMode::TwoLevel => self.find_k_nearest_two_level(vector, take),
443 IvfRoutingMode::Hnsw => self.find_k_nearest_hnsw(vector, take),
444 };
445 IvfProbePlan::new(
446 self.version,
447 float_probe_fingerprint(vector, take, mode),
448 clusters,
449 )
450 }
451
452 pub fn validate_routing(&self, mode: IvfRoutingMode) -> Result<(), String> {
453 match effective_routing_mode(mode, self.num_clusters as usize) {
454 IvfRoutingMode::Flat | IvfRoutingMode::Auto => Ok(()),
455 IvfRoutingMode::TwoLevel => {
456 let Some(FloatCentroidRouter::TwoLevel {
457 parent_centroids,
458 topology,
459 }) = self.routing_index.as_ref()
460 else {
461 return Err(
462 "two-level IVF routing was requested but the global codebook has no matching router"
463 .to_string(),
464 );
465 };
466 let parent_count = topology.parent_count();
467 if parent_count == 0
468 || parent_centroids.len() != parent_count.saturating_mul(self.dim)
469 || !topology.validate(self.num_clusters as usize)
470 || parent_centroids.iter().any(|value| !value.is_finite())
471 {
472 return Err("invalid float two-level IVF routing index".to_string());
473 }
474 Ok(())
475 }
476 IvfRoutingMode::Hnsw => {
477 let Some(FloatCentroidRouter::Hnsw(graph)) = self.routing_index.as_ref() else {
478 return Err(
479 "HNSW IVF routing was requested but the global codebook has no HNSW graph"
480 .to_string(),
481 );
482 };
483 if !graph.validate(self.num_clusters as usize) {
484 return Err("invalid float HNSW routing graph".to_string());
485 }
486 Ok(())
487 }
488 }
489 }
490
491 fn find_k_nearest_two_level(&self, vector: &[f32], k: usize) -> Vec<u32> {
492 self.find_k_nearest_two_level_impl::<false>(vector, k)
493 }
494
495 fn find_k_nearest_two_level_for_build(&self, vector: &[f32], k: usize) -> Vec<u32> {
496 self.find_k_nearest_two_level_impl::<true>(vector, k)
497 }
498
499 fn find_k_nearest_two_level_impl<const FOR_BUILD: bool>(
500 &self,
501 vector: &[f32],
502 k: usize,
503 ) -> Vec<u32> {
504 let Some(FloatCentroidRouter::TwoLevel {
505 parent_centroids,
506 topology,
507 }) = self.routing_index.as_ref()
508 else {
509 return self.find_k_nearest(vector, k);
510 };
511 if topology.parent_count() <= 1 {
512 return self.find_k_nearest(vector, k);
513 }
514
515 let mut parent_scores = vec![0.0; topology.parent_count()];
516 for (parent_id, score) in parent_scores.iter_mut().enumerate() {
517 let offset = parent_id * self.dim;
518 *score = squared_l2(vector, &parent_centroids[offset..offset + self.dim]);
519 }
520 let parents = if FOR_BUILD {
521 select_parent_beam_for_build::<false>(&parent_scores, topology, k)
522 } else {
523 select_parent_beam::<false>(&parent_scores, topology, k)
524 };
525 let candidate_capacity = parents
526 .iter()
527 .map(|&parent| topology.children(parent as usize).len())
528 .sum();
529 let mut candidates = Vec::with_capacity(candidate_capacity);
530 for parent in parents {
531 for &leaf in topology.children(parent as usize) {
532 candidates.push((leaf, squared_l2(vector, self.get_centroid(leaf))));
533 }
534 }
535 select_best_candidates::<false>(&mut candidates, k)
536 }
537
538 fn find_k_nearest_hnsw(&self, vector: &[f32], k: usize) -> Vec<u32> {
539 let Some(FloatCentroidRouter::Hnsw(graph)) = self.routing_index.as_ref() else {
540 return self.find_k_nearest(vector, k);
541 };
542 graph.search(|leaf| squared_l2(vector, self.get_centroid(leaf)), k)
543 }
544
545 pub fn find_k_nearest_with_distances(&self, vector: &[f32], k: usize) -> Vec<(u32, f32)> {
547 let mut distances: Vec<(u32, f32)> = (0..self.num_clusters)
548 .map(|c| {
549 let offset = c as usize * self.dim;
550 let dist: f32 = vector
551 .iter()
552 .zip(&self.centroids[offset..offset + self.dim])
553 .map(|(&a, &b)| (a - b) * (a - b))
554 .sum();
555 (c, dist)
556 })
557 .collect();
558
559 if distances.len() > k {
561 distances.select_nth_unstable_by(k, |a, b| a.1.total_cmp(&b.1));
562 distances.truncate(k);
563 }
564 distances.sort_unstable_by(|a, b| a.1.total_cmp(&b.1));
565 distances
566 }
567
568 pub fn assign(&self, vector: &[f32]) -> MultiAssignment {
570 self.assign_with_routing(vector, IvfRoutingMode::Flat)
571 }
572
573 pub fn assign_with_routing(&self, vector: &[f32], routing: IvfRoutingMode) -> MultiAssignment {
577 if let Some(ref soar_config) = self.soar_config {
578 self.assign_with_soar_and_routing(vector, soar_config, routing)
579 } else {
580 let primary_cluster = match effective_routing_mode(routing, self.num_clusters as usize)
581 {
582 IvfRoutingMode::Hnsw => self.find_nearest_hnsw_for_build(vector),
583 IvfRoutingMode::TwoLevel => self
584 .find_k_nearest_two_level_for_build(
585 vector,
586 BUILD_ASSIGNMENT_CANDIDATES.min(self.num_clusters as usize),
587 )
588 .first()
589 .copied()
590 .unwrap_or(0),
591 IvfRoutingMode::Flat | IvfRoutingMode::Auto => self.find_nearest(vector),
592 };
593 MultiAssignment {
594 primary_cluster,
595 secondary_clusters: Vec::new(),
596 }
597 }
598 }
599
600 pub fn assign_with_soar(&self, vector: &[f32], config: &SoarConfig) -> MultiAssignment {
602 self.assign_with_soar_and_routing(vector, config, IvfRoutingMode::Flat)
603 }
604
605 fn assign_with_soar_and_routing(
606 &self,
607 vector: &[f32],
608 config: &SoarConfig,
609 routing: IvfRoutingMode,
610 ) -> MultiAssignment {
611 let num_secondary = config.num_secondary.min(1);
616 let candidate_budget =
620 soar_build_candidate_budget(num_secondary, self.num_clusters as usize);
621 let leaf_ids: Vec<u32> = match effective_routing_mode(routing, self.num_clusters as usize) {
622 IvfRoutingMode::TwoLevel => {
623 self.two_level_candidate_leaves_for_build(vector, candidate_budget)
624 }
625 IvfRoutingMode::Hnsw => self.find_k_nearest_hnsw_for_build(vector, candidate_budget),
626 IvfRoutingMode::Flat | IvfRoutingMode::Auto => (0..self.num_clusters).collect(),
627 };
628 let leaf_distances: Vec<(u32, f32)> = leaf_ids
631 .into_iter()
632 .map(|cluster| (cluster, squared_l2(vector, self.get_centroid(cluster))))
633 .collect();
634 let primary = leaf_distances
635 .iter()
636 .min_by(|left, right| scored_cluster_order(left, right))
637 .map(|&(cluster, _)| cluster)
638 .unwrap_or(0);
639 let primary_centroid = self.get_centroid(primary);
640
641 let residual: Vec<f32> = vector
643 .iter()
644 .zip(primary_centroid)
645 .map(|(v, c)| v - c)
646 .collect();
647
648 let residual_norm_sq: f32 = residual.iter().map(|x| x * x).sum();
649
650 if config.selective && residual_norm_sq < config.spill_threshold * config.spill_threshold {
652 return MultiAssignment {
653 primary_cluster: primary,
654 secondary_clusters: Vec::new(),
655 };
656 }
657
658 let mut candidates: Vec<(u32, f32)> = leaf_distances
666 .into_iter()
667 .filter(|&(cluster, secondary_residual_norm_sq)| {
668 cluster != primary
669 && (!config.selective
670 || secondary_residual_norm_sq
671 <= MAX_SELECTIVE_SECONDARY_TO_PRIMARY_DISTANCE_RATIO_SQ
672 * residual_norm_sq)
673 })
674 .map(|(cluster, secondary_residual_norm_sq)| {
675 (
676 cluster,
677 soar_secondary_loss(
678 vector,
679 self.get_centroid(cluster),
680 &residual,
681 residual_norm_sq,
682 secondary_residual_norm_sq,
683 ),
684 )
685 })
686 .collect();
687
688 let take = num_secondary.min(candidates.len());
691 if candidates.len() > take {
692 candidates.select_nth_unstable_by(take, scored_cluster_order);
693 candidates.truncate(take);
694 }
695 candidates.sort_unstable_by(scored_cluster_order);
696
697 MultiAssignment {
698 primary_cluster: primary,
699 secondary_clusters: candidates
700 .iter()
701 .take(num_secondary)
702 .map(|(c, _)| *c)
703 .collect(),
704 }
705 }
706
707 fn two_level_candidate_leaves_for_build(&self, vector: &[f32], k: usize) -> Vec<u32> {
708 let Some(FloatCentroidRouter::TwoLevel {
709 parent_centroids,
710 topology,
711 }) = self.routing_index.as_ref()
712 else {
713 return (0..self.num_clusters).collect();
714 };
715 let mut parent_scores = vec![0.0; topology.parent_count()];
716 for (parent_id, score) in parent_scores.iter_mut().enumerate() {
717 let offset = parent_id * self.dim;
718 *score = squared_l2(vector, &parent_centroids[offset..offset + self.dim]);
719 }
720 let parents = select_parent_beam_for_build::<false>(&parent_scores, topology, k);
721 let capacity = parents
722 .iter()
723 .map(|&parent| topology.children(parent as usize).len())
724 .sum();
725 let mut leaves = Vec::with_capacity(capacity);
726 for parent in parents {
727 leaves.extend_from_slice(topology.children(parent as usize));
728 }
729 leaves
730 }
731
732 fn find_k_nearest_hnsw_for_build(&self, vector: &[f32], k: usize) -> Vec<u32> {
733 let Some(FloatCentroidRouter::Hnsw(graph)) = self.routing_index.as_ref() else {
734 return self.find_k_nearest(vector, k);
735 };
736 graph.search_for_build(|leaf| squared_l2(vector, self.get_centroid(leaf)), k)
737 }
738
739 fn find_nearest_hnsw_for_build(&self, vector: &[f32]) -> u32 {
742 let Some(FloatCentroidRouter::Hnsw(graph)) = self.routing_index.as_ref() else {
743 return self.find_nearest(vector);
744 };
745 graph
746 .search_best_for_build(|leaf| squared_l2(vector, self.get_centroid(leaf)))
747 .unwrap_or(0)
748 }
749
750 pub fn get_centroid(&self, cluster_id: u32) -> &[f32] {
752 let offset = cluster_id as usize * self.dim;
753 &self.centroids[offset..offset + self.dim]
754 }
755
756 pub fn compute_residual(&self, vector: &[f32], cluster_id: u32) -> Vec<f32> {
758 let centroid = self.get_centroid(cluster_id);
759 vector.iter().zip(centroid).map(|(&v, &c)| v - c).collect()
760 }
761
762 pub fn size_bytes(&self) -> usize {
764 let routing_bytes = self
765 .routing_index
766 .as_ref()
767 .map_or(0, |router| match router {
768 FloatCentroidRouter::TwoLevel {
769 parent_centroids,
770 topology,
771 } => {
772 parent_centroids.len() * size_of::<f32>()
773 + topology.parent_count() * size_of::<u32>()
774 + self.num_clusters as usize * size_of::<u32>()
775 }
776 FloatCentroidRouter::Hnsw(graph) => graph.size_bytes(),
777 });
778 self.centroids.len() * size_of::<f32>() + routing_bytes + 64
779 }
780
781 #[cfg(feature = "native")]
784 pub(crate) fn visit_routing_regions(&self, visit: &mut dyn FnMut(&'static str, &[u8])) {
785 if let Some(router) = &self.routing_index {
786 match router {
787 FloatCentroidRouter::TwoLevel {
788 parent_centroids,
789 topology,
790 } => {
791 topology.visit_resident_regions(visit);
792 visit(
793 "float parent centroids",
794 super::routing::bytes_of_slice(parent_centroids),
795 );
796 }
797 FloatCentroidRouter::Hnsw(graph) => graph.visit_resident_regions(visit),
798 }
799 }
800 }
801
802 #[cfg(feature = "native")]
803 pub(crate) fn visit_leaf_centroid_region(&self, visit: &mut dyn FnMut(&'static str, &[u8])) {
804 visit(
805 "float leaf centroids",
806 super::routing::bytes_of_slice(&self.centroids),
807 );
808 }
809
810 pub fn to_bytes(&self) -> std::io::Result<Vec<u8>> {
812 bincode::serde::encode_to_vec(self, bincode::config::standard())
813 .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))
814 }
815}
816
817#[inline]
818fn squared_l2(left: &[f32], right: &[f32]) -> f32 {
819 crate::structures::simd::squared_l2_f32(left, right)
820}
821
822#[inline]
823fn threshold_strictly_above(value: f32) -> f32 {
824 if !value.is_finite() {
825 return f32::INFINITY;
826 }
827 let next = value + value.abs().max(1.0) * (4.0 * f32::EPSILON);
832 if next > value { next } else { f32::INFINITY }
833}
834
835#[inline]
836fn soar_build_candidate_budget(num_secondary: usize, num_clusters: usize) -> usize {
837 num_secondary
838 .saturating_add(1)
839 .saturating_mul(64)
840 .max(BUILD_ASSIGNMENT_CANDIDATES)
841 .min(num_clusters)
842}
843
844#[inline]
845fn soar_secondary_loss(
846 vector: &[f32],
847 secondary_centroid: &[f32],
848 primary_residual: &[f32],
849 primary_residual_norm_sq: f32,
850 secondary_residual_norm_sq: f32,
851) -> f32 {
852 let residual_dot = vector
853 .iter()
854 .zip(secondary_centroid)
855 .zip(primary_residual)
856 .fold(0.0f32, |acc, ((&value, ¢roid), &primary)| {
857 acc.algebraic_add(primary.algebraic_mul(value - centroid))
858 });
859
860 let projection_norm_sq = if primary_residual_norm_sq > 0.0 {
863 residual_dot * residual_dot / primary_residual_norm_sq
864 } else {
865 0.0
866 };
867 secondary_residual_norm_sq + SOAR_LAMBDA * projection_norm_sq
868}
869
870#[inline]
871fn scored_cluster_order(left: &(u32, f32), right: &(u32, f32)) -> std::cmp::Ordering {
872 left.1
873 .total_cmp(&right.1)
874 .then_with(|| left.0.cmp(&right.0))
875}
876
877#[cfg(test)]
878mod tests {
879 use super::*;
880 use rand::prelude::*;
881
882 #[test]
883 fn test_coarse_centroids_basic() {
884 let dim = 64;
885 let n = 1000;
886 let num_clusters = 16;
887
888 let mut rng = rand::rngs::StdRng::seed_from_u64(42);
889 let vectors: Vec<Vec<f32>> = (0..n)
890 .map(|_| (0..dim).map(|_| rng.random::<f32>() - 0.5).collect())
891 .collect();
892
893 let config = CoarseConfig::new(dim, num_clusters);
894 let centroids = CoarseCentroids::train(&config, &vectors, "test");
895
896 assert_eq!(centroids.num_clusters, num_clusters as u32);
897 assert_eq!(centroids.dim, dim);
898 }
899
900 #[test]
901 fn contiguous_training_matches_row_wrapper() {
902 let dim = 8;
903 let vectors: Vec<Vec<f32>> = (0..128)
904 .map(|row| {
905 (0..dim)
906 .map(|column| ((row * 31 + column * 17) % 101) as f32 / 101.0)
907 .collect()
908 })
909 .collect();
910 let flat = vectors.iter().flatten().copied().collect::<Vec<_>>();
911 let config = CoarseConfig::new(dim, 12)
912 .with_seed(91)
913 .with_routing(IvfRoutingMode::TwoLevel);
914
915 let rows = CoarseCentroids::train(&config, &vectors, "test");
916 let contiguous = CoarseCentroids::train_contiguous(&config, &flat, vectors.len(), "test");
917
918 assert_eq!(rows.centroids, contiguous.centroids);
919 assert_eq!(
920 bincode::serde::encode_to_vec(&rows.routing_index, bincode::config::standard())
921 .unwrap(),
922 bincode::serde::encode_to_vec(&contiguous.routing_index, bincode::config::standard())
923 .unwrap(),
924 );
925 }
926
927 #[test]
928 fn test_find_nearest() {
929 let dim = 32;
930 let n = 500;
931 let num_clusters = 8;
932
933 let mut rng = rand::rngs::StdRng::seed_from_u64(123);
934 let vectors: Vec<Vec<f32>> = (0..n)
935 .map(|_| (0..dim).map(|_| rng.random::<f32>()).collect())
936 .collect();
937
938 let config = CoarseConfig::new(dim, num_clusters);
939 let centroids = CoarseCentroids::train(&config, &vectors, "test");
940
941 for v in &vectors {
943 let cluster = centroids.find_nearest(v);
944 assert!(cluster < centroids.num_clusters);
945 }
946 }
947
948 #[test]
949 fn scaled_l2_probes_keep_distinct_cache_identities() {
950 let centroids = CoarseCentroids {
951 num_clusters: 2,
952 dim: 2,
953 centroids: vec![1.0, 0.0, 10.0, 0.0],
954 version: 7,
955 soar_config: None,
956 routing_index: None,
957 };
958
959 let near = centroids.probe(&[1.0, 0.0], 1, IvfRoutingMode::Flat);
960 let scaled = centroids.probe(&[100.0, 0.0], 1, IvfRoutingMode::Flat);
961
962 assert_eq!(&*near.cluster_ids, &[0]);
963 assert_eq!(&*scaled.cluster_ids, &[1]);
964 assert_ne!(near.request_fingerprint, scaled.request_fingerprint);
965 }
966
967 #[test]
968 fn test_soar_assignment() {
969 let dim = 32;
970 let n = 100;
971 let num_clusters = 8;
972
973 let mut rng = rand::rngs::StdRng::seed_from_u64(456);
974 let vectors: Vec<Vec<f32>> = (0..n)
975 .map(|_| (0..dim).map(|_| rng.random::<f32>()).collect())
976 .collect();
977
978 let soar_config = SoarConfig {
979 num_secondary: 2,
980 selective: false,
981 spill_threshold: 0.0,
982 };
983 let config = CoarseConfig::new(dim, num_clusters).with_soar(soar_config);
984 let centroids = CoarseCentroids::train(&config, &vectors, "test");
985
986 let assignment = centroids.assign(&vectors[0]);
988 assert!(assignment.primary_cluster < centroids.num_clusters);
989 assert_eq!(centroids.soar_config.as_ref().unwrap().num_secondary, 1);
990 assert_eq!(assignment.secondary_clusters.len(), 1);
991
992 for &sec in &assignment.secondary_clusters {
994 assert_ne!(sec, assignment.primary_cluster);
995 }
996 }
997
998 #[test]
999 fn soar_loss_includes_distortion_and_normalized_projection() {
1000 let vector = [0.0, 0.0];
1001 let primary_residual = [2.0, 0.0];
1002 let secondary_centroid = [-3.0, -4.0];
1003
1004 let loss = soar_secondary_loss(&vector, &secondary_centroid, &primary_residual, 4.0, 25.0);
1007 assert!((loss - 34.0).abs() <= f32::EPSILON);
1008 }
1009
1010 #[test]
1011 fn soar_routing_keeps_an_oversampled_secondary_candidate_pool() {
1012 assert_eq!(soar_build_candidate_budget(1, 1_000), 128);
1013 assert_eq!(soar_build_candidate_budget(2, 1_000), 192);
1014 assert_eq!(soar_build_candidate_budget(8, 64), 64);
1015 }
1016
1017 #[test]
1018 fn two_level_build_assignment_checks_four_parents_for_primary_and_soar() {
1019 let leaves_per_parent = 512;
1020 let children: Vec<Vec<u32>> = (0..4)
1021 .map(|parent| {
1022 let first = parent * leaves_per_parent;
1023 (first..first + leaves_per_parent)
1024 .map(|leaf| leaf as u32)
1025 .collect()
1026 })
1027 .collect();
1028 let mut leaf_centroids = vec![1.0; 4 * leaves_per_parent];
1029 let best_leaf = 3 * leaves_per_parent;
1030 leaf_centroids[best_leaf] = 0.0;
1031 let centroids = CoarseCentroids {
1032 num_clusters: leaf_centroids.len() as u32,
1033 dim: 1,
1034 centroids: leaf_centroids,
1035 version: 1,
1036 soar_config: None,
1037 routing_index: Some(FloatCentroidRouter::TwoLevel {
1038 parent_centroids: vec![0.0, 10.0, 20.0, 30.0],
1039 topology: IvfRoutingTopology::from_children(&children),
1040 }),
1041 };
1042
1043 let query = [0.0];
1044 assert_eq!(
1045 &*centroids
1046 .probe(&query, 1, IvfRoutingMode::TwoLevel)
1047 .cluster_ids,
1048 &[0]
1049 );
1050 assert_eq!(
1051 centroids
1052 .assign_with_routing(&query, IvfRoutingMode::TwoLevel)
1053 .primary_cluster,
1054 best_leaf as u32
1055 );
1056 assert_eq!(
1057 centroids
1058 .assign_with_soar_and_routing(
1059 &query,
1060 &SoarConfig::full(),
1061 IvfRoutingMode::TwoLevel,
1062 )
1063 .primary_cluster,
1064 best_leaf as u32
1065 );
1066 }
1067
1068 #[test]
1069 fn selective_soar_calibrates_to_a_storage_budget() {
1070 let centroids = CoarseCentroids {
1071 num_clusters: 2,
1072 dim: 2,
1073 centroids: vec![0.0, 0.0, 10.0, 0.0],
1074 version: 1,
1075 soar_config: None,
1076 routing_index: None,
1077 };
1078 let vectors: Vec<Vec<f32>> = (0..100)
1079 .map(|index| vec![index as f32 / 100.0, 0.0])
1080 .collect();
1081 let flat = vectors.iter().flatten().copied().collect::<Vec<_>>();
1082 let threshold = centroids.calibrate_selective_spill_threshold(
1083 &flat,
1084 vectors.len(),
1085 IvfRoutingMode::Flat,
1086 0.30,
1087 );
1088 let spilled = vectors
1089 .iter()
1090 .filter(|vector| squared_l2(vector, centroids.get_centroid(0)).sqrt() >= threshold)
1091 .count();
1092 assert!((29..=31).contains(&spilled), "{spilled}");
1093 }
1094
1095 #[test]
1096 fn selective_soar_never_exceeds_budget_when_residuals_tie() {
1097 let centroids = CoarseCentroids {
1098 num_clusters: 2,
1099 dim: 2,
1100 centroids: vec![0.0, 0.0, 10.0, 0.0],
1101 version: 1,
1102 soar_config: None,
1103 routing_index: None,
1104 };
1105 let vectors = [1.0f32, 0.0].repeat(100);
1106 let threshold = centroids.calibrate_selective_spill_threshold(
1107 &vectors,
1108 100,
1109 IvfRoutingMode::Flat,
1110 0.30,
1111 );
1112 let config = SoarConfig::new().threshold(threshold);
1113 let spilled = vectors
1114 .chunks_exact(2)
1115 .filter(|vector| centroids.assign_with_soar(vector, &config).is_spilled())
1116 .count();
1117
1118 assert!(threshold > 1.0);
1119 assert!(spilled <= 30, "{spilled}");
1120 }
1121
1122 #[test]
1123 fn selective_soar_preserves_boundary_query_candidate_recall_with_bounded_postings() {
1124 const DIM: usize = 16;
1125 const CLUSTERS: usize = 16;
1126 const MEMBERS_PER_CLUSTER: usize = 128;
1127 const TOP_K: usize = 20;
1128 const TARGET_SPILL: f32 = 0.30;
1129
1130 fn normalize(values: &mut [f32]) {
1131 let norm = values.iter().map(|value| value * value).sum::<f32>().sqrt();
1132 for value in values {
1133 *value /= norm;
1134 }
1135 }
1136
1137 let mut rng = rand::rngs::StdRng::seed_from_u64(0x50a4_5eed);
1138 let source_centers: Vec<Vec<f32>> = (0..CLUSTERS)
1139 .map(|_| {
1140 let mut center: Vec<f32> = (0..DIM).map(|_| rng.random::<f32>() - 0.5).collect();
1141 normalize(&mut center);
1142 center
1143 })
1144 .collect();
1145 let corpus: Vec<Vec<f32>> = source_centers
1146 .iter()
1147 .flat_map(|center| {
1148 (0..MEMBERS_PER_CLUSTER)
1149 .map(|_| {
1150 let mut noise: Vec<f32> =
1151 (0..DIM).map(|_| rng.random::<f32>() - 0.5).collect();
1152 normalize(&mut noise);
1153 let mut vector: Vec<f32> = center
1154 .iter()
1155 .zip(noise)
1156 .map(|(&value, noise)| value + 0.75 * noise)
1157 .collect();
1158 normalize(&mut vector);
1159 vector
1160 })
1161 .collect::<Vec<_>>()
1162 })
1163 .collect();
1164
1165 let selective = CoarseCentroids::train(
1166 &CoarseConfig::new(DIM, CLUSTERS)
1167 .with_seed(0x1f4)
1168 .with_routing(IvfRoutingMode::Flat)
1169 .with_soar(SoarConfig::new().target_spill_fraction(TARGET_SPILL)),
1170 &corpus,
1171 "test",
1172 );
1173 let mut primary_only = selective.clone();
1176 primary_only.soar_config = None;
1177
1178 let primary_assignments: Vec<MultiAssignment> = corpus
1179 .iter()
1180 .map(|vector| primary_only.assign(vector))
1181 .collect();
1182 let selective_assignments: Vec<MultiAssignment> = corpus
1183 .iter()
1184 .map(|vector| selective.assign(vector))
1185 .collect();
1186 for (primary, spilled) in primary_assignments.iter().zip(&selective_assignments) {
1187 assert_eq!(
1188 primary.primary_cluster, spilled.primary_cluster,
1189 "SOAR policy must not change the primary posting"
1190 );
1191 }
1192
1193 let spilled = selective_assignments
1194 .iter()
1195 .filter(|assignment| assignment.is_spilled())
1196 .count();
1197 let posting_factor = selective_assignments
1198 .iter()
1199 .map(MultiAssignment::num_assignments)
1200 .sum::<usize>() as f32
1201 / corpus.len() as f32;
1202 let spill_budget = (corpus.len() as f32 * TARGET_SPILL).round() as usize;
1203 assert!(
1204 spilled > 0,
1205 "the smoke corpus must exercise selective spilling"
1206 );
1207 assert!(
1208 spilled <= spill_budget,
1209 "{spilled} spilled vectors exceeded the calibrated budget of {spill_budget}"
1210 );
1211 assert!(
1212 posting_factor <= 1.0 + TARGET_SPILL + f32::EPSILON,
1213 "posting amplification {posting_factor:.4} exceeded the 1.30 calibration target"
1214 );
1215
1216 let queries: Vec<Vec<f32>> = (0..selective.num_clusters)
1220 .map(|left| {
1221 let left_centroid = selective.get_centroid(left);
1222 let right = (0..selective.num_clusters)
1223 .filter(|&candidate| candidate != left)
1224 .min_by(|&a, &b| {
1225 squared_l2(left_centroid, selective.get_centroid(a))
1226 .total_cmp(&squared_l2(left_centroid, selective.get_centroid(b)))
1227 .then_with(|| a.cmp(&b))
1228 })
1229 .unwrap();
1230 let mut query: Vec<f32> = left_centroid
1231 .iter()
1232 .zip(selective.get_centroid(right))
1233 .map(|(&a, &b)| 0.51 * a + 0.49 * b)
1234 .collect();
1235 normalize(&mut query);
1236 query
1237 })
1238 .collect();
1239
1240 let mut gained_queries = 0usize;
1241 for nprobe in [1, 2] {
1242 let mut primary_hits = 0usize;
1243 let mut selective_hits = 0usize;
1244 for query in &queries {
1245 let primary_plan = primary_only.probe(query, nprobe, IvfRoutingMode::Flat);
1246 let selective_plan = selective.probe(query, nprobe, IvfRoutingMode::Flat);
1247 assert_eq!(
1248 primary_plan.cluster_ids, selective_plan.cluster_ids,
1249 "SOAR must not alter query routing"
1250 );
1251
1252 let mut truth: Vec<(usize, f32)> = corpus
1253 .iter()
1254 .enumerate()
1255 .map(|(document, vector)| (document, squared_l2(query, vector)))
1256 .collect();
1257 truth.select_nth_unstable_by(TOP_K, |left, right| {
1258 left.1
1259 .total_cmp(&right.1)
1260 .then_with(|| left.0.cmp(&right.0))
1261 });
1262 truth.truncate(TOP_K);
1263
1264 let query_primary_hits = truth
1265 .iter()
1266 .filter(|&&(document, _)| {
1267 primary_assignments[document]
1268 .all_clusters()
1269 .any(|cluster| primary_plan.cluster_ids.contains(&cluster))
1270 })
1271 .count();
1272 let query_selective_hits = truth
1273 .iter()
1274 .filter(|&&(document, _)| {
1275 selective_assignments[document]
1276 .all_clusters()
1277 .any(|cluster| selective_plan.cluster_ids.contains(&cluster))
1278 })
1279 .count();
1280 primary_hits += query_primary_hits;
1281 selective_hits += query_selective_hits;
1282 gained_queries += usize::from(query_selective_hits > query_primary_hits);
1283 }
1284
1285 let denominator = (queries.len() * TOP_K) as f32;
1286 let primary_recall = primary_hits as f32 / denominator;
1287 let selective_recall = selective_hits as f32 / denominator;
1288 assert!(
1289 selective_recall + 0.005 >= primary_recall,
1290 "selective SOAR candidate recall regressed at nprobe={nprobe}: \
1291 {selective_recall:.4} vs {primary_recall:.4}"
1292 );
1293 if nprobe == 1 {
1294 assert!(
1295 selective_recall >= primary_recall + 0.01,
1296 "selective SOAR must recover boundary candidates at nprobe=1: \
1297 {selective_recall:.4} vs {primary_recall:.4}"
1298 );
1299 }
1300 }
1301 assert!(
1302 gained_queries > 0,
1303 "boundary-query smoke did not exercise a selective SOAR recall gain"
1304 );
1305 }
1306
1307 #[test]
1308 fn soar_does_not_choose_an_arbitrarily_distant_orthogonal_centroid() {
1309 let centroids = CoarseCentroids {
1310 num_clusters: 3,
1311 dim: 2,
1312 centroids: vec![-1.0, 0.0, 0.0, -100.0, 1.1, 0.0],
1317 version: 1,
1318 soar_config: None,
1319 routing_index: None,
1320 };
1321
1322 let assignment = centroids.assign_with_soar(&[0.0, 0.0], &SoarConfig::full());
1323 assert_eq!(assignment.primary_cluster, 0);
1324 assert_eq!(assignment.secondary_clusters, vec![2]);
1325 }
1326
1327 #[test]
1328 fn selective_soar_rejects_a_far_secondary_residual() {
1329 let centroids = CoarseCentroids {
1330 num_clusters: 2,
1331 dim: 2,
1332 centroids: vec![0.0, 0.0, 10.0, 0.0],
1333 version: 1,
1334 soar_config: None,
1335 routing_index: None,
1336 };
1337 let config = SoarConfig::new().threshold(0.0);
1338
1339 let assignment = centroids.assign_with_soar(&[1.0, 0.0], &config);
1342 assert_eq!(assignment.primary_cluster, 0);
1343 assert!(assignment.secondary_clusters.is_empty());
1344 }
1345
1346 #[test]
1347 fn selective_soar_keeps_a_comparable_boundary_secondary() {
1348 let centroids = CoarseCentroids {
1349 num_clusters: 2,
1350 dim: 2,
1351 centroids: vec![0.0, 0.0, 2.0, 0.0],
1352 version: 1,
1353 soar_config: None,
1354 routing_index: None,
1355 };
1356 let config = SoarConfig::new().threshold(0.0);
1357
1358 let assignment = centroids.assign_with_soar(&[0.9, 0.1], &config);
1361 assert_eq!(assignment.primary_cluster, 0);
1362 assert_eq!(assignment.secondary_clusters, vec![1]);
1363 }
1364
1365 #[test]
1366 fn soar_secondary_ties_are_ordered_by_cluster_id_and_capped_to_one() {
1367 let centroids = CoarseCentroids {
1368 num_clusters: 3,
1369 dim: 2,
1370 centroids: vec![0.0, 0.0, 1.0, 0.0, -1.0, 0.0],
1371 version: 1,
1372 soar_config: None,
1373 routing_index: None,
1374 };
1375 let config = SoarConfig {
1376 num_secondary: 2,
1377 selective: false,
1378 spill_threshold: 0.0,
1379 };
1380
1381 let assignment = centroids.assign_with_soar(&[0.0, 0.0], &config);
1382 assert_eq!(assignment.primary_cluster, 0);
1383 assert_eq!(assignment.secondary_clusters, vec![1]);
1384 }
1385
1386 #[test]
1387 fn test_serialization() {
1388 let dim = 16;
1389 let n = 50;
1390 let num_clusters = 4;
1391
1392 let mut rng = rand::rngs::StdRng::seed_from_u64(789);
1393 let vectors: Vec<Vec<f32>> = (0..n)
1394 .map(|_| (0..dim).map(|_| rng.random::<f32>()).collect())
1395 .collect();
1396
1397 let config = CoarseConfig::new(dim, num_clusters);
1398 let centroids = CoarseCentroids::train(&config, &vectors, "test");
1399
1400 let bytes = bincode::serde::encode_to_vec(¢roids, bincode::config::standard()).unwrap();
1402 let (loaded, consumed): (CoarseCentroids, usize) =
1403 bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
1404 assert_eq!(consumed, bytes.len());
1405
1406 assert_eq!(loaded.num_clusters, centroids.num_clusters);
1407 assert_eq!(loaded.dim, centroids.dim);
1408 assert_eq!(loaded.centroids.len(), centroids.centroids.len());
1409 }
1410
1411 #[test]
1412 fn persisted_hnsw_and_two_level_routers_are_valid() {
1413 let dim = 4;
1414 let mut rng = rand::rngs::StdRng::seed_from_u64(991);
1415 let vectors: Vec<Vec<f32>> = (0..256)
1416 .map(|_| (0..dim).map(|_| rng.random::<f32>()).collect())
1417 .collect();
1418
1419 for routing in [IvfRoutingMode::Hnsw, IvfRoutingMode::TwoLevel] {
1420 let trained = CoarseCentroids::train(
1421 &CoarseConfig::new(dim, 16).with_routing(routing),
1422 &vectors,
1423 "test",
1424 );
1425 trained.validate_routing(routing).unwrap();
1426 let plan = trained.probe(&vectors[0], 8, routing);
1427 assert_eq!(plan.cluster_ids.len(), 8);
1428 assert!(
1429 plan.cluster_ids
1430 .iter()
1431 .all(|&cluster| cluster < trained.num_clusters)
1432 );
1433
1434 let bytes =
1435 bincode::serde::encode_to_vec(&trained, bincode::config::standard()).unwrap();
1436 let (loaded, consumed): (CoarseCentroids, usize) =
1437 bincode::serde::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
1438 assert_eq!(consumed, bytes.len());
1439 loaded.validate_routing(routing).unwrap();
1440 }
1441 }
1442}