1use std::sync::Arc;
13
14use arrow_array::cast::AsArray;
15use arrow_array::types::{Float16Type, Float32Type, Float64Type, UInt8Type};
16use arrow_array::{Array, ArrowPrimitiveType, FixedSizeListArray, Float32Array, ListArray};
17use arrow_schema::{ArrowError, DataType};
18
19pub mod cosine;
20pub mod cosine_u8;
21pub mod dot;
22pub mod dot_u8;
23pub mod hamming;
24pub mod l2;
25pub mod l2_u8;
26pub mod norm_l2;
27
28#[cfg(all(
41 target_arch = "x86_64",
42 not(all(target_feature = "avx2", target_feature = "fma"))
43))]
44pub(crate) enum BatchIter<L> {
45 Lazy(L),
47 Eager(std::vec::IntoIter<f32>),
49}
50
51#[cfg(all(
52 target_arch = "x86_64",
53 not(all(target_feature = "avx2", target_feature = "fma"))
54))]
55impl<L: Iterator<Item = f32>> Iterator for BatchIter<L> {
56 type Item = f32;
57
58 #[inline]
59 fn next(&mut self) -> Option<Self::Item> {
60 match self {
61 Self::Lazy(iter) => iter.next(),
62 Self::Eager(iter) => iter.next(),
63 }
64 }
65
66 #[inline]
67 fn size_hint(&self) -> (usize, Option<usize>) {
68 match self {
69 Self::Lazy(iter) => iter.size_hint(),
70 Self::Eager(iter) => iter.size_hint(),
71 }
72 }
73
74 #[inline]
79 fn fold<B, F>(self, init: B, f: F) -> B
80 where
81 F: FnMut(B, Self::Item) -> B,
82 {
83 match self {
84 Self::Lazy(iter) => iter.fold(init, f),
85 Self::Eager(iter) => iter.fold(init, f),
86 }
87 }
88
89 #[inline]
93 fn for_each<F>(self, f: F)
94 where
95 F: FnMut(Self::Item),
96 {
97 match self {
98 Self::Lazy(iter) => iter.for_each(f),
99 Self::Eager(iter) => iter.for_each(f),
100 }
101 }
102}
103
104#[cfg(all(
105 target_arch = "x86_64",
106 not(all(target_feature = "avx2", target_feature = "fma"))
107))]
108impl<L: ExactSizeIterator<Item = f32>> ExactSizeIterator for BatchIter<L> {
109 #[inline]
110 fn len(&self) -> usize {
111 match self {
112 Self::Lazy(iter) => iter.len(),
113 Self::Eager(iter) => iter.len(),
114 }
115 }
116}
117
118pub use cosine::*;
119pub use dot::*;
120pub use hamming::{
121 BinaryHashValues, Cluster, ClusteringResult, PairwiseResult, UnionFind, cluster_edges,
122 cluster_pairwise_result, extract_binary_hashes_from_fixed_list, extract_hashes_from_fixed_list,
123 hamming_distance_arrow_batch, hamming_u64, pairwise_hamming_distance,
124 pairwise_hamming_distance_binary, pairwise_hamming_distance_binary_parallel,
125 pairwise_hamming_distance_parallel,
126};
127pub use l2::*;
128use lance_core::deepsize::DeepSizeOf;
129pub use norm_l2::*;
130
131use crate::Result;
132
133#[derive(Debug, Copy, Clone, PartialEq, DeepSizeOf)]
135pub enum DistanceType {
136 L2,
137 Cosine,
138 Dot,
140 Hamming,
142}
143
144pub type MetricType = DistanceType;
146
147pub type DistanceFunc<T> = fn(&[T], &[T]) -> f32;
148pub type BatchDistanceFunc = fn(&[f32], &[f32], usize) -> Arc<Float32Array>;
149pub type ArrowBatchDistanceFunc = fn(&dyn Array, &FixedSizeListArray) -> Result<Arc<Float32Array>>;
150
151impl DistanceType {
152 pub fn arrow_batch_func(&self) -> ArrowBatchDistanceFunc {
156 match self {
157 Self::L2 => l2_distance_arrow_batch,
158 Self::Cosine => cosine_distance_arrow_batch,
159 Self::Dot => dot_distance_arrow_batch,
160 Self::Hamming => hamming_distance_arrow_batch,
161 }
162 }
163
164 pub fn func<T: L2 + Cosine + Dot>(&self) -> DistanceFunc<T> {
166 match self {
167 Self::L2 => l2,
168 Self::Cosine => cosine_distance,
169 Self::Dot => dot_distance,
170 Self::Hamming => todo!(),
171 }
172 }
173}
174
175impl std::fmt::Display for DistanceType {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 write!(
178 f,
179 "{}",
180 match self {
181 Self::L2 => "l2",
182 Self::Cosine => "cosine",
183 Self::Dot => "dot",
184 Self::Hamming => "hamming",
185 }
186 )
187 }
188}
189
190impl TryFrom<&str> for DistanceType {
191 type Error = ArrowError;
192
193 fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
194 match s.to_lowercase().as_str() {
195 "l2" | "euclidean" => Ok(Self::L2),
196 "cosine" => Ok(Self::Cosine),
197 "dot" => Ok(Self::Dot),
198 "hamming" => Ok(Self::Hamming),
199 _ => Err(ArrowError::InvalidArgumentError(format!(
200 "Metric type '{s}' is not supported"
201 ))),
202 }
203 }
204}
205
206pub fn multivec_distance(
207 query: &dyn Array,
208 vectors: &ListArray,
209 distance_type: DistanceType,
210) -> Result<Vec<f32>> {
211 let dim = if let DataType::FixedSizeList(_, dim) = vectors.value_type() {
212 dim as usize
213 } else {
214 return Err(ArrowError::InvalidArgumentError(
215 "vectors must be a list of fixed size list".to_string(),
216 ));
217 };
218
219 match query.data_type() {
222 DataType::Float16 | DataType::Float32 | DataType::Float64 | DataType::UInt8 => {}
223 _ => {
224 return Err(ArrowError::InvalidArgumentError(
225 "query must be a float array or binary array".to_string(),
226 ));
227 }
228 }
229
230 let mut dists = Vec::with_capacity(vectors.len());
231 for v in vectors.iter() {
232 match v {
233 None => dists.push(f32::NAN),
234 Some(v) => {
235 let multivector = v.as_fixed_size_list();
236 if multivector.len() == 0 {
237 dists.push(f32::NAN);
238 continue;
239 }
240
241 let sim = match distance_type {
242 DistanceType::Hamming => {
243 let query = query.as_primitive::<UInt8Type>().values();
244 query
245 .chunks_exact(dim)
246 .map(|q| {
247 multivector
248 .values()
249 .as_primitive::<UInt8Type>()
250 .values()
251 .chunks_exact(dim)
252 .map(|v| hamming::hamming(q, v))
253 .min_by(|a, b| a.partial_cmp(b).unwrap())
254 .unwrap()
255 })
256 .sum()
257 }
258 _ => match query.data_type() {
259 DataType::Float16 => multivec_distance_impl::<Float16Type>(
260 query,
261 multivector,
262 dim,
263 distance_type,
264 ),
265 DataType::Float32 => multivec_distance_impl::<Float32Type>(
266 query,
267 multivector,
268 dim,
269 distance_type,
270 ),
271 DataType::Float64 => multivec_distance_impl::<Float64Type>(
272 query,
273 multivector,
274 dim,
275 distance_type,
276 ),
277 _ => unreachable!("missed to check query type"),
278 },
279 };
280
281 dists.push(1.0 - sim);
282 }
283 }
284 }
285 Ok(dists)
286}
287
288fn multivec_distance_impl<T: ArrowPrimitiveType>(
289 query: &dyn Array,
290 multivector: &FixedSizeListArray,
291 dim: usize,
292 distance_type: DistanceType,
293) -> f32
294where
295 T::Native: L2 + Cosine + Dot,
296{
297 let query = query.as_primitive::<T>().values();
298 query
299 .chunks_exact(dim)
300 .map(|q| {
301 multivector
302 .values()
303 .as_primitive::<T>()
304 .values()
305 .chunks_exact(dim)
306 .map(|v| 1.0 - distance_type.func()(q, v))
307 .max_by(|a, b| a.total_cmp(b))
308 .unwrap()
309 })
310 .sum()
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 use std::sync::Arc;
318
319 use arrow_array::types::Float32Type;
320 use arrow_array::{Float32Array, ListArray};
321 use arrow_buffer::OffsetBuffer;
322 use arrow_schema::Field;
323
324 #[test]
325 fn test_multivec_distance_empty_row_is_nan() {
326 let query: Arc<dyn Array> = Arc::new(Float32Array::from_iter_values([1.0_f32, 2.0]));
327
328 let dim = 2;
329 let values = FixedSizeListArray::from_iter_primitive::<Float32Type, _, _>(
330 vec![Some(vec![Some(1.0_f32), Some(2.0)])],
331 dim,
332 );
333
334 let offsets = OffsetBuffer::from_lengths([0_usize, 1]);
336 let field = Arc::new(Field::new("item", values.data_type().clone(), true));
337 let vectors = ListArray::try_new(field, offsets, Arc::new(values), None).unwrap();
338
339 let dists = multivec_distance(query.as_ref(), &vectors, DistanceType::Dot).unwrap();
340 assert_eq!(dists.len(), 2);
341 assert!(dists[0].is_nan());
342 assert_eq!(dists[1], -4.0);
343 }
344}