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 {
51 let group_seed = hash_u64(seed, e as u64);
52 for p in 0..per_group {
53 let idx = hash_u64(group_seed, p as u64) % dim as u64;
54 indices.push(idx as u32);
55 }
56 }
57 } else {
58 for p in 0..active_count {
60 let idx = hash_u64(seed, p as u64) % dim as u64;
61 indices.push(idx as u32);
62 }
63 }
64
65 indices.sort_unstable();
66 indices.dedup();
67
68 let max_backfill_attempts = active_count * 10;
71 let mut backfill_counter = 0u64;
72 while indices.len() < active_count {
73 if backfill_counter as usize >= max_backfill_attempts {
74 break;
75 }
76 let idx = (hash_u64(seed.wrapping_add(0xDEAD), backfill_counter) % dim as u64) as u32;
77 backfill_counter += 1;
78 if indices.binary_search(&idx).is_err() {
79 let pos = indices.partition_point(|&x| x < idx);
80 indices.insert(pos, idx);
81 }
82 }
83
84 Self { dim, indices }
85 }
86
87 pub fn new_with_density(dim: usize, density_denom: usize, seed: u64) -> Self {
88 let active_count = (dim / density_denom.max(1)).max(1);
89 let mut indices = Vec::with_capacity(active_count);
90
91 for p in 0..active_count {
92 let idx = hash_u64(seed, p as u64) % dim as u64;
93 indices.push(idx as u32);
94 }
95
96 indices.sort_unstable();
97 indices.dedup();
98
99 let mut backfill_counter = 0u64;
100 while indices.len() < active_count {
101 if backfill_counter as usize >= active_count * 10 {
102 break;
103 }
104 let idx = (hash_u64(seed.wrapping_add(0xDEAD), backfill_counter) % dim as u64) as u32;
105 backfill_counter += 1;
106 if indices.binary_search(&idx).is_err() {
107 let pos = indices.partition_point(|&x| x < idx);
108 indices.insert(pos, idx);
109 }
110 }
111
112 Self { dim, indices }
113 }
114
115 pub fn from_indices(indices: Vec<u32>, dim: usize) -> Self {
118 debug_assert!(
119 indices.windows(2).all(|w| w[0] < w[1]),
120 "from_indices: indices must be sorted and deduplicated"
121 );
122 debug_assert!(
123 indices.last().is_none_or(|&last| (last as usize) < dim),
124 "from_indices: indices must be within [0, dim)"
125 );
126 Self { dim, indices }
127 }
128
129 pub fn from_dense(dense: &[f32], target_dim: usize) -> Self {
132 let active_count = (target_dim / DEFAULT_RHO_DENOM).max(1);
133 if dense.is_empty() {
134 return Self {
135 dim: target_dim,
136 indices: Vec::new(),
137 };
138 }
139
140 let vals: Vec<f64> = dense.iter().map(|&v| f64::from(v)).collect();
152 let mut terms = vec![0.0f64; vals.len()];
153 let mut projections: Vec<(u32, f64)> = (0..target_dim.div_ceil(2))
154 .map(|i| {
155 let mut kept = 0usize;
156 for (j, &val) in vals.iter().enumerate() {
157 let r = hash_u64(i as u64, j as u64) % 6;
158 terms[kept] = if r == 5 { -val } else { val };
159 kept += usize::from((r == 0) | (r == 5));
160 }
161 let mut dot_product = 0.0f64;
162 for &t in &terms[..kept] {
163 dot_product += t;
164 }
165 let index = i * 2 + usize::from(dot_product < 0.0);
166 (
167 index.min(target_dim.saturating_sub(1)) as u32,
168 dot_product.abs(),
169 )
170 })
171 .collect();
172
173 if projections.len() > active_count {
175 projections.select_nth_unstable_by(active_count - 1, |a, b| {
176 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
177 });
178 projections.truncate(active_count);
179 }
180
181 let mut indices: Vec<u32> = projections.into_iter().map(|(idx, _)| idx).collect();
182 indices.sort_unstable();
183
184 Self {
185 dim: target_dim,
186 indices,
187 }
188 }
189
190 pub fn from_scalar(value: f64, min_val: f64, max_val: f64, target_dim: usize) -> Self {
200 let range = max_val - min_val;
201 let normalized = if range.abs() < f64::EPSILON {
202 0.5
203 } else {
204 ((value - min_val) / range).clamp(0.0, 1.0)
205 };
206 let active_count = (target_dim / DEFAULT_RHO_DENOM).max(1);
207
208 let max_offset = (target_dim - active_count) as f64;
211 let start_offset = (normalized * max_offset).round() as usize;
212
213 const PERM_PRIME: u64 = 0x9E3779B97F4A7C55;
218
219 let mut indices = Vec::with_capacity(active_count);
220 for i in 0..active_count {
221 let logical_idx = (start_offset + i) as u64;
222 let idx = (logical_idx.wrapping_mul(PERM_PRIME) % target_dim as u64) as u32;
224 indices.push(idx);
225 }
226
227 indices.sort_unstable();
228 indices.dedup();
232
233 Self {
234 dim: target_dim,
235 indices,
236 }
237 }
238
239 pub fn indices(&self) -> &[u32] {
241 &self.indices
242 }
243
244 pub fn to_deltas(&self) -> Vec<u32> {
247 if self.indices.is_empty() {
248 return Vec::new();
249 }
250 let mut deltas = Vec::with_capacity(self.indices.len());
251 deltas.push(self.indices[0]);
252 for i in 1..self.indices.len() {
253 deltas.push(self.indices[i] - self.indices[i - 1]);
254 }
255 deltas
256 }
257
258 pub fn from_deltas(deltas: &[u32], dim: usize) -> Self {
260 if deltas.is_empty() {
261 return Self {
262 dim,
263 indices: Vec::new(),
264 };
265 }
266 let mut indices = Vec::with_capacity(deltas.len());
267 let mut current: u32 = 0;
268 for &d in deltas {
269 current = match current.checked_add(d) {
270 Some(v) if (v as usize) < dim => v,
271 _ => break,
272 };
273 indices.push(current);
274 }
275 Self { dim, indices }
276 }
277
278 pub fn hamming(&self, other: &Self) -> u32 {
280 let intersection =
281 super::intersection::sparse_intersection_count(&self.indices, &other.indices);
282 (self.indices.len() + other.indices.len() - 2 * intersection) as u32
283 }
284
285 pub fn similarity(&self, other: &Self) -> f64 {
289 let intersection =
290 super::intersection::sparse_intersection_count(&self.indices, &other.indices);
291 let union = self.indices.len() + other.indices.len() - intersection;
292 if union == 0 {
293 return 1.0;
294 }
295 intersection as f64 / union as f64
296 }
297
298 pub fn bind(&self, other: &Self) -> Self {
301 let indices = sorted_symmetric_difference(&self.indices, &other.indices);
302 Self {
303 dim: self.dim,
304 indices,
305 }
306 }
307
308 pub fn permute(&self, shifts: usize) -> Self {
310 if self.dim == 0 {
311 return self.clone();
312 }
313 let dim = self.dim as u32;
314 let shift = (shifts % self.dim) as u32;
315 let mut indices: Vec<u32> = self
316 .indices
317 .iter()
318 .map(|&idx| (idx + shift) % dim)
319 .collect();
320 indices.sort_unstable();
321 Self {
322 dim: self.dim,
323 indices,
324 }
325 }
326
327 pub fn hash_permute(&self, seed: u64) -> Self {
337 if self.dim == 0 {
338 return self.clone();
339 }
340 let d = self.dim as u64;
341 let mut indices: Vec<u32> = self
342 .indices
343 .iter()
344 .map(|&idx| (hash_u64(idx as u64, seed) % d) as u32)
345 .collect();
346 indices.sort_unstable();
347 indices.dedup();
348 Self {
349 dim: self.dim,
350 indices,
351 }
352 }
353
354 pub fn bundle<V: Borrow<Self>>(vectors: &[V]) -> Self {
357 if vectors.is_empty() {
358 return Self {
359 dim: 0,
360 indices: Vec::new(),
361 };
362 }
363 let dim = vectors[0].borrow().dim;
364 let n = vectors.len();
365
366 let mut all_indices: Vec<u32> = vectors
367 .iter()
368 .flat_map(|v| v.borrow().indices.iter().copied())
369 .collect();
370 all_indices.sort_unstable();
371
372 if all_indices.is_empty() {
373 return Self {
374 dim,
375 indices: Vec::new(),
376 };
377 }
378
379 let threshold = (n as u32).div_ceil(2);
381 let mut selected: Vec<(u32, u32)> = Vec::new();
382
383 let mut current = all_indices[0];
384 let mut count: u32 = 1;
385 for &idx in &all_indices[1..] {
386 if idx == current {
387 count += 1;
388 } else {
389 if count >= threshold {
390 selected.push((current, count));
391 }
392 current = idx;
393 count = 1;
394 }
395 }
396 if count >= threshold {
397 selected.push((current, count));
398 }
399
400 let target_count = (dim / DEFAULT_RHO_DENOM).max(1);
401
402 if selected.len() > target_count {
404 selected.select_nth_unstable_by(target_count - 1, |a, b| b.1.cmp(&a.1));
405 selected.truncate(target_count);
406 }
407
408 let mut indices: Vec<u32> = selected.into_iter().map(|(idx, _)| idx).collect();
409 indices.sort_unstable();
410
411 Self { dim, indices }
412 }
413
414 pub fn bundle_bloom<V: Borrow<Self>>(vectors: &[V]) -> Self {
423 if vectors.is_empty() {
424 return Self {
425 dim: 0,
426 indices: Vec::new(),
427 };
428 }
429 let dim = vectors[0].borrow().dim;
430
431 let mut all_indices: Vec<u32> = vectors
432 .iter()
433 .flat_map(|v| v.borrow().indices.iter().copied())
434 .collect();
435 all_indices.sort_unstable();
436 all_indices.dedup();
437
438 Self {
439 dim,
440 indices: all_indices,
441 }
442 }
443
444 pub fn containment_similarity(&self, other: &Self) -> f64 {
450 if self.indices.is_empty() {
451 return 1.0;
452 }
453 let intersection =
454 super::intersection::sparse_intersection_count(&self.indices, &other.indices);
455 intersection as f64 / self.indices.len() as f64
456 }
457
458 pub fn corrected_containment(&self, bloom_bundle: &Self) -> f64 {
465 let raw = self.containment_similarity(bloom_bundle);
466 let density = bloom_bundle.indices.len() as f64 / bloom_bundle.dim.max(1) as f64;
467 if density >= 1.0 - 1e-15 {
468 return 0.0;
469 }
470 ((raw - density) / (1.0 - density)).max(0.0)
471 }
472
473 pub fn bind_ordered(&self, other: &Self) -> Self {
479 self.permute(1).bind(other)
480 }
481
482 pub fn unbind_ordered_left(&self, other: &Self) -> Self {
485 let xored = self.bind(other);
486 xored.permute(self.dim - 1)
487 }
488
489 pub fn bundle_dp<V: Borrow<Self>>(vectors: &[V], epsilon: f64) -> Self {
492 let dim = vectors.first().map_or(0, |v| v.borrow().dim);
493 Self::bundle_dp_in_space(vectors, epsilon, dim)
494 }
495
496 pub(crate) fn bundle_dp_in_space<V: Borrow<Self>>(
497 vectors: &[V],
498 epsilon: f64,
499 dim: usize,
500 ) -> Self {
501 assert!(
502 epsilon.is_finite() && epsilon > 0.0,
503 "epsilon must be finite and positive"
504 );
505 assert!(
506 vectors.iter().all(|v| v.borrow().dim == dim),
507 "bundle dimensions must match"
508 );
509 if dim == 0 {
510 return Self::from_indices(Vec::new(), 0);
511 }
512 let contribution_limit = (dim / DEFAULT_RHO_DENOM).max(1);
513 let mut counts = vec![0.0; dim];
514 for vector in vectors {
515 for &index in vector.borrow().indices.iter().take(contribution_limit) {
516 counts[index as usize] += 1.0;
517 }
518 }
519 let scale = contribution_limit as f64 / epsilon;
522 assert!(
523 scale.is_finite() && scale > 0.0,
524 "epsilon is outside the supported numeric range"
525 );
526 let mut rng = rand::rng();
527 let mut scores: Vec<(u32, f64)> = counts
528 .into_iter()
529 .enumerate()
530 .map(|(i, count)| (i as u32, count + laplace_sample(&mut rng, scale)))
531 .collect();
532 scores.select_nth_unstable_by(contribution_limit - 1, |a, b| {
533 b.1.total_cmp(&a.1).then(a.0.cmp(&b.0))
534 });
535 scores.truncate(contribution_limit);
536 let mut indices: Vec<u32> = scores.into_iter().map(|(index, _)| index).collect();
537 indices.sort_unstable();
538 Self::from_indices(indices, dim)
539 }
540}
541
542fn laplace_sample(rng: &mut impl rand::RngExt, scale: f64) -> f64 {
544 let u = loop {
545 let sample = rng.random::<f64>() - 0.5;
546 if sample.abs() < 0.5 {
547 break sample;
548 }
549 };
550 -scale * u.signum() * (1.0 - 2.0 * u.abs()).ln()
551}
552
553pub fn hash_u64(a: u64, b: u64) -> u64 {
557 let mut z = a.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(b);
558 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
559 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
560 z ^ (z >> 31)
561}
562
563pub(crate) fn sorted_symmetric_difference(a: &[u32], b: &[u32]) -> Vec<u32> {
565 let mut result = Vec::with_capacity(a.len() + b.len());
566 let mut i = 0;
567 let mut j = 0;
568 while i < a.len() && j < b.len() {
569 match a[i].cmp(&b[j]) {
570 std::cmp::Ordering::Less => {
571 result.push(a[i]);
572 i += 1;
573 }
574 std::cmp::Ordering::Greater => {
575 result.push(b[j]);
576 j += 1;
577 }
578 std::cmp::Ordering::Equal => {
579 i += 1;
581 j += 1;
582 }
583 }
584 }
585 result.extend_from_slice(&a[i..]);
586 result.extend_from_slice(&b[j..]);
587 result
588}
589
590pub(crate) fn sorted_symmetric_difference_count(a: &[u32], b: &[u32]) -> usize {
592 let mut count = 0;
593 let mut i = 0;
594 let mut j = 0;
595 while i < a.len() && j < b.len() {
596 match a[i].cmp(&b[j]) {
597 std::cmp::Ordering::Less => {
598 count += 1;
599 i += 1;
600 }
601 std::cmp::Ordering::Greater => {
602 count += 1;
603 j += 1;
604 }
605 std::cmp::Ordering::Equal => {
606 i += 1;
607 j += 1;
608 }
609 }
610 }
611 count += a.len() - i;
612 count += b.len() - j;
613 count
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619
620 #[test]
621 fn test_entangled_self_similarity() {
622 let v = EntangledHVec::new_deterministic(16384, 42);
623 assert!(
624 (v.similarity(&v) - 1.0).abs() < 0.0001,
625 "Self-similarity should be 1.0"
626 );
627 }
628
629 #[test]
630 fn test_entangled_random_pair_distance() {
631 let a = EntangledHVec::new_deterministic(16384, 1);
633 let b = EntangledHVec::new_deterministic(16384, 2);
634 let sim = a.similarity(&b);
635 assert!(
637 sim < 0.05,
638 "Random sparse pair Jaccard {:.4} should be near 0",
639 sim
640 );
641 }
642
643 #[test]
644 fn test_entangled_bind_involution() {
645 let a = EntangledHVec::new_deterministic(16384, 1);
646 let b = EntangledHVec::new_deterministic(16384, 2);
647 let ab = a.bind(&b);
648 let recovered = ab.bind(&b);
649 assert_eq!(recovered.indices, a.indices);
651 }
652
653 #[test]
654 fn test_entangled_bundle_majority() {
655 let dim = 16384;
656 let base = EntangledHVec::new_deterministic(dim, 100);
657 let mut vecs = vec![base.clone(); 5];
659 vecs.push(EntangledHVec::new_deterministic(dim, 200));
660 vecs.push(EntangledHVec::new_deterministic(dim, 300));
661 let bundled = EntangledHVec::bundle(&vecs);
662 let sim_base = bundled.similarity(&base);
664 let sim_random = bundled.similarity(&EntangledHVec::new_deterministic(dim, 400));
665 assert!(
666 sim_base > sim_random,
667 "Bundle should be closer to majority element"
668 );
669 }
670
671 #[test]
672 fn test_from_dense_produces_sparse() {
673 let dense: Vec<f32> = (0..128).map(|i| (i as f32 - 64.0) / 64.0).collect();
674 let e = EntangledHVec::from_dense(&dense, 10000);
675 assert_eq!(e.dim, 10000);
676 let expected = 10000 / 256;
678 assert_eq!(e.indices.len(), expected);
679 }
680
681 #[test]
682 fn test_from_scalar() {
683 let e = EntangledHVec::from_scalar(0.5, 0.0, 1.0, 10000);
684 assert_eq!(e.dim, 10000);
685 assert!(!e.indices.is_empty());
686 }
687
688 #[test]
689 fn test_bundle_dp_produces_valid_output() {
690 let dim = 16384;
691 let base = EntangledHVec::new_deterministic(dim, 100);
692 let vecs = vec![base.clone(); 5];
693 let bundled = EntangledHVec::bundle_dp(&vecs, 1.0);
694 assert_eq!(bundled.dim, dim);
695 assert!(!bundled.indices.is_empty());
696 assert!(bundled.indices.windows(2).all(|w| w[0] < w[1]));
697 }
698
699 #[test]
700 fn test_bundle_dp_high_epsilon_matches_deterministic() {
701 let dim = 16384;
702 let base = EntangledHVec::new_deterministic(dim, 100);
703 let vecs = vec![base.clone(); 7];
704 let det = EntangledHVec::bundle(&vecs);
705 let dp = EntangledHVec::bundle_dp(&vecs, 1000.0);
707 let sim = det.similarity(&dp);
708 assert!(
709 sim > 0.8,
710 "High-epsilon DP bundle should closely match deterministic: {:.4}",
711 sim
712 );
713 }
714
715 #[test]
716 fn test_bundle_dp_low_epsilon_adds_noise() {
717 let dim = 16384;
718 let base = EntangledHVec::new_deterministic(dim, 100);
719 let noise = EntangledHVec::new_deterministic(dim, 200);
720 let vecs = vec![base.clone(), base.clone(), base.clone(), noise];
721 let det = EntangledHVec::bundle(&vecs);
722 let mut diffs = 0;
724 for _ in 0..5 {
725 let dp = EntangledHVec::bundle_dp(&vecs, 0.1);
726 if dp.indices != det.indices {
727 diffs += 1;
728 }
729 }
730 assert!(diffs > 0, "Low-epsilon DP should produce varied outputs");
731 }
732
733 #[test]
734 fn test_bloom_bundle_capacity() {
735 let dim = 16384;
736 let vecs: Vec<EntangledHVec> = (0..200)
737 .map(|i| EntangledHVec::new_deterministic(dim, i * 100))
738 .collect();
739 let bundle = EntangledHVec::bundle_bloom(&vecs);
740 assert_eq!(bundle.dim, dim);
741 for (i, v) in vecs.iter().enumerate() {
742 let sim = v.containment_similarity(&bundle);
743 assert!(
744 sim > 0.9,
745 "Item {} containment in bundle of 200 should be >0.9, got {:.4}",
746 i,
747 sim
748 );
749 }
750 let noise = EntangledHVec::new_deterministic(dim, 999999);
751 let noise_sim = noise.containment_similarity(&bundle);
752 assert!(
753 noise_sim < 0.9,
754 "Non-member containment should be low, got {:.4}",
755 noise_sim
756 );
757 }
758
759 #[test]
760 fn test_containment_self() {
761 let v = EntangledHVec::new_deterministic(16384, 42);
762 assert!(
763 (v.containment_similarity(&v) - 1.0).abs() < 0.0001,
764 "Self-containment should be 1.0"
765 );
766 }
767
768 #[test]
769 fn test_bind_ordered_non_commutative() {
770 let a = EntangledHVec::new_deterministic(16384, 1);
771 let b = EntangledHVec::new_deterministic(16384, 2);
772 let ab = a.bind_ordered(&b);
773 let ba = b.bind_ordered(&a);
774 assert_ne!(
775 ab.indices, ba.indices,
776 "bind_ordered must be non-commutative"
777 );
778 }
779
780 #[test]
781 fn test_bind_ordered_invertible() {
782 let a = EntangledHVec::new_deterministic(16384, 1);
783 let b = EntangledHVec::new_deterministic(16384, 2);
784 let c = a.bind_ordered(&b);
785 let recovered = c.unbind_ordered_left(&b);
786 assert_eq!(
787 recovered.indices, a.indices,
788 "unbind_ordered_left should recover A"
789 );
790 }
791
792 #[test]
793 fn test_from_scalar_locality() {
794 let dim = 16384;
795 let base_val = 0.5;
796 let v_base = EntangledHVec::from_scalar(base_val, 0.0, 1.0, dim);
797
798 let mut prev_sim = 1.0;
799 for i in 1..20 {
801 let offset = i as f64 * 0.02;
802 let v_curr = EntangledHVec::from_scalar(base_val + offset, 0.0, 1.0, dim);
803 let sim = v_base.similarity(&v_curr);
804
805 assert!(
806 sim <= prev_sim,
807 "Monotonicity violation at offset {}: sim={:.4} was prev={:.4}",
808 offset,
809 sim,
810 prev_sim
811 );
812 prev_sim = sim;
813 }
814 }
815
816 fn from_dense_reference(dense: &[f32], target_dim: usize) -> EntangledHVec {
818 let active_count = (target_dim / DEFAULT_RHO_DENOM).max(1);
819 if dense.is_empty() {
820 return EntangledHVec {
821 dim: target_dim,
822 indices: Vec::new(),
823 };
824 }
825 let mut projections: Vec<(u32, f64)> = (0..target_dim.div_ceil(2))
826 .map(|i| {
827 let mut dot_product = 0.0f64;
828 for (j, &val) in dense.iter().enumerate() {
829 let r = hash_u64(i as u64, j as u64) % 6;
830 if r == 0 {
831 dot_product += val as f64;
832 } else if r == 5 {
833 dot_product -= val as f64;
834 }
835 }
836 let index = i * 2 + usize::from(dot_product < 0.0);
837 (
838 index.min(target_dim.saturating_sub(1)) as u32,
839 dot_product.abs(),
840 )
841 })
842 .collect();
843 if projections.len() > active_count {
844 projections.select_nth_unstable_by(active_count - 1, |a, b| {
845 b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
846 });
847 projections.truncate(active_count);
848 }
849 let mut indices: Vec<u32> = projections.into_iter().map(|(idx, _)| idx).collect();
850 indices.sort_unstable();
851 EntangledHVec {
852 dim: target_dim,
853 indices,
854 }
855 }
856
857 #[test]
858 fn from_dense_matches_reference() {
859 let mut state = 0x1234_5678_9ABC_DEF0u64;
860 let mut next = move || {
861 state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
862 hash_u64(state, 0)
863 };
864 let unit = |x: u64| (x >> 11) as f64 / (1u64 << 53) as f64;
865 let mut cases = 0usize;
866 for emb in [64usize, 384, 768, 1536] {
867 for (d, n) in [(4096usize, 100usize), (16384, 30), (65536, 3)] {
868 for k in 0..n {
869 let v: Vec<f32> = (0..emb)
870 .map(|_| {
871 let r = next();
872 match k % 6 {
873 0 | 1 => (unit(r) * 2.0 - 1.0) as f32,
875 2 if k < 6 => 0.0,
877 2 | 3 => (r % 3) as f32 - 1.0,
879 4 => 0.5,
881 _ => match r % 5 {
883 0 => f32::MAX,
884 1 => f32::MIN,
885 2 => f32::MIN_POSITIVE / 4.0,
886 3 => -0.0,
887 _ => 0.0,
888 },
889 }
890 })
891 .collect();
892 let got = EntangledHVec::from_dense(&v, d);
893 let want = from_dense_reference(&v, d);
894 assert_eq!(got.dim, want.dim);
895 assert_eq!(got.indices, want.indices, "emb={emb} D={d} case={k}");
896 cases += 1;
897 }
898 }
899 }
900 assert!(cases >= 500);
901 }
902}