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 if doc.num_vectors() == 0 {
89 return;
90 }
91
92 for (i, q_vec) in query.rows().enumerate() {
93 let mut min_dist = f32::MAX;
95
96 for d_vec in doc.rows() {
97 let dist = InnerProduct::evaluate(q_vec, d_vec);
98 min_dist = min_dist.min(dist);
99 }
100
101 f(i, min_dist);
102 }
103 }
104
105 #[inline]
121 pub(crate) fn projected_eigen_kernel<F, T: Copy>(
122 query: QueryMatRef<'_, Standard<T>>,
123 doc: MatRef<'_, Standard<T>>,
124 mut f: F,
125 ) where
126 F: FnMut(usize, f32),
127 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
128 {
129 if doc.num_vectors() == 0 {
131 return;
132 }
133
134 for (i, q_vec) in query.rows().enumerate() {
135 let mut sum = 0.0f32;
136
137 for d_vec in doc.rows() {
138 let ip = InnerProduct::evaluate(q_vec, d_vec);
142 sum += -(ip * ip);
143 }
144
145 f(i, sum);
146 }
147 }
148}
149
150impl<T: Copy>
155 DistanceFunctionMut<
156 QueryMatRef<'_, Standard<T>>,
157 MatRef<'_, Standard<T>>,
158 Result<(), MaxSimError>,
159 > for MaxSim<'_>
160where
161 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
162{
163 #[inline(always)]
164 fn evaluate(
165 &mut self,
166 query: QueryMatRef<'_, Standard<T>>,
167 doc: MatRef<'_, Standard<T>>,
168 ) -> Result<(), MaxSimError> {
169 let size = self.size();
170 let n_queries = query.num_vectors();
171
172 if self.size() != query.num_vectors() {
173 return Err(MaxSimError::InvalidBufferLength(size, n_queries));
174 }
175
176 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
177 unsafe { *self.scores.get_unchecked_mut(i) = score };
180 });
181
182 Ok(())
183 }
184}
185
186impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
191 for Chamfer
192where
193 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
194{
195 #[inline(always)]
196 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
197 let mut sum = 0.0f32;
198
199 FallbackKernel::max_sim_kernel(query, doc, |_i, score| {
200 sum += score;
201 });
202
203 sum
204 }
205}
206
207impl<T: Copy> PureDistanceFunction<QueryMatRef<'_, Standard<T>>, MatRef<'_, Standard<T>>, f32>
212 for ProjectedEigen
213where
214 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
215{
216 #[inline(always)]
217 fn evaluate(query: QueryMatRef<'_, Standard<T>>, doc: MatRef<'_, Standard<T>>) -> f32 {
218 let mut sum = 0.0f32;
219
220 FallbackKernel::projected_eigen_kernel(query, doc, |_i, score| {
221 sum += score;
222 });
223
224 sum
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 fn make_query(data: &[f32], nrows: usize, ncols: usize) -> QueryMatRef<'_, Standard<f32>> {
234 MatRef::new(Standard::new(nrows, ncols).unwrap(), data)
235 .unwrap()
236 .into()
237 }
238
239 fn make_doc(data: &[f32], nrows: usize, ncols: usize) -> MatRef<'_, Standard<f32>> {
241 MatRef::new(Standard::new(nrows, ncols).unwrap(), data).unwrap()
242 }
243
244 fn naive_max_sim_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
246 doc.rows()
247 .map(|d_vec| {
248 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
249 -ip
250 })
251 .fold(f32::MAX, f32::min)
252 }
253
254 fn naive_projected_eigen_single(query_vec: &[f32], doc: &MatRef<'_, Standard<f32>>) -> f32 {
257 doc.rows()
258 .map(|d_vec| {
259 let ip: f32 = query_vec.iter().zip(d_vec.iter()).map(|(a, b)| a * b).sum();
260 -(ip * ip)
261 })
262 .sum()
263 }
264
265 fn make_test_data(len: usize, ceil: usize, shift: usize) -> Vec<f32> {
267 (0..len).map(|v| ((v + shift) % ceil) as f32).collect()
268 }
269
270 mod query_mat_ref {
271 use super::*;
272
273 #[test]
274 fn from_mat_ref_and_deref() {
275 let data = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
276 let view = MatRef::new(Standard::new(2, 3).unwrap(), &data).unwrap();
277 let query: QueryMatRef<_> = view.into();
278
279 assert_eq!(query.num_vectors(), 2);
281 assert_eq!(query.vector_dim(), 3);
282 assert_eq!(query.get_row(0), Some(&[1.0f32, 2.0, 3.0][..]));
283 }
284
285 #[test]
286 fn is_copy() {
287 let data = [1.0f32, 2.0];
288 let query = make_query(&data, 1, 2);
289 let copy = query;
290 let _ = (query, copy); }
292 }
293
294 mod distance_functions {
295 use diskann_utils::Reborrow;
296
297 use super::*;
298
299 #[test]
300 fn max_sim_panics_on_size_mismatch() {
301 let query = make_query(&[1.0, 2.0, 3.0, 4.0], 2, 2);
302 let doc = make_doc(&[1.0, 1.0], 1, 2);
303
304 let mut scores = vec![0.0f32; 3]; let r = MaxSim::new(&mut scores).evaluate(query, doc);
306 assert!(r.is_err());
307 }
308
309 #[test]
312 fn matches_naive_implementation() {
313 let test_cases = [
314 (1, 1, 4), (1, 5, 8), (5, 1, 8), (3, 4, 16), (7, 7, 32), (2, 3, 128), ];
321
322 for (nq, nd, dim) in test_cases.iter() {
323 let query_data = make_test_data(nq * dim, *dim, dim / 2);
324 let doc_data = make_test_data(nd * dim, *dim, *dim);
325
326 let query = make_query(&query_data, *nq, *dim);
327 let doc = make_doc(&doc_data, *nd, *dim);
328
329 let mut scores = vec![0.0f32; *nq];
331 let r = MaxSim::new(&mut scores).evaluate(query, doc);
332 assert!(r.is_ok());
333
334 let expected_scores: Vec<f32> = query
335 .rows()
336 .map(|q_vec| naive_max_sim_single(q_vec, &doc))
337 .collect();
338
339 for i in 0..*nq {
340 assert!(
341 (scores[i] - expected_scores[i]).abs() < 1e-10,
342 "MaxSim mismatch at {} for ({},{},{})",
343 i,
344 nq,
345 nd,
346 dim
347 );
348 }
349
350 FallbackKernel::max_sim_kernel(query, doc, |i, score| {
352 assert!((expected_scores[i] - score).abs() <= 1e-10)
353 });
354
355 let chamfer = Chamfer::evaluate(query, doc);
357 let expected_chamfer: f32 = expected_scores.iter().sum();
358
359 assert!(
360 (chamfer - expected_chamfer).abs() < 1e-10,
361 "Chamfer mismatch for ({},{},{})",
362 nq,
363 nd,
364 dim
365 );
366
367 let projected = ProjectedEigen::evaluate(query, doc);
369 let expected_projected: f32 = query
370 .rows()
371 .map(|q_vec| naive_projected_eigen_single(q_vec, &doc))
372 .sum();
373
374 assert!(
375 (projected - expected_projected).abs()
376 < 1e-6 * expected_projected.abs().max(1.0),
377 "ProjectedEigen mismatch for ({},{},{})",
378 nq,
379 nd,
380 dim
381 );
382 }
383 }
384
385 #[test]
386 fn chamfer_with_zero_queries_returns_zero() {
387 let query = make_query(&[], 0, 2);
388 let doc = make_doc(&[1.0, 0.0, 0.0, 1.0], 2, 2);
389
390 let result = Chamfer::evaluate(query, doc);
391
392 assert_eq!(result, 0.0);
394
395 let result = Chamfer::evaluate(QueryMatRef::from(doc), query.deref().reborrow());
396
397 assert_eq!(result, 0.0);
398 }
399
400 #[test]
401 fn projected_eigen_with_zero_docs_returns_zero() {
402 let query = make_query(&[1.0, 0.0, 0.0, 1.0], 2, 2);
403 let doc = make_doc(&[], 0, 2);
404
405 let result = ProjectedEigen::evaluate(query, doc);
407 assert_eq!(result, 0.0);
408 }
409 }
410}