1use std::collections::{BinaryHeap, HashMap};
8use std::sync::Arc;
9
10use arrow::array::AsArray;
11use arrow_array::{Array, ArrayRef, Float32Array, RecordBatch, UInt64Array};
12use arrow_schema::{DataType, Field, Schema, SchemaRef};
13use lance_core::deepsize::DeepSizeOf;
14use lance_core::{Error, ROW_ID_FIELD, Result};
15use lance_file::previous::reader::FileReader as PreviousFileReader;
16use lance_linalg::distance::DistanceType;
17use serde::{Deserialize, Serialize};
18
19use crate::{
20 metrics::MetricsCollector,
21 prefilter::PreFilter,
22 vector::{
23 ApproxMode, DIST_COL, Query,
24 graph::{OrderedFloat, OrderedNode},
25 quantizer::{Quantization, QuantizationType, Quantizer, QuantizerMetadata},
26 storage::{
27 DistCalculator, DistanceCalculatorOptions, QueryResidual, QueryScratch, VectorStore,
28 },
29 v3::subindex::IvfSubIndex,
30 },
31};
32
33use super::storage::{FLAT_COLUMN, FlatBinStorage, FlatFloatStorage};
34
35#[inline(always)]
36fn push_candidate_local(
37 res: &mut BinaryHeap<OrderedNode<u64>>,
38 k: usize,
39 row_id: u64,
40 dist: OrderedFloat,
41) {
42 if k == 0 {
43 return;
44 }
45 if res.len() < k {
46 res.push(OrderedNode::new(row_id, dist));
47 } else if res.peek().is_some_and(|node| node.dist > dist) {
48 res.pop();
49 res.push(OrderedNode::new(row_id, dist));
50 }
51}
52
53#[derive(Debug, Clone, Default, DeepSizeOf)]
56pub struct FlatIndex {}
57
58use std::sync::LazyLock;
59
60static ANN_SEARCH_SCHEMA: LazyLock<SchemaRef> = LazyLock::new(|| {
61 Schema::new(vec![
62 Field::new(DIST_COL, DataType::Float32, true),
63 ROW_ID_FIELD.clone(),
64 ])
65 .into()
66});
67
68#[derive(Default)]
69pub struct FlatQueryParams {
70 lower_bound: Option<f32>,
71 upper_bound: Option<f32>,
72 dist_q_c: f32,
73 approx_mode: ApproxMode,
74}
75
76impl From<&Query> for FlatQueryParams {
77 fn from(q: &Query) -> Self {
78 Self {
79 lower_bound: q.lower_bound,
80 upper_bound: q.upper_bound,
81 dist_q_c: q.dist_q_c,
82 approx_mode: q.approx_mode,
83 }
84 }
85}
86
87impl IvfSubIndex for FlatIndex {
88 type QueryParams = FlatQueryParams;
89 type BuildParams = ();
90
91 fn name() -> &'static str {
92 "FLAT"
93 }
94
95 fn metadata_key() -> &'static str {
96 "lance:flat"
97 }
98
99 fn schema() -> arrow_schema::SchemaRef {
100 Schema::new(vec![Field::new("__flat_marker", DataType::UInt64, false)]).into()
101 }
102
103 fn search(
104 &self,
105 query: ArrayRef,
106 k: usize,
107 params: Self::QueryParams,
108 storage: &impl VectorStore,
109 prefilter: Arc<dyn PreFilter>,
110 metrics: &dyn MetricsCollector,
111 ) -> Result<RecordBatch> {
112 let mut scratch = QueryScratch::new();
113 self.search_with_scratch(
114 query,
115 k,
116 params,
117 storage,
118 prefilter,
119 metrics,
120 None,
121 &mut scratch,
122 )
123 }
124
125 fn search_with_scratch(
126 &self,
127 query: ArrayRef,
128 k: usize,
129 params: Self::QueryParams,
130 storage: &impl VectorStore,
131 prefilter: Arc<dyn PreFilter>,
132 metrics: &dyn MetricsCollector,
133 residual: Option<QueryResidual<'_>>,
134 scratch: &mut QueryScratch,
135 ) -> Result<RecordBatch> {
136 let is_range_query = params.lower_bound.is_some() || params.upper_bound.is_some();
137 let row_ids = storage.row_ids();
138 let dist_calc = storage.dist_calculator_with_scratch(
139 query,
140 params.dist_q_c,
141 residual,
142 &mut scratch.query_f32,
143 DistanceCalculatorOptions {
144 approx_mode: params.approx_mode,
145 },
146 );
147 let mut res = BinaryHeap::with_capacity(k);
148 metrics.record_comparisons(storage.len());
149
150 match prefilter.is_empty() {
151 true => {
152 dist_calc.distance_all_with_scratch(
153 k,
154 &mut scratch.distances,
155 &mut scratch.u16,
156 &mut scratch.u8,
157 &mut scratch.u32,
158 );
159 let dists = scratch.distances.iter().copied();
160
161 if is_range_query {
162 let lower_bound = params.lower_bound.unwrap_or(f32::MIN).into();
163 let upper_bound = params.upper_bound.unwrap_or(f32::MAX).into();
164
165 for (&row_id, dist) in row_ids.zip(dists) {
166 let dist = dist.into();
167 if dist < lower_bound || dist >= upper_bound {
168 continue;
169 }
170 push_candidate_local(&mut res, k, row_id, dist);
171 }
172 } else {
173 for (&row_id, dist) in row_ids.zip(dists) {
174 let dist = dist.into();
175 push_candidate_local(&mut res, k, row_id, dist);
176 }
177 }
178 }
179 false => {
180 let row_addr_mask = prefilter.mask();
181 if is_range_query {
182 let lower_bound = params.lower_bound.unwrap_or(f32::MIN).into();
183 let upper_bound = params.upper_bound.unwrap_or(f32::MAX).into();
184 for (id, &row_addr) in row_ids.enumerate() {
185 if !row_addr_mask.selected(row_addr) {
186 continue;
187 }
188 let dist = dist_calc.distance(id as u32).into();
189 if dist < lower_bound || dist >= upper_bound {
190 continue;
191 }
192
193 push_candidate_local(&mut res, k, row_addr, dist);
194 }
195 } else {
196 for (id, &row_addr) in row_ids.enumerate() {
197 if !row_addr_mask.selected(row_addr) {
198 continue;
199 }
200
201 let dist = dist_calc.distance(id as u32).into();
202 push_candidate_local(&mut res, k, row_addr, dist);
203 }
204 }
205 }
206 };
207
208 let (row_ids, dists): (Vec<_>, Vec<_>) = res.into_iter().map(|r| (r.id, r.dist.0)).unzip();
211 let (row_ids, dists) = (UInt64Array::from(row_ids), Float32Array::from(dists));
212
213 Ok(RecordBatch::try_new(
214 ANN_SEARCH_SCHEMA.clone(),
215 vec![Arc::new(dists), Arc::new(row_ids)],
216 )?)
217 }
218
219 fn supports_global_topk_heap() -> bool {
220 true
221 }
222
223 fn accumulate_topk(
224 &self,
225 query: ArrayRef,
226 k: usize,
227 params: Self::QueryParams,
228 storage: &impl VectorStore,
229 prefilter: Arc<dyn PreFilter>,
230 res: &mut BinaryHeap<OrderedNode<u64>>,
231 metrics: &dyn MetricsCollector,
232 ) -> Result<()> {
233 let mut scratch = QueryScratch::new();
234 self.accumulate_topk_with_scratch(
235 query,
236 k,
237 params,
238 storage,
239 prefilter,
240 res,
241 None,
242 &mut scratch,
243 metrics,
244 )
245 }
246
247 fn accumulate_topk_with_scratch(
248 &self,
249 query: ArrayRef,
250 k: usize,
251 params: Self::QueryParams,
252 storage: &impl VectorStore,
253 prefilter: Arc<dyn PreFilter>,
254 res: &mut BinaryHeap<OrderedNode<u64>>,
255 residual: Option<QueryResidual<'_>>,
256 scratch: &mut QueryScratch,
257 metrics: &dyn MetricsCollector,
258 ) -> Result<()> {
259 let row_ids = storage.row_ids();
260 let dist_calc = storage.dist_calculator_with_scratch(
261 query,
262 params.dist_q_c,
263 residual,
264 &mut scratch.query_f32,
265 DistanceCalculatorOptions {
266 approx_mode: params.approx_mode,
267 },
268 );
269 metrics.record_comparisons(storage.len());
270
271 match prefilter.is_empty() {
272 true => {
273 dist_calc.accumulate_topk_with_scratch(
274 k,
275 params.lower_bound,
276 params.upper_bound,
277 |id| storage.row_id(id),
278 res,
279 &mut scratch.distances,
280 &mut scratch.u16,
281 &mut scratch.u8,
282 &mut scratch.u32,
283 );
284 }
285 false => {
286 let row_addr_mask = prefilter.mask();
287 dist_calc.accumulate_filtered_topk_with_scratch(
288 k,
289 params.lower_bound,
290 params.upper_bound,
291 row_ids.enumerate().map(|(id, &row_id)| (id as u32, row_id)),
292 |row_id| row_addr_mask.selected(row_id),
293 res,
294 &mut scratch.distances,
295 &mut scratch.u16,
296 &mut scratch.u8,
297 &mut scratch.u32,
298 );
299 }
300 };
301 Ok(())
302 }
303
304 fn load(_: RecordBatch) -> Result<Self> {
305 Ok(Self {})
306 }
307
308 fn index_vectors(_: &impl VectorStore, _: Self::BuildParams) -> Result<Self>
309 where
310 Self: Sized,
311 {
312 Ok(Self {})
313 }
314
315 fn remap(&self, _: &HashMap<u64, Option<u64>>, _: &impl VectorStore) -> Result<Self> {
316 Ok(self.clone())
317 }
318
319 fn to_batch(&self) -> Result<RecordBatch> {
320 Ok(RecordBatch::new_empty(Schema::empty().into()))
321 }
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, DeepSizeOf)]
325pub struct FlatMetadata {
326 pub dim: usize,
327}
328
329#[async_trait::async_trait]
330impl QuantizerMetadata for FlatMetadata {
331 async fn load(_: &PreviousFileReader) -> Result<Self> {
332 unimplemented!("Flat will be used in new index builder which doesn't require this")
333 }
334}
335
336#[derive(Debug, Clone, DeepSizeOf)]
337pub struct FlatQuantizer {
338 dim: usize,
339 distance_type: DistanceType,
340}
341
342impl FlatQuantizer {
343 pub fn new(dim: usize, distance_type: DistanceType) -> Self {
344 Self { dim, distance_type }
345 }
346}
347
348impl Quantization for FlatQuantizer {
349 type BuildParams = ();
350 type Metadata = FlatMetadata;
351 type Storage = FlatFloatStorage;
352
353 fn build(data: &dyn Array, distance_type: DistanceType, _: &Self::BuildParams) -> Result<Self> {
354 let dim = data.as_fixed_size_list().value_length();
355 Ok(Self::new(dim as usize, distance_type))
356 }
357
358 fn retrain(&mut self, _: &dyn Array) -> Result<()> {
359 Ok(())
360 }
361
362 fn code_dim(&self) -> usize {
363 self.dim
364 }
365
366 fn column(&self) -> &'static str {
367 FLAT_COLUMN
368 }
369
370 fn from_metadata(metadata: &Self::Metadata, distance_type: DistanceType) -> Result<Quantizer> {
371 Ok(Quantizer::Flat(Self {
372 dim: metadata.dim,
373 distance_type,
374 }))
375 }
376
377 fn metadata(&self, _: Option<crate::vector::quantizer::QuantizationMetadata>) -> FlatMetadata {
378 FlatMetadata { dim: self.dim }
379 }
380
381 fn metadata_key() -> &'static str {
382 "flat"
383 }
384
385 fn quantization_type() -> QuantizationType {
386 QuantizationType::Flat
387 }
388
389 fn quantize(&self, vectors: &dyn Array) -> Result<ArrayRef> {
390 Ok(vectors.slice(0, vectors.len()))
391 }
392
393 fn field(&self) -> Field {
394 Field::new(
395 FLAT_COLUMN,
396 DataType::FixedSizeList(
397 Arc::new(Field::new("item", DataType::Float32, true)),
398 self.dim as i32,
399 ),
400 true,
401 )
402 }
403}
404
405impl From<FlatQuantizer> for Quantizer {
406 fn from(value: FlatQuantizer) -> Self {
407 Self::Flat(value)
408 }
409}
410
411impl TryFrom<Quantizer> for FlatQuantizer {
412 type Error = Error;
413
414 fn try_from(value: Quantizer) -> Result<Self> {
415 match value {
416 Quantizer::Flat(quantizer) => Ok(quantizer),
417 _ => Err(Error::invalid_input("quantizer is not FlatQuantizer")),
418 }
419 }
420}
421
422#[derive(Debug, Clone, DeepSizeOf)]
423pub struct FlatBinQuantizer {
424 dim: usize,
425 distance_type: DistanceType,
426}
427
428impl FlatBinQuantizer {
429 pub fn new(dim: usize, distance_type: DistanceType) -> Self {
430 Self { dim, distance_type }
431 }
432}
433
434impl Quantization for FlatBinQuantizer {
435 type BuildParams = ();
436 type Metadata = FlatMetadata;
437 type Storage = FlatBinStorage;
438
439 fn build(data: &dyn Array, distance_type: DistanceType, _: &Self::BuildParams) -> Result<Self> {
440 let dim = data.as_fixed_size_list().value_length();
441 Ok(Self::new(dim as usize, distance_type))
442 }
443
444 fn retrain(&mut self, _: &dyn Array) -> Result<()> {
445 Ok(())
446 }
447
448 fn code_dim(&self) -> usize {
449 self.dim
450 }
451
452 fn column(&self) -> &'static str {
453 FLAT_COLUMN
454 }
455
456 fn from_metadata(metadata: &Self::Metadata, distance_type: DistanceType) -> Result<Quantizer> {
457 Ok(Quantizer::FlatBin(Self {
458 dim: metadata.dim,
459 distance_type,
460 }))
461 }
462
463 fn metadata(&self, _: Option<crate::vector::quantizer::QuantizationMetadata>) -> FlatMetadata {
464 FlatMetadata { dim: self.dim }
465 }
466
467 fn metadata_key() -> &'static str {
468 "flat"
469 }
470
471 fn quantization_type() -> QuantizationType {
472 QuantizationType::FlatBin
473 }
474
475 fn quantize(&self, vectors: &dyn Array) -> Result<ArrayRef> {
476 Ok(vectors.slice(0, vectors.len()))
477 }
478
479 fn field(&self) -> Field {
480 Field::new(
481 FLAT_COLUMN,
482 DataType::FixedSizeList(
483 Arc::new(Field::new("item", DataType::UInt8, true)),
484 self.dim as i32,
485 ),
486 true,
487 )
488 }
489}
490
491impl From<FlatBinQuantizer> for Quantizer {
492 fn from(value: FlatBinQuantizer) -> Self {
493 Self::FlatBin(value)
494 }
495}
496
497impl TryFrom<Quantizer> for FlatBinQuantizer {
498 type Error = Error;
499
500 fn try_from(value: Quantizer) -> Result<Self> {
501 match value {
502 Quantizer::FlatBin(quantizer) => Ok(quantizer),
503 _ => Err(Error::invalid_input("quantizer is not FlatBinQuantizer")),
504 }
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 use arrow_array::FixedSizeListArray;
513 use async_trait::async_trait;
514 use lance_arrow::FixedSizeListArrayExt;
515 use lance_select::{RowAddrMask, RowAddrTreeMap};
516
517 use crate::metrics::NoOpMetricsCollector;
518 use crate::prefilter::NoFilter;
519
520 struct MaskPreFilter {
521 mask: Arc<RowAddrMask>,
522 }
523
524 #[async_trait]
525 impl PreFilter for MaskPreFilter {
526 async fn wait_for_ready(&self) -> Result<()> {
527 Ok(())
528 }
529
530 fn is_empty(&self) -> bool {
531 false
532 }
533
534 fn mask(&self) -> Arc<RowAddrMask> {
535 self.mask.clone()
536 }
537
538 fn filter_row_ids<'a>(&self, row_ids: Box<dyn Iterator<Item = &'a u64> + 'a>) -> Vec<u64> {
539 self.mask.selected_indices(row_ids)
540 }
541 }
542
543 fn test_storage() -> FlatFloatStorage {
544 let values = Float32Array::from(vec![
545 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 3.0, 3.0, 4.0, 4.0, ]);
551 let vectors = FixedSizeListArray::try_new_from_values(values, 2).unwrap();
552 FlatFloatStorage::new(vectors, DistanceType::L2)
553 }
554
555 fn query() -> ArrayRef {
556 Arc::new(Float32Array::from(vec![1.0, 1.0]))
557 }
558
559 fn batch_results(batch: RecordBatch) -> Vec<(u64, f32)> {
560 let dists = batch
561 .column(0)
562 .as_primitive::<arrow_array::types::Float32Type>();
563 let row_ids = batch
564 .column(1)
565 .as_primitive::<arrow_array::types::UInt64Type>();
566 let mut results = row_ids
567 .values()
568 .iter()
569 .zip(dists.values().iter())
570 .map(|(row_id, dist)| (*row_id, *dist))
571 .collect::<Vec<_>>();
572 results.sort_by(|left, right| left.0.cmp(&right.0));
573 results
574 }
575
576 fn heap_results(heap: BinaryHeap<OrderedNode<u64>>) -> Vec<(u64, f32)> {
577 let mut results = heap
578 .into_iter()
579 .map(|node| (node.id, node.dist.0))
580 .collect::<Vec<_>>();
581 results.sort_by(|left, right| left.0.cmp(&right.0));
582 results
583 }
584
585 #[test]
586 fn test_flat_search_matches_accumulate_topk_without_prefilter() {
587 let index = FlatIndex::default();
588 let storage = test_storage();
589 let k = 3;
590 let search_results = batch_results(
591 index
592 .search(
593 query(),
594 k,
595 FlatQueryParams::default(),
596 &storage,
597 Arc::new(NoFilter),
598 &NoOpMetricsCollector,
599 )
600 .unwrap(),
601 );
602
603 let mut heap = BinaryHeap::with_capacity(k);
604 index
605 .accumulate_topk(
606 query(),
607 k,
608 FlatQueryParams::default(),
609 &storage,
610 Arc::new(NoFilter),
611 &mut heap,
612 &NoOpMetricsCollector,
613 )
614 .unwrap();
615
616 assert_eq!(search_results, heap_results(heap));
617 }
618
619 #[test]
620 fn test_flat_search_matches_accumulate_topk_with_prefilter() {
621 let index = FlatIndex::default();
622 let storage = test_storage();
623 let k = 2;
624 let filter = Arc::new(MaskPreFilter {
625 mask: Arc::new(RowAddrMask::from_allowed(RowAddrTreeMap::from_iter([
626 0_u64, 3, 4,
627 ]))),
628 });
629 let search_results = batch_results(
630 index
631 .search(
632 query(),
633 k,
634 FlatQueryParams::default(),
635 &storage,
636 filter.clone(),
637 &NoOpMetricsCollector,
638 )
639 .unwrap(),
640 );
641
642 let mut heap = BinaryHeap::with_capacity(k);
643 index
644 .accumulate_topk(
645 query(),
646 k,
647 FlatQueryParams::default(),
648 &storage,
649 filter,
650 &mut heap,
651 &NoOpMetricsCollector,
652 )
653 .unwrap();
654
655 assert_eq!(search_results, heap_results(heap));
656 assert_eq!(
657 search_results.iter().map(|(id, _)| *id).collect::<Vec<_>>(),
658 vec![0, 3]
659 );
660 }
661}