1use std::ops::Deref;
7
8use diskann_vector::distance::InnerProduct;
9use diskann_vector::{DistanceFunctionMut, PureDistanceFunction};
10
11use super::max_sim::{Chamfer, MaxSim};
12use super::projected_eigen::ProjectedEigen;
13use crate::multi_vector::{MatRef, MaxSimError, Repr, Standard};
14
15#[derive(Debug, Clone, Copy)]
36pub struct QueryMatRef<'a, T: Repr>(pub MatRef<'a, T>);
37
38impl<'a, T: Repr> From<MatRef<'a, T>> for QueryMatRef<'a, T> {
39 fn from(view: MatRef<'a, T>) -> Self {
40 Self(view)
41 }
42}
43
44impl<'a, T: Repr> Deref for QueryMatRef<'a, T> {
46 type Target = MatRef<'a, T>;
47
48 fn deref(&self) -> &Self::Target {
49 &self.0
50 }
51}
52
53pub struct FallbackKernel;
62
63impl FallbackKernel {
64 #[inline]
79 pub(crate) fn max_sim_kernel<F, T: Copy>(
80 query: QueryMatRef<'_, Standard<T>>,
81 doc: MatRef<'_, Standard<T>>,
82 mut f: F,
83 ) where
84 F: FnMut(usize, f32),
85 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
86 {
87 for (i, q_vec) in query.rows().enumerate() {
88 let mut min_dist = f32::MAX;
90
91 for d_vec in doc.rows() {
92 let dist = InnerProduct::evaluate(q_vec, d_vec);
93 min_dist = min_dist.min(dist);
94 }
95
96 f(i, min_dist);
97 }
98 }
99
100 #[inline]
116 pub(crate) fn projected_eigen_kernel<F, T: Copy>(
117 query: QueryMatRef<'_, Standard<T>>,
118 doc: MatRef<'_, Standard<T>>,
119 mut f: F,
120 ) where
121 F: FnMut(usize, f32),
122 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
123 {
124 if doc.num_vectors() == 0 {
126 return;
127 }
128
129 for (i, q_vec) in query.rows().enumerate() {
130 let mut sum = 0.0f32;
131
132 for d_vec in doc.rows() {
133 let ip = InnerProduct::evaluate(q_vec, d_vec);
137 sum += -(ip * ip);
138 }
139
140 f(i, sum);
141 }
142 }
143}
144
145impl<T: Copy>
150 DistanceFunctionMut<
151 QueryMatRef<'_, Standard<T>>,
152 MatRef<'_, Standard<T>>,
153 Result<(), MaxSimError>,
154 > for MaxSim<'_>
155where
156 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
157{
158 #[inline(always)]
159 fn evaluate(
160 &mut self,
161 query: QueryMatRef<'_, Standard<T>>,
162 doc: MatRef<'_, Standard<T>>,
163 ) -> Result<(), MaxSimError> {
164 let size = self.size();
165 let n_queries = query.num_vectors();
166
167 if self.size() != query.num_vectors() {
168 return Err(MaxSimError::InvalidBufferLength(size, n_queries));
169 }
170
171 if query.vector_dim() != doc.vector_dim() {
172 return Err(MaxSimError::UnequalDim(
173 doc.vector_dim(),
174 query.vector_dim(),
175 ));
176 }
177
178 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
179 unsafe { *self.scores.get_unchecked_mut(i) = score };
182 });
183
184 Ok(())
185 }
186}
187
188impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
193 for Chamfer
194where
195 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
196{
197 #[inline(always)]
198 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
199 let mut sum = 0.0f32;
200
201 FallbackKernel::max_sim_kernel(query, doc, |_i, score| {
202 sum += score;
203 });
204
205 sum
206 }
207}
208
209impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
214 for ProjectedEigen
215where
216 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
217{
218 #[inline(always)]
219 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
220 let mut sum = 0.0f32;
221
222 FallbackKernel::projected_eigen_kernel(query, doc, |_i, score| {
223 sum += score;
224 });
225
226 sum
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 fn make_query(data: &[f32], nrows: usize, ncols: usize) -> QueryMatRef<'_, Standard<f32>> {
236 MatRef::new(Standard::new(nrows, ncols).unwrap(), data)
237 .unwrap()
238 .into()
239 }
240
241 fn make_doc(data: &[f32], nrows: usize, ncols: usize) -> MatRef<'_, Standard<f32>> {
243 MatRef::new(Standard::new(nrows, ncols).unwrap(), data).unwrap()
244 }
245
246 fn naive_max_sim_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
248 doc.rows()
249 .map(|d_vec| {
250 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
251 -ip
252 })
253 .fold(f32::MAX, f32::min)
254 }
255
256 fn naive_projected_eigen_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
259 doc.rows()
260 .map(|d_vec| {
261 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
262 -(ip * ip)
263 })
264 .sum()
265 }
266
267 fn make_test_data(len: usize, ceil: usize, shift: usize) -> Vec<f32> {
269 (0..len).map(|v| ((v + shift) % ceil) as f32).collect()
270 }
271
272 mod query_mat_ref {
273 use super::*;
274
275 #[test]
276 fn from_mat_ref_and_deref() {
277 let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
278 let view = MatRef::new(Standard::new(2, 3).unwrap(), &data).unwrap();
279 let query: QueryMatRef<_> = view.into();
280
281 assert_eq!(query.num_vectors(), 2);
283 assert_eq!(query.vector_dim(), 3);
284 assert_eq!(query.get_row(0), Some(&[1.0f32, 2.0, 3.0][..]));
285 }
286
287 #[test]
288 fn is_copy() {
289 let data = [1.0f32, 2.0];
290 let query = make_query(&data, 1, 2);
291 let copy = query;
292 let _ = (query, copy); }
294 }
295
296 mod distance_functions {
297 use diskann_utils::Reborrow;
298
299 use super::*;
300
301 #[test]
302 fn max_sim_panics_on_size_mismatch() {
303 let query = make_query(&[1.0, 2.0, 3.0, 4.0], 2, 2);
304 let doc = make_doc(&[1.0, 1.0], 1, 2);
305
306 let mut scores = vec![0.0f32; 3]; let r = MaxSim::new(&mut scores).evaluate(query, doc);
308 assert!(r.is_err());
309 }
310
311 #[test]
314 fn matches_naive_implementation() {
315 let test_cases = [
316 (1, 1, 4), (1, 5, 8), (5, 1, 8), (3, 4, 16), (7, 7, 32), (2, 3, 128), ];
323
324 for (nq, nd, dim) in test_cases.iter() {
325 let query_data = make_test_data(nq * dim, *dim, dim / 2);
326 let doc_data = make_test_data(nd * dim, *dim, *dim);
327
328 let query = make_query(&query_data, *nq, *dim);
329 let doc = make_doc(&doc_data, *nd, *dim);
330
331 let mut scores = vec![0.0f32; *nq];
333 let r = MaxSim::new(&mut scores).evaluate(query, doc);
334 assert!(r.is_ok());
335
336 let expected_scores: Vec<f32> = query
337 .rows()
338 .map(|q_vec| naive_max_sim_single(q_vec, &doc))
339 .collect();
340
341 for i in 0..*nq {
342 assert!(
343 (scores[i] - expected_scores[i]).abs() < 1e-10,
344 "MaxSim mismatch at {} for ({},{},{})",
345 i,
346 nq,
347 nd,
348 dim
349 );
350 }
351
352 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
354 assert!((expected_scores[i] - score).abs() <= 1e-10)
355 });
356
357 let chamfer = Chamfer::evaluate(query, doc);
359 let expected_chamfer: f32 = expected_scores.iter().sum();
360
361 assert!(
362 (chamfer - expected_chamfer).abs() < 1e-10,
363 "Chamfer mismatch for ({},{},{})",
364 nq,
365 nd,
366 dim
367 );
368
369 let projected = ProjectedEigen::evaluate(query, doc);
371 let expected_projected: f32 = query
372 .rows()
373 .map(|q_vec| naive_projected_eigen_single(q_vec, &doc))
374 .sum();
375
376 assert!(
377 (projected - expected_projected).abs()
378 < 1e-6 * expected_projected.abs().max(1.0),
379 "ProjectedEigen mismatch for ({},{},{})",
380 nq,
381 nd,
382 dim
383 );
384 }
385 }
386
387 #[test]
388 fn chamfer_with_zero_queries_returns_zero() {
389 let query = make_query(&[], 0, 2);
390 let doc = make_doc(&[1.0, 0.0, 0.0, 1.0], 2, 2);
391
392 let result = Chamfer::evaluate(query, doc);
393
394 assert_eq!(result, 0.0);
396
397 let result = Chamfer::evaluate(QueryMatRef::from(doc), query.deref().reborrow());
398
399 assert_eq!(result, f32::INFINITY);
400 }
401
402 #[test]
403 fn projected_eigen_with_zero_docs_returns_zero() {
404 let query = make_query(&[1.0, 0.0, 0.0, 1.0], 2, 2);
405 let doc = make_doc(&[], 0, 2);
406
407 let result = ProjectedEigen::evaluate(query, doc);
409 assert_eq!(result, 0.0);
410 }
411 }
412}