Skip to main content

diskann_tools/utils/
ground_truth.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 */
5
6use crate::utils::compute_bitmap::compute_query_bitmaps;
7use bit_set::BitSet;
8use diskann_label_filter::{read_and_parse_queries, read_baselabels};
9
10use std::{io::Write, mem::size_of, str::FromStr};
11
12use bytemuck::cast_slice;
13use diskann::{
14    neighbor::{Neighbor, NeighborPriorityQueue},
15    utils::VectorRepr,
16};
17use diskann_providers::storage::{StorageReadProvider, StorageWriteProvider};
18use diskann_providers::utils::{
19    create_thread_pool, file_util, ParallelIteratorInPool, VectorDataIterator,
20};
21use diskann_utils::{
22    io::{read_bin, Metadata},
23    views::Matrix,
24};
25use diskann_vector::{distance::Metric, DistanceFunction};
26use itertools::Itertools;
27use rayon::prelude::*;
28use serde::{Deserialize, Serialize};
29
30use crate::utils::{search_index_utils, CMDResult, CMDToolError};
31
32pub fn read_labels_and_compute_bitmap(
33    base_label_filename: &str,
34    query_label_filename: &str,
35) -> CMDResult<Vec<BitSet>> {
36    // Read base labels
37    let base_labels = read_baselabels(base_label_filename)?;
38
39    // Read and parse queries
40    let parsed_queries = read_and_parse_queries(query_label_filename)?;
41
42    // Compute the query bitmaps
43    let query_bitmaps = compute_query_bitmaps(base_labels, parsed_queries);
44
45    match query_bitmaps {
46        Ok(bitmaps) => Ok(bitmaps),
47        Err(e) => Err(CMDToolError {
48            details: format!("Error computing query bitmaps: {}", e),
49        }),
50    }
51}
52
53fn build_query_bitmaps<StorageProvider: StorageReadProvider + StorageWriteProvider>(
54    storage_provider: &StorageProvider,
55    query_num: usize,
56    filter_bitmap_file: Option<&str>,
57    base_file_labels: Option<&str>,
58    query_file_labels: Option<&str>,
59) -> CMDResult<Option<Vec<BitSet>>> {
60    // both base_file_labels and query_file_labels are provided or both are not provided
61    if !((base_file_labels.is_some() && query_file_labels.is_some())
62        || (base_file_labels.is_none() && query_file_labels.is_none()))
63    {
64        return Err(CMDToolError {
65            details: "Both base_file_labels and query_file_labels must be provided or both must be not provided.".to_string(),
66        });
67    }
68
69    if base_file_labels.is_some() && filter_bitmap_file.is_some() {
70        return Err(CMDToolError {
71            details: "Both base_file_labels and filter_bitmap_file cannot be provided.".to_string(),
72        });
73    }
74
75    let mut query_bitmaps: Option<Vec<BitSet>> = None;
76
77    if let (Some(base_file_labels), Some(query_file_labels)) = (base_file_labels, query_file_labels)
78    {
79        query_bitmaps = Some(read_labels_and_compute_bitmap(
80            base_file_labels,
81            query_file_labels,
82        )?);
83    }
84
85    // Load the filter bitmaps
86    let filter_bitmaps = match filter_bitmap_file {
87        Some(filter_bitmap_file) => {
88            let filters =
89                search_index_utils::load_vector_filters(storage_provider, filter_bitmap_file)?;
90
91            if filters.len() != query_num {
92                return Err(CMDToolError {
93                    details: format!(
94                        "Mismatch in query and filter bitmap sizes: {} filters for {} queries",
95                        filters.len(),
96                        query_num
97                    ),
98                });
99            }
100
101            Some(filters)
102        }
103        None => None,
104    };
105
106    if let Some(filters) = filter_bitmaps {
107        let mut bitmaps = vec![BitSet::new(); query_num];
108        for (idx_query, filter) in filters.iter().enumerate() {
109            for item in filter.iter() {
110                if let Ok(idx) = (*item).try_into() {
111                    bitmaps[idx_query].insert(idx);
112                }
113            }
114        }
115        query_bitmaps = Some(bitmaps)
116    }
117
118    Ok(query_bitmaps)
119}
120
121#[allow(clippy::too_many_arguments)]
122#[allow(clippy::panic)]
123/// Computes the true nearest neighbors for a set of queries and writes them to a file.
124///
125/// # Arguments
126///
127/// * `distance_function` - e.g. L2
128/// * `base_file` - The file containing the base vectors.
129/// * `query_file` - The file containing the query vectors.
130/// * `ground_truth_file` - The file to write the ground truth results to.
131/// * `recall_at` - The number of neighbors to compute for each query.
132/// * `insert_file` - Optional file containing more dataset vectors. This may be useful if you are testing recall for an index that has points dynamically inserted into it.
133/// * `skip_base` - Optional number of base points to skip. This is useful if you want to compute the ground truth for a set where the first skip_base points are deleted from the index.
134pub fn compute_ground_truth_from_datafiles<
135    V: VectorRepr,
136    A: Serialize + for<'de> Deserialize<'de> + Default + Copy,
137    StorageProvider: StorageReadProvider + StorageWriteProvider,
138>(
139    storage_provider: &StorageProvider,
140    distance_function: Metric,
141    base_file: &str,
142    query_file: &str,
143    ground_truth_file: &str,
144    filter_bitmap_file: Option<&str>,
145    recall_at: u32,
146    insert_file: Option<&str>,
147    skip_base: Option<usize>,
148    associated_data_file: Option<String>,
149    base_file_labels: Option<&str>,
150    query_file_labels: Option<&str>,
151) -> CMDResult<()> {
152    let dataset_iterator = VectorDataIterator::<StorageProvider, V, A>::new(
153        base_file,
154        associated_data_file.clone(),
155        storage_provider,
156    )?;
157
158    let insert_iterator = match insert_file {
159        Some(insert_file) => {
160            let i = VectorDataIterator::<StorageProvider, V, A>::new(
161                insert_file,
162                Option::None,
163                storage_provider,
164            )?;
165            Some(i)
166        }
167        None => None,
168    };
169
170    // Load the query file
171    let query_data = read_bin::<V>(&mut storage_provider.open_reader(query_file)?)?;
172    let query_num = query_data.nrows();
173    let has_filter_bitmap_file = filter_bitmap_file.is_some();
174    let has_query_bitmaps = base_file_labels.is_some() && query_file_labels.is_some();
175    let query_bitmaps = build_query_bitmaps(
176        storage_provider,
177        query_num,
178        filter_bitmap_file,
179        base_file_labels,
180        query_file_labels,
181    )?;
182
183    let ground_truth_result = compute_ground_truth_from_data::<V, A, StorageProvider>(
184        distance_function,
185        dataset_iterator,
186        &query_data,
187        recall_at,
188        insert_iterator,
189        skip_base,
190        query_bitmaps,
191    );
192    assert!(
193        &ground_truth_result.is_ok(),
194        "Ground-truth computation failed"
195    );
196    let (ground_truth, id_to_associated_data) = ground_truth_result?;
197
198    assert_ne!(ground_truth.len(), 0, "No ground-truth results computed");
199
200    if has_filter_bitmap_file || has_query_bitmaps {
201        let ground_truth_collection = ground_truth
202            .into_iter()
203            .map(|npq| npq.into_iter().collect())
204            .collect();
205        write_range_search_ground_truth(
206            storage_provider,
207            ground_truth_file,
208            query_num,
209            ground_truth_collection,
210        )
211    } else {
212        // Write results and return
213        let id_to_associated_data = associated_data_file.map(|_| id_to_associated_data);
214        write_ground_truth::<A>(
215            storage_provider,
216            ground_truth_file,
217            query_num,
218            recall_at as usize,
219            ground_truth,
220            id_to_associated_data,
221        )
222    }
223}
224
225#[allow(clippy::too_many_arguments)]
226#[allow(clippy::panic)]
227/// Computes range-search ground truth for a set of queries and writes it to a file.
228///
229/// # Arguments
230///
231/// * `distance_function` - e.g. L2
232/// * `base_file` - The file containing the base vectors.
233/// * `query_file` - The file containing the query vectors.
234/// * `ground_truth_file` - The file to write the range-search ground truth results to.
235/// * `radius` - Distance threshold in DiskANN score space (smaller is better; cosine uses `1 - cos`, inner product uses `-dot`).
236/// * `filter_bitmap_file` - Optional filter bitmap file in range-groundtruth format.
237/// * `base_file_labels` - Optional labels file for base vectors.
238/// * `query_file_labels` - Optional labels file for query vectors.
239pub fn compute_range_ground_truth_from_datafiles<
240    V: VectorRepr,
241    A: for<'de> Deserialize<'de> + Default,
242    StorageProvider: StorageReadProvider + StorageWriteProvider,
243>(
244    storage_provider: &StorageProvider,
245    distance_function: Metric,
246    base_file: &str,
247    query_file: &str,
248    ground_truth_file: &str,
249    radius: f32,
250    filter_bitmap_file: Option<&str>,
251    base_file_labels: Option<&str>,
252    query_file_labels: Option<&str>,
253) -> CMDResult<()> {
254    let dataset_iterator = VectorDataIterator::<StorageProvider, V, A>::new(
255        base_file,
256        Option::None,
257        storage_provider,
258    )?;
259
260    let query_data = read_bin::<V>(&mut storage_provider.open_reader(query_file)?)?;
261    let query_num = query_data.nrows();
262
263    let query_bitmaps = build_query_bitmaps(
264        storage_provider,
265        query_num,
266        filter_bitmap_file,
267        base_file_labels,
268        query_file_labels,
269    )?;
270
271    let ground_truth = compute_range_ground_truth_from_data::<V, A, StorageProvider>(
272        distance_function,
273        dataset_iterator,
274        &query_data,
275        radius,
276        query_bitmaps,
277    )?;
278
279    assert_ne!(ground_truth.len(), 0, "No ground-truth results computed");
280
281    write_range_search_ground_truth(storage_provider, ground_truth_file, query_num, ground_truth)
282}
283
284#[allow(clippy::too_many_arguments)]
285pub fn compute_range_ground_truth_from_data<V, A, VectorReader>(
286    distance_function: Metric,
287    dataset_iter: VectorDataIterator<VectorReader, V, A>,
288    queries: &Matrix<V>,
289    radius: f32,
290    query_bitmaps: Option<Vec<BitSet>>,
291) -> CMDResult<Vec<Vec<Neighbor<u32>>>>
292where
293    V: VectorRepr,
294    A: for<'de> Deserialize<'de> + Default,
295    VectorReader: StorageReadProvider,
296{
297    let query_num = queries.nrows();
298    let query_dim = queries.ncols();
299
300    let mut ground_truth: Vec<Vec<Neighbor<u32>>> = vec![Vec::new(); query_num];
301    let mut queries_and_result: Vec<_> = queries.row_iter().zip(ground_truth.iter_mut()).collect();
302
303    let distance_comparer = V::distance(distance_function, Some(query_dim));
304
305    let batch_size = 10_000;
306    let mut data_batch: Vec<Box<[V]>> = Vec::with_capacity(batch_size);
307
308    let pool = create_thread_pool(0)?;
309
310    let mut num_base_points: usize = 0;
311
312    for chunk in dataset_iter.chunks(batch_size).into_iter() {
313        data_batch.clear();
314        for (data_vector, _associated_data) in chunk {
315            data_batch.push(data_vector);
316        }
317        let points = data_batch.len();
318
319        if points == 0 {
320            continue;
321        }
322
323        queries_and_result
324            .par_iter_mut()
325            .enumerate()
326            .for_each_in_pool(pool.as_ref(), |(idx_query, (query, query_results))| {
327                for (idx_in_batch, data) in data_batch.iter().enumerate() {
328                    let idx = (num_base_points + idx_in_batch) as u32;
329
330                    let allowed_by_bitmap = if let Some(ref bitmaps) = query_bitmaps {
331                        if let Ok(idx_usize) = idx.try_into() {
332                            bitmaps[idx_query].contains(idx_usize)
333                        } else {
334                            false
335                        }
336                    } else {
337                        true
338                    };
339
340                    if allowed_by_bitmap {
341                        let distance = distance_comparer.evaluate_similarity(data, query);
342                        if distance <= radius {
343                            query_results.push(Neighbor { id: idx, distance });
344                        }
345                    }
346                }
347            });
348
349        num_base_points += points;
350    }
351
352    Ok(ground_truth)
353}
354
355#[derive(Debug, Clone)]
356pub enum MultivecAggregationMethod {
357    AveragePairwise,
358    MinPairwise,
359    AvgofMins,
360}
361
362#[derive(Debug)]
363pub enum ParseAggrError {
364    InvalidFormat(String),
365}
366
367impl std::fmt::Display for ParseAggrError {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        match self {
370            Self::InvalidFormat(str) => write!(f, "Invalid format for Aggregation Method: {}", str),
371        }
372    }
373}
374
375impl std::error::Error for ParseAggrError {}
376
377impl FromStr for MultivecAggregationMethod {
378    type Err = ParseAggrError;
379
380    fn from_str(s: &str) -> Result<Self, Self::Err> {
381        match s.to_lowercase().as_str() {
382            "average_pairwise" => Ok(MultivecAggregationMethod::AveragePairwise),
383            "min_pairwise" => Ok(MultivecAggregationMethod::MinPairwise),
384            "avg_of_mins" => Ok(MultivecAggregationMethod::AvgofMins),
385            _ => Err(ParseAggrError::InvalidFormat(String::from(s))),
386        }
387    }
388}
389
390#[allow(clippy::too_many_arguments)]
391#[allow(clippy::panic)]
392/// Computes the true nearest neighbors for a set of queries and writes them to a file.
393///
394/// # Arguments
395///
396/// * `distance_function` - e.g. L2
397/// * `aggregation_method` - e.g. Average or Min
398/// * `base_file` - The file containing the base vectors.
399/// * `query_file` - The file containing the query vectors.
400/// * `ground_truth_file` - The file to write the ground truth results to.
401/// * `recall_at` - The number of neighbors to compute for each query.
402/// * `base_file_labels` - Optional labels file for the base vectors to filter which base vectors to consider per query.
403/// * `query_file_labels` - Optional labels file for the query vectors to filter which base vectors to consider per query.
404pub fn compute_multivec_ground_truth_from_datafiles<
405    V: VectorRepr,
406    StorageProvider: StorageReadProvider + StorageWriteProvider,
407>(
408    storage_provider: &StorageProvider,
409    distance_function: Metric,
410    aggregation_method: MultivecAggregationMethod,
411    base_file: &str,
412    query_file: &str,
413    ground_truth_file: &str,
414    recall_at: u32,
415    base_file_labels: Option<&str>,
416    query_file_labels: Option<&str>,
417) -> CMDResult<()> {
418    let (base_vectors, _, _, _) =
419        file_util::load_multivec_bin::<V, StorageProvider>(storage_provider, base_file)?;
420
421    let (query_vectors, query_num, query_dim, _) =
422        file_util::load_multivec_bin::<V, StorageProvider>(storage_provider, query_file)?;
423
424    // both base_file_labels and query_file_labels are provided or both are not provided
425    if !((base_file_labels.is_some() && query_file_labels.is_some())
426        || (base_file_labels.is_none() && query_file_labels.is_none()))
427    {
428        return Err(CMDToolError {
429            details: "Both base_file_labels and query_file_labels must be provided or both must be not provided.".to_string(),
430        });
431    }
432
433    let mut query_bitmaps: Option<Vec<BitSet>> = None;
434    if let (Some(base_file_labels), Some(query_file_labels)) = (base_file_labels, query_file_labels)
435    {
436        query_bitmaps = Some(read_labels_and_compute_bitmap(
437            base_file_labels,
438            query_file_labels,
439        )?);
440    }
441
442    let has_query_bitmaps = query_bitmaps.is_some();
443
444    let ground_truth = compute_multivec_ground_truth_from_data::<V>(
445        distance_function,
446        aggregation_method,
447        base_vectors,
448        query_vectors,
449        query_dim,
450        recall_at,
451        query_bitmaps,
452    )?;
453
454    if has_query_bitmaps {
455        let ground_truth_collection = ground_truth
456            .into_iter()
457            .map(|npq| npq.into_iter().collect())
458            .collect();
459        write_range_search_ground_truth(
460            storage_provider,
461            ground_truth_file,
462            query_num,
463            ground_truth_collection,
464        )
465    } else {
466        // Write results and return
467        write_ground_truth::<()>(
468            storage_provider,
469            ground_truth_file,
470            query_num,
471            recall_at as usize,
472            ground_truth,
473            Option::None,
474        )
475    }
476}
477
478fn write_range_search_ground_truth<StorageProvider: StorageReadProvider + StorageWriteProvider>(
479    storage_provider: &StorageProvider,
480    ground_truth_file: &str,
481    number_of_queries: usize,
482    ground_truth: Vec<Vec<Neighbor<u32>>>,
483) -> CMDResult<()> {
484    let mut file = storage_provider.create_for_write(ground_truth_file)?;
485
486    let queue_sizes: Vec<u32> = ground_truth
487        .iter()
488        .map(|queue| queue.len() as u32)
489        .collect();
490    let total_number_of_neighbors: usize = queue_sizes.iter().sum::<u32>() as usize;
491
492    // Metadata
493    Metadata::new(number_of_queries, total_number_of_neighbors)?.write(&mut file)?;
494
495    // Write queue sizes array.
496    let mut queue_sizes_buffer = vec![0; queue_sizes.len() * size_of::<u32>()];
497    queue_sizes_buffer.clone_from_slice(cast_slice::<u32, u8>(&queue_sizes));
498    file.write_all(&queue_sizes_buffer)?;
499
500    let mut neighbor_ids: Vec<u32> = Vec::with_capacity(total_number_of_neighbors);
501
502    // Write the neighbor IDs array.
503    for query_neighbors in ground_truth {
504        for neighbor in query_neighbors.iter() {
505            neighbor_ids.push(neighbor.id);
506        }
507    }
508
509    // Write neighbor IDs
510    let mut id_buffer = vec![0; total_number_of_neighbors * size_of::<u32>()];
511    id_buffer.clone_from_slice(cast_slice::<u32, u8>(&neighbor_ids));
512    file.write_all(&id_buffer)?;
513
514    // Make sure everything is written to disk
515    file.flush()?;
516
517    Ok(())
518}
519
520/// Writes out a ground truth file.  ground_truth is a vector of NeighborPriorityQueue objects
521/// where the order of queue objects corresponds to the order of queries used to compute this
522/// ground truth.
523fn write_ground_truth<A: Serialize + Copy>(
524    storage_provider: &impl StorageWriteProvider,
525    ground_truth_file: &str,
526    number_of_queries: usize,
527    number_of_neighbors: usize,
528    ground_truth: Vec<NeighborPriorityQueue<u32>>,
529    id_to_associated_data: Option<Vec<A>>,
530) -> CMDResult<()> {
531    let mut file = storage_provider.create_for_write(ground_truth_file)?;
532
533    Metadata::new(number_of_queries, number_of_neighbors)?.write(&mut file)?;
534
535    let mut gt_ids: Vec<u32> = Vec::with_capacity(number_of_neighbors * number_of_queries);
536    let mut gt_distances: Vec<f32> = Vec::with_capacity(number_of_neighbors * number_of_queries);
537
538    // In the file, we write the neighbor IDs array first, then write the distances array.
539    for mut query_neighbors in ground_truth {
540        while let Some(closest_node) = query_neighbors.closest_notvisited() {
541            gt_ids.push(closest_node.id);
542            gt_distances.push(closest_node.distance);
543        }
544    }
545
546    // Write neighbor IDs or Associated Data
547    if let Some(id_to_associated_data) = id_to_associated_data {
548        let mut associated_data_buffer = Vec::<u8>::new();
549        for id in gt_ids {
550            let associated_data = id_to_associated_data[id as usize];
551            let serialized_associated_data =
552                bincode::serialize(&associated_data).map_err(|e| CMDToolError {
553                    details: format!("Failed to serialize associated data: {}", e),
554                })?;
555            associated_data_buffer.extend_from_slice(serialized_associated_data.as_slice());
556        }
557        file.write_all(&associated_data_buffer)?;
558    } else {
559        let mut id_buffer = vec![0; number_of_queries * number_of_neighbors * size_of::<u32>()];
560        id_buffer.clone_from_slice(cast_slice::<u32, u8>(&gt_ids));
561        file.write_all(&id_buffer)?;
562    }
563
564    // Write neighbor distances
565    let mut distance_buffer = vec![0; number_of_queries * number_of_neighbors * size_of::<f32>()];
566    distance_buffer.clone_from_slice(cast_slice::<f32, u8>(&gt_distances));
567    file.write_all(&distance_buffer)?;
568
569    // Make sure everything is written to disk
570    file.flush()?;
571
572    Ok(())
573}
574
575type Npq = Vec<NeighborPriorityQueue<u32>>;
576/// Computes the true nearest neighbors for a set of queries and dataset iterators
577///
578/// # Arguments
579///
580/// * `distance_function` - e.g. L2
581/// * `dataset_iter` - The iterator over the dataset vectors and associated data.
582/// * `queries` - Query vectors as a row-major `Matrix` of shape `num_queries × query_dim`.
583///   `query_dim` is inferred from `queries.ncols()`.
584/// * `recall_at` - The number of neighbors to compute for each query.
585/// * `insert_iter` - Optional iterator containing more dataset vectors. This may be useful if you are testing recall for an index that has points dynamically inserted into it.
586/// * `skip_base` - Optional number of base points to skip. This is useful if you want to compute the ground truth for a set where the first skip_base points are deleted from the index.
587/// * `query_bitmaps` - Optional per-query bitmaps restricting which base point ids contribute to that query's neighbors.
588#[allow(clippy::too_many_arguments)]
589pub fn compute_ground_truth_from_data<V, A, VectorReader>(
590    distance_function: Metric,
591    dataset_iter: VectorDataIterator<VectorReader, V, A>,
592    queries: &Matrix<V>,
593    recall_at: u32,
594    insert_iter: Option<VectorDataIterator<VectorReader, V, A>>,
595    skip_base: Option<usize>,
596    query_bitmaps: Option<Vec<BitSet>>,
597) -> CMDResult<(Npq, Vec<A>)>
598where
599    V: VectorRepr,
600    A: for<'de> Deserialize<'de> + Default,
601    VectorReader: StorageReadProvider,
602{
603    let query_num = queries.nrows();
604    let query_dim = queries.ncols();
605
606    let mut neighbor_queues: Vec<NeighborPriorityQueue<u32>> = (0..query_num)
607        .map(|_| NeighborPriorityQueue::new(recall_at as usize))
608        .collect();
609    let mut queries_and_neighbor_queue: Vec<_> =
610        queries.row_iter().zip(neighbor_queues.iter_mut()).collect();
611
612    let distance_comparer = V::distance(distance_function, Some(query_dim));
613
614    let batch_size = 10_000;
615    let mut data_batch: Vec<Box<[V]>> = Vec::with_capacity(batch_size);
616
617    let pool = create_thread_pool(0)?;
618
619    let mut num_base_points: usize = 0;
620    let mut id_to_associated_data = Vec::<A>::new();
621    let skip_base = skip_base.unwrap_or(0);
622    // Loop over all the raw data
623    for chunk in dataset_iter.skip(skip_base).chunks(batch_size).into_iter() {
624        data_batch.clear();
625        for (data_vector, associated_data) in chunk {
626            data_batch.push(data_vector);
627            id_to_associated_data.push(associated_data);
628        }
629        let points = data_batch.len();
630
631        if points == 0 {
632            continue;
633        }
634
635        // For each node in the raw data, calculate the distance to each query vector and store it in the priority queue for that query.  This will find the closest N neighbors for each query.
636        queries_and_neighbor_queue
637            .par_iter_mut()
638            .enumerate()
639            .for_each_in_pool(
640                pool.as_ref(),
641                |(idx_query, (query, ref mut neighbor_queue))| {
642                    for (idx_in_batch, data) in data_batch.iter().enumerate() {
643                        let idx = (num_base_points + idx_in_batch) as u32;
644
645                        let allowed_by_bitmap = if let Some(ref bitmaps) = query_bitmaps {
646                            if let Ok(idx_usize) = idx.try_into() {
647                                bitmaps[idx_query].contains(idx_usize)
648                            } else {
649                                false
650                            }
651                        } else {
652                            true
653                        };
654
655                        if allowed_by_bitmap {
656                            let distance = distance_comparer.evaluate_similarity(data, query);
657                            neighbor_queue.insert(Neighbor { id: idx, distance });
658                        }
659                    }
660                },
661            );
662
663        num_base_points += points;
664    }
665
666    if let Some(insert_iter) = insert_iter {
667        for (insert_idx, (data_vector, _associated_data)) in insert_iter.enumerate() {
668            // For each node in the raw data, calculate the distance to each query vector and store it in the priority queue for that query.  This will find the closest N neighbors for each query.
669            for (idx_query, (query, ref mut neighbor_queue)) in
670                queries_and_neighbor_queue.iter_mut().enumerate()
671            {
672                let idx = (num_base_points + insert_idx) as u32;
673
674                let allowed_by_bitmap = if let Some(ref bitmaps) = query_bitmaps {
675                    if let Ok(idx_usize) = idx.try_into() {
676                        bitmaps[idx_query].contains(idx_usize)
677                    } else {
678                        false
679                    }
680                } else {
681                    true
682                };
683
684                if allowed_by_bitmap {
685                    let distance = distance_comparer.evaluate_similarity(&data_vector, query);
686                    neighbor_queue.insert(Neighbor { id: idx, distance })
687                }
688            }
689        }
690    }
691
692    Ok((neighbor_queues, id_to_associated_data))
693}
694
695#[allow(clippy::too_many_arguments)]
696pub fn compute_multivec_ground_truth_from_data<T>(
697    distance_function: Metric,
698    aggregation_method: MultivecAggregationMethod,
699    base_vectors: Vec<Matrix<T>>,
700    queries: Vec<Matrix<T>>,
701    query_dim: usize,
702    recall_at: u32,
703    query_bitmaps: Option<Vec<BitSet>>,
704) -> CMDResult<Vec<NeighborPriorityQueue<u32>>>
705where
706    T: VectorRepr,
707{
708    let query_num = queries.len();
709
710    let mut neighbor_queues: Vec<NeighborPriorityQueue<u32>> = Vec::with_capacity(query_num);
711    //
712    for _ in 0..query_num {
713        neighbor_queues.push(NeighborPriorityQueue::new(recall_at as usize));
714    }
715    let mut query_multivecs_and_neighbor_queue: Vec<_> =
716        queries.iter().zip(neighbor_queues.iter_mut()).collect();
717
718    let distance_comparer = T::distance(distance_function, Some(query_dim));
719
720    let pool = create_thread_pool(0)?;
721
722    // for each query multivec, compute chamfer distance in parallel
723
724    query_multivecs_and_neighbor_queue
725        .par_iter_mut()
726        .enumerate()
727        .for_each_in_pool(
728            pool.as_ref(),
729            |(query_idx, (query_multivec, neighbor_queue))| {
730                for (idx_base, base_multivec) in base_vectors.iter().enumerate() {
731                    // check if calculation is allowed by bitmap if present
732                    let allowed_by_bitmap = if let Some(ref bitmaps) = query_bitmaps {
733                        bitmaps[query_idx].contains(idx_base)
734                    } else {
735                        true
736                    };
737
738                    if allowed_by_bitmap {
739                        // compute distance between query_multivec and base_multivec
740                        let distance = match aggregation_method {
741                            MultivecAggregationMethod::AveragePairwise => {
742                                let mut total_distance = 0.0;
743                                for query_vec in query_multivec.row_iter() {
744                                    for base_vec in base_multivec.row_iter() {
745                                        let dist = distance_comparer
746                                            .evaluate_similarity(query_vec, base_vec);
747                                        total_distance += dist;
748                                    }
749                                }
750                                total_distance
751                                    / (query_multivec.nrows() * base_multivec.nrows()) as f32
752                            }
753                            MultivecAggregationMethod::MinPairwise => {
754                                let mut min_distance = f32::MAX;
755                                for query_vec in query_multivec.row_iter() {
756                                    for base_vec in base_multivec.row_iter() {
757                                        let dist = distance_comparer
758                                            .evaluate_similarity(query_vec, base_vec);
759                                        min_distance = min_distance.min(dist);
760                                    }
761                                }
762                                min_distance
763                            }
764                            MultivecAggregationMethod::AvgofMins => {
765                                let mut distance = 0_f32;
766                                for query_vec in query_multivec.row_iter() {
767                                    let mut local_min = f32::MAX;
768                                    for base_vec in base_multivec.row_iter() {
769                                        let dist = distance_comparer
770                                            .evaluate_similarity(query_vec, base_vec);
771                                        local_min = local_min.min(dist);
772                                    }
773                                    distance += local_min;
774                                }
775                                distance / query_multivec.nrows() as f32
776                            }
777                        };
778                        // insert into neighbor queue
779                        let idx = idx_base as u32;
780                        neighbor_queue.insert(Neighbor { id: idx, distance });
781                    }
782                }
783            },
784        );
785
786    Ok(neighbor_queues)
787}