1use 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 let base_labels = read_baselabels(base_label_filename)?;
38
39 let parsed_queries = read_and_parse_queries(query_label_filename)?;
41
42 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 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 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)]
123pub 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 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 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)]
227pub 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)]
392pub 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 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_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::new(number_of_queries, total_number_of_neighbors)?.write(&mut file)?;
494
495 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 for query_neighbors in ground_truth {
504 for neighbor in query_neighbors.iter() {
505 neighbor_ids.push(neighbor.id);
506 }
507 }
508
509 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 file.flush()?;
516
517 Ok(())
518}
519
520fn 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 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 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>(>_ids));
561 file.write_all(&id_buffer)?;
562 }
563
564 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>(>_distances));
567 file.write_all(&distance_buffer)?;
568
569 file.flush()?;
571
572 Ok(())
573}
574
575type Npq = Vec<NeighborPriorityQueue<u32>>;
576#[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 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 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 (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 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 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 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 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 let idx = idx_base as u32;
780 neighbor_queue.insert(Neighbor { id: idx, distance });
781 }
782 }
783 },
784 );
785
786 Ok(neighbor_queues)
787}