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]
118 pub(crate) fn projected_eigen_kernel<F, T: Copy>(
119 query: QueryMatRef<'_, Standard<T>>,
120 doc: MatRef<'_, Standard<T>>,
121 mut f: F,
122 ) where
123 F: FnMut(usize, f32),
124 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
125 {
126 if doc.num_vectors() == 0 {
128 return;
129 }
130
131 for (i, q_vec) in query.rows().enumerate() {
132 let mut sum = 0.0f32;
133
134 for d_vec in doc.rows() {
135 let ip = InnerProduct::evaluate(q_vec, d_vec);
139 sum += -(ip * ip);
140 }
141
142 f(i, sum);
143 }
144 }
145}
146
147impl<T: Copy>
152 DistanceFunctionMut<
153 QueryMatRef<'_, Standard<T>>,
154 MatRef<'_, Standard<T>>,
155 Result<(), MaxSimError>,
156 > for MaxSim<'_>
157where
158 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
159{
160 #[inline(always)]
161 fn evaluate(
162 &mut self,
163 query: QueryMatRef<'_, Standard<T>>,
164 doc: MatRef<'_, Standard<T>>,
165 ) -> Result<(), MaxSimError> {
166 let size = self.size();
167 let n_queries = query.num_vectors();
168
169 if self.size() != query.num_vectors() {
170 return Err(MaxSimError::InvalidBufferLength(size, n_queries));
171 }
172
173 if query.vector_dim() != doc.vector_dim() {
174 return Err(MaxSimError::UnequalDim(
175 doc.vector_dim(),
176 query.vector_dim(),
177 ));
178 }
179
180 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
181 unsafe { *self.scores.get_unchecked_mut(i) = score };
184 });
185
186 Ok(())
187 }
188}
189
190impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
195 for Chamfer
196where
197 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
198{
199 #[inline(always)]
200 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
201 let mut sum = 0.0f32;
202
203 FallbackKernel::max_sim_kernel(query, doc, |_i, score| {
204 sum += score;
205 });
206
207 sum
208 }
209}
210
211impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
216 for ProjectedEigen
217where
218 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
219{
220 #[inline(always)]
221 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
222 let mut sum = 0.0f32;
223
224 FallbackKernel::projected_eigen_kernel(query, doc, |_i, score| {
225 sum += score;
226 });
227
228 sum
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 fn make_query(data: &[f32], nrows: usize, ncols: usize) -> QueryMatRef<'_, Standard<f32>> {
238 MatRef::new(Standard::new(nrows, ncols).unwrap(), data)
239 .unwrap()
240 .into()
241 }
242
243 fn make_doc(data: &[f32], nrows: usize, ncols: usize) -> MatRef<'_, Standard<f32>> {
245 MatRef::new(Standard::new(nrows, ncols).unwrap(), data).unwrap()
246 }
247
248 fn naive_max_sim_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
250 doc.rows()
251 .map(|d_vec| {
252 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
253 -ip
254 })
255 .fold(f32::MAX, f32::min)
256 }
257
258 fn naive_projected_eigen_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
261 doc.rows()
262 .map(|d_vec| {
263 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
264 -(ip * ip)
265 })
266 .sum()
267 }
268
269 fn make_test_data(len: usize, ceil: usize, shift: usize) -> Vec<f32> {
271 (0..len).map(|v| ((v + shift) % ceil) as f32).collect()
272 }
273
274 mod query_mat_ref {
275 use super::*;
276
277 #[test]
278 fn from_mat_ref_and_deref() {
279 let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
280 let view = MatRef::new(Standard::new(2, 3).unwrap(), &data).unwrap();
281 let query: QueryMatRef<_> = view.into();
282
283 assert_eq!(query.num_vectors(), 2);
285 assert_eq!(query.vector_dim(), 3);
286 assert_eq!(query.get_row(0), Some(&[1.0f32, 2.0, 3.0][..]));
287 }
288
289 #[test]
290 fn is_copy() {
291 let data = [1.0f32, 2.0];
292 let query = make_query(&data, 1, 2);
293 let copy = query;
294 let _ = (query, copy); }
296 }
297
298 mod distance_functions {
299 use diskann_utils::Reborrow;
300
301 use super::*;
302
303 #[test]
304 fn max_sim_panics_on_size_mismatch() {
305 let query = make_query(&[1.0, 2.0, 3.0, 4.0], 2, 2);
306 let doc = make_doc(&[1.0, 1.0], 1, 2);
307
308 let mut scores = vec![0.0f32; 3]; let r = MaxSim::new(&mut scores).evaluate(query, doc);
310 assert!(r.is_err());
311 }
312
313 #[test]
316 fn matches_naive_implementation() {
317 let test_cases = [
318 (1, 1, 4), (1, 5, 8), (5, 1, 8), (3, 4, 16), (7, 7, 32), (2, 3, 128), ];
325
326 for (nq, nd, dim) in test_cases.iter() {
327 let query_data = make_test_data(nq * dim, *dim, dim / 2);
328 let doc_data = make_test_data(nd * dim, *dim, *dim);
329
330 let query = make_query(&query_data, *nq, *dim);
331 let doc = make_doc(&doc_data, *nd, *dim);
332
333 let mut scores = vec![0.0f32; *nq];
335 let r = MaxSim::new(&mut scores).evaluate(query, doc);
336 assert!(r.is_ok());
337
338 let expected_scores: Vec<f32> = query
339 .rows()
340 .map(|q_vec| naive_max_sim_single(q_vec, &doc))
341 .collect();
342
343 for i in 0..*nq {
344 assert!(
345 (scores[i] - expected_scores[i]).abs() < 1e-10,
346 "MaxSim mismatch at {} for ({},{},{})",
347 i,
348 nq,
349 nd,
350 dim
351 );
352 }
353
354 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
356 assert!((expected_scores[i] - score).abs() <= 1e-10)
357 });
358
359 let chamfer = Chamfer::evaluate(query, doc);
361 let expected_chamfer: f32 = expected_scores.iter().sum();
362
363 assert!(
364 (chamfer - expected_chamfer).abs() < 1e-10,
365 "Chamfer mismatch for ({},{},{})",
366 nq,
367 nd,
368 dim
369 );
370
371 let projected = ProjectedEigen::evaluate(query, doc);
373 let expected_projected: f32 = query
374 .rows()
375 .map(|q_vec| naive_projected_eigen_single(q_vec, &doc))
376 .sum();
377
378 assert!(
379 (projected - expected_projected).abs()
380 < 1e-6 * expected_projected.abs().max(1.0),
381 "ProjectedEigen mismatch for ({},{},{})",
382 nq,
383 nd,
384 dim
385 );
386 }
387 }
388
389 #[test]
390 fn chamfer_with_zero_queries_returns_zero() {
391 let query = make_query(&[], 0, 2);
392 let doc = make_doc(&[1.0, 0.0, 0.0, 1.0], 2, 2);
393
394 let result = Chamfer::evaluate(query, doc);
395
396 assert_eq!(result, 0.0);
398
399 let result = Chamfer::evaluate(QueryMatRef::from(doc), query.deref().reborrow());
400
401 assert_eq!(result, f32::INFINITY);
402 }
403
404 #[test]
405 fn projected_eigen_with_zero_docs_returns_zero() {
406 let query = make_query(&[1.0, 0.0, 0.0, 1.0], 2, 2);
407 let doc = make_doc(&[], 0, 2);
408
409 let result = ProjectedEigen::evaluate(query, doc);
411 assert_eq!(result, 0.0);
412 }
413 }
414}