1use std::ops::Deref;
9
10use diskann_vector::distance::InnerProduct;
11use diskann_vector::{DistanceFunctionMut, PureDistanceFunction};
12
13use super::max_sim::{Chamfer, MaxSim};
14use super::projected_eigen::ProjectedEigen;
15use crate::multi_vector::{MatRef, MaxSimError, Repr, Standard};
16
17#[derive(Debug, Clone, Copy)]
38pub struct QueryMatRef<'a, T: Repr>(pub MatRef<'a, T>);
39
40impl<'a, T: Repr> From<MatRef<'a, T>> for QueryMatRef<'a, T> {
41 fn from(view: MatRef<'a, T>) -> Self {
42 Self(view)
43 }
44}
45
46impl<'a, T: Repr> Deref for QueryMatRef<'a, T> {
48 type Target = MatRef<'a, T>;
49
50 fn deref(&self) -> &Self::Target {
51 &self.0
52 }
53}
54
55pub struct FallbackKernel;
64
65impl FallbackKernel {
66 #[inline]
81 pub(crate) fn max_sim_kernel<F, T: Copy>(
82 query: QueryMatRef<'_, Standard<T>>,
83 doc: MatRef<'_, Standard<T>>,
84 mut f: F,
85 ) where
86 F: FnMut(usize, f32),
87 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
88 {
89 for (i, q_vec) in query.rows().enumerate() {
90 let mut min_dist = f32::MAX;
92
93 for d_vec in doc.rows() {
94 let dist = InnerProduct::evaluate(q_vec, d_vec);
95 min_dist = min_dist.min(dist);
96 }
97
98 f(i, min_dist);
99 }
100 }
101
102 #[inline]
114 pub(crate) fn max_sim_kernel_i8<F>(
115 query: QueryMatRef<'_, Standard<i8>>,
116 doc: MatRef<'_, Standard<i8>>,
117 mut f: F,
118 ) where
119 F: FnMut(usize, i32),
120 {
121 for (i, q_vec) in query.rows().enumerate() {
122 let mut min_dist = i32::MAX;
124
125 for d_vec in doc.rows() {
126 min_dist = min_dist.min(-dot_i32(q_vec, d_vec));
127 }
128
129 f(i, min_dist);
130 }
131 }
132
133 #[inline]
149 pub(crate) fn projected_eigen_kernel<F, T: Copy>(
150 query: QueryMatRef<'_, Standard<T>>,
151 doc: MatRef<'_, Standard<T>>,
152 mut f: F,
153 ) where
154 F: FnMut(usize, f32),
155 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
156 {
157 if doc.num_vectors() == 0 {
159 return;
160 }
161
162 for (i, q_vec) in query.rows().enumerate() {
163 let mut sum = 0.0f32;
164
165 for d_vec in doc.rows() {
166 let ip = InnerProduct::evaluate(q_vec, d_vec);
170 sum += -(ip * ip);
171 }
172
173 f(i, sum);
174 }
175 }
176}
177
178fn dot_i32(a: &[i8], b: &[i8]) -> i32 {
181 a.iter()
182 .zip(b)
183 .map(|(&x, &y)| i32::from(x) * i32::from(y))
184 .sum()
185}
186
187impl<T: Copy>
192 DistanceFunctionMut<
193 QueryMatRef<'_, Standard<T>>,
194 MatRef<'_, Standard<T>>,
195 Result<(), MaxSimError>,
196 > for MaxSim<'_>
197where
198 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
199{
200 #[inline(always)]
201 fn evaluate(
202 &mut self,
203 query: QueryMatRef<'_, Standard<T>>,
204 doc: MatRef<'_, Standard<T>>,
205 ) -> Result<(), MaxSimError> {
206 let size = self.size();
207 let n_queries = query.num_vectors();
208
209 if self.size() != query.num_vectors() {
210 return Err(MaxSimError::InvalidBufferLength(size, n_queries));
211 }
212
213 if query.vector_dim() != doc.vector_dim() {
214 return Err(MaxSimError::UnequalDim(
215 doc.vector_dim(),
216 query.vector_dim(),
217 ));
218 }
219
220 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
221 unsafe { *self.scores.get_unchecked_mut(i) = score };
224 });
225
226 Ok(())
227 }
228}
229
230impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
235 for Chamfer
236where
237 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
238{
239 #[inline(always)]
240 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
241 let mut sum = 0.0f32;
242
243 FallbackKernel::max_sim_kernel(query, doc, |_i, score| {
244 sum += score;
245 });
246
247 sum
248 }
249}
250
251impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
256 for ProjectedEigen
257where
258 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
259{
260 #[inline(always)]
261 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
262 let mut sum = 0.0f32;
263
264 FallbackKernel::projected_eigen_kernel(query, doc, |_i, score| {
265 sum += score;
266 });
267
268 sum
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 fn make_query(data: &[f32], nrows: usize, ncols: usize) -> QueryMatRef<'_, Standard<f32>> {
278 MatRef::new(Standard::new(nrows, ncols).unwrap(), data)
279 .unwrap()
280 .into()
281 }
282
283 fn make_doc(data: &[f32], nrows: usize, ncols: usize) -> MatRef<'_, Standard<f32>> {
285 MatRef::new(Standard::new(nrows, ncols).unwrap(), data).unwrap()
286 }
287
288 fn naive_max_sim_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
290 doc.rows()
291 .map(|d_vec| {
292 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
293 -ip
294 })
295 .fold(f32::MAX, f32::min)
296 }
297
298 fn naive_projected_eigen_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
301 doc.rows()
302 .map(|d_vec| {
303 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
304 -(ip * ip)
305 })
306 .sum()
307 }
308
309 fn make_test_data(len: usize, ceil: usize, shift: usize) -> Vec<f32> {
311 (0..len).map(|v| ((v + shift) % ceil) as f32).collect()
312 }
313
314 mod query_mat_ref {
315 use super::*;
316
317 #[test]
318 fn from_mat_ref_and_deref() {
319 let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
320 let view = MatRef::new(Standard::new(2, 3).unwrap(), &data).unwrap();
321 let query: QueryMatRef<_> = view.into();
322
323 assert_eq!(query.num_vectors(), 2);
325 assert_eq!(query.vector_dim(), 3);
326 assert_eq!(query.get_row(0), Some(&[1.0f32, 2.0, 3.0][..]));
327 }
328
329 #[test]
330 fn is_copy() {
331 let data = [1.0f32, 2.0];
332 let query = make_query(&data, 1, 2);
333 let copy = query;
334 let _ = (query, copy); }
336 }
337
338 mod distance_functions {
339 use diskann_utils::Reborrow;
340
341 use super::*;
342
343 #[test]
344 fn max_sim_panics_on_size_mismatch() {
345 let query = make_query(&[1.0, 2.0, 3.0, 4.0], 2, 2);
346 let doc = make_doc(&[1.0, 1.0], 1, 2);
347
348 let mut scores = vec![0.0f32; 3]; let r = MaxSim::new(&mut scores).evaluate(query, doc);
350 assert!(r.is_err());
351 }
352
353 #[test]
356 fn matches_naive_implementation() {
357 let test_cases = [
358 (1, 1, 4), (1, 5, 8), (5, 1, 8), (3, 4, 16), (7, 7, 32), (2, 3, 128), ];
365
366 for (nq, nd, dim) in test_cases.iter() {
367 let query_data = make_test_data(nq * dim, *dim, dim / 2);
368 let doc_data = make_test_data(nd * dim, *dim, *dim);
369
370 let query = make_query(&query_data, *nq, *dim);
371 let doc = make_doc(&doc_data, *nd, *dim);
372
373 let mut scores = vec![0.0f32; *nq];
375 let r = MaxSim::new(&mut scores).evaluate(query, doc);
376 assert!(r.is_ok());
377
378 let expected_scores: Vec<f32> = query
379 .rows()
380 .map(|q_vec| naive_max_sim_single(q_vec, &doc))
381 .collect();
382
383 for i in 0..*nq {
384 assert!(
385 (scores[i] - expected_scores[i]).abs() < 1e-10,
386 "MaxSim mismatch at {} for ({},{},{})",
387 i,
388 nq,
389 nd,
390 dim
391 );
392 }
393
394 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
396 assert!((expected_scores[i] - score).abs() <= 1e-10)
397 });
398
399 let chamfer = Chamfer::evaluate(query, doc);
401 let expected_chamfer: f32 = expected_scores.iter().sum();
402
403 assert!(
404 (chamfer - expected_chamfer).abs() < 1e-10,
405 "Chamfer mismatch for ({},{},{})",
406 nq,
407 nd,
408 dim
409 );
410
411 let projected = ProjectedEigen::evaluate(query, doc);
413 let expected_projected: f32 = query
414 .rows()
415 .map(|q_vec| naive_projected_eigen_single(q_vec, &doc))
416 .sum();
417
418 assert!(
419 (projected - expected_projected).abs()
420 < 1e-6 * expected_projected.abs().max(1.0),
421 "ProjectedEigen mismatch for ({},{},{})",
422 nq,
423 nd,
424 dim
425 );
426 }
427 }
428
429 #[test]
430 fn chamfer_with_zero_queries_returns_zero() {
431 let query = make_query(&[], 0, 2);
432 let doc = make_doc(&[1.0, 0.0, 0.0, 1.0], 2, 2);
433
434 let result = Chamfer::evaluate(query, doc);
435
436 assert_eq!(result, 0.0);
438
439 let result = Chamfer::evaluate(QueryMatRef::from(doc), query.deref().reborrow());
440
441 assert_eq!(result, f32::INFINITY);
442 }
443
444 #[test]
445 fn projected_eigen_with_zero_docs_returns_zero() {
446 let query = make_query(&[1.0, 0.0, 0.0, 1.0], 2, 2);
447 let doc = make_doc(&[], 0, 2);
448
449 let result = ProjectedEigen::evaluate(query, doc);
451 assert_eq!(result, 0.0);
452 }
453 }
454}