1use std::sync::OnceLock;
35use wide::{f32x4, f64x2};
36
37#[cfg(target_arch = "aarch64")]
38use std::arch::aarch64::*;
39#[cfg(target_arch = "x86_64")]
40use std::arch::x86_64::*;
41
42pub const UNROLL: usize = 4;
55
56#[derive(Clone, Copy, Debug)]
58pub enum SimdLevel {
59 Scalar,
61 Sse,
63 Avx2,
65 Avx512,
67}
68
69static SIMD_LEVEL: OnceLock<SimdLevel> = OnceLock::new();
71
72pub fn detect_simd_level() -> SimdLevel {
83 *SIMD_LEVEL.get_or_init(|| {
84 #[cfg(target_arch = "x86_64")]
85 {
86 if is_x86_feature_detected!("avx512f") {
87 return SimdLevel::Avx512;
88 }
89 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
90 return SimdLevel::Avx2;
91 }
92 if is_x86_feature_detected!("sse4.1") {
93 return SimdLevel::Sse;
94 }
95 return SimdLevel::Scalar;
96 }
97
98 #[cfg(target_arch = "aarch64")]
99 {
100 SimdLevel::Sse
102 }
103
104 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
105 {
106 SimdLevel::Scalar
107 }
108 })
109}
110
111#[cfg(target_arch = "x86_64")]
128#[target_feature(enable = "avx")]
129#[inline]
130unsafe fn hsum_avx_f32(v: __m256) -> f32 {
131 unsafe {
132 let mut tmp = [0.0f32; 8];
133 _mm256_storeu_ps(tmp.as_mut_ptr(), v);
134 tmp.iter().sum()
135 }
136}
137
138#[cfg(target_arch = "x86_64")]
148#[target_feature(enable = "avx")]
149#[inline]
150pub unsafe fn hsum_avx_f64(v: __m256d) -> f64 {
151 unsafe {
152 let mut tmp = [0.0f64; 4];
153 _mm256_storeu_pd(tmp.as_mut_ptr(), v);
154 tmp.iter().sum()
155 }
156}
157
158#[cfg(target_arch = "x86_64")]
168#[target_feature(enable = "sse2")]
169#[inline]
170pub unsafe fn hsum_sse_f64(v: __m128d) -> f64 {
171 unsafe {
172 let mut tmp = [0.0f64; 2];
173 _mm_storeu_pd(tmp.as_mut_ptr(), v);
174 tmp[0] + tmp[1]
175 }
176}
177
178#[inline(always)]
196fn sum_squares_scalar_f32(a: &[f32]) -> f32 {
197 a.iter().map(|&x| x * x).sum()
198}
199
200#[inline(always)]
210fn sum_squares_sse_f32(a: &[f32]) -> f32 {
211 const W: usize = 4;
212 const BLOCK: usize = W * UNROLL;
213
214 let len = a.len();
215 let mut acc = [f32x4::ZERO; UNROLL];
216
217 unsafe {
220 let a_ptr = a.as_ptr();
221
222 let n_blocks = len / BLOCK;
223 for i in 0..n_blocks {
224 let base = i * BLOCK;
225 for (u, acc) in acc.iter_mut().enumerate() {
226 let off = base + u * W;
227 let va = f32x4::from(*(a_ptr.add(off) as *const [f32; W]));
228 *acc += va * va;
229 }
230 }
231
232 let mut total = acc[0];
233 for acc in &acc[1..] {
234 total += *acc;
235 }
236
237 let mut i = n_blocks * BLOCK;
238 while i + W <= len {
239 let va = f32x4::from(*(a_ptr.add(i) as *const [f32; W]));
240 total += va * va;
241 i += W;
242 }
243
244 let mut sum = total.reduce_add();
245 while i < len {
246 sum += a[i] * a[i];
247 i += 1;
248 }
249 sum
250 }
251}
252
253#[cfg(target_arch = "x86_64")]
263#[target_feature(enable = "avx2", enable = "fma")]
264unsafe fn sum_squares_avx2_f32(a: &[f32]) -> f32 {
265 const W: usize = 8;
266 const BLOCK: usize = W * UNROLL;
267
268 unsafe {
269 let len = a.len();
270 let a_ptr = a.as_ptr();
271 let mut acc = [_mm256_setzero_ps(); UNROLL];
272
273 let n_blocks = len / BLOCK;
274 for i in 0..n_blocks {
275 let base = i * BLOCK;
276 for (u, acc) in acc.iter_mut().enumerate() {
277 let va = _mm256_loadu_ps(a_ptr.add(base + u * W));
278 *acc = _mm256_fmadd_ps(va, va, *acc);
279 }
280 }
281
282 let mut total = acc[0];
283 for acc in &acc[1..] {
284 total = _mm256_add_ps(total, *acc);
285 }
286
287 let mut i = n_blocks * BLOCK;
288 while i + W <= len {
289 let va = _mm256_loadu_ps(a_ptr.add(i));
290 total = _mm256_fmadd_ps(va, va, total);
291 i += W;
292 }
293
294 let mut sum = hsum_avx_f32(total);
295 while i < len {
296 sum += a[i] * a[i];
297 i += 1;
298 }
299 sum
300 }
301}
302
303#[cfg(target_arch = "x86_64")]
313#[target_feature(enable = "avx512f")]
314unsafe fn sum_squares_avx512_f32(a: &[f32]) -> f32 {
315 const W: usize = 16;
316 const BLOCK: usize = W * UNROLL;
317
318 unsafe {
319 let len = a.len();
320 let a_ptr = a.as_ptr();
321 let mut acc = [_mm512_setzero_ps(); UNROLL];
322
323 let n_blocks = len / BLOCK;
324 for i in 0..n_blocks {
325 let base = i * BLOCK;
326 for (u, acc) in acc.iter_mut().enumerate() {
327 let va = _mm512_loadu_ps(a_ptr.add(base + u * W));
328 *acc = _mm512_fmadd_ps(va, va, *acc);
329 }
330 }
331
332 let mut total = acc[0];
333 for acc in &acc[1..] {
334 total = _mm512_add_ps(total, *acc);
335 }
336
337 let mut i = n_blocks * BLOCK;
338 while i + W <= len {
339 let va = _mm512_loadu_ps(a_ptr.add(i));
340 total = _mm512_fmadd_ps(va, va, total);
341 i += W;
342 }
343
344 let mut sum = _mm512_reduce_add_ps(total);
345 while i < len {
346 sum += a[i] * a[i];
347 i += 1;
348 }
349 sum
350 }
351}
352
353#[inline]
363pub fn sum_squares_simd_f32(a: &[f32]) -> f32 {
364 #[cfg(target_arch = "x86_64")]
365 unsafe {
368 match detect_simd_level() {
369 SimdLevel::Avx512 => sum_squares_avx512_f32(a),
370 SimdLevel::Avx2 => sum_squares_avx2_f32(a),
371 SimdLevel::Sse => sum_squares_sse_f32(a),
372 SimdLevel::Scalar => sum_squares_scalar_f32(a),
373 }
374 }
375
376 #[cfg(not(target_arch = "x86_64"))]
377 match detect_simd_level() {
378 SimdLevel::Sse => sum_squares_sse_f32(a),
379 _ => sum_squares_scalar_f32(a),
380 }
381}
382
383pub trait BixverseSimd:
390 Copy + Default + std::ops::AddAssign + std::ops::Mul<Output = Self>
391{
392 fn bxv_dot_simd(a: &[Self], b: &[Self]) -> Self;
403
404 fn bxv_sum(x: &[Self]) -> Self;
414
415 fn bxv_sum_squared_deviation(x: &[Self], mean: Self) -> Self;
427
428 fn bxv_axpy_simd(y: &mut [Self], a: Self, x: &[Self]) {
442 for (y_i, &x_i) in y.iter_mut().zip(x.iter()) {
443 *y_i += a * x_i;
444 }
445 }
446
447 fn bxv_exp_shift_sum(src: &[Self], shift: Self, dst: &mut [Self]) -> Self;
461}
462
463impl BixverseSimd for f32 {
464 #[inline]
465 fn bxv_dot_simd(a: &[f32], b: &[f32]) -> f32 {
466 dot_simd_f32(a, b)
467 }
468
469 #[inline]
470 fn bxv_sum(x: &[f32]) -> f32 {
471 sum_simd_f32(x)
472 }
473
474 #[inline]
475 fn bxv_sum_squared_deviation(x: &[f32], mean: f32) -> f32 {
476 sum_squared_dev_simd_f32(x, mean)
477 }
478
479 #[inline]
480 fn bxv_axpy_simd(y: &mut [f32], a: f32, x: &[f32]) {
481 axpy_simd_f32(y, a, x)
482 }
483
484 #[inline]
485 fn bxv_exp_shift_sum(src: &[f32], shift: f32, dst: &mut [f32]) -> f32 {
486 exp_shift_sum_simd_f32(src, shift, dst)
487 }
488}
489
490impl BixverseSimd for f64 {
491 #[inline]
492 fn bxv_dot_simd(a: &[f64], b: &[f64]) -> f64 {
493 dot_simd_f64(a, b)
494 }
495
496 #[inline]
497 fn bxv_sum(x: &[f64]) -> f64 {
498 sum_simd_f64(x)
499 }
500
501 #[inline]
502 fn bxv_sum_squared_deviation(x: &[f64], mean: f64) -> f64 {
503 sum_squared_dev_simd_f64(x, mean)
504 }
505
506 #[inline]
507 fn bxv_axpy_simd(y: &mut [f64], a: f64, x: &[f64]) {
508 axpy_simd_f64(y, a, x)
509 }
510
511 #[inline]
512 fn bxv_exp_shift_sum(src: &[f64], shift: f64, dst: &mut [f64]) -> f64 {
513 exp_shift_sum_scalar(src, shift, dst)
514 }
515}
516
517#[inline(always)]
536fn dot_scalar_f32(a: &[f32], b: &[f32]) -> f32 {
537 a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
538}
539
540#[inline(always)]
551fn dot_sse_f32(a: &[f32], b: &[f32]) -> f32 {
552 const W: usize = 4;
553 const BLOCK: usize = W * UNROLL;
554
555 let len = a.len();
556 let mut acc = [f32x4::ZERO; UNROLL];
557
558 unsafe {
561 let a_ptr = a.as_ptr();
562 let b_ptr = b.as_ptr();
563
564 let n_blocks = len / BLOCK;
565 for i in 0..n_blocks {
566 let base = i * BLOCK;
567 for (u, acc) in acc.iter_mut().enumerate() {
568 let off = base + u * W;
569 let va = f32x4::from(*(a_ptr.add(off) as *const [f32; W]));
570 let vb = f32x4::from(*(b_ptr.add(off) as *const [f32; W]));
571 *acc += va * vb;
572 }
573 }
574
575 let mut total = acc[0];
576 for acc in &acc[1..] {
577 total += *acc;
578 }
579
580 let mut i = n_blocks * BLOCK;
581 while i + W <= len {
582 let va = f32x4::from(*(a_ptr.add(i) as *const [f32; W]));
583 let vb = f32x4::from(*(b_ptr.add(i) as *const [f32; W]));
584 total += va * vb;
585 i += W;
586 }
587
588 let mut sum = total.reduce_add();
589 while i < len {
590 sum += a[i] * b[i];
591 i += 1;
592 }
593 sum
594 }
595}
596
597#[cfg(target_arch = "x86_64")]
608#[target_feature(enable = "avx2", enable = "fma")]
609unsafe fn dot_avx2_f32(a: &[f32], b: &[f32]) -> f32 {
610 const W: usize = 8;
611 const BLOCK: usize = W * UNROLL;
612
613 unsafe {
614 let len = a.len();
615 let a_ptr = a.as_ptr();
616 let b_ptr = b.as_ptr();
617 let mut acc = [_mm256_setzero_ps(); UNROLL];
618
619 let n_blocks = len / BLOCK;
620 for i in 0..n_blocks {
621 let base = i * BLOCK;
622 for (u, acc) in acc.iter_mut().enumerate() {
623 let off = base + u * W;
624 let va = _mm256_loadu_ps(a_ptr.add(off));
625 let vb = _mm256_loadu_ps(b_ptr.add(off));
626 *acc = _mm256_fmadd_ps(va, vb, *acc);
627 }
628 }
629
630 let mut total = acc[0];
631 for acc in &acc[1..] {
632 total = _mm256_add_ps(total, *acc);
633 }
634
635 let mut i = n_blocks * BLOCK;
636 while i + W <= len {
637 let va = _mm256_loadu_ps(a_ptr.add(i));
638 let vb = _mm256_loadu_ps(b_ptr.add(i));
639 total = _mm256_fmadd_ps(va, vb, total);
640 i += W;
641 }
642
643 let mut sum = hsum_avx_f32(total);
644 while i < len {
645 sum += a[i] * b[i];
646 i += 1;
647 }
648 sum
649 }
650}
651
652#[cfg(target_arch = "x86_64")]
663#[target_feature(enable = "avx512f")]
664unsafe fn dot_avx512_f32(a: &[f32], b: &[f32]) -> f32 {
665 const W: usize = 16;
666 const BLOCK: usize = W * UNROLL;
667
668 unsafe {
669 let len = a.len();
670 let a_ptr = a.as_ptr();
671 let b_ptr = b.as_ptr();
672 let mut acc = [_mm512_setzero_ps(); UNROLL];
673
674 let n_blocks = len / BLOCK;
675 for i in 0..n_blocks {
676 let base = i * BLOCK;
677 for (u, acc) in acc.iter_mut().enumerate() {
678 let off = base + u * W;
679 let va = _mm512_loadu_ps(a_ptr.add(off));
680 let vb = _mm512_loadu_ps(b_ptr.add(off));
681 *acc = _mm512_fmadd_ps(va, vb, *acc);
682 }
683 }
684
685 let mut total = acc[0];
686 for acc in &acc[1..] {
687 total = _mm512_add_ps(total, *acc);
688 }
689
690 let mut i = n_blocks * BLOCK;
691 while i + W <= len {
692 let va = _mm512_loadu_ps(a_ptr.add(i));
693 let vb = _mm512_loadu_ps(b_ptr.add(i));
694 total = _mm512_fmadd_ps(va, vb, total);
695 i += W;
696 }
697
698 let mut sum = _mm512_reduce_add_ps(total);
699 while i < len {
700 sum += a[i] * b[i];
701 i += 1;
702 }
703 sum
704 }
705}
706
707#[inline]
720pub fn dot_simd_f32(a: &[f32], b: &[f32]) -> f32 {
721 #[cfg(target_arch = "x86_64")]
722 unsafe {
724 match detect_simd_level() {
725 SimdLevel::Avx512 => dot_avx512_f32(a, b),
726 SimdLevel::Avx2 => dot_avx2_f32(a, b),
727 SimdLevel::Sse => dot_sse_f32(a, b),
728 SimdLevel::Scalar => dot_scalar_f32(a, b),
729 }
730 }
731
732 #[cfg(not(target_arch = "x86_64"))]
733 match detect_simd_level() {
734 SimdLevel::Sse => dot_sse_f32(a, b),
735 _ => dot_scalar_f32(a, b),
736 }
737}
738
739#[inline(always)]
754fn dot_scalar_f64(a: &[f64], b: &[f64]) -> f64 {
755 a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
756}
757
758#[inline(always)]
769fn dot_sse_f64(a: &[f64], b: &[f64]) -> f64 {
770 const W: usize = 2;
771 const BLOCK: usize = W * UNROLL;
772
773 let len = a.len();
774 let mut acc = [f64x2::ZERO; UNROLL];
775
776 unsafe {
779 let a_ptr = a.as_ptr();
780 let b_ptr = b.as_ptr();
781
782 let n_blocks = len / BLOCK;
783 for i in 0..n_blocks {
784 let base = i * BLOCK;
785 for (u, acc) in acc.iter_mut().enumerate() {
786 let off = base + u * W;
787 let va = f64x2::from(*(a_ptr.add(off) as *const [f64; W]));
788 let vb = f64x2::from(*(b_ptr.add(off) as *const [f64; W]));
789 *acc += va * vb;
790 }
791 }
792
793 let mut total = acc[0];
794 for acc in &acc[1..] {
795 total += *acc;
796 }
797
798 let mut i = n_blocks * BLOCK;
799 while i + W <= len {
800 let va = f64x2::from(*(a_ptr.add(i) as *const [f64; W]));
801 let vb = f64x2::from(*(b_ptr.add(i) as *const [f64; W]));
802 total += va * vb;
803 i += W;
804 }
805
806 let mut sum = total.reduce_add();
807 while i < len {
808 sum += a[i] * b[i];
809 i += 1;
810 }
811 sum
812 }
813}
814
815#[cfg(target_arch = "x86_64")]
826#[target_feature(enable = "avx2", enable = "fma")]
827unsafe fn dot_avx2_f64(a: &[f64], b: &[f64]) -> f64 {
828 const W: usize = 4;
829 const BLOCK: usize = W * UNROLL;
830
831 unsafe {
832 let len = a.len();
833 let a_ptr = a.as_ptr();
834 let b_ptr = b.as_ptr();
835 let mut acc = [_mm256_setzero_pd(); UNROLL];
836
837 let n_blocks = len / BLOCK;
838 for i in 0..n_blocks {
839 let base = i * BLOCK;
840 for (u, acc) in acc.iter_mut().enumerate() {
841 let off = base + u * W;
842 let va = _mm256_loadu_pd(a_ptr.add(off));
843 let vb = _mm256_loadu_pd(b_ptr.add(off));
844 *acc = _mm256_fmadd_pd(va, vb, *acc);
845 }
846 }
847
848 let mut total = acc[0];
849 for acc in &acc[1..] {
850 total = _mm256_add_pd(total, *acc);
851 }
852
853 let mut i = n_blocks * BLOCK;
854 while i + W <= len {
855 let va = _mm256_loadu_pd(a_ptr.add(i));
856 let vb = _mm256_loadu_pd(b_ptr.add(i));
857 total = _mm256_fmadd_pd(va, vb, total);
858 i += W;
859 }
860
861 let mut sum = hsum_avx_f64(total);
862 while i < len {
863 sum += a[i] * b[i];
864 i += 1;
865 }
866 sum
867 }
868}
869
870#[cfg(target_arch = "x86_64")]
881#[target_feature(enable = "avx512f")]
882unsafe fn dot_avx512_f64(a: &[f64], b: &[f64]) -> f64 {
883 const W: usize = 8;
884 const BLOCK: usize = W * UNROLL;
885
886 unsafe {
887 let len = a.len();
888 let a_ptr = a.as_ptr();
889 let b_ptr = b.as_ptr();
890 let mut acc = [_mm512_setzero_pd(); UNROLL];
891
892 let n_blocks = len / BLOCK;
893 for i in 0..n_blocks {
894 let base = i * BLOCK;
895 for (u, acc) in acc.iter_mut().enumerate() {
896 let off = base + u * W;
897 let va = _mm512_loadu_pd(a_ptr.add(off));
898 let vb = _mm512_loadu_pd(b_ptr.add(off));
899 *acc = _mm512_fmadd_pd(va, vb, *acc);
900 }
901 }
902
903 let mut total = acc[0];
904 for acc in &acc[1..] {
905 total = _mm512_add_pd(total, *acc);
906 }
907
908 let mut i = n_blocks * BLOCK;
909 while i + W <= len {
910 let va = _mm512_loadu_pd(a_ptr.add(i));
911 let vb = _mm512_loadu_pd(b_ptr.add(i));
912 total = _mm512_fmadd_pd(va, vb, total);
913 i += W;
914 }
915
916 let mut sum = _mm512_reduce_add_pd(total);
917 while i < len {
918 sum += a[i] * b[i];
919 i += 1;
920 }
921 sum
922 }
923}
924
925#[inline]
938pub fn dot_simd_f64(a: &[f64], b: &[f64]) -> f64 {
939 #[cfg(target_arch = "x86_64")]
940 unsafe {
942 match detect_simd_level() {
943 SimdLevel::Avx512 => dot_avx512_f64(a, b),
944 SimdLevel::Avx2 => dot_avx2_f64(a, b),
945 SimdLevel::Sse => dot_sse_f64(a, b),
946 SimdLevel::Scalar => dot_scalar_f64(a, b),
947 }
948 }
949
950 #[cfg(not(target_arch = "x86_64"))]
951 match detect_simd_level() {
952 SimdLevel::Sse => dot_sse_f64(a, b),
953 _ => dot_scalar_f64(a, b),
954 }
955}
956
957#[inline(always)]
972fn argmin_diff_scalar_f32(a: &[f32], b: &[f32]) -> (usize, f32) {
973 let mut min_val = a[0] - b[0];
974 let mut min_idx = 0usize;
975 for i in 1..a.len() {
976 let diff = a[i] - b[i];
977 if diff < min_val {
978 min_val = diff;
979 min_idx = i;
980 }
981 }
982 (min_idx, min_val)
983}
984
985#[inline(always)]
999fn reduce_lane_argmin_f32(vals: &[f32], idxs: &[f32]) -> (usize, f32) {
1000 let mut min_val = f32::INFINITY;
1001 let mut min_idx = usize::MAX;
1002 for (&value, &index) in vals.iter().zip(idxs.iter()) {
1003 let index = index as usize;
1004 if value < min_val || (value == min_val && index < min_idx) {
1005 min_val = value;
1006 min_idx = index;
1007 }
1008 }
1009 (min_idx, min_val)
1010}
1011
1012#[cfg(target_arch = "x86_64")]
1031#[target_feature(enable = "sse4.1")]
1032#[inline]
1033unsafe fn fold_argmin_sse(
1034 best: __m128,
1035 best_idx: __m128,
1036 cand: __m128,
1037 cand_idx: __m128,
1038) -> (__m128, __m128) {
1039 let lt = _mm_cmplt_ps(cand, best);
1040 let eq = _mm_cmpeq_ps(cand, best);
1041 let ilt = _mm_cmplt_ps(cand_idx, best_idx);
1042 let take = _mm_or_ps(lt, _mm_and_ps(eq, ilt));
1043 (
1044 _mm_blendv_ps(best, cand, take),
1045 _mm_blendv_ps(best_idx, cand_idx, take),
1046 )
1047}
1048
1049#[cfg(target_arch = "x86_64")]
1060#[target_feature(enable = "sse4.1")]
1061unsafe fn argmin_diff_sse_f32(a: &[f32], b: &[f32]) -> (usize, f32) {
1062 const W: usize = 4;
1063 const BLOCK: usize = W * UNROLL;
1064
1065 unsafe {
1066 let len = a.len();
1067 let a_ptr = a.as_ptr();
1068 let b_ptr = b.as_ptr();
1069 let n_blocks = len / BLOCK;
1070
1071 let mut best = [_mm_set1_ps(f32::INFINITY); UNROLL];
1072 let mut best_idx = [_mm_setzero_ps(); UNROLL];
1073 let lane_base = _mm_setr_ps(0.0, 1.0, 2.0, 3.0);
1074 let mut lane: [__m128; UNROLL] =
1075 std::array::from_fn(|u| _mm_add_ps(lane_base, _mm_set1_ps((u * W) as f32)));
1076 let step = _mm_set1_ps(BLOCK as f32);
1077
1078 for i in 0..n_blocks {
1079 let base = i * BLOCK;
1080 for u in 0..UNROLL {
1081 let off = base + u * W;
1082 let diff = _mm_sub_ps(_mm_loadu_ps(a_ptr.add(off)), _mm_loadu_ps(b_ptr.add(off)));
1083 let mask = _mm_cmplt_ps(diff, best[u]);
1086 best[u] = _mm_blendv_ps(best[u], diff, mask);
1087 best_idx[u] = _mm_blendv_ps(best_idx[u], lane[u], mask);
1088 lane[u] = _mm_add_ps(lane[u], step);
1089 }
1090 }
1091
1092 let mut acc = best[0];
1093 let mut acc_idx = best_idx[0];
1094 for u in 1..UNROLL {
1095 (acc, acc_idx) = fold_argmin_sse(acc, acc_idx, best[u], best_idx[u]);
1096 }
1097
1098 let mut i = n_blocks * BLOCK;
1102 while i + W <= len {
1103 let diff = _mm_sub_ps(_mm_loadu_ps(a_ptr.add(i)), _mm_loadu_ps(b_ptr.add(i)));
1104 let lane_v = _mm_add_ps(lane_base, _mm_set1_ps(i as f32));
1105 let mask = _mm_cmplt_ps(diff, acc);
1106 acc = _mm_blendv_ps(acc, diff, mask);
1107 acc_idx = _mm_blendv_ps(acc_idx, lane_v, mask);
1108 i += W;
1109 }
1110
1111 let mut vals = [0.0f32; W];
1112 let mut idxs = [0.0f32; W];
1113 _mm_storeu_ps(vals.as_mut_ptr(), acc);
1114 _mm_storeu_ps(idxs.as_mut_ptr(), acc_idx);
1115 let (mut min_idx, mut min_val) = reduce_lane_argmin_f32(&vals, &idxs);
1116
1117 while i < len {
1118 let diff = a[i] - b[i];
1119 if diff < min_val {
1120 min_val = diff;
1121 min_idx = i;
1122 }
1123 i += 1;
1124 }
1125
1126 (min_idx, min_val)
1127 }
1128}
1129
1130#[cfg(target_arch = "aarch64")]
1145#[target_feature(enable = "neon")]
1146#[inline]
1147unsafe fn fold_argmin_neon(
1148 best: float32x4_t,
1149 best_idx: float32x4_t,
1150 cand: float32x4_t,
1151 cand_idx: float32x4_t,
1152) -> (float32x4_t, float32x4_t) {
1153 let lt = vcltq_f32(cand, best);
1154 let eq = vceqq_f32(cand, best);
1155 let ilt = vcltq_f32(cand_idx, best_idx);
1156 let take = vorrq_u32(lt, vandq_u32(eq, ilt));
1157 (
1158 vbslq_f32(take, cand, best),
1159 vbslq_f32(take, cand_idx, best_idx),
1160 )
1161}
1162
1163#[cfg(target_arch = "aarch64")]
1174#[target_feature(enable = "neon")]
1175unsafe fn argmin_diff_neon_f32(a: &[f32], b: &[f32]) -> (usize, f32) {
1176 const W: usize = 4;
1177 const BLOCK: usize = W * UNROLL;
1178
1179 unsafe {
1180 let len = a.len();
1181 let a_ptr = a.as_ptr();
1182 let b_ptr = b.as_ptr();
1183 let n_blocks = len / BLOCK;
1184
1185 let mut best = [vdupq_n_f32(f32::INFINITY); UNROLL];
1186 let mut best_idx = [vdupq_n_f32(0.0); UNROLL];
1187 let lane_seed: [f32; W] = [0.0, 1.0, 2.0, 3.0];
1188 let lane_base = vld1q_f32(lane_seed.as_ptr());
1189 let mut lane: [float32x4_t; UNROLL] =
1190 std::array::from_fn(|u| vaddq_f32(lane_base, vdupq_n_f32((u * W) as f32)));
1191 let step = vdupq_n_f32(BLOCK as f32);
1192
1193 for i in 0..n_blocks {
1194 let base = i * BLOCK;
1195 for u in 0..UNROLL {
1196 let off = base + u * W;
1197 let diff = vsubq_f32(vld1q_f32(a_ptr.add(off)), vld1q_f32(b_ptr.add(off)));
1198 let mask = vcltq_f32(diff, best[u]);
1201 best[u] = vbslq_f32(mask, diff, best[u]);
1202 best_idx[u] = vbslq_f32(mask, lane[u], best_idx[u]);
1203 lane[u] = vaddq_f32(lane[u], step);
1204 }
1205 }
1206
1207 let mut acc = best[0];
1208 let mut acc_idx = best_idx[0];
1209 for u in 1..UNROLL {
1210 (acc, acc_idx) = fold_argmin_neon(acc, acc_idx, best[u], best_idx[u]);
1211 }
1212
1213 let mut i = n_blocks * BLOCK;
1216 while i + W <= len {
1217 let diff = vsubq_f32(vld1q_f32(a_ptr.add(i)), vld1q_f32(b_ptr.add(i)));
1218 let lane_v = vaddq_f32(lane_base, vdupq_n_f32(i as f32));
1219 let mask = vcltq_f32(diff, acc);
1220 acc = vbslq_f32(mask, diff, acc);
1221 acc_idx = vbslq_f32(mask, lane_v, acc_idx);
1222 i += W;
1223 }
1224
1225 let mut vals = [0.0f32; W];
1226 let mut idxs = [0.0f32; W];
1227 vst1q_f32(vals.as_mut_ptr(), acc);
1228 vst1q_f32(idxs.as_mut_ptr(), acc_idx);
1229 let (mut min_idx, mut min_val) = reduce_lane_argmin_f32(&vals, &idxs);
1230
1231 while i < len {
1232 let diff = a[i] - b[i];
1233 if diff < min_val {
1234 min_val = diff;
1235 min_idx = i;
1236 }
1237 i += 1;
1238 }
1239
1240 (min_idx, min_val)
1241 }
1242}
1243
1244#[cfg(target_arch = "x86_64")]
1259#[target_feature(enable = "avx")]
1260#[inline]
1261unsafe fn fold_argmin_avx(
1262 best: __m256,
1263 best_idx: __m256,
1264 cand: __m256,
1265 cand_idx: __m256,
1266) -> (__m256, __m256) {
1267 let lt = _mm256_cmp_ps(cand, best, _CMP_LT_OQ);
1268 let eq = _mm256_cmp_ps(cand, best, _CMP_EQ_OQ);
1269 let ilt = _mm256_cmp_ps(cand_idx, best_idx, _CMP_LT_OQ);
1270 let take = _mm256_or_ps(lt, _mm256_and_ps(eq, ilt));
1271 (
1272 _mm256_blendv_ps(best, cand, take),
1273 _mm256_blendv_ps(best_idx, cand_idx, take),
1274 )
1275}
1276
1277#[cfg(target_arch = "x86_64")]
1288#[target_feature(enable = "avx2", enable = "fma")]
1289unsafe fn argmin_diff_avx2_f32(a: &[f32], b: &[f32]) -> (usize, f32) {
1290 const W: usize = 8;
1291 const BLOCK: usize = W * UNROLL;
1292
1293 unsafe {
1294 let len = a.len();
1295 let a_ptr = a.as_ptr();
1296 let b_ptr = b.as_ptr();
1297 let n_blocks = len / BLOCK;
1298
1299 let mut best = [_mm256_set1_ps(f32::INFINITY); UNROLL];
1300 let mut best_idx = [_mm256_setzero_ps(); UNROLL];
1301 let lane_base = _mm256_setr_ps(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0);
1302 let mut lane: [__m256; UNROLL] =
1303 std::array::from_fn(|u| _mm256_add_ps(lane_base, _mm256_set1_ps((u * W) as f32)));
1304 let step = _mm256_set1_ps(BLOCK as f32);
1305
1306 for i in 0..n_blocks {
1307 let base = i * BLOCK;
1308 for u in 0..UNROLL {
1309 let off = base + u * W;
1310 let va = _mm256_loadu_ps(a_ptr.add(off));
1311 let vb = _mm256_loadu_ps(b_ptr.add(off));
1312 let diff = _mm256_sub_ps(va, vb);
1313 let mask = _mm256_cmp_ps(diff, best[u], _CMP_LT_OQ);
1316 best[u] = _mm256_blendv_ps(best[u], diff, mask);
1317 best_idx[u] = _mm256_blendv_ps(best_idx[u], lane[u], mask);
1318 lane[u] = _mm256_add_ps(lane[u], step);
1319 }
1320 }
1321
1322 let mut acc = best[0];
1323 let mut acc_idx = best_idx[0];
1324 for u in 1..UNROLL {
1325 (acc, acc_idx) = fold_argmin_avx(acc, acc_idx, best[u], best_idx[u]);
1326 }
1327
1328 let mut i = n_blocks * BLOCK;
1331 while i + W <= len {
1332 let diff = _mm256_sub_ps(_mm256_loadu_ps(a_ptr.add(i)), _mm256_loadu_ps(b_ptr.add(i)));
1333 let lane_v = _mm256_add_ps(lane_base, _mm256_set1_ps(i as f32));
1334 let mask = _mm256_cmp_ps(diff, acc, _CMP_LT_OQ);
1335 acc = _mm256_blendv_ps(acc, diff, mask);
1336 acc_idx = _mm256_blendv_ps(acc_idx, lane_v, mask);
1337 i += W;
1338 }
1339
1340 let mut vals = [0.0f32; W];
1341 let mut idxs = [0.0f32; W];
1342 _mm256_storeu_ps(vals.as_mut_ptr(), acc);
1343 _mm256_storeu_ps(idxs.as_mut_ptr(), acc_idx);
1344 let (mut min_idx, mut min_val) = reduce_lane_argmin_f32(&vals, &idxs);
1345
1346 while i < len {
1347 let diff = a[i] - b[i];
1348 if diff < min_val {
1349 min_val = diff;
1350 min_idx = i;
1351 }
1352 i += 1;
1353 }
1354
1355 (min_idx, min_val)
1356 }
1357}
1358
1359#[cfg(target_arch = "x86_64")]
1373#[target_feature(enable = "avx512f")]
1374unsafe fn argmin_diff_avx512_f32(a: &[f32], b: &[f32]) -> (usize, f32) {
1375 const W: usize = 16;
1376 const BLOCK: usize = W * UNROLL;
1377
1378 unsafe {
1379 let len = a.len();
1380 let a_ptr = a.as_ptr();
1381 let b_ptr = b.as_ptr();
1382 let n_blocks = len / BLOCK;
1383
1384 let mut best = [_mm512_set1_ps(f32::INFINITY); UNROLL];
1385 let mut best_idx = [_mm512_setzero_ps(); UNROLL];
1386 let lane_seed: [f32; W] = std::array::from_fn(|i| i as f32);
1387 let lane_base = _mm512_loadu_ps(lane_seed.as_ptr());
1388 let mut lane: [__m512; UNROLL] =
1389 std::array::from_fn(|u| _mm512_add_ps(lane_base, _mm512_set1_ps((u * W) as f32)));
1390 let step = _mm512_set1_ps(BLOCK as f32);
1391
1392 for i in 0..n_blocks {
1393 let base = i * BLOCK;
1394 for u in 0..UNROLL {
1395 let off = base + u * W;
1396 let va = _mm512_loadu_ps(a_ptr.add(off));
1397 let vb = _mm512_loadu_ps(b_ptr.add(off));
1398 let diff = _mm512_sub_ps(va, vb);
1399 let mask = _mm512_cmp_ps_mask(diff, best[u], _CMP_LT_OQ);
1402 best[u] = _mm512_mask_blend_ps(mask, best[u], diff);
1403 best_idx[u] = _mm512_mask_blend_ps(mask, best_idx[u], lane[u]);
1404 lane[u] = _mm512_add_ps(lane[u], step);
1405 }
1406 }
1407
1408 let mut acc = best[0];
1409 let mut acc_idx = best_idx[0];
1410 for u in 1..UNROLL {
1411 let lt = _mm512_cmp_ps_mask(best[u], acc, _CMP_LT_OQ);
1412 let eq = _mm512_cmp_ps_mask(best[u], acc, _CMP_EQ_OQ);
1413 let ilt = _mm512_cmp_ps_mask(best_idx[u], acc_idx, _CMP_LT_OQ);
1414 let take = lt | (eq & ilt);
1415 acc = _mm512_mask_blend_ps(take, acc, best[u]);
1416 acc_idx = _mm512_mask_blend_ps(take, acc_idx, best_idx[u]);
1417 }
1418
1419 let mut i = n_blocks * BLOCK;
1422 while i + W <= len {
1423 let diff = _mm512_sub_ps(_mm512_loadu_ps(a_ptr.add(i)), _mm512_loadu_ps(b_ptr.add(i)));
1424 let lane_v = _mm512_add_ps(lane_base, _mm512_set1_ps(i as f32));
1425 let mask = _mm512_cmp_ps_mask(diff, acc, _CMP_LT_OQ);
1426 acc = _mm512_mask_blend_ps(mask, acc, diff);
1427 acc_idx = _mm512_mask_blend_ps(mask, acc_idx, lane_v);
1428 i += W;
1429 }
1430
1431 let mut vals = [0.0f32; W];
1432 let mut idxs = [0.0f32; W];
1433 _mm512_storeu_ps(vals.as_mut_ptr(), acc);
1434 _mm512_storeu_ps(idxs.as_mut_ptr(), acc_idx);
1435 let (mut min_idx, mut min_val) = reduce_lane_argmin_f32(&vals, &idxs);
1436
1437 while i < len {
1438 let diff = a[i] - b[i];
1439 if diff < min_val {
1440 min_val = diff;
1441 min_idx = i;
1442 }
1443 i += 1;
1444 }
1445
1446 (min_idx, min_val)
1447 }
1448}
1449
1450#[inline]
1486pub fn argmin_diff_simd_f32(a: &[f32], b: &[f32]) -> (usize, f32) {
1487 debug_assert_eq!(a.len(), b.len(), "slices must match in length");
1488 debug_assert!(!a.is_empty(), "argmin over an empty slice is undefined");
1489 debug_assert!(
1490 a.len() < (1usize << 24),
1491 "lane indices are carried as f32 and stop being exact at 2^24"
1492 );
1493
1494 #[cfg(target_arch = "x86_64")]
1495 unsafe {
1497 match detect_simd_level() {
1498 SimdLevel::Avx512 => argmin_diff_avx512_f32(a, b),
1499 SimdLevel::Avx2 => argmin_diff_avx2_f32(a, b),
1500 SimdLevel::Sse => argmin_diff_sse_f32(a, b),
1501 SimdLevel::Scalar => argmin_diff_scalar_f32(a, b),
1502 }
1503 }
1504
1505 #[cfg(target_arch = "aarch64")]
1506 unsafe {
1509 match detect_simd_level() {
1510 SimdLevel::Sse => argmin_diff_neon_f32(a, b),
1511 _ => argmin_diff_scalar_f32(a, b),
1512 }
1513 }
1514
1515 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1516 argmin_diff_scalar_f32(a, b)
1517}
1518
1519#[inline(always)]
1537fn sum_scalar_f32(a: &[f32]) -> f32 {
1538 a.iter().sum()
1539}
1540
1541#[inline(always)]
1551fn sum_sse_f32(a: &[f32]) -> f32 {
1552 const W: usize = 4;
1553 const BLOCK: usize = W * UNROLL;
1554
1555 let len = a.len();
1556 let mut acc = [f32x4::ZERO; UNROLL];
1557
1558 unsafe {
1561 let a_ptr = a.as_ptr();
1562
1563 let n_blocks = len / BLOCK;
1564 for i in 0..n_blocks {
1565 let base = i * BLOCK;
1566 for (u, acc) in acc.iter_mut().enumerate() {
1567 let off = base + u * W;
1568 *acc += f32x4::from(*(a_ptr.add(off) as *const [f32; W]));
1569 }
1570 }
1571
1572 let mut total = acc[0];
1573 for acc in &acc[1..] {
1574 total += *acc;
1575 }
1576
1577 let mut i = n_blocks * BLOCK;
1578 while i + W <= len {
1579 total += f32x4::from(*(a_ptr.add(i) as *const [f32; W]));
1580 i += W;
1581 }
1582
1583 let mut sum = total.reduce_add();
1584 while i < len {
1585 sum += a[i];
1586 i += 1;
1587 }
1588 sum
1589 }
1590}
1591
1592#[cfg(target_arch = "x86_64")]
1602#[target_feature(enable = "avx2", enable = "fma")]
1603unsafe fn sum_avx2_f32(a: &[f32]) -> f32 {
1604 const W: usize = 8;
1605 const BLOCK: usize = W * UNROLL;
1606
1607 unsafe {
1608 let len = a.len();
1609 let a_ptr = a.as_ptr();
1610 let mut acc = [_mm256_setzero_ps(); UNROLL];
1611
1612 let n_blocks = len / BLOCK;
1613 for i in 0..n_blocks {
1614 let base = i * BLOCK;
1615 for (u, acc) in acc.iter_mut().enumerate() {
1616 *acc = _mm256_add_ps(*acc, _mm256_loadu_ps(a_ptr.add(base + u * W)));
1617 }
1618 }
1619
1620 let mut total = acc[0];
1621 for acc in &acc[1..] {
1622 total = _mm256_add_ps(total, *acc);
1623 }
1624
1625 let mut i = n_blocks * BLOCK;
1626 while i + W <= len {
1627 total = _mm256_add_ps(total, _mm256_loadu_ps(a_ptr.add(i)));
1628 i += W;
1629 }
1630
1631 let mut sum = hsum_avx_f32(total);
1632 while i < len {
1633 sum += a[i];
1634 i += 1;
1635 }
1636 sum
1637 }
1638}
1639
1640#[cfg(target_arch = "x86_64")]
1650#[target_feature(enable = "avx512f")]
1651unsafe fn sum_avx512_f32(a: &[f32]) -> f32 {
1652 const W: usize = 16;
1653 const BLOCK: usize = W * UNROLL;
1654
1655 unsafe {
1656 let len = a.len();
1657 let a_ptr = a.as_ptr();
1658 let mut acc = [_mm512_setzero_ps(); UNROLL];
1659
1660 let n_blocks = len / BLOCK;
1661 for i in 0..n_blocks {
1662 let base = i * BLOCK;
1663 for (u, acc) in acc.iter_mut().enumerate() {
1664 *acc = _mm512_add_ps(*acc, _mm512_loadu_ps(a_ptr.add(base + u * W)));
1665 }
1666 }
1667
1668 let mut total = acc[0];
1669 for acc in &acc[1..] {
1670 total = _mm512_add_ps(total, *acc);
1671 }
1672
1673 let mut i = n_blocks * BLOCK;
1674 while i + W <= len {
1675 total = _mm512_add_ps(total, _mm512_loadu_ps(a_ptr.add(i)));
1676 i += W;
1677 }
1678
1679 let mut sum = _mm512_reduce_add_ps(total);
1680 while i < len {
1681 sum += a[i];
1682 i += 1;
1683 }
1684 sum
1685 }
1686}
1687
1688#[inline]
1698pub fn sum_simd_f32(a: &[f32]) -> f32 {
1699 #[cfg(target_arch = "x86_64")]
1700 unsafe {
1702 match detect_simd_level() {
1703 SimdLevel::Avx512 => sum_avx512_f32(a),
1704 SimdLevel::Avx2 => sum_avx2_f32(a),
1705 SimdLevel::Sse => sum_sse_f32(a),
1706 SimdLevel::Scalar => sum_scalar_f32(a),
1707 }
1708 }
1709
1710 #[cfg(not(target_arch = "x86_64"))]
1711 match detect_simd_level() {
1712 SimdLevel::Sse => sum_sse_f32(a),
1713 _ => sum_scalar_f32(a),
1714 }
1715}
1716
1717#[inline(always)]
1731fn sum_scalar_f64(a: &[f64]) -> f64 {
1732 a.iter().sum()
1733}
1734
1735#[inline(always)]
1745fn sum_sse_f64(a: &[f64]) -> f64 {
1746 const W: usize = 2;
1747 const BLOCK: usize = W * UNROLL;
1748
1749 let len = a.len();
1750 let mut acc = [f64x2::ZERO; UNROLL];
1751
1752 unsafe {
1755 let a_ptr = a.as_ptr();
1756
1757 let n_blocks = len / BLOCK;
1758 for i in 0..n_blocks {
1759 let base = i * BLOCK;
1760 for (u, acc) in acc.iter_mut().enumerate() {
1761 let off = base + u * W;
1762 *acc += f64x2::from(*(a_ptr.add(off) as *const [f64; W]));
1763 }
1764 }
1765
1766 let mut total = acc[0];
1767 for acc in &acc[1..] {
1768 total += *acc;
1769 }
1770
1771 let mut i = n_blocks * BLOCK;
1772 while i + W <= len {
1773 total += f64x2::from(*(a_ptr.add(i) as *const [f64; W]));
1774 i += W;
1775 }
1776
1777 let mut sum = total.reduce_add();
1778 while i < len {
1779 sum += a[i];
1780 i += 1;
1781 }
1782 sum
1783 }
1784}
1785
1786#[cfg(target_arch = "x86_64")]
1796#[target_feature(enable = "avx2", enable = "fma")]
1797unsafe fn sum_avx2_f64(a: &[f64]) -> f64 {
1798 const W: usize = 4;
1799 const BLOCK: usize = W * UNROLL;
1800
1801 unsafe {
1802 let len = a.len();
1803 let a_ptr = a.as_ptr();
1804 let mut acc = [_mm256_setzero_pd(); UNROLL];
1805
1806 let n_blocks = len / BLOCK;
1807 for i in 0..n_blocks {
1808 let base = i * BLOCK;
1809 for (u, acc) in acc.iter_mut().enumerate() {
1810 *acc = _mm256_add_pd(*acc, _mm256_loadu_pd(a_ptr.add(base + u * W)));
1811 }
1812 }
1813
1814 let mut total = acc[0];
1815 for acc in &acc[1..] {
1816 total = _mm256_add_pd(total, *acc);
1817 }
1818
1819 let mut i = n_blocks * BLOCK;
1820 while i + W <= len {
1821 total = _mm256_add_pd(total, _mm256_loadu_pd(a_ptr.add(i)));
1822 i += W;
1823 }
1824
1825 let mut sum = hsum_avx_f64(total);
1826 while i < len {
1827 sum += a[i];
1828 i += 1;
1829 }
1830 sum
1831 }
1832}
1833
1834#[cfg(target_arch = "x86_64")]
1844#[target_feature(enable = "avx512f")]
1845unsafe fn sum_avx512_f64(a: &[f64]) -> f64 {
1846 const W: usize = 8;
1847 const BLOCK: usize = W * UNROLL;
1848
1849 unsafe {
1850 let len = a.len();
1851 let a_ptr = a.as_ptr();
1852 let mut acc = [_mm512_setzero_pd(); UNROLL];
1853
1854 let n_blocks = len / BLOCK;
1855 for i in 0..n_blocks {
1856 let base = i * BLOCK;
1857 for (u, acc) in acc.iter_mut().enumerate() {
1858 *acc = _mm512_add_pd(*acc, _mm512_loadu_pd(a_ptr.add(base + u * W)));
1859 }
1860 }
1861
1862 let mut total = acc[0];
1863 for acc in &acc[1..] {
1864 total = _mm512_add_pd(total, *acc);
1865 }
1866
1867 let mut i = n_blocks * BLOCK;
1868 while i + W <= len {
1869 total = _mm512_add_pd(total, _mm512_loadu_pd(a_ptr.add(i)));
1870 i += W;
1871 }
1872
1873 let mut sum = _mm512_reduce_add_pd(total);
1874 while i < len {
1875 sum += a[i];
1876 i += 1;
1877 }
1878 sum
1879 }
1880}
1881
1882#[inline]
1892pub fn sum_simd_f64(a: &[f64]) -> f64 {
1893 #[cfg(target_arch = "x86_64")]
1894 unsafe {
1896 match detect_simd_level() {
1897 SimdLevel::Avx512 => sum_avx512_f64(a),
1898 SimdLevel::Avx2 => sum_avx2_f64(a),
1899 SimdLevel::Sse => sum_sse_f64(a),
1900 SimdLevel::Scalar => sum_scalar_f64(a),
1901 }
1902 }
1903
1904 #[cfg(not(target_arch = "x86_64"))]
1905 match detect_simd_level() {
1906 SimdLevel::Sse => sum_sse_f64(a),
1907 _ => sum_scalar_f64(a),
1908 }
1909}
1910
1911#[inline(always)]
1930fn sum_squared_dev_scalar_f32(a: &[f32], mean: f32) -> f32 {
1931 a.iter().map(|&x| (x - mean).powi(2)).sum::<f32>()
1932}
1933
1934#[inline(always)]
1945fn sum_squared_dev_sse_f32(a: &[f32], mean: f32) -> f32 {
1946 const W: usize = 4;
1947 const BLOCK: usize = W * UNROLL;
1948
1949 let len = a.len();
1950 let mut acc = [f32x4::ZERO; UNROLL];
1951 let mean_vec = f32x4::splat(mean);
1952
1953 unsafe {
1956 let a_ptr = a.as_ptr();
1957
1958 let n_blocks = len / BLOCK;
1959 for i in 0..n_blocks {
1960 let base = i * BLOCK;
1961 for (u, acc) in acc.iter_mut().enumerate() {
1962 let off = base + u * W;
1963 let diff = f32x4::from(*(a_ptr.add(off) as *const [f32; W])) - mean_vec;
1964 *acc += diff * diff;
1965 }
1966 }
1967
1968 let mut total = acc[0];
1969 for acc in &acc[1..] {
1970 total += *acc;
1971 }
1972
1973 let mut i = n_blocks * BLOCK;
1974 while i + W <= len {
1975 let diff = f32x4::from(*(a_ptr.add(i) as *const [f32; W])) - mean_vec;
1976 total += diff * diff;
1977 i += W;
1978 }
1979
1980 let mut sum = total.reduce_add();
1981 while i < len {
1982 let diff = a[i] - mean;
1983 sum += diff * diff;
1984 i += 1;
1985 }
1986 sum
1987 }
1988}
1989
1990#[cfg(target_arch = "x86_64")]
2001#[target_feature(enable = "avx2", enable = "fma")]
2002unsafe fn sum_squared_dev_avx2_f32(a: &[f32], mean: f32) -> f32 {
2003 const W: usize = 8;
2004 const BLOCK: usize = W * UNROLL;
2005
2006 unsafe {
2007 let len = a.len();
2008 let a_ptr = a.as_ptr();
2009 let mean_vec = _mm256_set1_ps(mean);
2010 let mut acc = [_mm256_setzero_ps(); UNROLL];
2011
2012 let n_blocks = len / BLOCK;
2013 for i in 0..n_blocks {
2014 let base = i * BLOCK;
2015 for (u, acc) in acc.iter_mut().enumerate() {
2016 let diff = _mm256_sub_ps(_mm256_loadu_ps(a_ptr.add(base + u * W)), mean_vec);
2017 *acc = _mm256_fmadd_ps(diff, diff, *acc);
2018 }
2019 }
2020
2021 let mut total = acc[0];
2022 for acc in &acc[1..] {
2023 total = _mm256_add_ps(total, *acc);
2024 }
2025
2026 let mut i = n_blocks * BLOCK;
2027 while i + W <= len {
2028 let diff = _mm256_sub_ps(_mm256_loadu_ps(a_ptr.add(i)), mean_vec);
2029 total = _mm256_fmadd_ps(diff, diff, total);
2030 i += W;
2031 }
2032
2033 let mut sum = hsum_avx_f32(total);
2034 while i < len {
2035 let diff = a[i] - mean;
2036 sum += diff * diff;
2037 i += 1;
2038 }
2039 sum
2040 }
2041}
2042
2043#[cfg(target_arch = "x86_64")]
2054#[target_feature(enable = "avx512f")]
2055unsafe fn sum_squared_dev_avx512_f32(a: &[f32], mean: f32) -> f32 {
2056 const W: usize = 16;
2057 const BLOCK: usize = W * UNROLL;
2058
2059 unsafe {
2060 let len = a.len();
2061 let a_ptr = a.as_ptr();
2062 let mean_vec = _mm512_set1_ps(mean);
2063 let mut acc = [_mm512_setzero_ps(); UNROLL];
2064
2065 let n_blocks = len / BLOCK;
2066 for i in 0..n_blocks {
2067 let base = i * BLOCK;
2068 for (u, acc) in acc.iter_mut().enumerate() {
2069 let diff = _mm512_sub_ps(_mm512_loadu_ps(a_ptr.add(base + u * W)), mean_vec);
2070 *acc = _mm512_fmadd_ps(diff, diff, *acc);
2071 }
2072 }
2073
2074 let mut total = acc[0];
2075 for acc in &acc[1..] {
2076 total = _mm512_add_ps(total, *acc);
2077 }
2078
2079 let mut i = n_blocks * BLOCK;
2080 while i + W <= len {
2081 let diff = _mm512_sub_ps(_mm512_loadu_ps(a_ptr.add(i)), mean_vec);
2082 total = _mm512_fmadd_ps(diff, diff, total);
2083 i += W;
2084 }
2085
2086 let mut sum = _mm512_reduce_add_ps(total);
2087 while i < len {
2088 let diff = a[i] - mean;
2089 sum += diff * diff;
2090 i += 1;
2091 }
2092 sum
2093 }
2094}
2095
2096#[inline]
2107pub fn sum_squared_dev_simd_f32(a: &[f32], mean: f32) -> f32 {
2108 #[cfg(target_arch = "x86_64")]
2109 unsafe {
2111 match detect_simd_level() {
2112 SimdLevel::Avx512 => sum_squared_dev_avx512_f32(a, mean),
2113 SimdLevel::Avx2 => sum_squared_dev_avx2_f32(a, mean),
2114 SimdLevel::Sse => sum_squared_dev_sse_f32(a, mean),
2115 SimdLevel::Scalar => sum_squared_dev_scalar_f32(a, mean),
2116 }
2117 }
2118
2119 #[cfg(not(target_arch = "x86_64"))]
2120 match detect_simd_level() {
2121 SimdLevel::Sse => sum_squared_dev_sse_f32(a, mean),
2122 _ => sum_squared_dev_scalar_f32(a, mean),
2123 }
2124}
2125
2126#[inline(always)]
2141fn sum_squared_dev_scalar_f64(a: &[f64], mean: f64) -> f64 {
2142 a.iter().map(|&x| (x - mean).powi(2)).sum::<f64>()
2143}
2144
2145#[inline(always)]
2156fn sum_squared_dev_sse_f64(a: &[f64], mean: f64) -> f64 {
2157 const W: usize = 2;
2158 const BLOCK: usize = W * UNROLL;
2159
2160 let len = a.len();
2161 let mut acc = [f64x2::ZERO; UNROLL];
2162 let mean_vec = f64x2::splat(mean);
2163
2164 unsafe {
2167 let a_ptr = a.as_ptr();
2168
2169 let n_blocks = len / BLOCK;
2170 for i in 0..n_blocks {
2171 let base = i * BLOCK;
2172 for (u, acc) in acc.iter_mut().enumerate() {
2173 let off = base + u * W;
2174 let diff = f64x2::from(*(a_ptr.add(off) as *const [f64; W])) - mean_vec;
2175 *acc += diff * diff;
2176 }
2177 }
2178
2179 let mut total = acc[0];
2180 for acc in &acc[1..] {
2181 total += *acc;
2182 }
2183
2184 let mut i = n_blocks * BLOCK;
2185 while i + W <= len {
2186 let diff = f64x2::from(*(a_ptr.add(i) as *const [f64; W])) - mean_vec;
2187 total += diff * diff;
2188 i += W;
2189 }
2190
2191 let mut sum = total.reduce_add();
2192 while i < len {
2193 let diff = a[i] - mean;
2194 sum += diff * diff;
2195 i += 1;
2196 }
2197 sum
2198 }
2199}
2200
2201#[cfg(target_arch = "x86_64")]
2212#[target_feature(enable = "avx2", enable = "fma")]
2213unsafe fn sum_squared_dev_avx2_f64(a: &[f64], mean: f64) -> f64 {
2214 const W: usize = 4;
2215 const BLOCK: usize = W * UNROLL;
2216
2217 unsafe {
2218 let len = a.len();
2219 let a_ptr = a.as_ptr();
2220 let mean_vec = _mm256_set1_pd(mean);
2221 let mut acc = [_mm256_setzero_pd(); UNROLL];
2222
2223 let n_blocks = len / BLOCK;
2224 for i in 0..n_blocks {
2225 let base = i * BLOCK;
2226 for (u, acc) in acc.iter_mut().enumerate() {
2227 let diff = _mm256_sub_pd(_mm256_loadu_pd(a_ptr.add(base + u * W)), mean_vec);
2228 *acc = _mm256_fmadd_pd(diff, diff, *acc);
2229 }
2230 }
2231
2232 let mut total = acc[0];
2233 for acc in &acc[1..] {
2234 total = _mm256_add_pd(total, *acc);
2235 }
2236
2237 let mut i = n_blocks * BLOCK;
2238 while i + W <= len {
2239 let diff = _mm256_sub_pd(_mm256_loadu_pd(a_ptr.add(i)), mean_vec);
2240 total = _mm256_fmadd_pd(diff, diff, total);
2241 i += W;
2242 }
2243
2244 let mut sum = hsum_avx_f64(total);
2245 while i < len {
2246 let diff = a[i] - mean;
2247 sum += diff * diff;
2248 i += 1;
2249 }
2250 sum
2251 }
2252}
2253
2254#[cfg(target_arch = "x86_64")]
2265#[target_feature(enable = "avx512f")]
2266unsafe fn sum_squared_dev_avx512_f64(a: &[f64], mean: f64) -> f64 {
2267 const W: usize = 8;
2268 const BLOCK: usize = W * UNROLL;
2269
2270 unsafe {
2271 let len = a.len();
2272 let a_ptr = a.as_ptr();
2273 let mean_vec = _mm512_set1_pd(mean);
2274 let mut acc = [_mm512_setzero_pd(); UNROLL];
2275
2276 let n_blocks = len / BLOCK;
2277 for i in 0..n_blocks {
2278 let base = i * BLOCK;
2279 for (u, acc) in acc.iter_mut().enumerate() {
2280 let diff = _mm512_sub_pd(_mm512_loadu_pd(a_ptr.add(base + u * W)), mean_vec);
2281 *acc = _mm512_fmadd_pd(diff, diff, *acc);
2282 }
2283 }
2284
2285 let mut total = acc[0];
2286 for acc in &acc[1..] {
2287 total = _mm512_add_pd(total, *acc);
2288 }
2289
2290 let mut i = n_blocks * BLOCK;
2291 while i + W <= len {
2292 let diff = _mm512_sub_pd(_mm512_loadu_pd(a_ptr.add(i)), mean_vec);
2293 total = _mm512_fmadd_pd(diff, diff, total);
2294 i += W;
2295 }
2296
2297 let mut sum = _mm512_reduce_add_pd(total);
2298 while i < len {
2299 let diff = a[i] - mean;
2300 sum += diff * diff;
2301 i += 1;
2302 }
2303 sum
2304 }
2305}
2306
2307#[inline]
2318pub fn sum_squared_dev_simd_f64(a: &[f64], mean: f64) -> f64 {
2319 #[cfg(target_arch = "x86_64")]
2320 unsafe {
2322 match detect_simd_level() {
2323 SimdLevel::Avx512 => sum_squared_dev_avx512_f64(a, mean),
2324 SimdLevel::Avx2 => sum_squared_dev_avx2_f64(a, mean),
2325 SimdLevel::Sse => sum_squared_dev_sse_f64(a, mean),
2326 SimdLevel::Scalar => sum_squared_dev_scalar_f64(a, mean),
2327 }
2328 }
2329
2330 #[cfg(not(target_arch = "x86_64"))]
2331 match detect_simd_level() {
2332 SimdLevel::Sse => sum_squared_dev_sse_f64(a, mean),
2333 _ => sum_squared_dev_scalar_f64(a, mean),
2334 }
2335}
2336
2337#[inline(always)]
2358fn axpy_scalar_f32(y: &mut [f32], a: f32, x: &[f32]) {
2359 for (y_i, &x_i) in y.iter_mut().zip(x.iter()) {
2360 *y_i += a * x_i;
2361 }
2362}
2363
2364#[inline(always)]
2372fn axpy_sse_f32(y: &mut [f32], a: f32, x: &[f32]) {
2373 const W: usize = 4;
2374 let len = y.len();
2375 let chunks = len / W;
2376 let va = f32x4::splat(a);
2377
2378 unsafe {
2379 let x_ptr = x.as_ptr();
2380 let y_ptr = y.as_mut_ptr();
2381 for i in 0..chunks {
2382 let off = i * W;
2383 let vx = f32x4::from(*(x_ptr.add(off) as *const [f32; W]));
2384 let vy = f32x4::from(*(y_ptr.add(off) as *const [f32; W]));
2385 *(y_ptr.add(off) as *mut [f32; W]) = vx.mul_add(va, vy).to_array();
2386 }
2387 }
2388
2389 axpy_scalar_f32(&mut y[chunks * W..], a, &x[chunks * W..]);
2390}
2391
2392#[cfg(target_arch = "x86_64")]
2400#[target_feature(enable = "avx2", enable = "fma")]
2401unsafe fn axpy_avx2_f32(y: &mut [f32], a: f32, x: &[f32]) {
2402 const W: usize = 8;
2403 let len = y.len();
2404 let chunks = len / W;
2405
2406 unsafe {
2407 let va = _mm256_set1_ps(a);
2408 let x_ptr = x.as_ptr();
2409 let y_ptr = y.as_mut_ptr();
2410 for i in 0..chunks {
2411 let off = i * W;
2412 let vx = _mm256_loadu_ps(x_ptr.add(off));
2413 let vy = _mm256_loadu_ps(y_ptr.add(off));
2414 _mm256_storeu_ps(y_ptr.add(off), _mm256_fmadd_ps(vx, va, vy));
2415 }
2416 }
2417
2418 axpy_scalar_f32(&mut y[chunks * W..], a, &x[chunks * W..]);
2419}
2420
2421#[cfg(target_arch = "x86_64")]
2429#[target_feature(enable = "avx512f")]
2430unsafe fn axpy_avx512_f32(y: &mut [f32], a: f32, x: &[f32]) {
2431 const W: usize = 16;
2432 let len = y.len();
2433 let chunks = len / W;
2434
2435 unsafe {
2436 let va = _mm512_set1_ps(a);
2437 let x_ptr = x.as_ptr();
2438 let y_ptr = y.as_mut_ptr();
2439 for i in 0..chunks {
2440 let off = i * W;
2441 let vx = _mm512_loadu_ps(x_ptr.add(off));
2442 let vy = _mm512_loadu_ps(y_ptr.add(off));
2443 _mm512_storeu_ps(y_ptr.add(off), _mm512_fmadd_ps(vx, va, vy));
2444 }
2445 }
2446
2447 axpy_scalar_f32(&mut y[chunks * W..], a, &x[chunks * W..]);
2448}
2449
2450#[inline]
2462pub fn axpy_simd_f32(y: &mut [f32], a: f32, x: &[f32]) {
2463 assert_eq!(y.len(), x.len(), "axpy operands differ in length");
2464
2465 #[cfg(target_arch = "x86_64")]
2466 unsafe {
2468 match detect_simd_level() {
2469 SimdLevel::Avx512 => axpy_avx512_f32(y, a, x),
2470 SimdLevel::Avx2 => axpy_avx2_f32(y, a, x),
2471 SimdLevel::Sse => axpy_sse_f32(y, a, x),
2472 SimdLevel::Scalar => axpy_scalar_f32(y, a, x),
2473 }
2474 }
2475
2476 #[cfg(not(target_arch = "x86_64"))]
2477 match detect_simd_level() {
2478 SimdLevel::Sse => axpy_sse_f32(y, a, x),
2479 _ => axpy_scalar_f32(y, a, x),
2480 }
2481}
2482
2483#[inline(always)]
2495fn axpy_scalar_f64(y: &mut [f64], a: f64, x: &[f64]) {
2496 for (y_i, &x_i) in y.iter_mut().zip(x.iter()) {
2497 *y_i += a * x_i;
2498 }
2499}
2500
2501#[inline(always)]
2509fn axpy_sse_f64(y: &mut [f64], a: f64, x: &[f64]) {
2510 const W: usize = 2;
2511 let len = y.len();
2512 let chunks = len / W;
2513 let va = f64x2::splat(a);
2514
2515 unsafe {
2516 let x_ptr = x.as_ptr();
2517 let y_ptr = y.as_mut_ptr();
2518 for i in 0..chunks {
2519 let off = i * W;
2520 let vx = f64x2::from(*(x_ptr.add(off) as *const [f64; W]));
2521 let vy = f64x2::from(*(y_ptr.add(off) as *const [f64; W]));
2522 *(y_ptr.add(off) as *mut [f64; W]) = vx.mul_add(va, vy).to_array();
2523 }
2524 }
2525
2526 axpy_scalar_f64(&mut y[chunks * W..], a, &x[chunks * W..]);
2527}
2528
2529#[cfg(target_arch = "x86_64")]
2537#[target_feature(enable = "avx2", enable = "fma")]
2538unsafe fn axpy_avx2_f64(y: &mut [f64], a: f64, x: &[f64]) {
2539 const W: usize = 4;
2540 let len = y.len();
2541 let chunks = len / W;
2542
2543 unsafe {
2544 let va = _mm256_set1_pd(a);
2545 let x_ptr = x.as_ptr();
2546 let y_ptr = y.as_mut_ptr();
2547 for i in 0..chunks {
2548 let off = i * W;
2549 let vx = _mm256_loadu_pd(x_ptr.add(off));
2550 let vy = _mm256_loadu_pd(y_ptr.add(off));
2551 _mm256_storeu_pd(y_ptr.add(off), _mm256_fmadd_pd(vx, va, vy));
2552 }
2553 }
2554
2555 axpy_scalar_f64(&mut y[chunks * W..], a, &x[chunks * W..]);
2556}
2557
2558#[cfg(target_arch = "x86_64")]
2566#[target_feature(enable = "avx512f")]
2567unsafe fn axpy_avx512_f64(y: &mut [f64], a: f64, x: &[f64]) {
2568 const W: usize = 8;
2569 let len = y.len();
2570 let chunks = len / W;
2571
2572 unsafe {
2573 let va = _mm512_set1_pd(a);
2574 let x_ptr = x.as_ptr();
2575 let y_ptr = y.as_mut_ptr();
2576 for i in 0..chunks {
2577 let off = i * W;
2578 let vx = _mm512_loadu_pd(x_ptr.add(off));
2579 let vy = _mm512_loadu_pd(y_ptr.add(off));
2580 _mm512_storeu_pd(y_ptr.add(off), _mm512_fmadd_pd(vx, va, vy));
2581 }
2582 }
2583
2584 axpy_scalar_f64(&mut y[chunks * W..], a, &x[chunks * W..]);
2585}
2586
2587#[inline]
2599pub fn axpy_simd_f64(y: &mut [f64], a: f64, x: &[f64]) {
2600 assert_eq!(y.len(), x.len(), "axpy operands differ in length");
2601
2602 #[cfg(target_arch = "x86_64")]
2603 unsafe {
2605 match detect_simd_level() {
2606 SimdLevel::Avx512 => axpy_avx512_f64(y, a, x),
2607 SimdLevel::Avx2 => axpy_avx2_f64(y, a, x),
2608 SimdLevel::Sse => axpy_sse_f64(y, a, x),
2609 SimdLevel::Scalar => axpy_scalar_f64(y, a, x),
2610 }
2611 }
2612
2613 #[cfg(not(target_arch = "x86_64"))]
2614 match detect_simd_level() {
2615 SimdLevel::Sse => axpy_sse_f64(y, a, x),
2616 _ => axpy_scalar_f64(y, a, x),
2617 }
2618}
2619
2620#[inline(always)]
2647fn sum_widen_scalar_f32(a: &[f32]) -> f64 {
2648 a.iter().map(|&x| x as f64).sum()
2649}
2650
2651#[cfg(target_arch = "x86_64")]
2661#[target_feature(enable = "sse2")]
2662unsafe fn sum_widen_sse_f32(a: &[f32]) -> f64 {
2663 const W: usize = 4;
2664 const BLOCK: usize = W * UNROLL;
2665
2666 unsafe {
2667 let len = a.len();
2668 let a_ptr = a.as_ptr();
2669 let mut acc = [_mm_setzero_pd(); UNROLL];
2670
2671 let n_blocks = len / BLOCK;
2672 for i in 0..n_blocks {
2673 let base = i * BLOCK;
2674 for (u, acc) in acc.iter_mut().enumerate() {
2675 let v = _mm_loadu_ps(a_ptr.add(base + u * W));
2676 let lo = _mm_cvtps_pd(v);
2677 let hi = _mm_cvtps_pd(_mm_movehl_ps(v, v));
2678 *acc = _mm_add_pd(*acc, _mm_add_pd(lo, hi));
2679 }
2680 }
2681
2682 let mut total = acc[0];
2683 for acc in &acc[1..] {
2684 total = _mm_add_pd(total, *acc);
2685 }
2686
2687 let mut i = n_blocks * BLOCK;
2688 while i + W <= len {
2689 let v = _mm_loadu_ps(a_ptr.add(i));
2690 let lo = _mm_cvtps_pd(v);
2691 let hi = _mm_cvtps_pd(_mm_movehl_ps(v, v));
2692 total = _mm_add_pd(total, _mm_add_pd(lo, hi));
2693 i += W;
2694 }
2695
2696 let mut sum = hsum_sse_f64(total);
2697 while i < len {
2698 sum += a[i] as f64;
2699 i += 1;
2700 }
2701 sum
2702 }
2703}
2704
2705#[cfg(target_arch = "aarch64")]
2715#[target_feature(enable = "neon")]
2716unsafe fn sum_widen_neon_f32(a: &[f32]) -> f64 {
2717 const W: usize = 4;
2718 const BLOCK: usize = W * UNROLL;
2719
2720 unsafe {
2721 let len = a.len();
2722 let a_ptr = a.as_ptr();
2723 let mut acc = [vdupq_n_f64(0.0); UNROLL];
2724
2725 let n_blocks = len / BLOCK;
2726 for i in 0..n_blocks {
2727 let base = i * BLOCK;
2728 for (u, acc) in acc.iter_mut().enumerate() {
2729 let v = vld1q_f32(a_ptr.add(base + u * W));
2730 let lo = vcvt_f64_f32(vget_low_f32(v));
2731 let hi = vcvt_high_f64_f32(v);
2732 *acc = vaddq_f64(*acc, vaddq_f64(lo, hi));
2733 }
2734 }
2735
2736 let mut total = acc[0];
2737 for acc in &acc[1..] {
2738 total = vaddq_f64(total, *acc);
2739 }
2740
2741 let mut i = n_blocks * BLOCK;
2742 while i + W <= len {
2743 let v = vld1q_f32(a_ptr.add(i));
2744 let lo = vcvt_f64_f32(vget_low_f32(v));
2745 let hi = vcvt_high_f64_f32(v);
2746 total = vaddq_f64(total, vaddq_f64(lo, hi));
2747 i += W;
2748 }
2749
2750 let mut sum = vaddvq_f64(total);
2751 while i < len {
2752 sum += a[i] as f64;
2753 i += 1;
2754 }
2755 sum
2756 }
2757}
2758
2759#[cfg(target_arch = "x86_64")]
2769#[target_feature(enable = "avx2", enable = "fma")]
2770unsafe fn sum_widen_avx2_f32(a: &[f32]) -> f64 {
2771 const W: usize = 8;
2772 const BLOCK: usize = W * UNROLL;
2773
2774 unsafe {
2775 let len = a.len();
2776 let a_ptr = a.as_ptr();
2777 let mut acc = [_mm256_setzero_pd(); UNROLL];
2778
2779 let n_blocks = len / BLOCK;
2780 for i in 0..n_blocks {
2781 let base = i * BLOCK;
2782 for (u, acc) in acc.iter_mut().enumerate() {
2783 let p = a_ptr.add(base + u * W);
2784 let lo = _mm256_cvtps_pd(_mm_loadu_ps(p));
2785 let hi = _mm256_cvtps_pd(_mm_loadu_ps(p.add(4)));
2786 *acc = _mm256_add_pd(*acc, _mm256_add_pd(lo, hi));
2787 }
2788 }
2789
2790 let mut total = acc[0];
2791 for acc in &acc[1..] {
2792 total = _mm256_add_pd(total, *acc);
2793 }
2794
2795 let mut i = n_blocks * BLOCK;
2796 while i + W <= len {
2797 let p = a_ptr.add(i);
2798 let lo = _mm256_cvtps_pd(_mm_loadu_ps(p));
2799 let hi = _mm256_cvtps_pd(_mm_loadu_ps(p.add(4)));
2800 total = _mm256_add_pd(total, _mm256_add_pd(lo, hi));
2801 i += W;
2802 }
2803
2804 let mut sum = hsum_avx_f64(total);
2805 while i < len {
2806 sum += a[i] as f64;
2807 i += 1;
2808 }
2809 sum
2810 }
2811}
2812
2813#[cfg(target_arch = "x86_64")]
2823#[target_feature(enable = "avx512f")]
2824unsafe fn sum_widen_avx512_f32(a: &[f32]) -> f64 {
2825 const W: usize = 16;
2826 const BLOCK: usize = W * UNROLL;
2827
2828 unsafe {
2829 let len = a.len();
2830 let a_ptr = a.as_ptr();
2831 let mut acc = [_mm512_setzero_pd(); UNROLL];
2832
2833 let n_blocks = len / BLOCK;
2834 for i in 0..n_blocks {
2835 let base = i * BLOCK;
2836 for (u, acc) in acc.iter_mut().enumerate() {
2837 let p = a_ptr.add(base + u * W);
2838 let lo = _mm512_cvtps_pd(_mm256_loadu_ps(p));
2839 let hi = _mm512_cvtps_pd(_mm256_loadu_ps(p.add(8)));
2840 *acc = _mm512_add_pd(*acc, _mm512_add_pd(lo, hi));
2841 }
2842 }
2843
2844 let mut total = acc[0];
2845 for acc in &acc[1..] {
2846 total = _mm512_add_pd(total, *acc);
2847 }
2848
2849 let mut i = n_blocks * BLOCK;
2850 while i + W <= len {
2851 let p = a_ptr.add(i);
2852 let lo = _mm512_cvtps_pd(_mm256_loadu_ps(p));
2853 let hi = _mm512_cvtps_pd(_mm256_loadu_ps(p.add(8)));
2854 total = _mm512_add_pd(total, _mm512_add_pd(lo, hi));
2855 i += W;
2856 }
2857
2858 let mut sum = _mm512_reduce_add_pd(total);
2859 while i < len {
2860 sum += a[i] as f64;
2861 i += 1;
2862 }
2863 sum
2864 }
2865}
2866
2867#[inline]
2877pub fn sum_widen_simd_f32(a: &[f32]) -> f64 {
2878 #[cfg(target_arch = "x86_64")]
2879 unsafe {
2881 match detect_simd_level() {
2882 SimdLevel::Avx512 => sum_widen_avx512_f32(a),
2883 SimdLevel::Avx2 => sum_widen_avx2_f32(a),
2884 SimdLevel::Sse => sum_widen_sse_f32(a),
2885 SimdLevel::Scalar => sum_widen_scalar_f32(a),
2886 }
2887 }
2888
2889 #[cfg(target_arch = "aarch64")]
2890 unsafe {
2893 match detect_simd_level() {
2894 SimdLevel::Sse => sum_widen_neon_f32(a),
2895 _ => sum_widen_scalar_f32(a),
2896 }
2897 }
2898
2899 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
2900 sum_widen_scalar_f32(a)
2901}
2902
2903#[inline(always)]
2918fn sum_squared_dev_widen_scalar_f32(a: &[f32], mean: f64) -> f64 {
2919 a.iter()
2920 .map(|&x| {
2921 let d = x as f64 - mean;
2922 d * d
2923 })
2924 .sum()
2925}
2926
2927#[cfg(target_arch = "x86_64")]
2938#[target_feature(enable = "sse2")]
2939unsafe fn sum_squared_dev_widen_sse_f32(a: &[f32], mean: f64) -> f64 {
2940 const W: usize = 4;
2941 const BLOCK: usize = W * UNROLL;
2942
2943 unsafe {
2944 let len = a.len();
2945 let a_ptr = a.as_ptr();
2946 let mean_vec = _mm_set1_pd(mean);
2947 let mut acc = [_mm_setzero_pd(); UNROLL];
2948
2949 let n_blocks = len / BLOCK;
2950 for i in 0..n_blocks {
2951 let base = i * BLOCK;
2952 for (u, acc) in acc.iter_mut().enumerate() {
2953 let v = _mm_loadu_ps(a_ptr.add(base + u * W));
2954 let lo = _mm_sub_pd(_mm_cvtps_pd(v), mean_vec);
2955 let hi = _mm_sub_pd(_mm_cvtps_pd(_mm_movehl_ps(v, v)), mean_vec);
2956 *acc = _mm_add_pd(*acc, _mm_add_pd(_mm_mul_pd(lo, lo), _mm_mul_pd(hi, hi)));
2957 }
2958 }
2959
2960 let mut total = acc[0];
2961 for acc in &acc[1..] {
2962 total = _mm_add_pd(total, *acc);
2963 }
2964
2965 let mut i = n_blocks * BLOCK;
2966 while i + W <= len {
2967 let v = _mm_loadu_ps(a_ptr.add(i));
2968 let lo = _mm_sub_pd(_mm_cvtps_pd(v), mean_vec);
2969 let hi = _mm_sub_pd(_mm_cvtps_pd(_mm_movehl_ps(v, v)), mean_vec);
2970 total = _mm_add_pd(total, _mm_add_pd(_mm_mul_pd(lo, lo), _mm_mul_pd(hi, hi)));
2971 i += W;
2972 }
2973
2974 let mut sum = hsum_sse_f64(total);
2975 while i < len {
2976 let d = a[i] as f64 - mean;
2977 sum += d * d;
2978 i += 1;
2979 }
2980 sum
2981 }
2982}
2983
2984#[cfg(target_arch = "aarch64")]
2995#[target_feature(enable = "neon")]
2996unsafe fn sum_squared_dev_widen_neon_f32(a: &[f32], mean: f64) -> f64 {
2997 const W: usize = 4;
2998 const BLOCK: usize = W * UNROLL;
2999
3000 unsafe {
3001 let len = a.len();
3002 let a_ptr = a.as_ptr();
3003 let mean_vec = vdupq_n_f64(mean);
3004 let mut acc = [vdupq_n_f64(0.0); UNROLL];
3005
3006 let n_blocks = len / BLOCK;
3007 for i in 0..n_blocks {
3008 let base = i * BLOCK;
3009 for (u, acc) in acc.iter_mut().enumerate() {
3010 let v = vld1q_f32(a_ptr.add(base + u * W));
3011 let lo = vsubq_f64(vcvt_f64_f32(vget_low_f32(v)), mean_vec);
3012 let hi = vsubq_f64(vcvt_high_f64_f32(v), mean_vec);
3013 *acc = vfmaq_f64(vfmaq_f64(*acc, lo, lo), hi, hi);
3014 }
3015 }
3016
3017 let mut total = acc[0];
3018 for acc in &acc[1..] {
3019 total = vaddq_f64(total, *acc);
3020 }
3021
3022 let mut i = n_blocks * BLOCK;
3023 while i + W <= len {
3024 let v = vld1q_f32(a_ptr.add(i));
3025 let lo = vsubq_f64(vcvt_f64_f32(vget_low_f32(v)), mean_vec);
3026 let hi = vsubq_f64(vcvt_high_f64_f32(v), mean_vec);
3027 total = vfmaq_f64(vfmaq_f64(total, lo, lo), hi, hi);
3028 i += W;
3029 }
3030
3031 let mut sum = vaddvq_f64(total);
3032 while i < len {
3033 let d = a[i] as f64 - mean;
3034 sum += d * d;
3035 i += 1;
3036 }
3037 sum
3038 }
3039}
3040
3041#[cfg(target_arch = "x86_64")]
3052#[target_feature(enable = "avx2", enable = "fma")]
3053unsafe fn sum_squared_dev_widen_avx2_f32(a: &[f32], mean: f64) -> f64 {
3054 const W: usize = 8;
3055 const BLOCK: usize = W * UNROLL;
3056
3057 unsafe {
3058 let len = a.len();
3059 let a_ptr = a.as_ptr();
3060 let mean_vec = _mm256_set1_pd(mean);
3061 let mut acc = [_mm256_setzero_pd(); UNROLL];
3062
3063 let n_blocks = len / BLOCK;
3064 for i in 0..n_blocks {
3065 let base = i * BLOCK;
3066 for (u, acc) in acc.iter_mut().enumerate() {
3067 let p = a_ptr.add(base + u * W);
3068 let lo = _mm256_sub_pd(_mm256_cvtps_pd(_mm_loadu_ps(p)), mean_vec);
3069 let hi = _mm256_sub_pd(_mm256_cvtps_pd(_mm_loadu_ps(p.add(4))), mean_vec);
3070 *acc = _mm256_fmadd_pd(hi, hi, _mm256_fmadd_pd(lo, lo, *acc));
3071 }
3072 }
3073
3074 let mut total = acc[0];
3075 for acc in &acc[1..] {
3076 total = _mm256_add_pd(total, *acc);
3077 }
3078
3079 let mut i = n_blocks * BLOCK;
3080 while i + W <= len {
3081 let p = a_ptr.add(i);
3082 let lo = _mm256_sub_pd(_mm256_cvtps_pd(_mm_loadu_ps(p)), mean_vec);
3083 let hi = _mm256_sub_pd(_mm256_cvtps_pd(_mm_loadu_ps(p.add(4))), mean_vec);
3084 total = _mm256_fmadd_pd(hi, hi, _mm256_fmadd_pd(lo, lo, total));
3085 i += W;
3086 }
3087
3088 let mut sum = hsum_avx_f64(total);
3089 while i < len {
3090 let d = a[i] as f64 - mean;
3091 sum += d * d;
3092 i += 1;
3093 }
3094 sum
3095 }
3096}
3097
3098#[cfg(target_arch = "x86_64")]
3109#[target_feature(enable = "avx512f")]
3110unsafe fn sum_squared_dev_widen_avx512_f32(a: &[f32], mean: f64) -> f64 {
3111 const W: usize = 16;
3112 const BLOCK: usize = W * UNROLL;
3113
3114 unsafe {
3115 let len = a.len();
3116 let a_ptr = a.as_ptr();
3117 let mean_vec = _mm512_set1_pd(mean);
3118 let mut acc = [_mm512_setzero_pd(); UNROLL];
3119
3120 let n_blocks = len / BLOCK;
3121 for i in 0..n_blocks {
3122 let base = i * BLOCK;
3123 for (u, acc) in acc.iter_mut().enumerate() {
3124 let p = a_ptr.add(base + u * W);
3125 let lo = _mm512_sub_pd(_mm512_cvtps_pd(_mm256_loadu_ps(p)), mean_vec);
3126 let hi = _mm512_sub_pd(_mm512_cvtps_pd(_mm256_loadu_ps(p.add(8))), mean_vec);
3127 *acc = _mm512_fmadd_pd(hi, hi, _mm512_fmadd_pd(lo, lo, *acc));
3128 }
3129 }
3130
3131 let mut total = acc[0];
3132 for acc in &acc[1..] {
3133 total = _mm512_add_pd(total, *acc);
3134 }
3135
3136 let mut i = n_blocks * BLOCK;
3137 while i + W <= len {
3138 let p = a_ptr.add(i);
3139 let lo = _mm512_sub_pd(_mm512_cvtps_pd(_mm256_loadu_ps(p)), mean_vec);
3140 let hi = _mm512_sub_pd(_mm512_cvtps_pd(_mm256_loadu_ps(p.add(8))), mean_vec);
3141 total = _mm512_fmadd_pd(hi, hi, _mm512_fmadd_pd(lo, lo, total));
3142 i += W;
3143 }
3144
3145 let mut sum = _mm512_reduce_add_pd(total);
3146 while i < len {
3147 let d = a[i] as f64 - mean;
3148 sum += d * d;
3149 i += 1;
3150 }
3151 sum
3152 }
3153}
3154
3155#[inline]
3166pub fn sum_squared_dev_widen_simd_f32(a: &[f32], mean: f64) -> f64 {
3167 #[cfg(target_arch = "x86_64")]
3168 unsafe {
3170 match detect_simd_level() {
3171 SimdLevel::Avx512 => sum_squared_dev_widen_avx512_f32(a, mean),
3172 SimdLevel::Avx2 => sum_squared_dev_widen_avx2_f32(a, mean),
3173 SimdLevel::Sse => sum_squared_dev_widen_sse_f32(a, mean),
3174 SimdLevel::Scalar => sum_squared_dev_widen_scalar_f32(a, mean),
3175 }
3176 }
3177
3178 #[cfg(target_arch = "aarch64")]
3179 unsafe {
3181 match detect_simd_level() {
3182 SimdLevel::Sse => sum_squared_dev_widen_neon_f32(a, mean),
3183 _ => sum_squared_dev_widen_scalar_f32(a, mean),
3184 }
3185 }
3186
3187 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
3188 sum_squared_dev_widen_scalar_f32(a, mean)
3189}
3190
3191#[inline(always)]
3207fn exp_shift_sum_scalar<T: num_traits::Float + std::iter::Sum>(
3208 src: &[T],
3209 shift: T,
3210 dst: &mut [T],
3211) -> T {
3212 let mut total = T::zero();
3213 for (d, &x) in dst.iter_mut().zip(src) {
3214 *d = (x - shift).exp();
3215 total = total + *d;
3216 }
3217 total
3218}
3219
3220const EXP_LANES: usize = 8;
3223
3224#[inline(always)]
3241fn exp_poly_f32(x: f32) -> f32 {
3242 const LN2_HI: f32 = 0.693_359_4;
3243 const LN2_LO: f32 = -2.121_944_4e-4;
3244 let x = x.clamp(-87.3, 88.0);
3245 let r = (x * std::f32::consts::LOG2_E).round();
3246 let f = x - r * LN2_HI - r * LN2_LO;
3247 let p = 1.0 / 5040.0_f32;
3248 let p = p * f + 1.0 / 720.0;
3249 let p = p * f + 1.0 / 120.0;
3250 let p = p * f + 1.0 / 24.0;
3251 let p = p * f + 1.0 / 6.0;
3252 let p = p * f + 0.5;
3253 let e = p * f * f + f + 1.0;
3254 e * f32::from_bits(((r as i32 + 127) << 23) as u32)
3255}
3256
3257#[inline(always)]
3269fn exp_shift_sum_poly_f32(src: &[f32], shift: f32, dst: &mut [f32]) -> f32 {
3270 let mut acc = [0.0f32; EXP_LANES];
3271 let mut src_chunks = src.chunks_exact(EXP_LANES);
3272 let mut dst_chunks = dst.chunks_exact_mut(EXP_LANES);
3273 for (d, s) in (&mut dst_chunks).zip(&mut src_chunks) {
3274 for j in 0..EXP_LANES {
3275 d[j] = exp_poly_f32(s[j] - shift);
3276 acc[j] += d[j];
3277 }
3278 }
3279 let mut total: f32 = acc.iter().sum();
3280 for (d, &x) in dst_chunks
3281 .into_remainder()
3282 .iter_mut()
3283 .zip(src_chunks.remainder())
3284 {
3285 *d = exp_poly_f32(x - shift);
3286 total += *d;
3287 }
3288 total
3289}
3290
3291#[inline]
3303pub fn exp_shift_sum_simd_f32(src: &[f32], shift: f32, dst: &mut [f32]) -> f32 {
3304 exp_shift_sum_poly_f32(src, shift, dst)
3305}
3306
3307#[cfg(test)]
3312mod tests {
3313 use super::*;
3314 use approx::assert_relative_eq;
3315 use rand::prelude::*;
3316 use rand::rngs::StdRng;
3317
3318 const LENGTHS: [usize; 22] = [
3322 1, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 100, 127, 257, 666, 1000,
3323 ];
3324
3325 #[test]
3333 fn test_argmin_diff_simd_matches_scalar() {
3334 let mut rng = StdRng::seed_from_u64(42);
3335
3336 for len in LENGTHS {
3337 for tied in [false, true] {
3338 let (a, b): (Vec<f32>, Vec<f32>) = (0..len)
3339 .map(|_| {
3340 if tied {
3341 (rng.random_range(0..3) as f32, 0.0f32)
3343 } else {
3344 (
3345 rng.random::<f32>() * 2.0 - 1.0,
3346 rng.random::<f32>() * 2.0 - 1.0,
3347 )
3348 }
3349 })
3350 .unzip();
3351
3352 let (simd_idx, simd_val) = argmin_diff_simd_f32(&a, &b);
3353 let (scalar_idx, scalar_val) = argmin_diff_scalar_f32(&a, &b);
3354
3355 assert_eq!(
3356 simd_idx, scalar_idx,
3357 "index mismatch at len {} (tied {:?})",
3358 len, tied
3359 );
3360 assert_eq!(
3361 simd_val, scalar_val,
3362 "value mismatch at len {} (tied {:?})",
3363 len, tied
3364 );
3365 }
3366 }
3367 }
3368
3369 #[test]
3373 fn test_argmin_diff_widths_agree() {
3374 let mut rng = StdRng::seed_from_u64(7);
3375 for len in LENGTHS {
3376 for tied in [false, true] {
3377 let (a, b): (Vec<f32>, Vec<f32>) = (0..len)
3378 .map(|_| {
3379 if tied {
3380 (rng.random_range(0..3) as f32, 0.0f32)
3381 } else {
3382 (rng.random::<f32>(), rng.random::<f32>())
3383 }
3384 })
3385 .unzip();
3386
3387 let reference = argmin_diff_scalar_f32(&a, &b);
3388
3389 #[cfg(target_arch = "aarch64")]
3390 unsafe {
3391 assert_eq!(
3392 argmin_diff_neon_f32(&a, &b),
3393 reference,
3394 "neon at len {} (tied {:?})",
3395 len,
3396 tied
3397 );
3398 }
3399
3400 #[cfg(target_arch = "x86_64")]
3401 unsafe {
3402 if is_x86_feature_detected!("sse4.1") {
3403 assert_eq!(
3404 argmin_diff_sse_f32(&a, &b),
3405 reference,
3406 "sse at len {} (tied {:?})",
3407 len,
3408 tied
3409 );
3410 }
3411 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
3412 assert_eq!(
3413 argmin_diff_avx2_f32(&a, &b),
3414 reference,
3415 "avx2 at len {} (tied {:?})",
3416 len,
3417 tied
3418 );
3419 }
3420 if is_x86_feature_detected!("avx512f") {
3421 assert_eq!(
3422 argmin_diff_avx512_f32(&a, &b),
3423 reference,
3424 "avx512 at len {} (tied {:?})",
3425 len,
3426 tied
3427 );
3428 }
3429 }
3430 }
3431 }
3432 }
3433
3434 #[test]
3442 fn test_reduction_widths_agree() {
3443 let mut rng = StdRng::seed_from_u64(99);
3444
3445 for len in LENGTHS {
3446 let a32: Vec<f32> = (0..len).map(|_| rng.random::<f32>() * 2.0 - 1.0).collect();
3447 let b32: Vec<f32> = (0..len).map(|_| rng.random::<f32>() * 2.0 - 1.0).collect();
3448 let a64: Vec<f64> = a32.iter().map(|&x| x as f64).collect();
3449 let b64: Vec<f64> = b32.iter().map(|&x| x as f64).collect();
3450 let mean32 = sum_scalar_f32(&a32) / len as f32;
3451 let mean64 = sum_scalar_f64(&a64) / len as f64;
3452
3453 assert_relative_eq!(
3454 sum_squares_sse_f32(&a32),
3455 sum_squares_scalar_f32(&a32),
3456 epsilon = 1e-4,
3457 max_relative = 1e-5
3458 );
3459 assert_relative_eq!(
3460 dot_sse_f32(&a32, &b32),
3461 dot_scalar_f32(&a32, &b32),
3462 epsilon = 1e-4,
3463 max_relative = 1e-5
3464 );
3465 assert_relative_eq!(
3466 sum_sse_f32(&a32),
3467 sum_scalar_f32(&a32),
3468 epsilon = 1e-4,
3469 max_relative = 1e-5
3470 );
3471 assert_relative_eq!(
3472 sum_squared_dev_sse_f32(&a32, mean32),
3473 sum_squared_dev_scalar_f32(&a32, mean32),
3474 epsilon = 1e-4,
3475 max_relative = 1e-5
3476 );
3477 assert_relative_eq!(
3478 dot_sse_f64(&a64, &b64),
3479 dot_scalar_f64(&a64, &b64),
3480 epsilon = 1e-12,
3481 max_relative = 1e-12
3482 );
3483 assert_relative_eq!(
3484 sum_sse_f64(&a64),
3485 sum_scalar_f64(&a64),
3486 epsilon = 1e-12,
3487 max_relative = 1e-12
3488 );
3489 assert_relative_eq!(
3490 sum_squared_dev_sse_f64(&a64, mean64),
3491 sum_squared_dev_scalar_f64(&a64, mean64),
3492 epsilon = 1e-12,
3493 max_relative = 1e-12
3494 );
3495
3496 #[cfg(target_arch = "x86_64")]
3497 unsafe {
3498 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
3499 assert_relative_eq!(
3500 sum_squares_avx2_f32(&a32),
3501 sum_squares_scalar_f32(&a32),
3502 epsilon = 1e-4,
3503 max_relative = 1e-5
3504 );
3505 assert_relative_eq!(
3506 dot_avx2_f32(&a32, &b32),
3507 dot_scalar_f32(&a32, &b32),
3508 epsilon = 1e-4,
3509 max_relative = 1e-5
3510 );
3511 assert_relative_eq!(
3512 sum_avx2_f32(&a32),
3513 sum_scalar_f32(&a32),
3514 epsilon = 1e-4,
3515 max_relative = 1e-5
3516 );
3517 assert_relative_eq!(
3518 sum_squared_dev_avx2_f32(&a32, mean32),
3519 sum_squared_dev_scalar_f32(&a32, mean32),
3520 epsilon = 1e-4,
3521 max_relative = 1e-5
3522 );
3523 assert_relative_eq!(
3524 dot_avx2_f64(&a64, &b64),
3525 dot_scalar_f64(&a64, &b64),
3526 epsilon = 1e-12,
3527 max_relative = 1e-12
3528 );
3529 assert_relative_eq!(
3530 sum_avx2_f64(&a64),
3531 sum_scalar_f64(&a64),
3532 epsilon = 1e-12,
3533 max_relative = 1e-12
3534 );
3535 assert_relative_eq!(
3536 sum_squared_dev_avx2_f64(&a64, mean64),
3537 sum_squared_dev_scalar_f64(&a64, mean64),
3538 epsilon = 1e-12,
3539 max_relative = 1e-12
3540 );
3541 }
3542
3543 if is_x86_feature_detected!("avx512f") {
3544 assert_relative_eq!(
3545 sum_squares_avx512_f32(&a32),
3546 sum_squares_scalar_f32(&a32),
3547 epsilon = 1e-4,
3548 max_relative = 1e-5
3549 );
3550 assert_relative_eq!(
3551 dot_avx512_f32(&a32, &b32),
3552 dot_scalar_f32(&a32, &b32),
3553 epsilon = 1e-4,
3554 max_relative = 1e-5
3555 );
3556 assert_relative_eq!(
3557 sum_avx512_f32(&a32),
3558 sum_scalar_f32(&a32),
3559 epsilon = 1e-4,
3560 max_relative = 1e-5
3561 );
3562 assert_relative_eq!(
3563 sum_squared_dev_avx512_f32(&a32, mean32),
3564 sum_squared_dev_scalar_f32(&a32, mean32),
3565 epsilon = 1e-4,
3566 max_relative = 1e-5
3567 );
3568 assert_relative_eq!(
3569 dot_avx512_f64(&a64, &b64),
3570 dot_scalar_f64(&a64, &b64),
3571 epsilon = 1e-12,
3572 max_relative = 1e-12
3573 );
3574 assert_relative_eq!(
3575 sum_avx512_f64(&a64),
3576 sum_scalar_f64(&a64),
3577 epsilon = 1e-12,
3578 max_relative = 1e-12
3579 );
3580 assert_relative_eq!(
3581 sum_squared_dev_avx512_f64(&a64, mean64),
3582 sum_squared_dev_scalar_f64(&a64, mean64),
3583 epsilon = 1e-12,
3584 max_relative = 1e-12
3585 );
3586 }
3587 }
3588 }
3589 }
3590
3591 #[test]
3596 fn test_widening_reductions_match_f64_reference() {
3597 let mut rng = StdRng::seed_from_u64(7);
3598
3599 for len in LENGTHS {
3600 let a: Vec<f32> = (0..len).map(|_| rng.random::<f32>() * 2.0 - 1.0).collect();
3601 let mean = sum_widen_scalar_f32(&a) / len as f64;
3602 let sum_ref = sum_widen_scalar_f32(&a);
3603 let dev_ref = sum_squared_dev_widen_scalar_f32(&a, mean);
3604
3605 #[cfg(target_arch = "aarch64")]
3606 unsafe {
3608 assert_relative_eq!(sum_widen_neon_f32(&a), sum_ref, epsilon = 1e-12);
3609 assert_relative_eq!(
3610 sum_squared_dev_widen_neon_f32(&a, mean),
3611 dev_ref,
3612 epsilon = 1e-12
3613 );
3614 }
3615
3616 #[cfg(target_arch = "x86_64")]
3617 unsafe {
3619 assert_relative_eq!(sum_widen_sse_f32(&a), sum_ref, epsilon = 1e-12);
3620 assert_relative_eq!(
3621 sum_squared_dev_widen_sse_f32(&a, mean),
3622 dev_ref,
3623 epsilon = 1e-12
3624 );
3625
3626 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
3627 assert_relative_eq!(sum_widen_avx2_f32(&a), sum_ref, epsilon = 1e-12);
3628 assert_relative_eq!(
3629 sum_squared_dev_widen_avx2_f32(&a, mean),
3630 dev_ref,
3631 epsilon = 1e-12
3632 );
3633 }
3634
3635 if is_x86_feature_detected!("avx512f") {
3636 assert_relative_eq!(sum_widen_avx512_f32(&a), sum_ref, epsilon = 1e-12);
3637 assert_relative_eq!(
3638 sum_squared_dev_widen_avx512_f32(&a, mean),
3639 dev_ref,
3640 epsilon = 1e-12
3641 );
3642 }
3643 }
3644 }
3645 }
3646
3647 #[test]
3652 fn test_widening_sum_survives_large_counts() {
3653 let mut rng = StdRng::seed_from_u64(11);
3654 let values: Vec<f32> = (0..50_000)
3655 .map(|_| (rng.random::<f32>() * 1e5).round())
3656 .collect();
3657
3658 let exact: f64 = values.iter().map(|&x| x as f64).sum();
3659 let widened = sum_widen_simd_f32(&values);
3660 let naive = values.iter().sum::<f32>() as f64;
3661
3662 assert_relative_eq!(widened, exact, max_relative = 1e-12);
3663 assert!(
3664 (naive - exact).abs() > (widened - exact).abs() * 1e3,
3665 "f32 accumulation was expected to drift badly: naive {naive}, exact {exact}"
3666 );
3667 }
3668
3669 #[test]
3678 fn test_axpy_widths_agree() {
3679 let mut rng = StdRng::seed_from_u64(2024);
3680
3681 for len in LENGTHS {
3682 let x32: Vec<f32> = (0..len).map(|_| rng.random::<f32>() * 2.0 - 1.0).collect();
3683 let y32: Vec<f32> = (0..len).map(|_| rng.random::<f32>() * 2.0 - 1.0).collect();
3684 let x64: Vec<f64> = x32.iter().map(|&v| v as f64).collect();
3685 let y64: Vec<f64> = y32.iter().map(|&v| v as f64).collect();
3686 let a32 = 0.75_f32;
3687 let a64 = 0.75_f64;
3688
3689 let mut want32 = y32.clone();
3690 axpy_scalar_f32(&mut want32, a32, &x32);
3691 let mut want64 = y64.clone();
3692 axpy_scalar_f64(&mut want64, a64, &x64);
3693
3694 let mut got32 = y32.clone();
3696 axpy_simd_f32(&mut got32, a32, &x32);
3697 let mut got64 = y64.clone();
3698 axpy_simd_f64(&mut got64, a64, &x64);
3699 for i in 0..len {
3700 assert_relative_eq!(got32[i], want32[i], epsilon = 1e-6, max_relative = 1e-6);
3701 assert_relative_eq!(got64[i], want64[i], epsilon = 1e-14, max_relative = 1e-14);
3702 }
3703
3704 let mut sse32 = y32.clone();
3705 axpy_sse_f32(&mut sse32, a32, &x32);
3706 let mut sse64 = y64.clone();
3707 axpy_sse_f64(&mut sse64, a64, &x64);
3708 for i in 0..len {
3709 assert_relative_eq!(sse32[i], want32[i], epsilon = 1e-6, max_relative = 1e-6);
3710 assert_relative_eq!(sse64[i], want64[i], epsilon = 1e-14, max_relative = 1e-14);
3711 }
3712
3713 #[cfg(target_arch = "x86_64")]
3714 {
3715 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
3716 let mut avx32 = y32.clone();
3717 let mut avx64 = y64.clone();
3718 unsafe {
3719 axpy_avx2_f32(&mut avx32, a32, &x32);
3720 axpy_avx2_f64(&mut avx64, a64, &x64);
3721 }
3722 for i in 0..len {
3723 assert_relative_eq!(
3724 avx32[i],
3725 want32[i],
3726 epsilon = 1e-6,
3727 max_relative = 1e-6
3728 );
3729 assert_relative_eq!(
3730 avx64[i],
3731 want64[i],
3732 epsilon = 1e-14,
3733 max_relative = 1e-14
3734 );
3735 }
3736 }
3737
3738 if is_x86_feature_detected!("avx512f") {
3739 let mut avx32 = y32.clone();
3740 let mut avx64 = y64.clone();
3741 unsafe {
3742 axpy_avx512_f32(&mut avx32, a32, &x32);
3743 axpy_avx512_f64(&mut avx64, a64, &x64);
3744 }
3745 for i in 0..len {
3746 assert_relative_eq!(
3747 avx32[i],
3748 want32[i],
3749 epsilon = 1e-6,
3750 max_relative = 1e-6
3751 );
3752 assert_relative_eq!(
3753 avx64[i],
3754 want64[i],
3755 epsilon = 1e-14,
3756 max_relative = 1e-14
3757 );
3758 }
3759 }
3760 }
3761 }
3762 }
3763
3764 #[test]
3768 fn test_axpy_trivial_multipliers() {
3769 let x = vec![1.5_f32, -2.0, 3.25, 0.5, 7.0];
3770 let y0 = vec![0.5_f32, 0.25, -1.0, 2.0, -3.5];
3771
3772 let mut y = y0.clone();
3773 axpy_simd_f32(&mut y, 0.0, &x);
3774 assert_eq!(y, y0);
3775
3776 let mut y = y0.clone();
3777 axpy_simd_f32(&mut y, 1.0, &x);
3778 let want: Vec<f32> = y0.iter().zip(x.iter()).map(|(&a, &b)| a + b).collect();
3779 assert_eq!(y, want);
3780 }
3781}