1use std::ptr::NonNull;
9
10use super::super::MinMaxQuantizer;
11use super::super::vectors::DataMutRef;
12use crate::CompressInto;
13use crate::bits::{Representation, Unsigned};
14use crate::minmax::{self, Data};
15use crate::multi_vector::matrix::{
16 Defaulted, NewMut, NewOwned, NewRef, Repr, ReprMut, ReprOwned, SliceError,
17};
18use crate::multi_vector::{LayoutError, Mat, MatMut, MatRef, Standard};
19use crate::scalar::InputContainsNaN;
20use crate::utils;
21
22#[derive(Debug, Clone, Copy)]
32pub struct MinMaxMeta<const NBITS: usize> {
33 nrows: usize,
34 intrinsic_dim: usize,
35}
36
37impl<const NBITS: usize> MinMaxMeta<NBITS>
38where
39 Unsigned: Representation<NBITS>,
40{
41 pub fn new(nrows: usize, intrinsic_dim: usize) -> Self {
43 Self {
44 nrows,
45 intrinsic_dim,
46 }
47 }
48
49 pub fn intrinsic_dim(&self) -> usize {
51 self.intrinsic_dim
52 }
53
54 pub fn ncols(&self) -> usize {
57 Data::<NBITS>::canonical_bytes(self.intrinsic_dim)
58 }
59
60 fn bytes(&self) -> usize {
61 std::mem::size_of::<u8>() * self.nrows() * self.ncols()
62 }
63}
64
65unsafe impl<const NBITS: usize> Repr for MinMaxMeta<NBITS>
70where
71 Unsigned: Representation<NBITS>,
72{
73 type Row<'a> = crate::minmax::DataRef<'a, NBITS>;
74
75 fn nrows(&self) -> usize {
76 self.nrows
77 }
78
79 fn layout(&self) -> Result<std::alloc::Layout, LayoutError> {
85 Ok(std::alloc::Layout::array::<u8>(
86 self.nrows() * self.ncols(),
87 )?)
88 }
89
90 unsafe fn get_row<'a>(self, ptr: NonNull<u8>, i: usize) -> Self::Row<'a> {
97 debug_assert!(i < self.nrows);
98 let len = self.ncols();
99
100 unsafe {
104 let row_ptr = ptr.as_ptr().add(i * len);
105 let slice = std::slice::from_raw_parts(row_ptr, len);
106
107 minmax::DataRef::<'a, NBITS>::from_canonical_unchecked(slice, self.intrinsic_dim)
108 }
109 }
110}
111
112unsafe impl<const NBITS: usize> ReprMut for MinMaxMeta<NBITS>
115where
116 Unsigned: Representation<NBITS>,
117{
118 type RowMut<'a> = crate::minmax::DataMutRef<'a, NBITS>;
119
120 unsafe fn get_row_mut<'a>(self, ptr: NonNull<u8>, i: usize) -> Self::RowMut<'a> {
128 debug_assert!(i < self.nrows);
129 let len = self.ncols();
130
131 unsafe {
136 let row_ptr = ptr.as_ptr().add(i * len);
137 let slice = std::slice::from_raw_parts_mut(row_ptr, len);
138
139 minmax::DataMutRef::<'a, NBITS>::from_canonical_front_mut_unchecked(
140 slice,
141 self.intrinsic_dim,
142 )
143 }
144 }
145}
146
147unsafe impl<const NBITS: usize> ReprOwned for MinMaxMeta<NBITS>
151where
152 Unsigned: Representation<NBITS>,
153{
154 unsafe fn drop(self, ptr: NonNull<u8>) {
159 let slice_ptr = std::ptr::slice_from_raw_parts_mut(ptr.as_ptr(), self.bytes());
160
161 let _ = unsafe { Box::from_raw(slice_ptr) };
164 }
165}
166
167unsafe impl<const NBITS: usize> NewOwned<Defaulted> for MinMaxMeta<NBITS>
171where
172 Unsigned: Representation<NBITS>,
173{
174 type Error = crate::error::Infallible;
175 fn new_owned(self, _: Defaulted) -> Result<Mat<Self>, Self::Error> {
176 let b: Box<[u8]> = (0..self.bytes()).map(|_| u8::default()).collect();
177
178 let ptr = utils::box_into_nonnull(b).cast::<u8>();
179
180 let mat = unsafe { Mat::from_raw_parts(self, ptr) };
183 Ok(mat)
184 }
185}
186
187unsafe impl<const NBITS: usize> NewRef<u8> for MinMaxMeta<NBITS>
190where
191 Unsigned: Representation<NBITS>,
192{
193 type Error = SliceError;
194
195 fn new_ref(self, slice: &[u8]) -> Result<MatRef<'_, Self>, Self::Error> {
196 let expected = self.bytes();
197 if slice.len() != expected {
198 return Err(SliceError::LengthMismatch {
199 expected,
200 found: slice.len(),
201 });
202 }
203
204 Ok(unsafe { MatRef::from_raw_parts(self, utils::as_nonnull(slice).cast::<u8>()) })
206 }
207}
208
209unsafe impl<const NBITS: usize> NewMut<u8> for MinMaxMeta<NBITS>
212where
213 Unsigned: Representation<NBITS>,
214{
215 type Error = SliceError;
216
217 fn new_mut(self, slice: &mut [u8]) -> Result<MatMut<'_, Self>, Self::Error> {
218 let expected = self.bytes();
219 if slice.len() != expected {
220 return Err(SliceError::LengthMismatch {
221 expected,
222 found: slice.len(),
223 });
224 }
225
226 Ok(unsafe { MatMut::from_raw_parts(self, utils::as_nonnull_mut(slice).cast::<u8>()) })
228 }
229}
230
231impl<'a, 'b, const NBITS: usize, T>
236 CompressInto<MatRef<'a, Standard<T>>, MatMut<'b, MinMaxMeta<NBITS>>> for MinMaxQuantizer
237where
238 T: Copy + Into<f32>,
239 Unsigned: Representation<NBITS>,
240{
241 type Error = InputContainsNaN;
242
243 type Output = ();
244
245 fn compress_into(
261 &self,
262 from: MatRef<'a, Standard<T>>,
263 mut to: MatMut<'b, MinMaxMeta<NBITS>>,
264 ) -> Result<(), Self::Error> {
265 assert_eq!(
266 from.num_vectors(),
267 to.num_vectors(),
268 "input and output must have the same number of vectors: {} != {}",
269 from.num_vectors(),
270 to.num_vectors()
271 );
272 assert_eq!(
273 from.vector_dim(),
274 self.dim(),
275 "input vectors must match quantizer dimension: {} != {}",
276 from.vector_dim(),
277 self.dim()
278 );
279 assert_eq!(
280 to.repr().intrinsic_dim(),
281 self.output_dim(),
282 "output intrinsic dimension must match quantizer output dimension: {} != {}",
283 to.repr().intrinsic_dim(),
284 self.output_dim()
285 );
286
287 for (from_row, to_row) in from.rows().zip(to.rows_mut()) {
288 let _ = <MinMaxQuantizer as CompressInto<&[T], DataMutRef<'_, NBITS>>>::compress_into(
290 self, from_row, to_row,
291 )?;
292 }
293
294 Ok(())
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::algorithms::Transform;
302 use crate::algorithms::transforms::NullTransform;
303 use crate::minmax::vectors::DataRef;
304 use crate::num::Positive;
305 use diskann_utils::{Reborrow, ReborrowMut};
306 use std::num::NonZeroUsize;
307
308 const TEST_DIMS: &[usize] = &[1, 2, 3, 4, 7, 8, 16, 31, 32, 64];
310 const TEST_NVECS: &[usize] = &[1, 2, 3, 5, 10];
312
313 macro_rules! expand_to_bitrates {
315 ($name:ident, $func:ident) => {
316 #[test]
317 fn $name() {
318 $func::<1>();
319 $func::<2>();
320 $func::<4>();
321 $func::<8>();
322 }
323 };
324 }
325
326 fn make_quantizer(dim: usize) -> MinMaxQuantizer {
332 MinMaxQuantizer::new(
333 Transform::Null(NullTransform::new(NonZeroUsize::new(dim).unwrap())),
334 Positive::new(1.0).unwrap(),
335 )
336 }
337
338 fn generate_test_data(num_vectors: usize, dim: usize) -> Vec<f32> {
340 (0..num_vectors * dim)
341 .map(|i| {
342 let row = i / dim;
343 let col = i % dim;
344 ((row as f32 + 1.0) * (col as f32 + 0.5)).sin() * 10.0 + (row as f32)
346 })
347 .collect()
348 }
349
350 fn compress_single_vector<const NBITS: usize>(
352 quantizer: &MinMaxQuantizer,
353 input: &[f32],
354 dim: usize,
355 ) -> Vec<u8>
356 where
357 Unsigned: Representation<NBITS>,
358 {
359 let row_bytes = Data::<NBITS>::canonical_bytes(dim);
360 let mut output = vec![0u8; row_bytes];
361 let output_ref =
362 crate::minmax::DataMutRef::<NBITS>::from_canonical_front_mut(&mut output, dim).unwrap();
363 let _ = <MinMaxQuantizer as CompressInto<&[f32], DataMutRef<'_, NBITS>>>::compress_into(
364 quantizer, input, output_ref,
365 )
366 .expect("single-vector compression should succeed");
367 output
368 }
369
370 mod construction {
375 use super::*;
376
377 fn test_new_owned<const NBITS: usize>()
379 where
380 Unsigned: Representation<NBITS>,
381 {
382 for &dim in TEST_DIMS {
383 for &num_vectors in TEST_NVECS {
384 let meta = MinMaxMeta::<NBITS>::new(num_vectors, dim);
385 let mat: Mat<MinMaxMeta<NBITS>> =
386 Mat::new(meta, Defaulted).expect("NewOwned should succeed");
387
388 assert_eq!(mat.num_vectors(), num_vectors);
390 assert_eq!(mat.repr().intrinsic_dim(), dim);
391
392 let expected_row_bytes = Data::<NBITS>::canonical_bytes(dim);
394 assert_eq!(mat.repr().nrows(), num_vectors);
395 assert_eq!(mat.repr().ncols(), expected_row_bytes);
396
397 let expected_bytes = expected_row_bytes * num_vectors;
399 let layout = mat.repr().layout().expect("layout should succeed");
400 assert_eq!(layout.size(), expected_bytes);
401
402 for i in 0..num_vectors {
404 let row = mat.get_row(i);
405 assert!(row.is_some(), "row {i} should exist");
406 assert_eq!(row.unwrap().len(), dim);
407 }
408
409 assert!(mat.get_row(num_vectors).is_none());
411 }
412 }
413 }
414
415 expand_to_bitrates!(new_owned, test_new_owned);
416
417 fn test_new_ref<const NBITS: usize>()
419 where
420 Unsigned: Representation<NBITS>,
421 {
422 for &dim in TEST_DIMS {
423 for &num_vectors in TEST_NVECS {
424 let meta = MinMaxMeta::<NBITS>::new(num_vectors, dim);
425 let expected_row_bytes = Data::<NBITS>::canonical_bytes(dim);
426 let expected_bytes = expected_row_bytes * num_vectors;
427
428 let data = vec![0u8; expected_bytes];
430 let mat_ref = MatRef::new(meta, &data);
431 assert!(mat_ref.is_ok(), "NewRef should succeed for correct size");
432 let mat_ref = mat_ref.unwrap();
433
434 assert_eq!(mat_ref.num_vectors(), num_vectors);
436 assert_eq!(mat_ref.repr().intrinsic_dim(), dim);
437
438 assert_eq!(mat_ref.repr().nrows(), num_vectors);
440 assert_eq!(mat_ref.repr().ncols(), expected_row_bytes);
441
442 let layout = mat_ref.repr().layout().expect("layout should succeed");
444 assert_eq!(layout.size(), expected_bytes);
445 }
446 }
447 }
448
449 expand_to_bitrates!(new_ref, test_new_ref);
450
451 fn test_new_mut<const NBITS: usize>()
453 where
454 Unsigned: Representation<NBITS>,
455 {
456 for &dim in TEST_DIMS {
457 for &num_vectors in TEST_NVECS {
458 let meta = MinMaxMeta::<NBITS>::new(num_vectors, dim);
459 let expected_row_bytes = Data::<NBITS>::canonical_bytes(dim);
460 let expected_bytes = expected_row_bytes * num_vectors;
461
462 let mut data = vec![0u8; expected_bytes];
463 let mat_mut = MatMut::new(meta, &mut data);
464 assert!(mat_mut.is_ok(), "NewMut should succeed for correct size");
465 let mat_mut = mat_mut.unwrap();
466
467 assert_eq!(mat_mut.num_vectors(), num_vectors);
469 assert_eq!(mat_mut.repr().intrinsic_dim(), dim);
470
471 assert_eq!(mat_mut.repr().nrows(), num_vectors);
473 assert_eq!(mat_mut.repr().ncols(), expected_row_bytes);
474
475 let layout = mat_mut.repr().layout().expect("layout should succeed");
477 assert_eq!(layout.size(), expected_bytes);
478 }
479 }
480 }
481
482 expand_to_bitrates!(new_mut, test_new_mut);
483
484 #[test]
485 fn slice_length_mismatch_errors() {
486 let dim = 4;
487 let num_vectors = 2;
488 let meta = MinMaxMeta::<8>::new(num_vectors, dim);
489 let expected_bytes = DataRef::<8>::canonical_bytes(dim) * num_vectors;
490
491 let short_data = vec![0u8; expected_bytes - 1];
493 let result = MatRef::new(meta, &short_data);
494 assert!(
495 matches!(result, Err(SliceError::LengthMismatch { .. })),
496 "should fail for too-short slice"
497 );
498
499 let long_data = vec![0u8; expected_bytes + 1];
501 let result = MatRef::new(meta, &long_data);
502 assert!(
503 matches!(result, Err(SliceError::LengthMismatch { .. })),
504 "should fail for too-long slice"
505 );
506
507 let mut short_mut = vec![0u8; expected_bytes - 1];
509 let result = MatMut::new(meta, &mut short_mut);
510 assert!(
511 matches!(result, Err(SliceError::LengthMismatch { .. })),
512 "MatMut should fail for too-short slice"
513 );
514 }
515 }
516
517 mod compress_into {
522 use super::*;
523
524 fn test_compress_matches_single<const NBITS: usize>()
527 where
528 Unsigned: Representation<NBITS>,
529 {
530 for &dim in TEST_DIMS {
531 for &num_vectors in TEST_NVECS {
532 let quantizer = make_quantizer(dim);
533 let input_data = generate_test_data(num_vectors, dim);
534
535 let input_view =
537 MatRef::new(Standard::new(num_vectors, dim).unwrap(), &input_data)
538 .expect("input view creation");
539
540 let mut multi_mat: Mat<MinMaxMeta<NBITS>> =
541 Mat::new(MinMaxMeta::new(num_vectors, dim), Defaulted)
542 .expect("output mat creation");
543
544 quantizer
545 .compress_into(input_view, multi_mat.reborrow_mut())
546 .expect("multi-vector compression");
547
548 for i in 0..num_vectors {
550 let row_input = &input_data[i * dim..(i + 1) * dim];
551 let expected_bytes =
552 compress_single_vector::<NBITS>(&quantizer, row_input, dim);
553
554 let actual_row = multi_mat.get_row(i).expect("row should exist");
555
556 let expected_ref = unsafe {
559 DataRef::<NBITS>::from_canonical_unchecked(&expected_bytes, dim)
560 };
561 assert_eq!(
562 actual_row.meta(),
563 expected_ref.meta(),
564 "metadata mismatch at row {i}, dim={dim}, num_vectors={num_vectors}, NBITS={NBITS}"
565 );
566
567 for j in 0..dim {
569 assert_eq!(
570 actual_row.vector().get(j).unwrap(),
571 expected_ref.vector().get(j).unwrap(),
572 "quantized value mismatch at row {i}, col {j}"
573 );
574 }
575 }
576 }
577 }
578 }
579
580 expand_to_bitrates!(compress_matches_single, test_compress_matches_single);
581
582 fn test_row_iteration<const NBITS: usize>()
584 where
585 Unsigned: Representation<NBITS>,
586 {
587 let dim = 8;
588 let num_vectors = 5;
589 let quantizer = make_quantizer(dim);
590 let input_data = generate_test_data(num_vectors, dim);
591
592 let input_view = MatRef::new(Standard::new(num_vectors, dim).unwrap(), &input_data)
593 .expect("input view");
594
595 let mut mat: Mat<MinMaxMeta<NBITS>> =
596 Mat::new(MinMaxMeta::new(num_vectors, dim), Defaulted).expect("mat creation");
597
598 quantizer
599 .compress_into(input_view, mat.reborrow_mut())
600 .expect("compression");
601
602 let view = mat.reborrow();
604 let mut count = 0;
605 for row in view.rows() {
606 assert_eq!(row.len(), dim, "row should have correct dimension");
607 count += 1;
608 }
609 assert_eq!(count, num_vectors);
610 }
611
612 expand_to_bitrates!(row_iteration, test_row_iteration);
613 }
614
615 mod error_cases {
620 use super::*;
621
622 #[test]
623 #[should_panic(expected = "input and output must have the same number of vectors")]
624 fn compress_into_vector_count_mismatch() {
625 const NBITS: usize = 8;
626 let dim = 4;
627 let quantizer = make_quantizer(dim);
628
629 let input_data = generate_test_data(3, dim);
631 let input_view =
632 MatRef::new(Standard::new(3, dim).unwrap(), &input_data).expect("input view");
633
634 let mut mat: Mat<MinMaxMeta<NBITS>> =
636 Mat::new(MinMaxMeta::new(2, dim), Defaulted).expect("mat creation");
637
638 let _ = quantizer.compress_into(input_view, mat.reborrow_mut());
639 }
640
641 #[test]
642 #[should_panic(expected = "input vectors must match quantizer dimension")]
643 fn compress_into_input_dim_mismatch() {
644 const NBITS: usize = 8;
645 let quantizer = make_quantizer(4); let input_data = generate_test_data(2, 8);
649 let input_view =
650 MatRef::new(Standard::new(2, 8).unwrap(), &input_data).expect("input view");
651
652 let mut mat: Mat<MinMaxMeta<NBITS>> =
654 Mat::new(MinMaxMeta::new(2, 4), Defaulted).expect("mat creation");
655
656 let _ = quantizer.compress_into(input_view, mat.reborrow_mut());
657 }
658
659 #[test]
660 #[should_panic(
661 expected = "output intrinsic dimension must match quantizer output dimension"
662 )]
663 fn compress_into_output_dim_mismatch() {
664 const NBITS: usize = 8;
665 let quantizer = make_quantizer(4);
666
667 let input_data = generate_test_data(2, 4);
669 let input_view =
670 MatRef::new(Standard::new(2, 4).unwrap(), &input_data).expect("input view");
671
672 let row_bytes = Data::<NBITS>::canonical_bytes(8);
674 let mut output_data = vec![0u8; 2 * row_bytes];
675 let output_view =
676 MatMut::new(MinMaxMeta::<NBITS>::new(2, 8), &mut output_data).expect("output view");
677
678 let _ = quantizer.compress_into(input_view, output_view);
679 }
680 }
681}