Skip to main content

lance_index/vector/bq/
transform.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use 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
27// the inner product of quantized vector and the residual vector.
28pub const ADD_FACTORS_COLUMN: &str = "__add_factors";
29// the inner product of quantized vector and the centroid vector.
30pub 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        // for dot product, the add factor is `1 - v*c + |c|^2`, so we need to compute |c|^2
73        let centroids_norm_square = (distance_type == DistanceType::Dot)
74            .then(|| Float32Array::from(norm_squared_fsl(&centroids)));
75        let rotated_centroids = (rq.metadata_ref().query_estimator
76            == RabitQueryEstimator::RawQuery)
77            .then(|| rq.rotate_fsl_to_f32(&centroids))
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, &centroid_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            // for L2, |v-c|^2 is just the distance to the centroid
317            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            // Preserve the released residual-query estimator and factor layout.
334            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                    // for dot, the add factor is `1 - v*c + |c|^2 = dist_v_c + |c|^2`
359                    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            // New RQ indexes use the RaBitQ-Library raw-query estimator.
415            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}