1use serde::{Deserialize, Serialize};
5use std::borrow::Borrow;
6
7pub const DEFAULT_RHO_DENOM: usize = 256;
13
14const NUM_ENTANGLEMENTS: usize = 64;
20
21#[derive(Clone, Debug, Serialize, Deserialize)]
32pub struct EntangledHVec {
33 pub(crate) dim: usize,
34 pub(crate) indices: Vec<u32>,
36}
37
38impl EntangledHVec {
39 pub fn new_deterministic(dim: usize, seed: u64) -> Self {
44 let active_count = (dim / DEFAULT_RHO_DENOM).max(1);
45 let mut indices = Vec::with_capacity(active_count);
46
47 let per_group = active_count / NUM_ENTANGLEMENTS;
48 if per_group > 0 {
49 for e in 0..NUM_ENTANGLEMENTS {
50 let group_seed = hash_u64(seed, e as u64);
51 for p in 0..per_group {
52 let idx = hash_u64(group_seed, p as u64) % dim as u64;
53 indices.push(idx as u32);
54 }
55 }
56 } else {
57 for p in 0..active_count {
59 let idx = hash_u64(seed, p as u64) % dim as u64;
60 indices.push(idx as u32);
61 }
62 }
63
64 indices.sort_unstable();
65 indices.dedup();
66
67 backfill_collisions(&mut indices, active_count, dim, seed);
68
69 Self { dim, indices }
70 }
71
72 pub fn new_with_density(dim: usize, density_denom: usize, seed: u64) -> Self {
73 let active_count = (dim / density_denom.max(1)).max(1);
74 let mut indices = Vec::with_capacity(active_count);
75
76 for p in 0..active_count {
77 let idx = hash_u64(seed, p as u64) % dim as u64;
78 indices.push(idx as u32);
79 }
80
81 indices.sort_unstable();
82 indices.dedup();
83
84 backfill_collisions(&mut indices, active_count, dim, seed);
85
86 Self { dim, indices }
87 }
88
89 pub fn from_indices(indices: Vec<u32>, dim: usize) -> Self {
92 debug_assert!(
93 indices.windows(2).all(|w| w[0] < w[1]),
94 "from_indices: indices must be sorted and deduplicated"
95 );
96 debug_assert!(
97 indices.last().is_none_or(|&last| (last as usize) < dim),
98 "from_indices: indices must be within [0, dim)"
99 );
100 Self { dim, indices }
101 }
102
103 pub fn from_dense(dense: &[f32], target_dim: usize) -> Self {
106 let active_count = (target_dim / DEFAULT_RHO_DENOM).max(1);
107 if dense.is_empty() {
108 return Self {
109 dim: target_dim,
110 indices: Vec::new(),
111 };
112 }
113
114 let vals: Vec<f64> = dense.iter().map(|&v| f64::from(v)).collect();
123 let mut terms = vec![0.0f64; vals.len()];
124 let mut projections: Vec<(u32, f64)> = (0..target_dim.div_ceil(2))
125 .map(|i| {
126 let mut kept = 0usize;
127 for (j, &val) in vals.iter().enumerate() {
128 let r = hash_u64(i as u64, j as u64) % 6;
129 terms[kept] = if r == 5 { -val } else { val };
130 kept += usize::from((r == 0) | (r == 5));
131 }
132 let mut dot_product = 0.0f64;
133 for &t in &terms[..kept] {
134 dot_product += t;
135 }
136 let index = i * 2 + usize::from(dot_product < 0.0);
137 (
138 index.min(target_dim.saturating_sub(1)) as u32,
139 dot_product.abs(),
140 )
141 })
142 .collect();
143
144 if projections.len() > active_count {
145 projections.select_nth_unstable_by(active_count - 1, |a, b| {
146 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
147 });
148 projections.truncate(active_count);
149 }
150
151 let mut indices: Vec<u32> = projections.into_iter().map(|(idx, _)| idx).collect();
152 indices.sort_unstable();
153
154 Self {
155 dim: target_dim,
156 indices,
157 }
158 }
159
160 pub fn from_scalar(value: f64, min_val: f64, max_val: f64, target_dim: usize) -> Self {
170 let range = max_val - min_val;
171 let normalized = if range.abs() < f64::EPSILON {
172 0.5
173 } else {
174 ((value - min_val) / range).clamp(0.0, 1.0)
175 };
176 let active_count = (target_dim / DEFAULT_RHO_DENOM).max(1);
177
178 let max_offset = (target_dim - active_count) as f64;
179 let start_offset = (normalized * max_offset).round() as usize;
180
181 const PERM_PRIME: u64 = 0x9E3779B97F4A7C55;
185
186 let mut indices = Vec::with_capacity(active_count);
187 for i in 0..active_count {
188 let logical_idx = (start_offset + i) as u64;
189 let idx = (logical_idx.wrapping_mul(PERM_PRIME) % target_dim as u64) as u32;
190 indices.push(idx);
191 }
192
193 indices.sort_unstable();
194 indices.dedup();
196
197 Self {
198 dim: target_dim,
199 indices,
200 }
201 }
202
203 pub fn indices(&self) -> &[u32] {
205 &self.indices
206 }
207
208 pub fn to_deltas(&self) -> Vec<u32> {
211 if self.indices.is_empty() {
212 return Vec::new();
213 }
214 let mut deltas = Vec::with_capacity(self.indices.len());
215 deltas.push(self.indices[0]);
216 for i in 1..self.indices.len() {
217 deltas.push(self.indices[i] - self.indices[i - 1]);
218 }
219 deltas
220 }
221
222 pub fn from_deltas(deltas: &[u32], dim: usize) -> Self {
224 if deltas.is_empty() {
225 return Self {
226 dim,
227 indices: Vec::new(),
228 };
229 }
230 let mut indices = Vec::with_capacity(deltas.len());
231 let mut current: u32 = 0;
232 for &d in deltas {
233 current = match current.checked_add(d) {
234 Some(v) if (v as usize) < dim => v,
235 _ => break,
236 };
237 indices.push(current);
238 }
239 Self { dim, indices }
240 }
241
242 pub fn hamming(&self, other: &Self) -> u32 {
244 let intersection =
245 super::intersection::sparse_intersection_count(&self.indices, &other.indices);
246 (self.indices.len() + other.indices.len() - 2 * intersection) as u32
247 }
248
249 pub fn similarity(&self, other: &Self) -> f64 {
253 let intersection =
254 super::intersection::sparse_intersection_count(&self.indices, &other.indices);
255 let union = self.indices.len() + other.indices.len() - intersection;
256 if union == 0 {
257 return 1.0;
258 }
259 intersection as f64 / union as f64
260 }
261
262 pub fn bind(&self, other: &Self) -> Self {
265 let indices = sorted_symmetric_difference(&self.indices, &other.indices);
266 Self {
267 dim: self.dim,
268 indices,
269 }
270 }
271
272 pub fn permute(&self, shifts: usize) -> Self {
274 if self.dim == 0 {
275 return self.clone();
276 }
277 let dim = self.dim as u32;
278 let shift = (shifts % self.dim) as u32;
279 let mut indices: Vec<u32> = self
280 .indices
281 .iter()
282 .map(|&idx| (idx + shift) % dim)
283 .collect();
284 indices.sort_unstable();
285 Self {
286 dim: self.dim,
287 indices,
288 }
289 }
290
291 pub fn hash_permute(&self, seed: u64) -> Self {
301 if self.dim == 0 {
302 return self.clone();
303 }
304 let d = self.dim as u64;
305 let mut indices: Vec<u32> = self
306 .indices
307 .iter()
308 .map(|&idx| (hash_u64(idx as u64, seed) % d) as u32)
309 .collect();
310 indices.sort_unstable();
311 indices.dedup();
312 Self {
313 dim: self.dim,
314 indices,
315 }
316 }
317
318 pub fn bundle<V: Borrow<Self>>(vectors: &[V]) -> Self {
321 if vectors.is_empty() {
322 return Self {
323 dim: 0,
324 indices: Vec::new(),
325 };
326 }
327 let dim = vectors[0].borrow().dim;
328 let n = vectors.len();
329 let threshold = (n as u32).div_ceil(2);
330 let target_count = (dim / DEFAULT_RHO_DENOM).max(1);
331
332 if let Some(selected) = count_majority(vectors, dim, threshold) {
333 return Self::from_selected(selected, dim, target_count);
334 }
335
336 let mut all_indices: Vec<u32> = vectors
337 .iter()
338 .flat_map(|v| v.borrow().indices.iter().copied())
339 .collect();
340 all_indices.sort_unstable();
341
342 if all_indices.is_empty() {
343 return Self {
344 dim,
345 indices: Vec::new(),
346 };
347 }
348
349 let mut selected: Vec<(u32, u32)> = Vec::new();
351
352 let mut current = all_indices[0];
353 let mut count: u32 = 1;
354 for &idx in &all_indices[1..] {
355 if idx == current {
356 count += 1;
357 } else {
358 if count >= threshold {
359 selected.push((current, count));
360 }
361 current = idx;
362 count = 1;
363 }
364 }
365 if count >= threshold {
366 selected.push((current, count));
367 }
368 Self::from_selected(selected, dim, target_count)
369 }
370
371 pub(crate) fn from_selected(
375 mut selected: Vec<(u32, u32)>,
376 dim: usize,
377 target_count: usize,
378 ) -> Self {
379 if selected.len() > target_count {
380 selected.select_nth_unstable_by(target_count - 1, |a, b| b.1.cmp(&a.1));
381 selected.truncate(target_count);
382 }
383
384 let mut indices: Vec<u32> = selected.into_iter().map(|(idx, _)| idx).collect();
385 indices.sort_unstable();
386
387 Self { dim, indices }
388 }
389
390 pub fn bundle_bloom<V: Borrow<Self>>(vectors: &[V]) -> Self {
399 if vectors.is_empty() {
400 return Self {
401 dim: 0,
402 indices: Vec::new(),
403 };
404 }
405 let dim = vectors[0].borrow().dim;
406
407 let mut all_indices: Vec<u32> = vectors
408 .iter()
409 .flat_map(|v| v.borrow().indices.iter().copied())
410 .collect();
411 all_indices.sort_unstable();
412 all_indices.dedup();
413
414 Self {
415 dim,
416 indices: all_indices,
417 }
418 }
419
420 pub fn containment_similarity(&self, other: &Self) -> f64 {
426 if self.indices.is_empty() {
427 return 1.0;
428 }
429 let intersection =
430 super::intersection::sparse_intersection_count(&self.indices, &other.indices);
431 intersection as f64 / self.indices.len() as f64
432 }
433
434 pub fn corrected_containment(&self, bloom_bundle: &Self) -> f64 {
441 let raw = self.containment_similarity(bloom_bundle);
442 let density = bloom_bundle.indices.len() as f64 / bloom_bundle.dim.max(1) as f64;
443 if density >= 1.0 - 1e-15 {
444 return 0.0;
445 }
446 ((raw - density) / (1.0 - density)).max(0.0)
447 }
448
449 pub fn bind_ordered(&self, other: &Self) -> Self {
455 self.permute(1).bind(other)
456 }
457
458 pub fn unbind_ordered_left(&self, other: &Self) -> Self {
461 let xored = self.bind(other);
462 xored.permute(self.dim - 1)
463 }
464
465 pub fn bundle_dp<V: Borrow<Self>>(vectors: &[V], epsilon: f64) -> Self {
468 let dim = vectors.first().map_or(0, |v| v.borrow().dim);
469 Self::bundle_dp_in_space(vectors, epsilon, dim)
470 }
471
472 pub(crate) fn dp_scale(dim: usize, epsilon: f64) -> Option<f64> {
473 let scale = (dim / DEFAULT_RHO_DENOM).max(1) as f64 / epsilon;
474 (epsilon.is_finite() && epsilon > 0.0 && scale > 0.0 && scale <= f64::MAX / 64.0)
476 .then_some(scale)
477 }
478
479 pub(crate) fn bundle_dp_in_space<V: Borrow<Self>>(
480 vectors: &[V],
481 epsilon: f64,
482 dim: usize,
483 ) -> Self {
484 assert!(
485 epsilon.is_finite() && epsilon > 0.0,
486 "epsilon must be finite and positive"
487 );
488 assert!(
489 vectors.iter().all(|v| v.borrow().dim == dim),
490 "bundle dimensions must match"
491 );
492 if dim == 0 {
493 return Self::from_indices(Vec::new(), 0);
494 }
495 let scale =
496 Self::dp_scale(dim, epsilon).expect("epsilon is outside the supported numeric range");
497 let contribution_limit = (dim / DEFAULT_RHO_DENOM).max(1);
498 let mut counts = vec![0.0; dim];
499 for vector in vectors {
500 for &index in vector.borrow().indices.iter().take(contribution_limit) {
501 counts[index as usize] += 1.0;
502 }
503 }
504 use rand::SeedableRng;
505 let mut rng = rand::rngs::StdRng::try_from_rng(&mut rand::rngs::SysRng)
506 .expect("operating system randomness unavailable");
507 let mut scores: Vec<(u32, f64)> = counts
508 .into_iter()
509 .enumerate()
510 .map(|(i, count)| (i as u32, count + laplace_sample(&mut rng, scale)))
511 .collect();
512 scores.select_nth_unstable_by(contribution_limit - 1, |a, b| {
513 b.1.total_cmp(&a.1).then(a.0.cmp(&b.0))
514 });
515 scores.truncate(contribution_limit);
516 let mut indices: Vec<u32> = scores.into_iter().map(|(index, _)| index).collect();
517 indices.sort_unstable();
518 Self::from_indices(indices, dim)
519 }
520}
521
522const BUNDLE_COUNTING_MAX_DIM: usize = 1 << 16;
525
526thread_local! {
527 static BUNDLE_COUNTS: std::cell::RefCell<Vec<u32>> = const { std::cell::RefCell::new(Vec::new()) };
529}
530
531fn count_majority<V: Borrow<EntangledHVec>>(
536 vectors: &[V],
537 dim: usize,
538 threshold: u32,
539) -> Option<Vec<(u32, u32)>> {
540 if dim > BUNDLE_COUNTING_MAX_DIM {
541 return None;
542 }
543 let mut total = 0usize;
544 for v in vectors {
545 let indices = &v.borrow().indices;
546 if indices.iter().any(|&idx| idx as usize >= dim) {
549 return None;
550 }
551 total += indices.len();
552 }
553 BUNDLE_COUNTS.with(|counts| {
554 let mut counts = counts.borrow_mut();
555 if counts.len() < dim {
556 counts.resize(dim, 0);
557 }
558 let counts = &mut counts[..dim];
559 debug_assert!(counts.iter().all(|&c| c == 0));
560 for v in vectors {
561 for &idx in &v.borrow().indices {
562 counts[idx as usize] += 1;
563 }
564 }
565 let mut selected = Vec::new();
566 if total < dim {
567 for v in vectors {
570 for &idx in &v.borrow().indices {
571 let n = std::mem::take(&mut counts[idx as usize]);
572 if n >= threshold {
573 selected.push((idx, n));
574 }
575 }
576 }
577 selected.sort_unstable_by_key(|&(idx, _)| idx);
578 } else {
579 for (idx, slot) in counts.iter_mut().enumerate() {
580 let n = std::mem::take(slot);
581 if n >= threshold {
582 selected.push((idx as u32, n));
583 }
584 }
585 }
586 Some(selected)
587 })
588}
589
590fn laplace_sample(rng: &mut impl rand::RngExt, scale: f64) -> f64 {
592 let u = loop {
593 let sample = rng.random::<f64>() - 0.5;
594 if sample.abs() < 0.5 {
595 break sample;
596 }
597 };
598 -scale * u.signum() * (1.0 - 2.0 * u.abs()).ln()
599}
600
601fn backfill_collisions(indices: &mut Vec<u32>, active_count: usize, dim: usize, seed: u64) {
605 let mut counter = 0u64;
606 while indices.len() < active_count && (counter as usize) < active_count * 10 {
607 let idx = (hash_u64(seed.wrapping_add(0xDEAD), counter) % dim as u64) as u32;
608 counter += 1;
609 if let Err(pos) = indices.binary_search(&idx) {
610 indices.insert(pos, idx);
611 }
612 }
613}
614
615pub fn hash_u64(a: u64, b: u64) -> u64 {
619 let mut z = a.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(b);
620 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
621 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
622 z ^ (z >> 31)
623}
624
625pub(crate) fn sorted_symmetric_difference(a: &[u32], b: &[u32]) -> Vec<u32> {
627 let mut result = Vec::with_capacity(a.len() + b.len());
628 let mut i = 0;
629 let mut j = 0;
630 while i < a.len() && j < b.len() {
631 match a[i].cmp(&b[j]) {
632 std::cmp::Ordering::Less => {
633 result.push(a[i]);
634 i += 1;
635 }
636 std::cmp::Ordering::Greater => {
637 result.push(b[j]);
638 j += 1;
639 }
640 std::cmp::Ordering::Equal => {
641 i += 1;
643 j += 1;
644 }
645 }
646 }
647 result.extend_from_slice(&a[i..]);
648 result.extend_from_slice(&b[j..]);
649 result
650}
651
652pub(crate) fn sorted_symmetric_difference_count(a: &[u32], b: &[u32]) -> usize {
654 let mut count = 0;
655 let mut i = 0;
656 let mut j = 0;
657 while i < a.len() && j < b.len() {
658 match a[i].cmp(&b[j]) {
659 std::cmp::Ordering::Less => {
660 count += 1;
661 i += 1;
662 }
663 std::cmp::Ordering::Greater => {
664 count += 1;
665 j += 1;
666 }
667 std::cmp::Ordering::Equal => {
668 i += 1;
669 j += 1;
670 }
671 }
672 }
673 count += a.len() - i;
674 count += b.len() - j;
675 count
676}
677
678#[cfg(test)]
679mod tests {
680 use super::*;
681
682 #[test]
683 fn self_similarity_and_containment_are_one() {
684 let v = EntangledHVec::new_deterministic(16384, 42);
685 assert!(
686 (v.similarity(&v) - 1.0).abs() < 0.0001,
687 "Self-similarity should be 1.0"
688 );
689 assert!(
690 (v.containment_similarity(&v) - 1.0).abs() < 0.0001,
691 "Self-containment should be 1.0"
692 );
693 }
694
695 #[test]
696 fn test_entangled_random_pair_distance() {
697 let a = EntangledHVec::new_deterministic(16384, 1);
698 let b = EntangledHVec::new_deterministic(16384, 2);
699 let sim = a.similarity(&b);
700 assert!(
702 sim < 0.05,
703 "Random sparse pair Jaccard {:.4} should be near 0",
704 sim
705 );
706 }
707
708 #[test]
709 fn test_entangled_bind_involution() {
710 let a = EntangledHVec::new_deterministic(16384, 1);
711 let b = EntangledHVec::new_deterministic(16384, 2);
712 let ab = a.bind(&b);
713 let recovered = ab.bind(&b);
714 assert_eq!(recovered.indices, a.indices);
716 }
717
718 #[test]
719 fn test_entangled_bundle_majority() {
720 let dim = 16384;
721 let base = EntangledHVec::new_deterministic(dim, 100);
722 let mut vecs = vec![base.clone(); 5];
723 vecs.push(EntangledHVec::new_deterministic(dim, 200));
724 vecs.push(EntangledHVec::new_deterministic(dim, 300));
725 let bundled = EntangledHVec::bundle(&vecs);
726 let sim_base = bundled.similarity(&base);
727 let sim_random = bundled.similarity(&EntangledHVec::new_deterministic(dim, 400));
728 assert!(
729 sim_base > sim_random,
730 "Bundle should be closer to majority element"
731 );
732 }
733
734 #[test]
735 fn test_from_dense_produces_sparse() {
736 let dense: Vec<f32> = (0..128).map(|i| (i as f32 - 64.0) / 64.0).collect();
737 let e = EntangledHVec::from_dense(&dense, 10000);
738 assert_eq!(e.dim, 10000);
739 let expected = 10000 / 256;
740 assert_eq!(e.indices.len(), expected);
741 }
742
743 #[test]
744 fn test_bundle_dp_produces_valid_output() {
745 let dim = 16384;
746 let base = EntangledHVec::new_deterministic(dim, 100);
747 let vecs = vec![base.clone(); 5];
748 let bundled = EntangledHVec::bundle_dp(&vecs, 1.0);
749 assert_eq!(bundled.dim, dim);
750 assert!(!bundled.indices.is_empty());
751 assert!(bundled.indices.windows(2).all(|w| w[0] < w[1]));
752 }
753
754 #[test]
755 #[should_panic(expected = "epsilon is outside the supported numeric range")]
756 fn dp_rejects_finite_scales_whose_inverse_cdf_can_overflow() {
757 let vectors = [EntangledHVec::from_indices(vec![1], 256)];
758 EntangledHVec::bundle_dp(&vectors, 1e-308);
759 }
760
761 #[test]
762 fn test_bundle_dp_high_epsilon_matches_deterministic() {
763 let dim = 16384;
764 let base = EntangledHVec::new_deterministic(dim, 100);
765 let vecs = vec![base.clone(); 7];
766 let det = EntangledHVec::bundle(&vecs);
767 let dp = EntangledHVec::bundle_dp(&vecs, 1000.0);
769 let sim = det.similarity(&dp);
770 assert!(
771 sim > 0.8,
772 "High-epsilon DP bundle should closely match deterministic: {:.4}",
773 sim
774 );
775 }
776
777 #[test]
778 fn test_bundle_dp_low_epsilon_adds_noise() {
779 let dim = 16384;
780 let base = EntangledHVec::new_deterministic(dim, 100);
781 let noise = EntangledHVec::new_deterministic(dim, 200);
782 let vecs = vec![base.clone(), base.clone(), base.clone(), noise];
783 let det = EntangledHVec::bundle(&vecs);
784 let mut diffs = 0;
785 for _ in 0..5 {
786 let dp = EntangledHVec::bundle_dp(&vecs, 0.1);
787 if dp.indices != det.indices {
788 diffs += 1;
789 }
790 }
791 assert!(diffs > 0, "Low-epsilon DP should produce varied outputs");
792 }
793
794 #[test]
795 fn test_bloom_bundle_capacity() {
796 let dim = 16384;
797 let vecs: Vec<EntangledHVec> = (0..200)
798 .map(|i| EntangledHVec::new_deterministic(dim, i * 100))
799 .collect();
800 let bundle = EntangledHVec::bundle_bloom(&vecs);
801 assert_eq!(bundle.dim, dim);
802 for (i, v) in vecs.iter().enumerate() {
803 let sim = v.containment_similarity(&bundle);
804 assert!(
805 sim > 0.9,
806 "Item {} containment in bundle of 200 should be >0.9, got {:.4}",
807 i,
808 sim
809 );
810 }
811 let noise = EntangledHVec::new_deterministic(dim, 999999);
812 let noise_sim = noise.containment_similarity(&bundle);
813 assert!(
814 noise_sim < 0.9,
815 "Non-member containment should be low, got {:.4}",
816 noise_sim
817 );
818 }
819
820 #[test]
821 fn test_bind_ordered_non_commutative() {
822 let a = EntangledHVec::new_deterministic(16384, 1);
823 let b = EntangledHVec::new_deterministic(16384, 2);
824 let ab = a.bind_ordered(&b);
825 let ba = b.bind_ordered(&a);
826 assert_ne!(
827 ab.indices, ba.indices,
828 "bind_ordered must be non-commutative"
829 );
830 }
831
832 #[test]
833 fn test_bind_ordered_invertible() {
834 let a = EntangledHVec::new_deterministic(16384, 1);
835 let b = EntangledHVec::new_deterministic(16384, 2);
836 let c = a.bind_ordered(&b);
837 let recovered = c.unbind_ordered_left(&b);
838 assert_eq!(
839 recovered.indices, a.indices,
840 "unbind_ordered_left should recover A"
841 );
842 }
843
844 #[test]
845 fn test_from_scalar_locality() {
846 let dim = 16384;
847 let base_val = 0.5;
848 let v_base = EntangledHVec::from_scalar(base_val, 0.0, 1.0, dim);
849 assert_eq!(v_base.dim, dim);
850 assert!(!v_base.indices.is_empty());
851
852 let mut prev_sim = 1.0;
853 for i in 1..20 {
854 let offset = i as f64 * 0.02;
855 let v_curr = EntangledHVec::from_scalar(base_val + offset, 0.0, 1.0, dim);
856 let sim = v_base.similarity(&v_curr);
857
858 assert!(
859 sim <= prev_sim,
860 "Monotonicity violation at offset {}: sim={:.4} was prev={:.4}",
861 offset,
862 sim,
863 prev_sim
864 );
865 prev_sim = sim;
866 }
867 }
868
869 fn from_dense_reference(dense: &[f32], target_dim: usize) -> EntangledHVec {
871 let active_count = (target_dim / DEFAULT_RHO_DENOM).max(1);
872 if dense.is_empty() {
873 return EntangledHVec {
874 dim: target_dim,
875 indices: Vec::new(),
876 };
877 }
878 let mut projections: Vec<(u32, f64)> = (0..target_dim.div_ceil(2))
879 .map(|i| {
880 let mut dot_product = 0.0f64;
881 for (j, &val) in dense.iter().enumerate() {
882 let r = hash_u64(i as u64, j as u64) % 6;
883 if r == 0 {
884 dot_product += val as f64;
885 } else if r == 5 {
886 dot_product -= val as f64;
887 }
888 }
889 let index = i * 2 + usize::from(dot_product < 0.0);
890 (
891 index.min(target_dim.saturating_sub(1)) as u32,
892 dot_product.abs(),
893 )
894 })
895 .collect();
896 if projections.len() > active_count {
897 projections.select_nth_unstable_by(active_count - 1, |a, b| {
898 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
899 });
900 projections.truncate(active_count);
901 }
902 let mut indices: Vec<u32> = projections.into_iter().map(|(idx, _)| idx).collect();
903 indices.sort_unstable();
904 EntangledHVec {
905 dim: target_dim,
906 indices,
907 }
908 }
909
910 fn bundle_reference(vectors: &[EntangledHVec]) -> Vec<u32> {
912 let dim = vectors[0].dim;
913 let threshold = (vectors.len() as u32).div_ceil(2);
914 let mut all: Vec<u32> = vectors
915 .iter()
916 .flat_map(|v| v.indices.iter().copied())
917 .collect();
918 all.sort_unstable();
919 let mut selected: Vec<(u32, u32)> = Vec::new();
920 for run in all.chunk_by(|a, b| a == b) {
921 if run.len() as u32 >= threshold {
922 selected.push((run[0], run.len() as u32));
923 }
924 }
925 let target = (dim / DEFAULT_RHO_DENOM).max(1);
926 if selected.len() > target {
927 selected.select_nth_unstable_by(target - 1, |a, b| b.1.cmp(&a.1));
928 selected.truncate(target);
929 }
930 let mut indices: Vec<u32> = selected.into_iter().map(|(i, _)| i).collect();
931 indices.sort_unstable();
932 indices
933 }
934
935 #[test]
936 fn bundle_matches_sorting_reference() {
937 for dim in [1usize, 2, 255, 256, 257, 1024, 16384, 65535, 65536, 65537] {
940 for n in [1usize, 2, 3, 4, 7, 10, 64] {
941 for overlap in [0u64, 3] {
942 let vectors: Vec<EntangledHVec> = (0..n as u64)
943 .map(|i| {
944 let seed = if overlap > 0 { i % overlap } else { i };
945 EntangledHVec::new_with_density(dim, 16, seed * 7919 + dim as u64)
946 })
947 .collect();
948 assert_eq!(
949 EntangledHVec::bundle(&vectors).indices,
950 bundle_reference(&vectors),
951 "dim {dim} n {n} overlap {overlap}"
952 );
953 }
954 }
955 }
956 let bad = vec![
958 EntangledHVec {
959 dim: 8,
960 indices: vec![1, 9],
961 },
962 EntangledHVec {
963 dim: 8,
964 indices: vec![1, 9],
965 },
966 ];
967 assert_eq!(EntangledHVec::bundle(&bad).indices, bundle_reference(&bad));
968 }
969
970 #[test]
971 fn from_dense_matches_reference() {
972 let mut state = 0x1234_5678_9ABC_DEF0u64;
973 let mut next = move || {
974 state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
975 hash_u64(state, 0)
976 };
977 let unit = |x: u64| (x >> 11) as f64 / (1u64 << 53) as f64;
978 let mut cases = 0usize;
979 for emb in [64usize, 384, 768, 1536] {
980 for (d, n) in [(4096usize, 100usize), (16384, 30), (65536, 3)] {
981 for k in 0..n {
982 let v: Vec<f32> = (0..emb)
983 .map(|_| {
984 let r = next();
985 match k % 6 {
986 0 | 1 => (unit(r) * 2.0 - 1.0) as f32,
988 2 if k < 6 => 0.0,
990 2 | 3 => (r % 3) as f32 - 1.0,
992 4 => 0.5,
994 _ => match r % 5 {
996 0 => f32::MAX,
997 1 => f32::MIN,
998 2 => f32::MIN_POSITIVE / 4.0,
999 3 => -0.0,
1000 _ => 0.0,
1001 },
1002 }
1003 })
1004 .collect();
1005 let got = EntangledHVec::from_dense(&v, d);
1006 let want = from_dense_reference(&v, d);
1007 assert_eq!(got.dim, want.dim);
1008 assert_eq!(got.indices, want.indices, "emb={emb} D={d} case={k}");
1009 cases += 1;
1010 }
1011 }
1012 }
1013 assert!(cases >= 500);
1014 }
1015}