1use nodedb_codec::vector_quant::{
11 codec::{AdcLut, VectorCodec},
12 layout::{QuantHeader, QuantMode, UnifiedQuantizedVector},
13};
14
15use crate::quantize::binary;
16
17pub struct BinaryCodec {
24 pub dim: usize,
25}
26
27pub struct BinaryQuantized(pub UnifiedQuantizedVector);
31
32impl AsRef<UnifiedQuantizedVector> for BinaryQuantized {
33 #[inline]
34 fn as_ref(&self) -> &UnifiedQuantizedVector {
35 &self.0
36 }
37}
38
39#[inline]
42fn packed_bits_of(q: &BinaryQuantized) -> &[u8] {
43 q.0.packed_bits()
44}
45
46impl VectorCodec for BinaryCodec {
49 type Quantized = BinaryQuantized;
50 type Query = Vec<u8>;
52
53 fn encode(&self, v: &[f32]) -> Self::Quantized {
61 let bits = binary::encode(v);
62 let header = QuantHeader {
63 quant_mode: QuantMode::Binary as u16,
64 dim: self.dim as u16,
65 global_scale: 0.0,
66 residual_norm: 0.0,
67 dot_quantized: 0.0,
68 outlier_bitmask: 0,
69 reserved: [0; 8],
70 };
71 let uqv = UnifiedQuantizedVector::new(header, &bits, &[])
72 .expect("BinaryCodec::encode: layout construction is infallible (no outliers)");
73 BinaryQuantized(uqv)
74 }
75
76 fn prepare_query(&self, q: &[f32]) -> Self::Query {
78 binary::encode(q)
79 }
80
81 fn adc_lut(&self, _q: &Self::Query) -> Option<AdcLut> {
83 None
84 }
85
86 #[inline]
88 fn fast_symmetric_distance(&self, q: &Self::Quantized, v: &Self::Quantized) -> f32 {
89 binary::hamming_distance(packed_bits_of(q), packed_bits_of(v)) as f32
90 }
91
92 #[inline]
94 fn exact_asymmetric_distance(&self, q: &Self::Query, v: &Self::Quantized) -> f32 {
95 binary::hamming_distance(q, packed_bits_of(v)) as f32
96 }
97}
98
99#[cfg(test)]
102mod tests {
103 use super::*;
104
105 fn make_codec(dim: usize) -> BinaryCodec {
106 BinaryCodec { dim }
107 }
108
109 #[test]
111 fn encode_packed_bits_matches_raw_encode() {
112 let dim = 8;
113 let codec = make_codec(dim);
114 let v = vec![1.0f32, -1.0, 1.0, -1.0, 0.5, -0.5, 1.0, -1.0];
115 let raw = binary::encode(&v);
116 let quantized = codec.encode(&v);
117 assert_eq!(quantized.as_ref().packed_bits(), raw.as_slice());
118 }
119
120 #[test]
122 fn fast_symmetric_distance_is_non_negative_finite() {
123 let codec = make_codec(8);
124 let a = codec.encode(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0]);
125 let b = codec.encode(&[-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0]);
126 let d = codec.fast_symmetric_distance(&a, &b);
127 assert!(d.is_finite(), "expected finite distance, got {d}");
128 assert!(d >= 0.0, "expected non-negative distance, got {d}");
129 }
130
131 #[test]
133 fn exact_asymmetric_distance_is_non_negative_finite() {
134 let codec = make_codec(8);
135 let q = codec.prepare_query(&[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0]);
136 let v = codec.encode(&[-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0]);
137 let d = codec.exact_asymmetric_distance(&q, &v);
138 assert!(d.is_finite(), "expected finite distance, got {d}");
139 assert!(d >= 0.0, "expected non-negative distance, got {d}");
140 }
141
142 #[test]
144 fn opposite_vectors_have_max_hamming_distance() {
145 let dim = 8;
146 let codec = make_codec(dim);
147 let a = codec.encode(&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]);
148 let b = codec.encode(&[-1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0]);
149 let d = codec.fast_symmetric_distance(&a, &b);
150 assert_eq!(d, dim as f32);
151 }
152
153 fn use_vector_codec<C: VectorCodec>(c: &C, q: &[f32], v: &[f32]) -> f32 {
155 let qv = c.encode(v);
156 let qq = c.prepare_query(q);
157 c.fast_symmetric_distance(&qv, &qv) + c.exact_asymmetric_distance(&qq, &qv)
158 }
159
160 #[test]
161 fn trait_bounds_compile() {
162 let codec = make_codec(8);
163 let result = use_vector_codec(
164 &codec,
165 &[1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0],
166 &[-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0],
167 );
168 assert!(result.is_finite());
169 }
170}