| 185 | type Query = BbqQuery; |
| 186 | |
| 187 | fn encode(&self, v: &[f32]) -> BbqQuantized { |
| 188 | // Step 1: center. |
| 189 | let mut centered = Vec::with_capacity(self.dim); |
| 190 | self.center(v, &mut centered); |
| 191 | |
| 192 | // Step 2: pack sign bits. |
| 193 | let packed = Self::pack_signs(¢ered); |
| 194 | |
| 195 | // Step 3: corrective factors. |
| 196 | // |
| 197 | // residual_norm (4 B → header.residual_norm): |
| 198 | // ‖v′‖ where v′ = v − c. Used in the symmetric distance estimate. |
| 199 | let residual_norm = Self::norm(¢ered); |
| 200 | |
| 201 | // dot_quantized (4 B → header.dot_quantized): |
| 202 | // ⟨v′, sign(v′)⟩ / ‖v′‖. Measures how well the sign quantization |
| 203 | // captures the direction of v′. |
| 204 | let sign_fp: Vec<f32> = centered |
| 205 | .iter() |
| 206 | .map(|&x| if x >= 0.0 { 1.0 } else { -1.0 }) |
| 207 | .collect(); |
| 208 | let dot_vs = Self::dot(¢ered, &sign_fp); |
| 209 | let dot_quantized = if residual_norm > 0.0 { |
| 210 | dot_vs / residual_norm |
| 211 | } else { |
| 212 | 0.0 |
| 213 | }; |
| 214 | |
| 215 | // global_scale (4 B → header.global_scale): |
| 216 | // ⟨v′, c⟩ / ‖c‖. Captures how aligned the centered vector is with |
| 217 | // the centroid direction — used as a query-alignment corrective. |
| 218 | let centroid_norm = Self::norm(&self.centroid); |
| 219 | let dot_vc = Self::dot(¢ered, &self.centroid); |
| 220 | let query_alignment = if centroid_norm > 0.0 { |
| 221 | dot_vc / centroid_norm |
| 222 | } else { |
| 223 | 0.0 |
| 224 | }; |
| 225 | |
| 226 | // reserved[0..2]: 2 bytes reserved for future correctives (zero-filled). |
| 227 | let reserved = [0u8; 8]; |
| 228 | // reserved[0..2] are the 2 reserved corrective bytes; remainder is zero. |
| 229 | |
| 230 | let header = QuantHeader { |
| 231 | quant_mode: QuantMode::Bbq as u16, |
| 232 | dim: self.dim as u16, |
| 233 | global_scale: query_alignment, |
| 234 | residual_norm, |
| 235 | dot_quantized, |
| 236 | outlier_bitmask: 0, |
| 237 | reserved, |
| 238 | }; |
| 239 | |
| 240 | let uqv = UnifiedQuantizedVector::new(header, &packed, &[]).expect( |
| 241 | "BBQ encode: UnifiedQuantizedVector construction must succeed with no outliers", |
| 242 | ); |
| 243 | BbqQuantized(uqv) |
| 244 | } |