1use serde::{Deserialize, Serialize};
15use std::io;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20pub enum StorageMode {
21 #[default]
23 Full,
24 SQ8,
26 Binary,
29}
30
31#[derive(Debug, Clone)]
48pub struct BinaryQuantizedVector {
49 pub data: Vec<u8>,
51 dimension: usize,
53}
54
55impl BinaryQuantizedVector {
56 #[must_use]
68 pub fn from_f32(vector: &[f32]) -> Self {
69 assert!(!vector.is_empty(), "Cannot quantize empty vector");
70
71 let dimension = vector.len();
72 let num_bytes = dimension.div_ceil(8);
74 let mut data = vec![0u8; num_bytes];
75
76 for (i, &value) in vector.iter().enumerate() {
77 if value >= 0.0 {
78 let byte_idx = i / 8;
80 let bit_idx = i % 8;
81 data[byte_idx] |= 1 << bit_idx;
82 }
83 }
84
85 Self { data, dimension }
86 }
87
88 #[must_use]
90 pub fn dimension(&self) -> usize {
91 self.dimension
92 }
93
94 #[must_use]
96 pub fn memory_size(&self) -> usize {
97 self.data.len()
98 }
99
100 #[must_use]
104 pub fn get_bits(&self) -> Vec<bool> {
105 (0..self.dimension)
106 .map(|i| {
107 let byte_idx = i / 8;
108 let bit_idx = i % 8;
109 (self.data[byte_idx] >> bit_idx) & 1 == 1
110 })
111 .collect()
112 }
113
114 #[must_use]
123 pub fn hamming_distance(&self, other: &Self) -> u32 {
124 debug_assert_eq!(
125 self.dimension, other.dimension,
126 "Dimension mismatch in hamming_distance"
127 );
128
129 self.data
131 .iter()
132 .zip(other.data.iter())
133 .map(|(&a, &b)| (a ^ b).count_ones())
134 .sum()
135 }
136
137 #[must_use]
141 #[allow(clippy::cast_precision_loss)]
142 pub fn hamming_similarity(&self, other: &Self) -> f32 {
143 let distance = self.hamming_distance(other);
144 1.0 - (distance as f32 / self.dimension as f32)
145 }
146
147 #[must_use]
149 pub fn to_bytes(&self) -> Vec<u8> {
150 let mut bytes = Vec::with_capacity(4 + self.data.len());
151 #[allow(clippy::cast_possible_truncation)]
153 bytes.extend_from_slice(&(self.dimension as u32).to_le_bytes());
154 bytes.extend_from_slice(&self.data);
155 bytes
156 }
157
158 pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
164 if bytes.len() < 4 {
165 return Err(io::Error::new(
166 io::ErrorKind::InvalidData,
167 "Not enough bytes for BinaryQuantizedVector header",
168 ));
169 }
170
171 let dimension = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
172 let expected_data_len = dimension.div_ceil(8);
173
174 if bytes.len() < 4 + expected_data_len {
175 return Err(io::Error::new(
176 io::ErrorKind::InvalidData,
177 format!(
178 "Not enough bytes for BinaryQuantizedVector data: expected {}, got {}",
179 4 + expected_data_len,
180 bytes.len()
181 ),
182 ));
183 }
184
185 let data = bytes[4..4 + expected_data_len].to_vec();
186
187 Ok(Self { data, dimension })
188 }
189}
190
191#[derive(Debug, Clone)]
196pub struct QuantizedVector {
197 pub data: Vec<u8>,
199 pub min: f32,
201 pub max: f32,
203}
204
205impl QuantizedVector {
206 #[must_use]
216 pub fn from_f32(vector: &[f32]) -> Self {
217 assert!(!vector.is_empty(), "Cannot quantize empty vector");
218
219 let min = vector.iter().copied().fold(f32::INFINITY, f32::min);
220 let max = vector.iter().copied().fold(f32::NEG_INFINITY, f32::max);
221
222 let range = max - min;
223 let data = if range < f32::EPSILON {
224 vec![128u8; vector.len()]
226 } else {
227 let scale = 255.0 / range;
228 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
229 vector
230 .iter()
231 .map(|&v| {
232 let normalized = (v - min) * scale;
233 normalized.round().clamp(0.0, 255.0) as u8
236 })
237 .collect()
238 };
239
240 Self { data, min, max }
241 }
242
243 #[must_use]
247 pub fn to_f32(&self) -> Vec<f32> {
248 let range = self.max - self.min;
249 if range < f32::EPSILON {
250 vec![self.min; self.data.len()]
252 } else {
253 let scale = range / 255.0;
254 self.data
255 .iter()
256 .map(|&v| f32::from(v) * scale + self.min)
257 .collect()
258 }
259 }
260
261 #[must_use]
263 pub fn dimension(&self) -> usize {
264 self.data.len()
265 }
266
267 #[must_use]
269 pub fn memory_size(&self) -> usize {
270 self.data.len() + 8 }
272
273 #[must_use]
275 pub fn to_bytes(&self) -> Vec<u8> {
276 let mut bytes = Vec::with_capacity(8 + self.data.len());
277 bytes.extend_from_slice(&self.min.to_le_bytes());
278 bytes.extend_from_slice(&self.max.to_le_bytes());
279 bytes.extend_from_slice(&self.data);
280 bytes
281 }
282
283 pub fn from_bytes(bytes: &[u8]) -> io::Result<Self> {
289 if bytes.len() < 8 {
290 return Err(io::Error::new(
291 io::ErrorKind::InvalidData,
292 "Not enough bytes for QuantizedVector header",
293 ));
294 }
295
296 let min = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
297 let max = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
298 let data = bytes[8..].to_vec();
299
300 Ok(Self { data, min, max })
301 }
302}
303
304#[must_use]
308pub fn dot_product_quantized(query: &[f32], quantized: &QuantizedVector) -> f32 {
309 debug_assert_eq!(
310 query.len(),
311 quantized.data.len(),
312 "Dimension mismatch in dot_product_quantized"
313 );
314
315 let range = quantized.max - quantized.min;
316 if range < f32::EPSILON {
317 let value = quantized.min;
319 return query.iter().sum::<f32>() * value;
320 }
321
322 let scale = range / 255.0;
323 let offset = quantized.min;
324
325 query
327 .iter()
328 .zip(quantized.data.iter())
329 .map(|(&q, &v)| q * (f32::from(v) * scale + offset))
330 .sum()
331}
332
333#[must_use]
335pub fn euclidean_squared_quantized(query: &[f32], quantized: &QuantizedVector) -> f32 {
336 debug_assert_eq!(
337 query.len(),
338 quantized.data.len(),
339 "Dimension mismatch in euclidean_squared_quantized"
340 );
341
342 let range = quantized.max - quantized.min;
343 if range < f32::EPSILON {
344 let value = quantized.min;
346 return query.iter().map(|&q| (q - value).powi(2)).sum();
347 }
348
349 let scale = range / 255.0;
350 let offset = quantized.min;
351
352 query
353 .iter()
354 .zip(quantized.data.iter())
355 .map(|(&q, &v)| {
356 let dequantized = f32::from(v) * scale + offset;
357 (q - dequantized).powi(2)
358 })
359 .sum()
360}
361
362#[must_use]
366pub fn cosine_similarity_quantized(query: &[f32], quantized: &QuantizedVector) -> f32 {
367 let dot = dot_product_quantized(query, quantized);
368
369 let query_norm: f32 = query.iter().map(|&x| x * x).sum::<f32>().sqrt();
371
372 let reconstructed = quantized.to_f32();
374 let quantized_norm: f32 = reconstructed.iter().map(|&x| x * x).sum::<f32>().sqrt();
375
376 if query_norm < f32::EPSILON || quantized_norm < f32::EPSILON {
377 return 0.0;
378 }
379
380 dot / (query_norm * quantized_norm)
381}
382
383#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
388use std::arch::x86_64::*;
389
390#[must_use]
395pub fn dot_product_quantized_simd(query: &[f32], quantized: &QuantizedVector) -> f32 {
396 debug_assert_eq!(
397 query.len(),
398 quantized.data.len(),
399 "Dimension mismatch in dot_product_quantized_simd"
400 );
401
402 let range = quantized.max - quantized.min;
403 if range < f32::EPSILON {
404 let value = quantized.min;
405 return query.iter().sum::<f32>() * value;
406 }
407
408 let scale = range / 255.0;
409 let offset = quantized.min;
410
411 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
412 {
413 simd_dot_product_avx2(query, &quantized.data, scale, offset)
414 }
415
416 #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
417 {
418 query
420 .iter()
421 .zip(quantized.data.iter())
422 .map(|(&q, &v)| q * (f32::from(v) * scale + offset))
423 .sum()
424 }
425}
426
427#[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
428#[inline]
429fn simd_dot_product_avx2(query: &[f32], data: &[u8], scale: f32, offset: f32) -> f32 {
430 let len = query.len();
431 let simd_len = len / 8;
432 let remainder = len % 8;
433
434 let mut sum = 0.0f32;
435
436 for i in 0..simd_len {
438 let base = i * 8;
439 for j in 0..8 {
441 let dequant = f32::from(data[base + j]) * scale + offset;
442 sum += query[base + j] * dequant;
443 }
444 }
445
446 let base = simd_len * 8;
448 for i in 0..remainder {
449 let dequant = f32::from(data[base + i]) * scale + offset;
450 sum += query[base + i] * dequant;
451 }
452
453 sum
454}
455
456#[must_use]
458pub fn euclidean_squared_quantized_simd(query: &[f32], quantized: &QuantizedVector) -> f32 {
459 debug_assert_eq!(
460 query.len(),
461 quantized.data.len(),
462 "Dimension mismatch in euclidean_squared_quantized_simd"
463 );
464
465 let range = quantized.max - quantized.min;
466 if range < f32::EPSILON {
467 let value = quantized.min;
468 return query.iter().map(|&q| (q - value).powi(2)).sum();
469 }
470
471 let scale = range / 255.0;
472 let offset = quantized.min;
473
474 let len = query.len();
476 let chunks = len / 4;
477 let remainder = len % 4;
478 let mut sum = 0.0f32;
479
480 for i in 0..chunks {
481 let base = i * 4;
482 let d0 = f32::from(quantized.data[base]) * scale + offset;
483 let d1 = f32::from(quantized.data[base + 1]) * scale + offset;
484 let d2 = f32::from(quantized.data[base + 2]) * scale + offset;
485 let d3 = f32::from(quantized.data[base + 3]) * scale + offset;
486
487 let diff0 = query[base] - d0;
488 let diff1 = query[base + 1] - d1;
489 let diff2 = query[base + 2] - d2;
490 let diff3 = query[base + 3] - d3;
491
492 sum += diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
493 }
494
495 let base = chunks * 4;
496 for i in 0..remainder {
497 let dequant = f32::from(quantized.data[base + i]) * scale + offset;
498 let diff = query[base + i] - dequant;
499 sum += diff * diff;
500 }
501
502 sum
503}
504
505#[must_use]
509pub fn cosine_similarity_quantized_simd(query: &[f32], quantized: &QuantizedVector) -> f32 {
510 let dot = dot_product_quantized_simd(query, quantized);
511
512 let query_norm_sq: f32 = query.iter().map(|&x| x * x).sum();
514
515 let range = quantized.max - quantized.min;
517 let scale = if range < f32::EPSILON {
518 0.0
519 } else {
520 range / 255.0
521 };
522 let offset = quantized.min;
523
524 let quantized_norm_sq: f32 = quantized
525 .data
526 .iter()
527 .map(|&v| {
528 let dequant = f32::from(v) * scale + offset;
529 dequant * dequant
530 })
531 .sum();
532
533 let denom = (query_norm_sq * quantized_norm_sq).sqrt();
534 if denom < f32::EPSILON {
535 return 0.0;
536 }
537
538 dot / denom
539}
540
541#[cfg(test)]
542mod tests {
543 use super::*;
544
545 #[test]
550 fn test_dot_product_quantized_simd_simple() {
551 let query = vec![1.0, 0.0, 0.0];
552 let vector = vec![1.0, 0.0, 0.0];
553 let quantized = QuantizedVector::from_f32(&vector);
554
555 let result = dot_product_quantized_simd(&query, &quantized);
556 assert!(
557 (result - 1.0).abs() < 0.1,
558 "Result {result} not close to 1.0"
559 );
560 }
561
562 #[test]
563 #[allow(clippy::cast_precision_loss)]
564 fn test_dot_product_quantized_simd_768d() {
565 let dimension = 768;
566 let query: Vec<f32> = (0..dimension).map(|i| (i as f32) / 1000.0).collect();
567 let vector: Vec<f32> = (0..dimension).map(|i| (i as f32) / 1000.0).collect();
568 let quantized = QuantizedVector::from_f32(&vector);
569
570 let scalar = dot_product_quantized(&query, &quantized);
571 let simd = dot_product_quantized_simd(&query, &quantized);
572
573 let rel_error = ((scalar - simd) / scalar).abs();
575 assert!(rel_error < 0.01, "Relative error {rel_error} too high");
576 }
577
578 #[test]
579 #[allow(clippy::cast_precision_loss)]
580 fn test_euclidean_squared_quantized_simd_768d() {
581 let dimension = 768;
582 let query: Vec<f32> = (0..dimension).map(|i| (i as f32) / 1000.0).collect();
583 let vector: Vec<f32> = (0..dimension).map(|i| ((i + 10) as f32) / 1000.0).collect();
584 let quantized = QuantizedVector::from_f32(&vector);
585
586 let scalar = euclidean_squared_quantized(&query, &quantized);
587 let simd = euclidean_squared_quantized_simd(&query, &quantized);
588
589 let rel_error = ((scalar - simd) / scalar).abs();
590 assert!(rel_error < 0.01, "Relative error {rel_error} too high");
591 }
592
593 #[test]
594 fn test_cosine_similarity_quantized_simd_identical() {
595 let vector = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
596 let quantized = QuantizedVector::from_f32(&vector);
597
598 let similarity = cosine_similarity_quantized_simd(&vector, &quantized);
599 assert!(
600 (similarity - 1.0).abs() < 0.05,
601 "Similarity {similarity} not close to 1.0"
602 );
603 }
604
605 #[test]
610 fn test_quantize_simple_vector() {
611 let vector = vec![0.0, 0.5, 1.0];
613
614 let quantized = QuantizedVector::from_f32(&vector);
616
617 assert_eq!(quantized.dimension(), 3);
619 assert!((quantized.min - 0.0).abs() < f32::EPSILON);
620 assert!((quantized.max - 1.0).abs() < f32::EPSILON);
621 assert_eq!(quantized.data[0], 0); assert_eq!(quantized.data[1], 128); assert_eq!(quantized.data[2], 255); }
625
626 #[test]
627 fn test_quantize_negative_values() {
628 let vector = vec![-1.0, 0.0, 1.0];
630
631 let quantized = QuantizedVector::from_f32(&vector);
633
634 assert!((quantized.min - (-1.0)).abs() < f32::EPSILON);
636 assert!((quantized.max - 1.0).abs() < f32::EPSILON);
637 assert_eq!(quantized.data[0], 0); assert_eq!(quantized.data[1], 128); assert_eq!(quantized.data[2], 255); }
641
642 #[test]
643 fn test_quantize_constant_vector() {
644 let vector = vec![0.5, 0.5, 0.5];
646
647 let quantized = QuantizedVector::from_f32(&vector);
649
650 assert_eq!(quantized.dimension(), 3);
652 for &v in &quantized.data {
654 assert_eq!(v, 128);
655 }
656 }
657
658 #[test]
659 fn test_dequantize_roundtrip() {
660 let original = vec![0.1, 0.5, 0.9, -0.3, 0.0];
662
663 let quantized = QuantizedVector::from_f32(&original);
665 let reconstructed = quantized.to_f32();
666
667 assert_eq!(reconstructed.len(), original.len());
669 for (orig, recon) in original.iter().zip(reconstructed.iter()) {
670 let error = (orig - recon).abs();
671 let max_error = (quantized.max - quantized.min) / 255.0;
673 assert!(
674 error <= max_error + f32::EPSILON,
675 "Error {error} exceeds max {max_error}"
676 );
677 }
678 }
679
680 #[test]
681 #[allow(clippy::cast_precision_loss)]
682 fn test_memory_reduction() {
683 let dimension = 768;
685 let vector: Vec<f32> = (0..dimension)
686 .map(|i| i as f32 / dimension as f32)
687 .collect();
688
689 let quantized = QuantizedVector::from_f32(&vector);
691
692 let f32_size = dimension * 4; let sq8_size = quantized.memory_size(); assert_eq!(f32_size, 3072);
697 assert_eq!(sq8_size, 776);
698 #[allow(clippy::cast_precision_loss)]
700 let ratio = f32_size as f32 / sq8_size as f32;
701 assert!(ratio > 3.9);
702 }
703
704 #[test]
705 fn test_serialization_roundtrip() {
706 let vector = vec![0.1, 0.5, 0.9, -0.3];
708 let quantized = QuantizedVector::from_f32(&vector);
709
710 let bytes = quantized.to_bytes();
712 let deserialized = QuantizedVector::from_bytes(&bytes).unwrap();
713
714 assert!((deserialized.min - quantized.min).abs() < f32::EPSILON);
716 assert!((deserialized.max - quantized.max).abs() < f32::EPSILON);
717 assert_eq!(deserialized.data, quantized.data);
718 }
719
720 #[test]
721 fn test_from_bytes_invalid() {
722 let bytes = vec![0u8; 5];
724
725 let result = QuantizedVector::from_bytes(&bytes);
727
728 assert!(result.is_err());
730 }
731
732 #[test]
737 fn test_dot_product_quantized_simple() {
738 let query = vec![1.0, 0.0, 0.0];
740 let vector = vec![1.0, 0.0, 0.0];
741 let quantized = QuantizedVector::from_f32(&vector);
742
743 let dot = dot_product_quantized(&query, &quantized);
745
746 assert!(
748 (dot - 1.0).abs() < 0.1,
749 "Dot product {dot} not close to 1.0"
750 );
751 }
752
753 #[test]
754 fn test_dot_product_quantized_orthogonal() {
755 let query = vec![1.0, 0.0, 0.0];
757 let vector = vec![0.0, 1.0, 0.0];
758 let quantized = QuantizedVector::from_f32(&vector);
759
760 let dot = dot_product_quantized(&query, &quantized);
762
763 assert!(dot.abs() < 0.1, "Dot product {dot} not close to 0");
765 }
766
767 #[test]
768 fn test_euclidean_squared_quantized() {
769 let query = vec![0.0, 0.0, 0.0];
771 let vector = vec![1.0, 0.0, 0.0];
772 let quantized = QuantizedVector::from_f32(&vector);
773
774 let dist = euclidean_squared_quantized(&query, &quantized);
776
777 assert!(
779 (dist - 1.0).abs() < 0.1,
780 "Euclidean squared {dist} not close to 1.0"
781 );
782 }
783
784 #[test]
785 fn test_euclidean_squared_quantized_same_point() {
786 let vector = vec![0.5, 0.5, 0.5];
788 let quantized = QuantizedVector::from_f32(&vector);
789
790 let dist = euclidean_squared_quantized(&vector, &quantized);
792
793 assert!(dist < 0.01, "Distance to self {dist} should be ~0");
795 }
796
797 #[test]
798 fn test_cosine_similarity_quantized_identical() {
799 let vector = vec![1.0, 2.0, 3.0];
801 let quantized = QuantizedVector::from_f32(&vector);
802
803 let similarity = cosine_similarity_quantized(&vector, &quantized);
805
806 assert!(
808 (similarity - 1.0).abs() < 0.05,
809 "Cosine similarity to self {similarity} not close to 1.0"
810 );
811 }
812
813 #[test]
814 fn test_cosine_similarity_quantized_opposite() {
815 let query = vec![1.0, 0.0, 0.0];
817 let vector = vec![-1.0, 0.0, 0.0];
818 let quantized = QuantizedVector::from_f32(&vector);
819
820 let similarity = cosine_similarity_quantized(&query, &quantized);
822
823 assert!(
825 (similarity - (-1.0)).abs() < 0.1,
826 "Cosine similarity {similarity} not close to -1.0"
827 );
828 }
829
830 #[test]
835 #[allow(clippy::cast_precision_loss)]
836 fn test_recall_accuracy_high_dimension() {
837 let dimension = 768;
839 let num_vectors = 100;
840
841 let vectors: Vec<Vec<f32>> = (0..num_vectors)
843 .map(|i| {
844 (0..dimension)
845 .map(|j| {
846 let x = ((i * 7 + j * 13) % 1000) as f32 / 1000.0;
847 x * 2.0 - 1.0 })
849 .collect()
850 })
851 .collect();
852
853 let quantized: Vec<QuantizedVector> = vectors
855 .iter()
856 .map(|v| QuantizedVector::from_f32(v))
857 .collect();
858
859 let query = &vectors[0];
861
862 let mut f32_distances: Vec<(usize, f32)> = vectors
864 .iter()
865 .enumerate()
866 .map(|(i, v)| {
867 let dot: f32 = query.iter().zip(v.iter()).map(|(a, b)| a * b).sum();
868 (i, dot)
869 })
870 .collect();
871
872 let mut sq8_distances: Vec<(usize, f32)> = quantized
873 .iter()
874 .enumerate()
875 .map(|(i, q)| (i, dot_product_quantized(query, q)))
876 .collect();
877
878 f32_distances.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
880 sq8_distances.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
881
882 let k = 10;
884 let f32_top_k: std::collections::HashSet<usize> =
885 f32_distances.iter().take(k).map(|(i, _)| *i).collect();
886 let sq8_top_k: std::collections::HashSet<usize> =
887 sq8_distances.iter().take(k).map(|(i, _)| *i).collect();
888
889 let recall = f32_top_k.intersection(&sq8_top_k).count() as f32 / k as f32;
890
891 assert!(
892 recall >= 0.8,
893 "Recall@{k} is {recall}, expected >= 0.8 (80%)"
894 );
895 }
896
897 #[test]
898 fn test_storage_mode_enum() {
899 let full = StorageMode::Full;
901 let sq8 = StorageMode::SQ8;
902 let binary = StorageMode::Binary;
903 let default = StorageMode::default();
904
905 assert_eq!(full, StorageMode::Full);
907 assert_eq!(sq8, StorageMode::SQ8);
908 assert_eq!(binary, StorageMode::Binary);
909 assert_eq!(default, StorageMode::Full);
910 assert_ne!(full, sq8);
911 assert_ne!(sq8, binary);
912 }
913
914 #[test]
919 fn test_binary_quantize_simple_vector() {
920 let vector = vec![-1.0, 0.5, -0.5, 1.0];
922
923 let binary = BinaryQuantizedVector::from_f32(&vector);
925
926 assert_eq!(binary.dimension(), 4);
928 assert_eq!(binary.data.len(), 1); }
934
935 #[test]
936 fn test_binary_quantize_768d_memory() {
937 let vector: Vec<f32> = (0..768)
939 .map(|i| if i % 2 == 0 { 0.5 } else { -0.5 })
940 .collect();
941
942 let binary = BinaryQuantizedVector::from_f32(&vector);
944
945 assert_eq!(binary.dimension(), 768);
947 assert_eq!(binary.data.len(), 96); let f32_size = 768 * 4;
954 let binary_size = binary.memory_size();
955 assert_eq!(binary_size, 96);
956 #[allow(clippy::cast_precision_loss)]
957 let ratio = f32_size as f32 / binary_size as f32;
958 assert!(ratio >= 32.0, "Expected 32x reduction, got {ratio}x");
959 }
960
961 #[test]
962 fn test_binary_quantize_threshold_at_zero() {
963 let vector = vec![0.0, 0.001, -0.001, f32::EPSILON];
965
966 let binary = BinaryQuantizedVector::from_f32(&vector);
968
969 let bits = binary.get_bits();
972 assert!(bits[0], "0.0 should be 1");
973 assert!(bits[1], "0.001 should be 1");
974 assert!(!bits[2], "-0.001 should be 0");
975 assert!(bits[3], "EPSILON should be 1");
976 }
977
978 #[test]
979 fn test_binary_hamming_distance_identical() {
980 let vector = vec![0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5];
982 let binary = BinaryQuantizedVector::from_f32(&vector);
983
984 let distance = binary.hamming_distance(&binary);
986
987 assert_eq!(distance, 0);
989 }
990
991 #[test]
992 fn test_binary_hamming_distance_opposite() {
993 let v1 = vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
995 let v2 = vec![-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0];
996 let b1 = BinaryQuantizedVector::from_f32(&v1);
997 let b2 = BinaryQuantizedVector::from_f32(&v2);
998
999 let distance = b1.hamming_distance(&b2);
1001
1002 assert_eq!(distance, 8);
1004 }
1005
1006 #[test]
1007 fn test_binary_hamming_distance_half_different() {
1008 let v1 = vec![1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0];
1010 let v2 = vec![1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0];
1011 let b1 = BinaryQuantizedVector::from_f32(&v1);
1012 let b2 = BinaryQuantizedVector::from_f32(&v2);
1013
1014 let distance = b1.hamming_distance(&b2);
1016
1017 assert_eq!(distance, 4);
1019 }
1020
1021 #[test]
1022 fn test_binary_serialization_roundtrip() {
1023 let vector: Vec<f32> = (0..768)
1025 .map(|i| if i % 3 == 0 { 0.5 } else { -0.5 })
1026 .collect();
1027 let binary = BinaryQuantizedVector::from_f32(&vector);
1028
1029 let bytes = binary.to_bytes();
1031 let deserialized = BinaryQuantizedVector::from_bytes(&bytes).unwrap();
1032
1033 assert_eq!(deserialized.dimension(), binary.dimension());
1035 assert_eq!(deserialized.data, binary.data);
1036 assert_eq!(deserialized.hamming_distance(&binary), 0);
1037 }
1038
1039 #[test]
1040 fn test_binary_from_bytes_invalid() {
1041 let bytes = vec![0u8; 3];
1043
1044 let result = BinaryQuantizedVector::from_bytes(&bytes);
1046
1047 assert!(result.is_err());
1049 }
1050}