1use std::num::NonZeroUsize;
8
9use diskann_vector::distance::InnerProduct;
10use diskann_vector::{DistanceFunctionMut, PureDistanceFunction};
11use diskann_wide::Architecture;
12use diskann_wide::arch::Scalar;
13#[cfg(target_arch = "aarch64")]
14use diskann_wide::arch::aarch64::Neon;
15#[cfg(target_arch = "x86_64")]
16use diskann_wide::arch::x86_64::{V3, V4};
17
18use super::isa::{MaxSimIsa, NotSupported};
19use super::kernel::{Erase, MaxSimKernel};
20use super::max_sim::{MaxSim, MaxSimError};
21use crate::matrix_kernels as mk;
22use crate::multi_vector::distance::QueryMatRef;
23use crate::multi_vector::{BlockTransposed, Mat, MatRef, Standard};
24
25#[derive(Debug, Clone, Copy)]
33struct Pack<const NR: usize>;
34
35#[derive(Debug)]
36struct Prepared<A, Q, const NR: usize> {
37 arch: A,
38 prepared: Q,
39 _packing: Pack<NR>,
40}
41
42impl<A, const GROUP: usize, const NR: usize> MaxSimKernel<f32>
43 for Prepared<A, BlockTransposed<f32, GROUP>, NR>
44where
45 A: Architecture,
46 for<'a> mk::maxsim::packed_f32_x_unpacked_f32::Driver<'a, A, GROUP, NR>: mk::Drive,
47{
48 fn nrows(&self) -> usize {
49 self.prepared.nrows()
50 }
51
52 fn compute_max_sim(
53 &self,
54 doc: MatRef<'_, Standard<f32>>,
55 scores: &mut [f32],
56 ) -> Result<(), MaxSimError> {
57 if scores.len() != self.nrows() {
58 return Err(MaxSimError::InvalidBufferLength(scores.len(), self.nrows()));
59 }
60
61 if doc.vector_dim() != self.prepared.ncols() {
62 return Err(MaxSimError::UnequalDim(
63 doc.vector_dim(),
64 self.prepared.ncols(),
65 ));
66 }
67
68 let Some(k) = NonZeroUsize::new(self.prepared.ncols()).map(mk::DimK::new) else {
69 scores.fill(if doc.num_vectors() == 0 {
70 f32::MAX
71 } else {
72 0.0
73 });
74 return Ok(());
75 };
76
77 let Some(a) = mk::blocks::packed::View::from_block_transposed(self.prepared.as_view())
78 else {
79 return Ok(());
80 };
81
82 let Some(b) = mk::blocks::unpacked::View::from_matrix_view(doc.as_matrix_view()) else {
83 scores.fill(f32::MAX);
84 return Ok(());
85 };
86
87 let mut driver = unsafe {
91 mk::maxsim::packed_f32_x_unpacked_f32::Driver::new(
92 self.arch,
93 a,
94 b,
95 scores,
96 k,
97 mk::Cache::detect(),
98 )
99 };
100
101 mk::Drive::drive(&mut driver);
102
103 scores.iter_mut().for_each(|s| *s = -*s);
104
105 Ok(())
106 }
107}
108
109impl<A, const GROUP: usize, const NR: usize> MaxSimKernel<half::f16>
110 for Prepared<A, BlockTransposed<f32, GROUP>, NR>
111where
112 A: Architecture,
113 for<'a> mk::maxsim::packed_f32_x_unpacked_f16::Driver<'a, A, GROUP, NR>: mk::Drive,
114{
115 fn nrows(&self) -> usize {
116 self.prepared.nrows()
117 }
118
119 fn compute_max_sim(
120 &self,
121 doc: MatRef<'_, Standard<half::f16>>,
122 scores: &mut [f32],
123 ) -> Result<(), MaxSimError> {
124 if scores.len() != self.nrows() {
125 return Err(MaxSimError::InvalidBufferLength(scores.len(), self.nrows()));
126 }
127
128 if doc.vector_dim() != self.prepared.ncols() {
129 return Err(MaxSimError::UnequalDim(
130 doc.vector_dim(),
131 self.prepared.ncols(),
132 ));
133 }
134
135 let Some(k) = NonZeroUsize::new(self.prepared.ncols()).map(mk::DimK::new) else {
136 scores.fill(if doc.num_vectors() == 0 {
137 f32::MAX
138 } else {
139 0.0
140 });
141 return Ok(());
142 };
143
144 let Some(a) = mk::blocks::packed::View::from_block_transposed(self.prepared.as_view())
145 else {
146 return Ok(());
147 };
148
149 let Some(b) = mk::blocks::unpacked::View::from_matrix_view(doc.as_matrix_view()) else {
150 scores.fill(f32::MAX);
151 return Ok(());
152 };
153
154 let mut driver = unsafe {
158 mk::maxsim::packed_f32_x_unpacked_f16::Driver::new(
159 self.arch,
160 a,
161 b,
162 scores,
163 k,
164 mk::Cache::detect(),
165 )
166 };
167
168 mk::Drive::drive(&mut driver);
169
170 scores.iter_mut().for_each(|s| *s = -*s);
171
172 Ok(())
173 }
174}
175
176struct ReferenceKernel<T: Copy> {
181 query: Mat<Standard<T>>,
182}
183
184impl<T: Copy + std::fmt::Debug> std::fmt::Debug for ReferenceKernel<T> {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 f.debug_struct("ReferenceKernel")
187 .field("nrows", &self.query.num_vectors())
188 .finish()
189 }
190}
191
192impl<T: Copy> ReferenceKernel<T> {
193 fn new(query: MatRef<'_, Standard<T>>) -> Self {
194 Self {
195 query: query.to_owned(),
196 }
197 }
198}
199
200impl<T> MaxSimKernel<T> for ReferenceKernel<T>
201where
202 T: Copy + Send + Sync + std::fmt::Debug + 'static,
203 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
204{
205 fn nrows(&self) -> usize {
206 self.query.num_vectors()
207 }
208
209 fn compute_max_sim(
210 &self,
211 doc: MatRef<'_, Standard<T>>,
212 scores: &mut [f32],
213 ) -> Result<(), MaxSimError> {
214 if scores.len() != self.nrows() {
215 return Err(MaxSimError::InvalidBufferLength(scores.len(), self.nrows()));
216 }
217 if doc.vector_dim() != self.query.vector_dim() {
218 return Err(MaxSimError::UnequalDim(
219 doc.vector_dim(),
220 self.query.vector_dim(),
221 ));
222 }
223 if doc.num_vectors() == 0 {
224 scores.fill(f32::MAX);
225 return Ok(());
226 }
227 let query: QueryMatRef<'_, Standard<T>> = self.query.as_view().into();
228 let mut max_sim = MaxSim::new(scores);
229 max_sim.evaluate(query, doc)
230 }
231}
232
233struct BuildAndErase<E>(E);
238
239impl<E: Erase<f32>> diskann_wide::arch::Target1<Scalar, E::Output, MatRef<'_, Standard<f32>>>
242 for BuildAndErase<E>
243{
244 fn run(self, arch: Scalar, query: MatRef<'_, Standard<f32>>) -> E::Output {
245 let prepared = BlockTransposed::<f32, 8>::from_matrix_view(query.as_matrix_view());
246 self.0.erase(Prepared {
247 arch,
248 prepared,
249 _packing: Pack::<2>,
250 })
251 }
252}
253
254#[cfg(target_arch = "x86_64")]
255impl<E: Erase<f32>> diskann_wide::arch::Target1<V3, E::Output, MatRef<'_, Standard<f32>>>
256 for BuildAndErase<E>
257{
258 fn run(self, arch: V3, query: MatRef<'_, Standard<f32>>) -> E::Output {
259 let prepared = BlockTransposed::<f32, 16>::from_matrix_view(query.as_matrix_view());
260 self.0.erase(Prepared {
261 arch,
262 prepared,
263 _packing: Pack::<6>,
264 })
265 }
266}
267
268#[cfg(target_arch = "x86_64")]
269impl<E: Erase<f32>> diskann_wide::arch::Target1<V4, E::Output, MatRef<'_, Standard<f32>>>
270 for BuildAndErase<E>
271{
272 fn run(self, arch: V4, query: MatRef<'_, Standard<f32>>) -> E::Output {
273 let prepared = BlockTransposed::<f32, 32>::from_matrix_view(query.as_matrix_view());
274 self.0.erase(Prepared {
275 arch,
276 prepared,
277 _packing: Pack::<6>,
278 })
279 }
280}
281
282#[cfg(target_arch = "aarch64")]
283impl<E: Erase<f32>> diskann_wide::arch::Target1<Neon, E::Output, MatRef<'_, Standard<f32>>>
284 for BuildAndErase<E>
285{
286 fn run(self, arch: Neon, query: MatRef<'_, Standard<f32>>) -> E::Output {
287 let prepared = BlockTransposed::<f32, 8>::from_matrix_view(query.as_matrix_view());
288 self.0.erase(Prepared {
289 arch,
290 prepared,
291 _packing: Pack::<6>,
292 })
293 }
294}
295
296impl<E: Erase<half::f16>>
299 diskann_wide::arch::Target1<Scalar, E::Output, MatRef<'_, Standard<half::f16>>>
300 for BuildAndErase<E>
301{
302 fn run(self, arch: Scalar, query: MatRef<'_, Standard<half::f16>>) -> E::Output {
303 let prepared = BlockTransposed::<f32, 8>::from_matrix_view(
304 query
305 .as_matrix_view()
306 .map(|v| diskann_wide::cast_f16_to_f32(*v))
307 .as_view(),
308 );
309 self.0.erase(Prepared {
310 arch,
311 prepared,
312 _packing: Pack::<2>,
313 })
314 }
315}
316
317#[cfg(target_arch = "x86_64")]
318impl<E: Erase<half::f16>>
319 diskann_wide::arch::Target1<V3, E::Output, MatRef<'_, Standard<half::f16>>>
320 for BuildAndErase<E>
321{
322 fn run(self, arch: V3, query: MatRef<'_, Standard<half::f16>>) -> E::Output {
323 let prepared = BlockTransposed::<f32, 16>::from_matrix_view(
324 query
325 .as_matrix_view()
326 .map(|v| diskann_wide::cast_f16_to_f32(*v))
327 .as_view(),
328 );
329 self.0.erase(Prepared {
330 arch,
331 prepared,
332 _packing: Pack::<6>,
333 })
334 }
335}
336
337#[cfg(target_arch = "x86_64")]
338impl<E: Erase<half::f16>>
339 diskann_wide::arch::Target1<V4, E::Output, MatRef<'_, Standard<half::f16>>>
340 for BuildAndErase<E>
341{
342 fn run(self, arch: V4, query: MatRef<'_, Standard<half::f16>>) -> E::Output {
343 let prepared = BlockTransposed::<f32, 32>::from_matrix_view(
344 query
345 .as_matrix_view()
346 .map(|v| diskann_wide::cast_f16_to_f32(*v))
347 .as_view(),
348 );
349 self.0.erase(Prepared {
350 arch,
351 prepared,
352 _packing: Pack::<6>,
353 })
354 }
355}
356
357#[cfg(target_arch = "aarch64")]
358impl<E: Erase<half::f16>>
359 diskann_wide::arch::Target1<Neon, E::Output, MatRef<'_, Standard<half::f16>>>
360 for BuildAndErase<E>
361{
362 fn run(self, arch: Neon, query: MatRef<'_, Standard<half::f16>>) -> E::Output {
363 let prepared = BlockTransposed::<f32, 8>::from_matrix_view(
365 query
366 .as_matrix_view()
367 .map(|v| diskann_wide::cast_f16_to_f32(*v))
368 .as_view(),
369 );
370 self.0.erase(Prepared {
371 arch,
372 prepared,
373 _packing: Pack::<6>,
374 })
375 }
376}
377
378mod sealed {
383 pub trait Sealed {}
384}
385
386pub trait MaxSimElement: sealed::Sealed + Sized + Copy + Send + Sync + 'static {
392 fn build<E: Erase<Self>>(
400 isa: MaxSimIsa,
401 query: MatRef<'_, Standard<Self>>,
402 erase: E,
403 ) -> Result<E::Output, NotSupported>;
404}
405
406impl sealed::Sealed for f32 {}
407impl sealed::Sealed for half::f16 {}
408
409impl MaxSimElement for f32 {
410 fn build<E: Erase<f32>>(
411 isa: MaxSimIsa,
412 query: MatRef<'_, Standard<f32>>,
413 erase: E,
414 ) -> Result<E::Output, NotSupported> {
415 match isa {
416 MaxSimIsa::Auto => Ok(diskann_wide::arch::dispatch1_no_features(
417 BuildAndErase(erase),
418 query,
419 )),
420 MaxSimIsa::Scalar => Ok(Scalar::new().run1(BuildAndErase(erase), query)),
421 #[cfg(target_arch = "x86_64")]
422 MaxSimIsa::X86_64_V3 => {
423 let arch = V3::new_checked().ok_or(NotSupported {
424 isa,
425 reason: "AVX2/FMA unavailable on this CPU",
426 })?;
427 Ok(arch.run1(BuildAndErase(erase), query))
428 }
429 #[cfg(target_arch = "x86_64")]
430 MaxSimIsa::X86_64_V4 => {
431 let arch = V4::new_checked().ok_or(NotSupported {
432 isa,
433 reason: "AVX-512 unavailable on this CPU",
434 })?;
435 Ok(arch.run1(BuildAndErase(erase), query))
436 }
437 #[cfg(not(target_arch = "x86_64"))]
438 MaxSimIsa::X86_64_V3 | MaxSimIsa::X86_64_V4 => Err(NotSupported {
439 isa,
440 reason: "x86_64 target only",
441 }),
442 #[cfg(target_arch = "aarch64")]
443 MaxSimIsa::Neon => {
444 let arch = Neon::new_checked().ok_or(NotSupported {
445 isa,
446 reason: "Neon unavailable on this CPU",
447 })?;
448 Ok(arch.run1(BuildAndErase(erase), query))
449 }
450 #[cfg(not(target_arch = "aarch64"))]
451 MaxSimIsa::Neon => Err(NotSupported {
452 isa,
453 reason: "aarch64 target only",
454 }),
455 MaxSimIsa::Reference => Ok(erase.erase(ReferenceKernel::<f32>::new(query))),
456 }
457 }
458}
459
460impl MaxSimElement for half::f16 {
461 fn build<E: Erase<half::f16>>(
462 isa: MaxSimIsa,
463 query: MatRef<'_, Standard<half::f16>>,
464 erase: E,
465 ) -> Result<E::Output, NotSupported> {
466 match isa {
467 MaxSimIsa::Auto => Ok(diskann_wide::arch::dispatch1_no_features(
468 BuildAndErase(erase),
469 query,
470 )),
471 MaxSimIsa::Scalar => Ok(Scalar::new().run1(BuildAndErase(erase), query)),
472 #[cfg(target_arch = "x86_64")]
473 MaxSimIsa::X86_64_V3 => {
474 let arch = V3::new_checked().ok_or(NotSupported {
475 isa,
476 reason: "AVX2/FMA unavailable on this CPU",
477 })?;
478 Ok(arch.run1(BuildAndErase(erase), query))
479 }
480 #[cfg(target_arch = "x86_64")]
481 MaxSimIsa::X86_64_V4 => {
482 let arch = V4::new_checked().ok_or(NotSupported {
483 isa,
484 reason: "AVX-512 unavailable on this CPU",
485 })?;
486 Ok(arch.run1(BuildAndErase(erase), query))
487 }
488 #[cfg(not(target_arch = "x86_64"))]
489 MaxSimIsa::X86_64_V3 | MaxSimIsa::X86_64_V4 => Err(NotSupported {
490 isa,
491 reason: "x86_64 target only",
492 }),
493 #[cfg(target_arch = "aarch64")]
494 MaxSimIsa::Neon => {
495 let arch = Neon::new_checked().ok_or(NotSupported {
496 isa,
497 reason: "Neon unavailable on this CPU",
498 })?;
499 Ok(arch.run1(BuildAndErase(erase), query))
500 }
501 #[cfg(not(target_arch = "aarch64"))]
502 MaxSimIsa::Neon => Err(NotSupported {
503 isa,
504 reason: "aarch64 target only",
505 }),
506 MaxSimIsa::Reference => Ok(erase.erase(ReferenceKernel::<half::f16>::new(query))),
507 }
508 }
509}
510
511pub fn build_max_sim<T: MaxSimElement, E: Erase<T>>(
524 isa: MaxSimIsa,
525 query: MatRef<'_, Standard<T>>,
526 erase: E,
527) -> Result<E::Output, NotSupported> {
528 T::build(isa, query, erase)
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use crate::multi_vector::{BoxErase, Chamfer, MaxSim, QueryMatRef};
535
536 trait FromF32 {
540 fn from_f32(v: f32) -> Self;
541 }
542
543 impl FromF32 for f32 {
544 fn from_f32(v: f32) -> Self {
545 v
546 }
547 }
548
549 impl FromF32 for half::f16 {
550 fn from_f32(v: f32) -> Self {
551 diskann_wide::cast_f32_to_f16(v)
552 }
553 }
554
555 fn make_mat<T: Copy>(data: &[T], nrows: usize, ncols: usize) -> MatRef<'_, Standard<T>> {
556 MatRef::new(Standard::new(nrows, ncols).unwrap(), data).unwrap()
557 }
558
559 fn make_test_data<T: FromF32>(len: usize, ceil: usize, shift: usize) -> Vec<T> {
560 (0..len)
561 .map(|v| T::from_f32(((v + shift) % ceil) as f32))
562 .collect()
563 }
564
565 const TEST_CASES: &[(usize, usize, usize)] = &[
572 (0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 0, 0), (1, 1, 4), (5, 3, 5), (17, 4, 64), (16, 6, 32), ];
581
582 fn check_chamfer_matches<T>(tol: f32, label: &str)
583 where
584 T: MaxSimElement + FromF32,
585 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
586 {
587 for &(nq, nd, dim) in TEST_CASES {
588 let query_data = make_test_data::<T>(nq * dim, dim, dim / 2);
589 let doc_data = make_test_data::<T>(nd * dim, dim, dim);
590
591 let query = make_mat(&query_data, nq, dim);
592 let doc = make_mat(&doc_data, nd, dim);
593
594 let expected = Chamfer::evaluate(QueryMatRef::from(query), doc);
595
596 let kernel = build_max_sim::<T, _>(MaxSimIsa::Auto, query, BoxErase).unwrap();
597 let mut scores = vec![0.0f32; nq];
598 kernel.compute_max_sim(doc, &mut scores).unwrap();
599 let actual: f32 = scores.iter().sum();
600
601 assert!(
602 (actual - expected).abs() < tol,
603 "{label}Chamfer mismatch for ({nq},{nd},{dim}): actual={actual}, expected={expected}",
604 );
605 }
606 }
607
608 fn check_max_sim_matches<T>(tol: f32, label: &str)
609 where
610 T: MaxSimElement + FromF32,
611 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
612 {
613 for &(nq, nd, dim) in TEST_CASES {
614 let query_data = make_test_data::<T>(nq * dim, dim, dim / 2);
615 let doc_data = make_test_data::<T>(nd * dim, dim, dim);
616
617 let query = make_mat(&query_data, nq, dim);
618 let doc = make_mat(&doc_data, nd, dim);
619
620 let mut expected_scores = vec![0.0f32; nq];
621 let _ = MaxSim::new(&mut expected_scores).evaluate(QueryMatRef::from(query), doc);
622
623 let kernel = build_max_sim::<T, _>(MaxSimIsa::Auto, query, BoxErase).unwrap();
624 let mut actual_scores = vec![0.0f32; nq];
625 kernel.compute_max_sim(doc, &mut actual_scores).unwrap();
626
627 for i in 0..nq {
628 assert!(
629 (actual_scores[i] - expected_scores[i]).abs() < tol,
630 "{label}MaxSim[{i}] mismatch for ({nq},{nd},{dim}): actual={}, expected={}",
631 actual_scores[i],
632 expected_scores[i],
633 );
634 }
635 }
636 }
637
638 #[test]
639 fn dimensions_f32() {
640 let data = vec![1.0f32; 5 * 8];
641 let query = make_mat(&data, 5, 8);
642 let kernel = build_max_sim::<f32, _>(MaxSimIsa::Auto, query, BoxErase).unwrap();
643 assert_eq!(kernel.nrows(), 5);
644 }
645
646 #[test]
647 fn dimensions_f16() {
648 let data = vec![diskann_wide::cast_f32_to_f16(1.0); 5 * 8];
649 let query = make_mat(data.as_slice(), 5, 8);
650 let kernel = build_max_sim::<half::f16, _>(MaxSimIsa::Auto, query, BoxErase).unwrap();
651 assert_eq!(kernel.nrows(), 5);
652 }
653
654 fn check_size_mismatch<T>(label: &str)
655 where
656 T: MaxSimElement + FromF32,
657 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
658 {
659 let query_data = make_test_data::<T>(3 * 4, 4, 0);
660 let doc_data = make_test_data::<T>(2 * 4, 4, 1);
661 let query = make_mat(&query_data, 3, 4);
662 let doc = make_mat(&doc_data, 2, 4);
663
664 for isa in [MaxSimIsa::Auto, MaxSimIsa::Reference] {
665 let kernel = build_max_sim::<T, _>(isa, query, BoxErase).unwrap();
666
667 let mut too_short = vec![0.0f32; 2];
668 match kernel.compute_max_sim(doc, &mut too_short) {
669 Err(MaxSimError::InvalidBufferLength(2, 3)) => {}
670 other => {
671 panic!("{label}({isa:?}) expected InvalidBufferLength(2, 3), got {other:?}",)
672 }
673 }
674
675 let mut too_long = vec![0.0f32; 4];
676 match kernel.compute_max_sim(doc, &mut too_long) {
677 Err(MaxSimError::InvalidBufferLength(4, 3)) => {}
678 other => {
679 panic!("{label}({isa:?}) expected InvalidBufferLength(4, 3), got {other:?}",)
680 }
681 }
682 }
683 }
684
685 fn check_zero_docs_fills_sentinel<T>(label: &str)
686 where
687 T: MaxSimElement + FromF32,
688 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
689 {
690 let query_data = make_test_data::<T>(3 * 4, 4, 0);
691 let doc_data: Vec<T> = Vec::new();
692 let query = make_mat(&query_data, 3, 4);
693 let doc = make_mat(doc_data.as_slice(), 0, 4);
694
695 for isa in [MaxSimIsa::Auto, MaxSimIsa::Reference] {
696 let kernel = build_max_sim::<T, _>(isa, query, BoxErase).unwrap();
697 let mut scores = vec![0.0f32; 3];
698 kernel.compute_max_sim(doc, &mut scores).unwrap();
699 for (i, &s) in scores.iter().enumerate() {
700 assert_eq!(
701 s,
702 f32::MAX,
703 "{label}({isa:?}) zero-doc slot {i} should be f32::MAX sentinel",
704 );
705 }
706 }
707 }
708
709 fn check_zero_query<T>(label: &str)
710 where
711 T: MaxSimElement + FromF32,
712 InnerProduct: for<'a, 'b> PureDistanceFunction<&'a [T], &'b [T], f32>,
713 {
714 let query_data: Vec<T> = Vec::new();
715 let doc_data = make_test_data::<T>(2 * 4, 4, 0);
716 let query = make_mat(query_data.as_slice(), 0, 4);
717 let doc = make_mat(&doc_data, 2, 4);
718
719 for isa in [MaxSimIsa::Auto, MaxSimIsa::Reference] {
720 let kernel = build_max_sim::<T, _>(isa, query, BoxErase).unwrap();
721 assert_eq!(
722 kernel.nrows(),
723 0,
724 "{label}({isa:?}) empty query should yield nrows=0",
725 );
726 let mut scores: Vec<f32> = Vec::new();
727 kernel
728 .compute_max_sim(doc, &mut scores)
729 .unwrap_or_else(|e| panic!("{label}({isa:?}) expected Ok, got {e:?}"));
730 }
731 }
732
733 macro_rules! test_matches_fallback {
734 ($mod_name:ident, $ty:ty, $tol:expr, $label:literal) => {
735 mod $mod_name {
736 use super::*;
737
738 #[test]
739 fn chamfer_matches_fallback() {
740 check_chamfer_matches::<$ty>($tol, $label);
741 }
742
743 #[test]
744 fn max_sim_matches_fallback() {
745 check_max_sim_matches::<$ty>($tol, $label);
746 }
747
748 #[test]
749 fn errors_on_size_mismatch() {
750 check_size_mismatch::<$ty>($label);
751 }
752
753 #[test]
754 fn zero_docs_fills_sentinel() {
755 check_zero_docs_fills_sentinel::<$ty>($label);
756 }
757
758 #[test]
759 fn zero_query_returns_ok() {
760 check_zero_query::<$ty>($label);
761 }
762 }
763 };
764 }
765
766 test_matches_fallback!(f32, f32, 1e-10, "f32 ");
767 test_matches_fallback!(f16, half::f16, 1e-10, "f16 ");
768}