1use diskann_vector::{DistanceFunctionMut, PureDistanceFunction};
9
10use super::super::vectors::{DataRef, MinMaxIP};
11use super::meta::MinMaxMeta;
12use crate::bits::{Representation, Unsigned};
13use crate::distances::{self, UnequalLengths};
14use crate::multi_vector::distance::QueryMatRef;
15use crate::multi_vector::{Chamfer, MatRef, MaxSim};
16
17pub struct MinMaxKernel;
26
27impl MinMaxKernel {
28 #[inline(always)]
39 pub(crate) fn max_sim_kernel<const NBITS: usize, const MBITS: usize, F>(
40 query: QueryMatRef<'_, MinMaxMeta<NBITS>>,
41 doc: MatRef<'_, MinMaxMeta<MBITS>>,
42 mut f: F,
43 ) -> Result<(), UnequalLengths>
44 where
45 Unsigned: Representation<NBITS> + Representation<MBITS>,
46 distances::InnerProduct: for<'x, 'y> PureDistanceFunction<
47 crate::bits::BitSlice<'x, NBITS, Unsigned>,
48 crate::bits::BitSlice<'y, MBITS, Unsigned>,
49 distances::MathematicalResult<u32>,
50 >,
51 F: FnMut(usize, f32),
52 {
53 for (i, q_ref) in query.rows().enumerate() {
54 let mut min_distance = f32::MAX;
56
57 for d_ref in doc.rows() {
58 let dist = <MinMaxIP as PureDistanceFunction<
60 DataRef<'_, NBITS>,
61 DataRef<'_, MBITS>,
62 distances::Result<f32>,
63 >>::evaluate(q_ref, d_ref)?;
64
65 min_distance = min_distance.min(dist);
66 }
67
68 f(i, min_distance);
69 }
70
71 Ok(())
72 }
73}
74
75impl<const NBITS: usize, const MBITS: usize>
80 DistanceFunctionMut<QueryMatRef<'_, MinMaxMeta<NBITS>>, MatRef<'_, MinMaxMeta<MBITS>>>
81 for MaxSim<'_>
82where
83 Unsigned: Representation<NBITS> + Representation<MBITS>,
84 distances::InnerProduct: for<'x, 'y> PureDistanceFunction<
85 crate::bits::BitSlice<'x, NBITS, Unsigned>,
86 crate::bits::BitSlice<'y, MBITS, Unsigned>,
87 distances::MathematicalResult<u32>,
88 >,
89{
90 #[inline(always)]
91 fn evaluate(
92 &mut self,
93 query: QueryMatRef<'_, MinMaxMeta<NBITS>>,
94 doc: MatRef<'_, MinMaxMeta<MBITS>>,
95 ) {
96 assert!(
97 self.size() == query.num_vectors(),
98 "scores buffer not right size : {} != {}",
99 self.size(),
100 query.num_vectors()
101 );
102
103 let _ = MinMaxKernel::max_sim_kernel(query, doc, |i, score| {
104 let _ = self.set(i, score);
107 });
108 }
109}
110
111impl<const NBITS: usize, const MBITS: usize>
116 PureDistanceFunction<QueryMatRef<'_, MinMaxMeta<NBITS>>, MatRef<'_, MinMaxMeta<MBITS>>, f32>
117 for Chamfer
118where
119 Unsigned: Representation<NBITS> + Representation<MBITS>,
120 distances::InnerProduct: for<'a, 'b> PureDistanceFunction<
121 crate::bits::BitSlice<'a, NBITS, Unsigned>,
122 crate::bits::BitSlice<'b, MBITS, Unsigned>,
123 distances::MathematicalResult<u32>,
124 >,
125{
126 #[inline(always)]
127 fn evaluate(
128 query: QueryMatRef<'_, MinMaxMeta<NBITS>>,
129 doc: MatRef<'_, MinMaxMeta<MBITS>>,
130 ) -> f32 {
131 let mut sum = 0.0f32;
132
133 let _ = MinMaxKernel::max_sim_kernel(query, doc, |_i, score| {
134 sum += score;
135 });
136
137 sum
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::CompressInto;
145 use crate::algorithms::Transform;
146 use crate::algorithms::transforms::NullTransform;
147 use crate::bits::{Representation, Unsigned};
148 use crate::minmax::{Data, MinMaxQuantizer};
149 use crate::multi_vector::{Defaulted, Mat, Standard};
150 use crate::num::Positive;
151 use diskann_utils::ReborrowMut;
152 use std::num::NonZeroUsize;
153
154 macro_rules! expand_to_bitrates {
155 ($name:ident, $func:ident) => {
156 #[test]
157 fn $name() {
158 $func::<1, 1>();
160 $func::<2, 2>();
161 $func::<4, 4>();
162 $func::<8, 8>();
163 $func::<8, 4>();
165 $func::<8, 2>();
166 $func::<8, 1>();
167 }
168 };
169 }
170
171 const TEST_CASES: &[(usize, usize, usize)] = &[
173 (1, 1, 4), (1, 5, 8), (5, 1, 8), (3, 4, 16), (7, 7, 32), (2, 3, 128), ];
180
181 fn make_quantizer(dim: usize) -> MinMaxQuantizer {
182 MinMaxQuantizer::new(
183 Transform::Null(NullTransform::new(NonZeroUsize::new(dim).unwrap())),
184 Positive::new(1.0).unwrap(),
185 )
186 }
187
188 fn generate_input_mat(n: usize, dim: usize, offset: usize) -> Vec<f32> {
189 (0..n * dim)
190 .map(|idx| {
191 let i = idx / dim;
192 let j = idx % dim;
193 ((i + offset) * dim + j) as f32 * 0.1
194 })
195 .collect()
196 }
197
198 fn compress_mat<const NBITS: usize>(
199 quantizer: &MinMaxQuantizer,
200 input: &[f32],
201 n: usize,
202 dim: usize,
203 ) -> Mat<MinMaxMeta<NBITS>>
204 where
205 Unsigned: Representation<NBITS>,
206 {
207 let input_mat = MatRef::new(Standard::<f32>::new(n, dim).unwrap(), input).unwrap();
208 let mut output: Mat<MinMaxMeta<NBITS>> =
209 Mat::new(MinMaxMeta::new(n, dim), Defaulted).unwrap();
210 quantizer
211 .compress_into(input_mat, output.reborrow_mut())
212 .unwrap();
213 output
214 }
215
216 fn naive_max_sim_single<const NBITS: usize, const MBITS: usize>(
218 query: DataRef<'_, NBITS>,
219 doc: &MatRef<'_, MinMaxMeta<MBITS>>,
220 ) -> f32
221 where
222 Unsigned: Representation<NBITS> + Representation<MBITS>,
223 distances::InnerProduct: for<'x, 'y> PureDistanceFunction<
224 crate::bits::BitSlice<'x, NBITS, Unsigned>,
225 crate::bits::BitSlice<'y, MBITS, Unsigned>,
226 distances::MathematicalResult<u32>,
227 >,
228 {
229 doc.rows()
230 .map(|d| {
231 <MinMaxIP as PureDistanceFunction<
232 DataRef<'_, NBITS>,
233 DataRef<'_, MBITS>,
234 distances::Result<f32>,
235 >>::evaluate(query, d)
236 .unwrap()
237 })
238 .fold(f32::MAX, f32::min)
239 }
240
241 fn test_matches_naive<const NBITS: usize, const MBITS: usize>()
242 where
243 Unsigned: Representation<NBITS> + Representation<MBITS>,
244 distances::InnerProduct: for<'x, 'y> PureDistanceFunction<
245 crate::bits::BitSlice<'x, NBITS, Unsigned>,
246 crate::bits::BitSlice<'y, MBITS, Unsigned>,
247 distances::MathematicalResult<u32>,
248 >,
249 {
250 for &(nq, nd, dim) in TEST_CASES {
251 let quantizer = make_quantizer(dim);
252
253 let query_data = generate_input_mat(nq, dim, 0);
254 let doc_data = generate_input_mat(nd, dim, nq);
255
256 let query_mat = compress_mat::<NBITS>(&quantizer, &query_data, nq, dim);
257 let doc_mat = compress_mat::<MBITS>(&quantizer, &doc_data, nd, dim);
258
259 let query: QueryMatRef<_> = query_mat.as_view().into();
260 let doc = doc_mat.as_view();
261
262 let expected: Vec<f32> = query
264 .rows()
265 .map(|q| naive_max_sim_single(q, &doc))
266 .collect();
267
268 let mut scores = vec![0.0f32; nq];
269 MaxSim::new(&mut scores).evaluate(query, doc);
270
271 for (i, (&got, &exp)) in scores.iter().zip(expected.iter()).enumerate() {
272 assert!(
273 (got - exp).abs() < 1e-5,
274 "({NBITS},{MBITS}) ({nq},{nd},{dim}) MaxSim[{i}]: {got} != {exp}"
275 );
276 }
277
278 let mut kernel_scores = vec![0.0f32; nq];
280 MinMaxKernel::max_sim_kernel(query, doc, |i, s| kernel_scores[i] = s).unwrap();
281 assert_eq!(
282 scores, kernel_scores,
283 "({NBITS},{MBITS}) ({nq},{nd},{dim}) kernel mismatch"
284 );
285
286 let chamfer = Chamfer::evaluate(query, doc);
288 let sum: f32 = scores.iter().sum();
289 assert!(
290 (chamfer - sum).abs() < 1e-4,
291 "({NBITS},{MBITS}) ({nq},{nd},{dim}) Chamfer {chamfer} != sum {sum}"
292 );
293 }
294 }
295
296 expand_to_bitrates!(matches_naive, test_matches_naive);
297
298 #[test]
299 #[should_panic(expected = "scores buffer not right size")]
300 fn max_sim_panics_on_size_mismatch() {
301 let dim = 4;
302 let row_bytes = Data::<8>::canonical_bytes(dim);
303 let query_data = vec![0u8; 2 * row_bytes];
304 let doc_data = vec![0u8; 3 * row_bytes];
305
306 let query: QueryMatRef<_> = MatRef::new(MinMaxMeta::<8>::new(2, dim), &query_data)
307 .unwrap()
308 .into();
309 let doc = MatRef::new(MinMaxMeta::<8>::new(3, dim), &doc_data).unwrap();
310
311 let mut scores = vec![0.0f32; 5]; MaxSim::new(&mut scores).evaluate(query, doc);
313 }
314}