Encode a single vector into a [`UnifiedQuantizedVector`] with `QuantMode::RaBitQ`. The header fields populated are: - `global_scale` = `residual_norm` (‖v−c‖); both store the same value so that consumers that use either field without context still have the magnitude available. - `residual_norm` = ‖v−c‖. - `dot_quantized` = ⟨residual, dequantised_sign_vector⟩ / ‖v−c‖; used for IP-bias correction w
(&self, v: &[f32])
| 215 | /// - `dot_quantized` = ⟨residual, dequantised_sign_vector⟩ / ‖v−c‖; |
| 216 | /// used for IP-bias correction when `bias_correct = true`. |
| 217 | fn encode_inner(&self, v: &[f32]) -> UnifiedQuantizedVector { |
| 218 | let dim = self.dim; |
| 219 | |
| 220 | // Step 1: residual = v - centroid |
| 221 | let residual: Vec<f32> = v |
| 222 | .iter() |
| 223 | .zip(self.centroid.iter()) |
| 224 | .map(|(&vi, &ci)| vi - ci) |
| 225 | .collect(); |
| 226 | |
| 227 | // Step 2: ‖residual‖ |
| 228 | let residual_norm = residual.iter().map(|x| x * x).sum::<f32>().sqrt(); |
| 229 | |
| 230 | // Step 3: rotate |
| 231 | let rotated = self.apply_rotation(&residual); |
| 232 | |
| 233 | // Step 4: sign-pack → 1-bit code |
| 234 | let packed = sign_pack(&rotated, dim); |
| 235 | |
| 236 | // Step 5: compute dot_quantized = ⟨residual, R⁻¹·sign(rotated)⟩ / ‖residual‖ |
| 237 | // Inverse rotation of the sign vector, then dot with original residual. |
| 238 | let signs_fp = sign_unpack(&packed, dim); |
| 239 | // Inverse WHT rotation: apply WHT again then re-apply D⁻¹ = D (since D² = I). |
| 240 | let pow2 = next_pow2(dim); |
| 241 | let mut sign_buf = vec![0.0f32; pow2]; |
| 242 | for (i, &s) in signs_fp.iter().enumerate() { |
| 243 | sign_buf[i] = s; |
| 244 | } |
| 245 | wht_inplace(&mut sign_buf); |
| 246 | // Re-apply signed diagonal (D is its own inverse since flips are ±1) |
| 247 | let mut seed = self.rotation_seed; |
| 248 | #[allow(clippy::needless_range_loop)] |
| 249 | for i in 0..dim { |
| 250 | let flip = if xorshift64(&mut seed) & 1 == 0 { |
| 251 | 1.0f32 |
| 252 | } else { |
| 253 | -1.0f32 |
| 254 | }; |
| 255 | sign_buf[i] *= flip; |
| 256 | } |
| 257 | let dot_raw: f32 = residual |
| 258 | .iter() |
| 259 | .zip(sign_buf.iter().take(dim)) |
| 260 | .map(|(&r, &s)| r * s) |
| 261 | .sum(); |
| 262 | let dot_quantized = if residual_norm > 0.0 { |
| 263 | dot_raw / residual_norm |
| 264 | } else { |
| 265 | 0.0 |
| 266 | }; |
| 267 | |
| 268 | let header = QuantHeader { |
| 269 | quant_mode: QuantMode::RaBitQ as u16, |
| 270 | dim: dim as u16, |
| 271 | global_scale: residual_norm, |
| 272 | residual_norm, |
| 273 | dot_quantized, |
| 274 | outlier_bitmask: 0, |
no test coverage detected