1use std::fmt::{Debug, Formatter};
5use std::sync::{Arc, LazyLock};
6
7use arrow::array::AsArray;
8use arrow::datatypes::{Float16Type, Float32Type, Float64Type, UInt32Type};
9use arrow_array::{
10 Array, ArrowNativeTypeOp, FixedSizeListArray, Float32Array, RecordBatch, UInt32Array,
11};
12use arrow_schema::DataType;
13use lance_arrow::RecordBatchExt;
14use lance_core::{Error, Result};
15use lance_linalg::distance::{DistanceType, norm_squared_fsl};
16use tracing::instrument;
17
18use crate::vector::bq::builder::RabitQuantizer;
19use crate::vector::bq::rabit_ex_bits;
20use crate::vector::bq::storage::{
21 RABIT_BLOCKED_EX_CODE_COLUMN, RABIT_CODE_COLUMN, RabitQueryEstimator,
22};
23use crate::vector::quantizer::Quantization;
24use crate::vector::transform::Transformer;
25use crate::vector::{CENTROID_DIST_COLUMN, PART_ID_COLUMN};
26
27pub const ADD_FACTORS_COLUMN: &str = "__add_factors";
29pub const SCALE_FACTORS_COLUMN: &str = "__scale_factors";
31pub const EX_ADD_FACTORS_COLUMN: &str = "__add_factors_ex";
32pub const EX_SCALE_FACTORS_COLUMN: &str = "__scale_factors_ex";
33pub const ERROR_FACTORS_COLUMN: &str = "__error_factors";
34
35const RABIT_ERROR_EPSILON: f32 = 1.9;
36
37pub static ADD_FACTORS_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
38 arrow_schema::Field::new(ADD_FACTORS_COLUMN, arrow_schema::DataType::Float32, true)
39});
40pub static SCALE_FACTORS_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
41 arrow_schema::Field::new(SCALE_FACTORS_COLUMN, arrow_schema::DataType::Float32, true)
42});
43pub static EX_ADD_FACTORS_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
44 arrow_schema::Field::new(EX_ADD_FACTORS_COLUMN, arrow_schema::DataType::Float32, true)
45});
46pub static EX_SCALE_FACTORS_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
47 arrow_schema::Field::new(
48 EX_SCALE_FACTORS_COLUMN,
49 arrow_schema::DataType::Float32,
50 true,
51 )
52});
53pub static ERROR_FACTORS_FIELD: LazyLock<arrow_schema::Field> = LazyLock::new(|| {
54 arrow_schema::Field::new(ERROR_FACTORS_COLUMN, arrow_schema::DataType::Float32, true)
55});
56
57pub struct RQTransformer {
58 rq: RabitQuantizer,
59 distance_type: DistanceType,
60 centroids_norm_square: Option<Float32Array>,
61 rotated_centroids: Option<Vec<f32>>,
62 vector_column: String,
63}
64
65impl RQTransformer {
66 pub fn new(
67 rq: RabitQuantizer,
68 distance_type: DistanceType,
69 centroids: FixedSizeListArray,
70 vector_column: impl Into<String>,
71 ) -> Result<Self> {
72 let centroids_norm_square = (distance_type == DistanceType::Dot)
74 .then(|| Float32Array::from(norm_squared_fsl(¢roids)));
75 let rotated_centroids = (rq.metadata_ref().query_estimator
76 == RabitQueryEstimator::RawQuery)
77 .then(|| rq.rotate_fsl_to_f32(¢roids))
78 .transpose()?;
79
80 Ok(Self {
81 rq,
82 distance_type,
83 centroids_norm_square,
84 rotated_centroids,
85 vector_column: vector_column.into(),
86 })
87 }
88}
89
90struct RabitRawQueryFactors {
91 add_factors: Float32Array,
92 scale_factors: Float32Array,
93 error_factors: Float32Array,
94 ex_add_factors: Option<Float32Array>,
95 ex_scale_factors: Option<Float32Array>,
96}
97
98#[inline]
99fn factor_ratio(numerator: f32, denominator: f32) -> f32 {
100 if denominator == 0.0 {
101 0.0
102 } else {
103 numerator / denominator
104 }
105}
106
107#[inline]
108fn binary_factor_value(rotated_residual: f32) -> f32 {
109 if rotated_residual.is_sign_positive() {
110 0.5
111 } else {
112 -0.5
113 }
114}
115
116#[inline]
117fn error_factor_value(
118 distance_type: DistanceType,
119 norm_square: f32,
120 binary_res_dot: f32,
121 code_dim: usize,
122) -> f32 {
123 if code_dim <= 1 || norm_square <= 0.0 || binary_res_dot == 0.0 {
124 return 0.0;
125 }
126
127 let code_norm_square = code_dim as f32 * 0.25;
128 let alignment = norm_square * code_norm_square / binary_res_dot.powi(2);
129 let angular_error = ((alignment - 1.0).max(0.0) / (code_dim as f32 - 1.0)).sqrt();
130 let error = norm_square.sqrt() * RABIT_ERROR_EPSILON * angular_error;
131 match distance_type {
132 DistanceType::L2 => 2.0 * error,
133 DistanceType::Dot => error,
134 _ => unreachable!(),
135 }
136}
137
138#[allow(clippy::too_many_arguments)]
139fn compute_raw_query_factors(
140 distance_type: DistanceType,
141 res_norm_square: &Float32Array,
142 rotated_residuals: &[f32],
143 rotated_centroids: &[f32],
144 part_ids: &UInt32Array,
145 ex_code_values: Option<&[u8]>,
146 ex_res_dot_dists: Option<&[f32]>,
147 ex_bits: u8,
148 code_dim: usize,
149) -> Result<RabitRawQueryFactors> {
150 if !matches!(distance_type, DistanceType::L2 | DistanceType::Dot) {
151 return Err(Error::index(format!(
152 "RQ Transform: distance type {} not supported",
153 distance_type
154 )));
155 }
156
157 let num_rows = res_norm_square.len();
158 debug_assert_eq!(rotated_residuals.len(), num_rows * code_dim);
159 if let Some(ex_code_values) = ex_code_values {
160 debug_assert_eq!(ex_code_values.len(), num_rows * code_dim);
161 }
162 if let Some(ex_res_dot_dists) = ex_res_dot_dists {
163 debug_assert_eq!(ex_res_dot_dists.len(), num_rows);
164 }
165
166 let has_ex_codes = ex_bits != 0;
167 let ex_code_bias = -((1u32 << ex_bits) as f32 - 0.5);
168 let mut add_factors = Vec::with_capacity(num_rows);
169 let mut scale_factors = Vec::with_capacity(num_rows);
170 let mut error_factors = Vec::with_capacity(num_rows);
171 let mut ex_add_factors = has_ex_codes.then(|| Vec::with_capacity(num_rows));
172 let mut ex_scale_factors = has_ex_codes.then(|| Vec::with_capacity(num_rows));
173
174 for (row_idx, &norm_square) in res_norm_square.values().iter().enumerate() {
175 let part_id = part_ids.value(row_idx) as usize;
176 let centroid_start = part_id.checked_mul(code_dim).ok_or_else(|| {
177 Error::invalid_input(format!(
178 "RQ Transform: partition id {} overflows code_dim {}",
179 part_id, code_dim
180 ))
181 })?;
182 let centroid_end = centroid_start.checked_add(code_dim).ok_or_else(|| {
183 Error::invalid_input(format!(
184 "RQ Transform: partition id {} plus code_dim {} overflows",
185 part_id, code_dim
186 ))
187 })?;
188 if centroid_end > rotated_centroids.len() {
189 return Err(Error::invalid_input(format!(
190 "RQ Transform: partition id {} out of range for {} rotated centroids",
191 part_id,
192 rotated_centroids.len() / code_dim
193 )));
194 }
195
196 let row_start = row_idx * code_dim;
197 let row_end = row_start + code_dim;
198 let residual = &rotated_residuals[row_start..row_end];
199 let centroid = &rotated_centroids[centroid_start..centroid_end];
200 let ex_values = ex_code_values.map(|values| &values[row_start..row_end]);
201
202 let mut binary_res_dot = 0.0f32;
203 let mut binary_cent_dot = 0.0f32;
204 let mut ex_cent_dot = 0.0f32;
205 let mut residual_centroid_dot = 0.0f32;
206 for (dim_idx, (&residual_value, ¢roid_value)) in
207 residual.iter().zip(centroid.iter()).enumerate()
208 {
209 let residual_value: f32 = residual_value;
210 let centroid_value: f32 = centroid_value;
211 let binary_code = if residual_value.is_sign_positive() {
212 1u32
213 } else {
214 0u32
215 };
216 let binary_factor = binary_factor_value(residual_value);
217
218 binary_res_dot += residual_value * binary_factor;
219 binary_cent_dot += centroid_value * binary_factor;
220 if let Some(ex_values) = ex_values {
221 let ex_code_value = ex_values[dim_idx];
222 let ex_factor =
223 ((binary_code << ex_bits) + ex_code_value as u32) as f32 + ex_code_bias;
224 ex_cent_dot += centroid_value * ex_factor;
225 }
226 residual_centroid_dot += residual_value * centroid_value;
227 }
228
229 let binary_correction = factor_ratio(norm_square * binary_cent_dot, binary_res_dot);
230 let ex_res_dot = ex_res_dot_dists
231 .map(|values| values[row_idx])
232 .unwrap_or_default();
233 let ex_correction = factor_ratio(norm_square * ex_cent_dot, ex_res_dot);
234 error_factors.push(error_factor_value(
235 distance_type,
236 norm_square,
237 binary_res_dot,
238 code_dim,
239 ));
240
241 match distance_type {
242 DistanceType::L2 => {
243 add_factors.push(norm_square + 2.0 * binary_correction);
244 scale_factors.push(factor_ratio(-2.0 * norm_square, binary_res_dot));
245 if let Some(ex_add_factors) = ex_add_factors.as_mut() {
246 ex_add_factors.push(norm_square + 2.0 * ex_correction);
247 }
248 if let Some(ex_scale_factors) = ex_scale_factors.as_mut() {
249 ex_scale_factors.push(factor_ratio(-2.0 * norm_square, ex_res_dot));
250 }
251 }
252 DistanceType::Dot => {
253 let dot_base = 1.0 - residual_centroid_dot;
254 add_factors.push(dot_base + binary_correction);
255 scale_factors.push(factor_ratio(-norm_square, binary_res_dot));
256 if let Some(ex_add_factors) = ex_add_factors.as_mut() {
257 ex_add_factors.push(dot_base + ex_correction);
258 }
259 if let Some(ex_scale_factors) = ex_scale_factors.as_mut() {
260 ex_scale_factors.push(factor_ratio(-norm_square, ex_res_dot));
261 }
262 }
263 _ => unreachable!(),
264 }
265 }
266
267 Ok(RabitRawQueryFactors {
268 add_factors: Float32Array::from(add_factors),
269 scale_factors: Float32Array::from(scale_factors),
270 error_factors: Float32Array::from(error_factors),
271 ex_add_factors: ex_add_factors.map(Float32Array::from),
272 ex_scale_factors: ex_scale_factors.map(Float32Array::from),
273 })
274}
275
276impl Debug for RQTransformer {
277 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
278 write!(f, "RabitTransformer(vector_column={})", self.vector_column)
279 }
280}
281
282impl Transformer for RQTransformer {
283 #[instrument(name = "RQTransformer::transform", level = "debug", skip_all)]
284 fn transform(&self, batch: &RecordBatch) -> Result<RecordBatch> {
285 let has_split_codes = self.rq.num_bits() == 1
286 || (batch.column_by_name(RABIT_BLOCKED_EX_CODE_COLUMN).is_some()
287 && batch.column_by_name(EX_ADD_FACTORS_COLUMN).is_some()
288 && batch.column_by_name(EX_SCALE_FACTORS_COLUMN).is_some());
289 if batch.column_by_name(RABIT_CODE_COLUMN).is_some() && has_split_codes {
290 return Ok(batch.clone());
291 }
292
293 let residual_vectors = batch
294 .column_by_name(&self.vector_column)
295 .ok_or(Error::index(format!(
296 "RQ Transform: column {} not found in batch",
297 self.vector_column
298 )))?;
299 let residual_vectors = residual_vectors
300 .as_fixed_size_list_opt()
301 .ok_or(Error::index(format!(
302 "RQ Transform: column {} is not a fixed size list, got {}",
303 self.vector_column,
304 residual_vectors.data_type(),
305 )))?;
306
307 let dist_v_c = batch
308 .column_by_name(CENTROID_DIST_COLUMN)
309 .ok_or(Error::index(format!(
310 "RQ Transform: column {} not found in batch",
311 CENTROID_DIST_COLUMN
312 )))?;
313 let dist_v_c = dist_v_c.as_primitive::<Float32Type>();
314
315 let res_norm_square = match self.distance_type {
316 DistanceType::L2 => dist_v_c.clone(),
318 DistanceType::Dot => Float32Array::from(norm_squared_fsl(residual_vectors)),
319 _ => {
320 return Err(Error::index(format!(
321 "RQ Transform: distance type {} not supported",
322 self.distance_type
323 )));
324 }
325 };
326
327 let rq_codes = self.rq.quantize_split(residual_vectors)?;
328 let codes_fsl = rq_codes.binary_codes.as_fixed_size_list();
329 debug_assert_eq!(codes_fsl.len(), batch.num_rows());
330
331 let mut batch = batch.try_with_column(self.rq.field(), rq_codes.binary_codes)?;
332 if self.rq.metadata_ref().query_estimator == RabitQueryEstimator::ResidualQuery {
333 let ip_rq_res = match residual_vectors.value_type() {
335 DataType::Float16 => Float32Array::from(
336 self.rq
337 .codes_res_dot_dists::<Float16Type>(residual_vectors)?,
338 ),
339 DataType::Float32 => Float32Array::from(
340 self.rq
341 .codes_res_dot_dists::<Float32Type>(residual_vectors)?,
342 ),
343 DataType::Float64 => Float32Array::from(
344 self.rq
345 .codes_res_dot_dists::<Float64Type>(residual_vectors)?,
346 ),
347 _ => {
348 return Err(Error::index(format!(
349 "RQ Transform: unsupported residual vector data type: {}",
350 residual_vectors.data_type()
351 )));
352 }
353 };
354
355 let add_factors = match self.distance_type {
356 DistanceType::L2 => res_norm_square.clone(),
357 DistanceType::Dot => {
358 let part_ids = &batch[PART_ID_COLUMN];
360 let part_ids = part_ids.as_primitive::<UInt32Type>();
361 let centroids_norm_square = self.centroids_norm_square.as_ref().ok_or(
362 Error::index("RQ Transform: centroids norm square not found".to_string()),
363 )?;
364 let centroids_norm_square =
365 arrow::compute::take(centroids_norm_square, part_ids, None)?;
366 let centroids_norm_square = centroids_norm_square.as_primitive::<Float32Type>();
367 Float32Array::from_iter_values(
368 dist_v_c
369 .values()
370 .iter()
371 .zip(centroids_norm_square.values().iter())
372 .map(|(dist_v_c, centroids_norm_square)| {
373 dist_v_c + centroids_norm_square
374 }),
375 )
376 }
377 _ => {
378 return Err(Error::index(format!(
379 "RQ Transform: distance type {} not supported",
380 self.distance_type
381 )));
382 }
383 };
384
385 let scale_factors = match self.distance_type {
386 DistanceType::L2 => Float32Array::from_iter_values(
387 res_norm_square.values().iter().zip(ip_rq_res.values()).map(
388 |(res_norm_square, ip_rq_res)| {
389 (-2.0 * res_norm_square)
390 .div_checked(*ip_rq_res)
391 .unwrap_or_default()
392 },
393 ),
394 ),
395 DistanceType::Dot => Float32Array::from_iter_values(
396 res_norm_square.values().iter().zip(ip_rq_res.values()).map(
397 |(res_norm_square, ip_rq_res)| {
398 -res_norm_square.div_checked(*ip_rq_res).unwrap_or_default()
399 },
400 ),
401 ),
402 _ => {
403 return Err(Error::index(format!(
404 "RQ Transform: distance type {} not supported",
405 self.distance_type
406 )));
407 }
408 };
409
410 batch = batch
411 .try_with_column(ADD_FACTORS_FIELD.clone(), Arc::new(add_factors))?
412 .try_with_column(SCALE_FACTORS_FIELD.clone(), Arc::new(scale_factors))?;
413 } else {
414 let ex_bits = rabit_ex_bits(self.rq.num_bits())?;
416 let ex_codes = rq_codes.ex_codes;
417 let ex_res_dot_dists = rq_codes.ex_res_dot_dists;
418 let rotated_residuals = rq_codes.rotated_residuals.ok_or_else(|| {
419 Error::internal("RabitQ quantization did not return rotated residuals".to_string())
420 })?;
421 let ex_code_values = rq_codes.ex_code_values;
422 if ex_bits != 0
423 && (ex_codes.is_none() || ex_res_dot_dists.is_none() || ex_code_values.is_none())
424 {
425 return Err(Error::internal(
426 "RabitQ multi-bit quantization did not return split-code values".to_string(),
427 ));
428 }
429
430 let part_ids = batch[PART_ID_COLUMN].as_primitive::<UInt32Type>();
431 let rotated_centroids = self.rotated_centroids.as_ref().ok_or_else(|| {
432 Error::internal("RabitQ raw-query transformer is missing rotated centroids")
433 })?;
434 let raw_query_factors = compute_raw_query_factors(
435 self.distance_type,
436 &res_norm_square,
437 &rotated_residuals,
438 rotated_centroids,
439 part_ids,
440 ex_code_values.as_deref(),
441 ex_res_dot_dists.as_deref(),
442 ex_bits,
443 self.rq.dim(),
444 )?;
445
446 batch = batch
447 .try_with_column(
448 ADD_FACTORS_FIELD.clone(),
449 Arc::new(raw_query_factors.add_factors),
450 )?
451 .try_with_column(
452 SCALE_FACTORS_FIELD.clone(),
453 Arc::new(raw_query_factors.scale_factors),
454 )?
455 .try_with_column(
456 ERROR_FACTORS_FIELD.clone(),
457 Arc::new(raw_query_factors.error_factors),
458 )?;
459
460 if let Some(ex_codes) = ex_codes {
461 batch = batch.try_with_column(
462 crate::vector::bq::storage::rabit_ex_code_field(
463 self.rq.dim(),
464 self.rq.num_bits(),
465 )?
466 .expect("ex-code field should exist for num_bits > 1"),
467 ex_codes,
468 )?;
469 }
470 if let Some(ex_add_factors) = raw_query_factors.ex_add_factors {
471 batch = batch
472 .try_with_column(EX_ADD_FACTORS_FIELD.clone(), Arc::new(ex_add_factors))?;
473 }
474 if let Some(ex_scale_factors) = raw_query_factors.ex_scale_factors {
475 batch = batch
476 .try_with_column(EX_SCALE_FACTORS_FIELD.clone(), Arc::new(ex_scale_factors))?;
477 }
478 }
479
480 let batch = batch
481 .drop_column(&self.vector_column)?
482 .drop_column(CENTROID_DIST_COLUMN)?;
483 Ok(batch)
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use std::sync::Arc;
490
491 use arrow::array::AsArray;
492 use arrow::datatypes::{Float32Type, UInt8Type};
493 use arrow_array::{ArrayRef, FixedSizeListArray, Float32Array, RecordBatch, UInt32Array};
494 use lance_arrow::FixedSizeListArrayExt;
495 use lance_linalg::distance::DistanceType;
496
497 use crate::vector::bq::RQRotationType;
498 use crate::vector::bq::builder::RabitQuantizer;
499 use crate::vector::bq::ex_dot::blocked_ex_code_bytes;
500 use crate::vector::bq::storage::RABIT_BLOCKED_EX_CODE_COLUMN;
501 use crate::vector::transform::Transformer;
502 use crate::vector::{CENTROID_DIST_COLUMN, PART_ID_COLUMN};
503
504 use super::{
505 ADD_FACTORS_COLUMN, ERROR_FACTORS_COLUMN, EX_ADD_FACTORS_COLUMN, EX_SCALE_FACTORS_COLUMN,
506 RQTransformer, compute_raw_query_factors, error_factor_value,
507 };
508
509 #[test]
510 fn test_rq_transformer_writes_multi_bit_ex_factors() {
511 let rq = RabitQuantizer::new_with_rotation::<Float32Type>(4, 8, RQRotationType::Fast);
512 let centroids =
513 FixedSizeListArray::try_new_from_values(Float32Array::from(vec![0.0f32; 8]), 8)
514 .unwrap();
515 let transformer =
516 RQTransformer::new(rq.clone(), DistanceType::L2, centroids, "vector").unwrap();
517
518 let residual_vectors = FixedSizeListArray::try_new_from_values(
519 Float32Array::from(vec![
520 1.0, -2.0, 3.0, -4.0, 1.5, -2.5, 3.5, -4.5, 0.5, -1.0, 1.5, -2.0, 2.5, -3.0, 3.5,
521 -4.0,
522 ]),
523 8,
524 )
525 .unwrap();
526 let res_norm_square = Float32Array::from(vec![73.0f32, 47.0]);
527 let batch = RecordBatch::try_from_iter(vec![
528 ("vector", Arc::new(residual_vectors.clone()) as ArrayRef),
529 (
530 PART_ID_COLUMN,
531 Arc::new(UInt32Array::from(vec![0, 0])) as ArrayRef,
532 ),
533 (
534 CENTROID_DIST_COLUMN,
535 Arc::new(res_norm_square.clone()) as ArrayRef,
536 ),
537 ])
538 .unwrap();
539
540 let transformed = transformer.transform(&batch).unwrap();
541 assert!(
542 transformed
543 .column_by_name(RABIT_BLOCKED_EX_CODE_COLUMN)
544 .is_some()
545 );
546 assert_eq!(
547 transformed[RABIT_BLOCKED_EX_CODE_COLUMN]
548 .as_fixed_size_list()
549 .value_length(),
550 blocked_ex_code_bytes(8, 3) as i32
551 );
552 assert!(
553 transformed[RABIT_BLOCKED_EX_CODE_COLUMN]
554 .as_fixed_size_list()
555 .values()
556 .as_primitive::<UInt8Type>()
557 .values()
558 .iter()
559 .any(|value| *value != 0)
560 );
561 let expected_ex_dots = rq
562 .quantize_split(&residual_vectors)
563 .unwrap()
564 .ex_res_dot_dists
565 .unwrap();
566 let ex_add_factors = transformed[EX_ADD_FACTORS_COLUMN].as_primitive::<Float32Type>();
567 assert_eq!(ex_add_factors.values(), res_norm_square.values());
568 let ex_scale_factors = transformed[EX_SCALE_FACTORS_COLUMN].as_primitive::<Float32Type>();
569 for ((actual, norm), ex_dot) in ex_scale_factors
570 .values()
571 .iter()
572 .zip(res_norm_square.values())
573 .zip(expected_ex_dots)
574 {
575 let expected = if ex_dot == 0.0 {
576 0.0
577 } else {
578 -2.0 * norm / ex_dot
579 };
580 assert!((actual - expected).abs() < 1e-6);
581 }
582 assert!(transformed.column_by_name("vector").is_none());
583 assert!(transformed.column_by_name(CENTROID_DIST_COLUMN).is_none());
584 assert!(transformed.column_by_name(ADD_FACTORS_COLUMN).is_some());
585 assert!(transformed.column_by_name(ERROR_FACTORS_COLUMN).is_some());
586 }
587
588 #[test]
589 fn test_rq_transformer_caches_rotated_centroids_for_raw_query() {
590 let centroids =
591 FixedSizeListArray::try_new_from_values(Float32Array::from(vec![0.0f32; 8]), 8)
592 .unwrap();
593 let raw_query_rq =
594 RabitQuantizer::new_with_rotation::<Float32Type>(1, 8, RQRotationType::Fast);
595 let raw_query_transformer =
596 RQTransformer::new(raw_query_rq, DistanceType::L2, centroids.clone(), "vector")
597 .unwrap();
598 assert_eq!(
599 raw_query_transformer
600 .rotated_centroids
601 .as_ref()
602 .unwrap()
603 .len(),
604 8
605 );
606
607 let multi_bit_rq =
608 RabitQuantizer::new_with_rotation::<Float32Type>(4, 8, RQRotationType::Fast);
609 let multi_bit_transformer =
610 RQTransformer::new(multi_bit_rq, DistanceType::L2, centroids, "vector").unwrap();
611 assert_eq!(
612 multi_bit_transformer
613 .rotated_centroids
614 .as_ref()
615 .unwrap()
616 .len(),
617 8
618 );
619 }
620
621 #[test]
622 fn test_raw_query_factors_match_reference_l2_formula() {
623 let res_norm_square = Float32Array::from(vec![5.0f32, 7.0]);
624 let rotated_residuals = vec![2.0, -1.0, 0.0, 0.0];
625 let rotated_centroids = vec![3.0, 4.0];
626 let part_ids = UInt32Array::from(vec![0, 0]);
627 let ex_code_values = vec![1, 0, 0, 0];
628 let ex_res_dot_dists = vec![4.5, 0.0];
629
630 let factors = compute_raw_query_factors(
631 DistanceType::L2,
632 &res_norm_square,
633 &rotated_residuals,
634 &rotated_centroids,
635 &part_ids,
636 Some(&ex_code_values),
637 Some(&ex_res_dot_dists),
638 1,
639 2,
640 )
641 .unwrap();
642
643 assert!((factors.add_factors.value(0) - 1.6666667).abs() < 1e-5);
644 assert!((factors.scale_factors.value(0) + 6.6666665).abs() < 1e-5);
645 let expected_error = error_factor_value(DistanceType::L2, 5.0, 1.5, 2);
646 assert!((factors.error_factors.value(0) - expected_error).abs() < 1e-5);
647 let ex_add_factors = factors.ex_add_factors.unwrap();
648 let ex_scale_factors = factors.ex_scale_factors.unwrap();
649 assert!((ex_add_factors.value(0) - 1.6666667).abs() < 1e-5);
650 assert!((ex_scale_factors.value(0) + 2.2222223).abs() < 1e-5);
651 assert_eq!(factors.add_factors.value(1), 7.0);
652 assert_eq!(factors.scale_factors.value(1), 0.0);
653 assert_eq!(factors.error_factors.value(1), 0.0);
654 assert_eq!(ex_add_factors.value(1), 7.0);
655 assert_eq!(ex_scale_factors.value(1), 0.0);
656 }
657
658 #[test]
659 fn test_raw_query_factors_match_reference_dot_formula() {
660 let res_norm_square = Float32Array::from(vec![5.0f32]);
661 let rotated_residuals = vec![2.0, -1.0];
662 let rotated_centroids = vec![3.0, 4.0];
663 let part_ids = UInt32Array::from(vec![0]);
664 let ex_code_values = vec![1, 0];
665 let ex_res_dot_dists = vec![4.5];
666
667 let factors = compute_raw_query_factors(
668 DistanceType::Dot,
669 &res_norm_square,
670 &rotated_residuals,
671 &rotated_centroids,
672 &part_ids,
673 Some(&ex_code_values),
674 Some(&ex_res_dot_dists),
675 1,
676 2,
677 )
678 .unwrap();
679
680 assert!((factors.add_factors.value(0) + 2.6666667).abs() < 1e-5);
681 assert!((factors.scale_factors.value(0) + 3.3333333).abs() < 1e-5);
682 let expected_error = error_factor_value(DistanceType::Dot, 5.0, 1.5, 2);
683 assert!((factors.error_factors.value(0) - expected_error).abs() < 1e-5);
684 let ex_add_factors = factors.ex_add_factors.unwrap();
685 let ex_scale_factors = factors.ex_scale_factors.unwrap();
686 assert!((ex_add_factors.value(0) + 2.6666667).abs() < 1e-5);
687 assert!((ex_scale_factors.value(0) + 1.1111112).abs() < 1e-5);
688 }
689}