1#[cfg(target_arch = "x86_64")]
53#[inline(always)]
54unsafe fn hsum_256(v: std::arch::x86_64::__m256d) -> f64 {
55 use std::arch::x86_64::*;
56 unsafe {
59 let hi = _mm256_extractf128_pd(v, 1);
61 let lo = _mm256_castpd256_pd128(v);
62 let sum128 = _mm_add_pd(lo, hi); let hi64 = _mm_unpackhi_pd(sum128, sum128);
64 _mm_cvtsd_f64(_mm_add_sd(sum128, hi64))
65 }
66}
67
68#[cfg(target_arch = "x86_64")]
75#[target_feature(enable = "avx512f")]
76#[inline]
77unsafe fn hsum_512(v: std::arch::x86_64::__m512d) -> f64 {
78 use std::arch::x86_64::*;
79 unsafe {
82 let lo = _mm512_castpd512_pd256(v);
83 let hi = _mm512_extractf64x4_pd(v, 1);
84 let sum256 = _mm256_add_pd(lo, hi);
85 hsum_256(sum256)
86 }
87}
88
89#[cfg(target_arch = "x86_64")]
95#[inline(always)]
96unsafe fn hmin_256(v: std::arch::x86_64::__m256d) -> f64 {
97 use std::arch::x86_64::*;
98 unsafe {
101 let hi = _mm256_extractf128_pd(v, 1);
102 let lo = _mm256_castpd256_pd128(v);
103 let m = _mm_min_pd(lo, hi);
104 let hi64 = _mm_unpackhi_pd(m, m);
105 _mm_cvtsd_f64(_mm_min_sd(m, hi64))
106 }
107}
108
109#[cfg(target_arch = "x86_64")]
115#[inline(always)]
116unsafe fn hmax_256(v: std::arch::x86_64::__m256d) -> f64 {
117 use std::arch::x86_64::*;
118 unsafe {
121 let hi = _mm256_extractf128_pd(v, 1);
122 let lo = _mm256_castpd256_pd128(v);
123 let m = _mm_max_pd(lo, hi);
124 let hi64 = _mm_unpackhi_pd(m, m);
125 _mm_cvtsd_f64(_mm_max_sd(m, hi64))
126 }
127}
128
129#[cfg(target_arch = "x86_64")]
135#[target_feature(enable = "avx512f")]
136#[inline]
137unsafe fn hmin_512(v: std::arch::x86_64::__m512d) -> f64 {
138 use std::arch::x86_64::*;
139 unsafe {
142 let lo = _mm512_castpd512_pd256(v);
143 let hi = _mm512_extractf64x4_pd(v, 1);
144 let m = _mm256_min_pd(lo, hi);
145 hmin_256(m)
146 }
147}
148
149#[cfg(target_arch = "x86_64")]
155#[target_feature(enable = "avx512f")]
156#[inline]
157unsafe fn hmax_512(v: std::arch::x86_64::__m512d) -> f64 {
158 use std::arch::x86_64::*;
159 unsafe {
162 let lo = _mm512_castpd512_pd256(v);
163 let hi = _mm512_extractf64x4_pd(v, 1);
164 let m = _mm256_max_pd(lo, hi);
165 hmax_256(m)
166 }
167}
168
169#[inline]
175#[allow(dead_code)]
176fn scalar_sum(data: &[f64]) -> f64 {
177 let chunks = data.chunks_exact(4);
178 let remainder = chunks.remainder();
179 let (mut a0, mut a1, mut a2, mut a3) = (0.0_f64, 0.0, 0.0, 0.0);
180 let (mut c0, mut c1, mut c2, mut c3) = (0.0_f64, 0.0, 0.0, 0.0);
181 for c in chunks {
182 let y0 = c[0] - c0;
183 let t0 = a0 + y0;
184 c0 = (t0 - a0) - y0;
185 a0 = t0;
186 let y1 = c[1] - c1;
187 let t1 = a1 + y1;
188 c1 = (t1 - a1) - y1;
189 a1 = t1;
190 let y2 = c[2] - c2;
191 let t2 = a2 + y2;
192 c2 = (t2 - a2) - y2;
193 a2 = t2;
194 let y3 = c[3] - c3;
195 let t3 = a3 + y3;
196 c3 = (t3 - a3) - y3;
197 a3 = t3;
198 }
199 let mut sum = 0.0_f64;
201 let mut c = 0.0_f64;
202 for &v in &[a0, a1, a2, a3] {
203 let y = v - c;
204 let t = sum + y;
205 c = (t - sum) - y;
206 sum = t;
207 }
208 for &v in remainder {
209 let y = v - c;
210 let t = sum + y;
211 c = (t - sum) - y;
212 sum = t;
213 }
214 sum
215}
216
217#[inline]
219#[allow(dead_code)]
220fn scalar_variance(data: &[f64], mean: f64) -> f64 {
221 let chunks = data.chunks_exact(4);
222 let remainder = chunks.remainder();
223 let (mut a0, mut a1, mut a2, mut a3) = (0.0_f64, 0.0, 0.0, 0.0);
224 let (mut c0, mut c1, mut c2, mut c3) = (0.0_f64, 0.0, 0.0, 0.0);
226 for c in chunks {
227 let (d0, d1, d2, d3) = (c[0] - mean, c[1] - mean, c[2] - mean, c[3] - mean);
228 let y0 = d0 * d0 - c0;
229 let t0 = a0 + y0;
230 c0 = (t0 - a0) - y0;
231 a0 = t0;
232 let y1 = d1 * d1 - c1;
233 let t1 = a1 + y1;
234 c1 = (t1 - a1) - y1;
235 a1 = t1;
236 let y2 = d2 * d2 - c2;
237 let t2 = a2 + y2;
238 c2 = (t2 - a2) - y2;
239 a2 = t2;
240 let y3 = d3 * d3 - c3;
241 let t3 = a3 + y3;
242 c3 = (t3 - a3) - y3;
243 a3 = t3;
244 }
245 let mut s = (a0 + a1) + (a2 + a3);
246 let mut cs = (c0 + c1) + (c2 + c3);
247 for &v in remainder {
248 let d = v - mean;
249 let y = d * d - cs;
250 let t = s + y;
251 cs = (t - s) - y;
252 s = t;
253 }
254 s / data.len() as f64
255}
256
257#[inline]
259#[allow(dead_code)]
260fn scalar_min_max(data: &[f64]) -> (f64, f64) {
261 let mut min_val = f64::INFINITY;
262 let mut max_val = f64::NEG_INFINITY;
263 for &v in data {
264 if v < min_val {
265 min_val = v;
266 }
267 if v > max_val {
268 max_val = v;
269 }
270 }
271 (min_val, max_val)
272}
273
274#[inline]
276#[allow(dead_code)]
277fn scalar_dot_product(a: &[f64], b: &[f64]) -> f64 {
278 let chunks = a.len() / 4;
279 let (mut a0, mut a1, mut a2, mut a3) = (0.0_f64, 0.0, 0.0, 0.0);
280 for i in 0..chunks {
281 let base = i * 4;
282 a0 += a[base] * b[base];
283 a1 += a[base + 1] * b[base + 1];
284 a2 += a[base + 2] * b[base + 2];
285 a3 += a[base + 3] * b[base + 3];
286 }
287 let mut t = (a0 + a1) + (a2 + a3);
288 for i in (chunks * 4)..a.len() {
289 t += a[i] * b[i];
290 }
291 t
292}
293
294#[cfg(target_arch = "x86_64")]
312#[target_feature(enable = "avx512f")]
313unsafe fn avx512_sum(data: &[f64]) -> f64 {
314 use std::arch::x86_64::*;
315 unsafe {
319 let mut acc0 = _mm512_setzero_pd();
320 let mut acc1 = _mm512_setzero_pd();
321 let mut acc2 = _mm512_setzero_pd();
322 let mut acc3 = _mm512_setzero_pd();
323 let mut comp0 = _mm512_setzero_pd();
325 let mut comp1 = _mm512_setzero_pd();
326 let mut comp2 = _mm512_setzero_pd();
327 let mut comp3 = _mm512_setzero_pd();
328 let chunks = data.len() / 32;
329 let ptr = data.as_ptr();
330 for i in 0..chunks {
331 let b = i * 32;
332 let v0 = _mm512_loadu_pd(ptr.add(b));
333 let y0 = _mm512_sub_pd(v0, comp0);
334 let t0 = _mm512_add_pd(acc0, y0);
335 comp0 = _mm512_sub_pd(_mm512_sub_pd(t0, acc0), y0);
336 acc0 = t0;
337
338 let v1 = _mm512_loadu_pd(ptr.add(b + 8));
339 let y1 = _mm512_sub_pd(v1, comp1);
340 let t1 = _mm512_add_pd(acc1, y1);
341 comp1 = _mm512_sub_pd(_mm512_sub_pd(t1, acc1), y1);
342 acc1 = t1;
343
344 let v2 = _mm512_loadu_pd(ptr.add(b + 16));
345 let y2 = _mm512_sub_pd(v2, comp2);
346 let t2 = _mm512_add_pd(acc2, y2);
347 comp2 = _mm512_sub_pd(_mm512_sub_pd(t2, acc2), y2);
348 acc2 = t2;
349
350 let v3 = _mm512_loadu_pd(ptr.add(b + 24));
351 let y3 = _mm512_sub_pd(v3, comp3);
352 let t3 = _mm512_add_pd(acc3, y3);
353 comp3 = _mm512_sub_pd(_mm512_sub_pd(t3, acc3), y3);
354 acc3 = t3;
355 }
356 let sum = _mm512_add_pd(_mm512_add_pd(acc0, acc1), _mm512_add_pd(acc2, acc3));
357 let mut total = hsum_512(sum);
358 let mut c = 0.0_f64;
360 for &v in &data[chunks * 32..] {
361 let y = v - c;
362 let t = total + y;
363 c = (t - total) - y;
364 total = t;
365 }
366 total
367 }
368}
369
370#[cfg(target_arch = "x86_64")]
371#[target_feature(enable = "avx512f")]
372unsafe fn avx512_variance(data: &[f64], mean: f64) -> f64 {
373 use std::arch::x86_64::*;
374 unsafe {
378 let vmean = _mm512_set1_pd(mean);
379 let mut acc0 = _mm512_setzero_pd();
380 let mut acc1 = _mm512_setzero_pd();
381 let mut acc2 = _mm512_setzero_pd();
382 let mut acc3 = _mm512_setzero_pd();
383 let chunks = data.len() / 32;
384 let ptr = data.as_ptr();
385 for i in 0..chunks {
386 let b = i * 32;
387 let d0 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b)), vmean);
388 let d1 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b + 8)), vmean);
389 let d2 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b + 16)), vmean);
390 let d3 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b + 24)), vmean);
391 acc0 = _mm512_fmadd_pd(d0, d0, acc0);
392 acc1 = _mm512_fmadd_pd(d1, d1, acc1);
393 acc2 = _mm512_fmadd_pd(d2, d2, acc2);
394 acc3 = _mm512_fmadd_pd(d3, d3, acc3);
395 }
396 let sum = _mm512_add_pd(_mm512_add_pd(acc0, acc1), _mm512_add_pd(acc2, acc3));
397 let mut total = hsum_512(sum);
398 for &v in &data[chunks * 32..] {
399 let d = v - mean;
400 total += d * d;
401 }
402 total / data.len() as f64
403 }
404}
405
406#[cfg(target_arch = "x86_64")]
407#[target_feature(enable = "avx512f")]
408unsafe fn avx512_min_max(data: &[f64]) -> (f64, f64) {
409 use std::arch::x86_64::*;
410 unsafe {
414 let mut vmin0 = _mm512_set1_pd(f64::INFINITY);
415 let mut vmax0 = _mm512_set1_pd(f64::NEG_INFINITY);
416 let mut vmin1 = vmin0;
417 let mut vmax1 = vmax0;
418 let chunks = data.len() / 16;
419 let ptr = data.as_ptr();
420 for i in 0..chunks {
421 let b = i * 16;
422 let a = _mm512_loadu_pd(ptr.add(b));
423 let c = _mm512_loadu_pd(ptr.add(b + 8));
424 vmin0 = _mm512_min_pd(a, vmin0);
425 vmax0 = _mm512_max_pd(a, vmax0);
426 vmin1 = _mm512_min_pd(c, vmin1);
427 vmax1 = _mm512_max_pd(c, vmax1);
428 }
429 let vmin = _mm512_min_pd(vmin0, vmin1);
430 let vmax = _mm512_max_pd(vmax0, vmax1);
431 let mut min_val = hmin_512(vmin);
432 let mut max_val = hmax_512(vmax);
433 for &v in &data[chunks * 16..] {
434 if v < min_val {
435 min_val = v;
436 }
437 if v > max_val {
438 max_val = v;
439 }
440 }
441 (min_val, max_val)
442 }
443}
444
445#[cfg(target_arch = "x86_64")]
446#[target_feature(enable = "avx512f")]
447unsafe fn avx512_dot_product(a: &[f64], b: &[f64]) -> f64 {
448 use std::arch::x86_64::*;
449 unsafe {
453 let mut acc0 = _mm512_setzero_pd();
454 let mut acc1 = _mm512_setzero_pd();
455 let mut acc2 = _mm512_setzero_pd();
456 let mut acc3 = _mm512_setzero_pd();
457 let chunks = a.len() / 32;
458 let pa = a.as_ptr();
459 let pb = b.as_ptr();
460 for i in 0..chunks {
461 let base = i * 32;
462 acc0 = _mm512_fmadd_pd(
463 _mm512_loadu_pd(pa.add(base)),
464 _mm512_loadu_pd(pb.add(base)),
465 acc0,
466 );
467 acc1 = _mm512_fmadd_pd(
468 _mm512_loadu_pd(pa.add(base + 8)),
469 _mm512_loadu_pd(pb.add(base + 8)),
470 acc1,
471 );
472 acc2 = _mm512_fmadd_pd(
473 _mm512_loadu_pd(pa.add(base + 16)),
474 _mm512_loadu_pd(pb.add(base + 16)),
475 acc2,
476 );
477 acc3 = _mm512_fmadd_pd(
478 _mm512_loadu_pd(pa.add(base + 24)),
479 _mm512_loadu_pd(pb.add(base + 24)),
480 acc3,
481 );
482 }
483 let sum = _mm512_add_pd(_mm512_add_pd(acc0, acc1), _mm512_add_pd(acc2, acc3));
484 let mut total = hsum_512(sum);
485 for i in (chunks * 32)..a.len() {
486 total += a[i] * b[i];
487 }
488 total
489 }
490}
491
492#[cfg(target_arch = "x86_64")]
504#[target_feature(enable = "avx2,fma")]
505unsafe fn avx2_sum(data: &[f64]) -> f64 {
506 use std::arch::x86_64::*;
507 unsafe {
511 let mut acc0 = _mm256_setzero_pd();
512 let mut acc1 = _mm256_setzero_pd();
513 let mut acc2 = _mm256_setzero_pd();
514 let mut acc3 = _mm256_setzero_pd();
515 let mut comp0 = _mm256_setzero_pd();
517 let mut comp1 = _mm256_setzero_pd();
518 let mut comp2 = _mm256_setzero_pd();
519 let mut comp3 = _mm256_setzero_pd();
520 let chunks = data.len() / 16;
521 let ptr = data.as_ptr();
522 for i in 0..chunks {
523 let b = i * 16;
524 let v0 = _mm256_loadu_pd(ptr.add(b));
525 let y0 = _mm256_sub_pd(v0, comp0);
526 let t0 = _mm256_add_pd(acc0, y0);
527 comp0 = _mm256_sub_pd(_mm256_sub_pd(t0, acc0), y0);
528 acc0 = t0;
529
530 let v1 = _mm256_loadu_pd(ptr.add(b + 4));
531 let y1 = _mm256_sub_pd(v1, comp1);
532 let t1 = _mm256_add_pd(acc1, y1);
533 comp1 = _mm256_sub_pd(_mm256_sub_pd(t1, acc1), y1);
534 acc1 = t1;
535
536 let v2 = _mm256_loadu_pd(ptr.add(b + 8));
537 let y2 = _mm256_sub_pd(v2, comp2);
538 let t2 = _mm256_add_pd(acc2, y2);
539 comp2 = _mm256_sub_pd(_mm256_sub_pd(t2, acc2), y2);
540 acc2 = t2;
541
542 let v3 = _mm256_loadu_pd(ptr.add(b + 12));
543 let y3 = _mm256_sub_pd(v3, comp3);
544 let t3 = _mm256_add_pd(acc3, y3);
545 comp3 = _mm256_sub_pd(_mm256_sub_pd(t3, acc3), y3);
546 acc3 = t3;
547 }
548 let sum = _mm256_add_pd(_mm256_add_pd(acc0, acc1), _mm256_add_pd(acc2, acc3));
549 let mut total = hsum_256(sum);
550 let mut c = 0.0_f64;
552 for &v in &data[chunks * 16..] {
553 let y = v - c;
554 let t = total + y;
555 c = (t - total) - y;
556 total = t;
557 }
558 total
559 }
560}
561
562#[cfg(target_arch = "x86_64")]
563#[target_feature(enable = "avx2,fma")]
564unsafe fn avx2_variance(data: &[f64], mean: f64) -> f64 {
565 use std::arch::x86_64::*;
566 unsafe {
570 let vmean = _mm256_set1_pd(mean);
571 let mut acc0 = _mm256_setzero_pd();
572 let mut acc1 = _mm256_setzero_pd();
573 let mut acc2 = _mm256_setzero_pd();
574 let mut acc3 = _mm256_setzero_pd();
575 let chunks = data.len() / 16;
576 let ptr = data.as_ptr();
577 for i in 0..chunks {
578 let b = i * 16;
579 let d0 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b)), vmean);
580 let d1 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b + 4)), vmean);
581 let d2 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b + 8)), vmean);
582 let d3 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b + 12)), vmean);
583 acc0 = _mm256_fmadd_pd(d0, d0, acc0);
584 acc1 = _mm256_fmadd_pd(d1, d1, acc1);
585 acc2 = _mm256_fmadd_pd(d2, d2, acc2);
586 acc3 = _mm256_fmadd_pd(d3, d3, acc3);
587 }
588 let sum = _mm256_add_pd(_mm256_add_pd(acc0, acc1), _mm256_add_pd(acc2, acc3));
589 let mut total = hsum_256(sum);
590 for &v in &data[chunks * 16..] {
591 let d = v - mean;
592 total += d * d;
593 }
594 total / data.len() as f64
595 }
596}
597
598#[cfg(target_arch = "x86_64")]
599#[target_feature(enable = "avx2")]
600unsafe fn avx2_min_max(data: &[f64]) -> (f64, f64) {
601 use std::arch::x86_64::*;
602 unsafe {
606 let mut vmin0 = _mm256_set1_pd(f64::INFINITY);
607 let mut vmax0 = _mm256_set1_pd(f64::NEG_INFINITY);
608 let mut vmin1 = vmin0;
609 let mut vmax1 = vmax0;
610 let chunks = data.len() / 8;
611 let ptr = data.as_ptr();
612 for i in 0..chunks {
613 let b = i * 8;
614 let a = _mm256_loadu_pd(ptr.add(b));
615 let c = _mm256_loadu_pd(ptr.add(b + 4));
616 vmin0 = _mm256_min_pd(a, vmin0);
617 vmax0 = _mm256_max_pd(a, vmax0);
618 vmin1 = _mm256_min_pd(c, vmin1);
619 vmax1 = _mm256_max_pd(c, vmax1);
620 }
621 let vmin = _mm256_min_pd(vmin0, vmin1);
622 let vmax = _mm256_max_pd(vmax0, vmax1);
623 let mut min_val = hmin_256(vmin);
624 let mut max_val = hmax_256(vmax);
625 for &v in &data[chunks * 8..] {
626 if v < min_val {
627 min_val = v;
628 }
629 if v > max_val {
630 max_val = v;
631 }
632 }
633 (min_val, max_val)
634 }
635}
636
637#[cfg(target_arch = "x86_64")]
638#[target_feature(enable = "avx2,fma")]
639unsafe fn avx2_dot_product(a: &[f64], b: &[f64]) -> f64 {
640 use std::arch::x86_64::*;
641 unsafe {
645 let mut acc0 = _mm256_setzero_pd();
646 let mut acc1 = _mm256_setzero_pd();
647 let mut acc2 = _mm256_setzero_pd();
648 let mut acc3 = _mm256_setzero_pd();
649 let chunks = a.len() / 16;
650 let pa = a.as_ptr();
651 let pb = b.as_ptr();
652 for i in 0..chunks {
653 let base = i * 16;
654 acc0 = _mm256_fmadd_pd(
655 _mm256_loadu_pd(pa.add(base)),
656 _mm256_loadu_pd(pb.add(base)),
657 acc0,
658 );
659 acc1 = _mm256_fmadd_pd(
660 _mm256_loadu_pd(pa.add(base + 4)),
661 _mm256_loadu_pd(pb.add(base + 4)),
662 acc1,
663 );
664 acc2 = _mm256_fmadd_pd(
665 _mm256_loadu_pd(pa.add(base + 8)),
666 _mm256_loadu_pd(pb.add(base + 8)),
667 acc2,
668 );
669 acc3 = _mm256_fmadd_pd(
670 _mm256_loadu_pd(pa.add(base + 12)),
671 _mm256_loadu_pd(pb.add(base + 12)),
672 acc3,
673 );
674 }
675 let sum = _mm256_add_pd(_mm256_add_pd(acc0, acc1), _mm256_add_pd(acc2, acc3));
676 let mut total = hsum_256(sum);
677 for i in (chunks * 16)..a.len() {
678 total += a[i] * b[i];
679 }
680 total
681 }
682}
683
684#[cfg(target_arch = "x86_64")]
687#[inline]
688fn sse2_sum(data: &[f64]) -> f64 {
689 use std::arch::x86_64::*;
690 unsafe {
692 let mut acc0 = _mm_setzero_pd();
693 let mut acc1 = _mm_setzero_pd();
694 let mut acc2 = _mm_setzero_pd();
695 let mut acc3 = _mm_setzero_pd();
696 let mut comp0 = _mm_setzero_pd();
698 let mut comp1 = _mm_setzero_pd();
699 let mut comp2 = _mm_setzero_pd();
700 let mut comp3 = _mm_setzero_pd();
701 let chunks = data.len() / 8;
702 let ptr = data.as_ptr();
703 for i in 0..chunks {
704 let b = i * 8;
705 let v0 = _mm_loadu_pd(ptr.add(b));
706 let y0 = _mm_sub_pd(v0, comp0);
707 let t0 = _mm_add_pd(acc0, y0);
708 comp0 = _mm_sub_pd(_mm_sub_pd(t0, acc0), y0);
709 acc0 = t0;
710
711 let v1 = _mm_loadu_pd(ptr.add(b + 2));
712 let y1 = _mm_sub_pd(v1, comp1);
713 let t1 = _mm_add_pd(acc1, y1);
714 comp1 = _mm_sub_pd(_mm_sub_pd(t1, acc1), y1);
715 acc1 = t1;
716
717 let v2 = _mm_loadu_pd(ptr.add(b + 4));
718 let y2 = _mm_sub_pd(v2, comp2);
719 let t2 = _mm_add_pd(acc2, y2);
720 comp2 = _mm_sub_pd(_mm_sub_pd(t2, acc2), y2);
721 acc2 = t2;
722
723 let v3 = _mm_loadu_pd(ptr.add(b + 6));
724 let y3 = _mm_sub_pd(v3, comp3);
725 let t3 = _mm_add_pd(acc3, y3);
726 comp3 = _mm_sub_pd(_mm_sub_pd(t3, acc3), y3);
727 acc3 = t3;
728 }
729 let sum = _mm_add_pd(_mm_add_pd(acc0, acc1), _mm_add_pd(acc2, acc3));
730 let mut total = _mm_cvtsd_f64(sum) + _mm_cvtsd_f64(_mm_unpackhi_pd(sum, sum));
731 let mut c = 0.0_f64;
733 for &v in &data[chunks * 8..] {
734 let y = v - c;
735 let t = total + y;
736 c = (t - total) - y;
737 total = t;
738 }
739 total
740 }
741}
742
743#[cfg(target_arch = "x86_64")]
744#[inline]
745fn sse2_variance(data: &[f64], mean: f64) -> f64 {
746 use std::arch::x86_64::*;
747 unsafe {
749 let vmean = _mm_set1_pd(mean);
750 let mut acc0 = _mm_setzero_pd();
751 let mut acc1 = _mm_setzero_pd();
752 let mut acc2 = _mm_setzero_pd();
753 let mut acc3 = _mm_setzero_pd();
754 let chunks = data.len() / 8;
755 let ptr = data.as_ptr();
756 for i in 0..chunks {
757 let b = i * 8;
758 let d0 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b)), vmean);
759 let d1 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b + 2)), vmean);
760 let d2 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b + 4)), vmean);
761 let d3 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b + 6)), vmean);
762 acc0 = _mm_add_pd(acc0, _mm_mul_pd(d0, d0));
763 acc1 = _mm_add_pd(acc1, _mm_mul_pd(d1, d1));
764 acc2 = _mm_add_pd(acc2, _mm_mul_pd(d2, d2));
765 acc3 = _mm_add_pd(acc3, _mm_mul_pd(d3, d3));
766 }
767 let sum = _mm_add_pd(_mm_add_pd(acc0, acc1), _mm_add_pd(acc2, acc3));
768 let mut total = _mm_cvtsd_f64(sum) + _mm_cvtsd_f64(_mm_unpackhi_pd(sum, sum));
769 for &v in &data[chunks * 8..] {
770 let d = v - mean;
771 total += d * d;
772 }
773 total / data.len() as f64
774 }
775}
776
777#[cfg(target_arch = "x86_64")]
778#[inline]
779fn sse2_min_max(data: &[f64]) -> (f64, f64) {
780 use std::arch::x86_64::*;
781 unsafe {
783 let mut vmin0 = _mm_set1_pd(f64::INFINITY);
784 let mut vmax0 = _mm_set1_pd(f64::NEG_INFINITY);
785 let mut vmin1 = vmin0;
786 let mut vmax1 = vmax0;
787 let chunks = data.len() / 4;
788 let ptr = data.as_ptr();
789 for i in 0..chunks {
790 let b = i * 4;
791 let a = _mm_loadu_pd(ptr.add(b));
792 let c = _mm_loadu_pd(ptr.add(b + 2));
793 vmin0 = _mm_min_pd(a, vmin0);
794 vmax0 = _mm_max_pd(a, vmax0);
795 vmin1 = _mm_min_pd(c, vmin1);
796 vmax1 = _mm_max_pd(c, vmax1);
797 }
798 let vmin = _mm_min_pd(vmin0, vmin1);
799 let vmax = _mm_max_pd(vmax0, vmax1);
800 let mut min_val = f64::min(
801 _mm_cvtsd_f64(vmin),
802 _mm_cvtsd_f64(_mm_unpackhi_pd(vmin, vmin)),
803 );
804 let mut max_val = f64::max(
805 _mm_cvtsd_f64(vmax),
806 _mm_cvtsd_f64(_mm_unpackhi_pd(vmax, vmax)),
807 );
808 for &v in &data[chunks * 4..] {
809 if v < min_val {
810 min_val = v;
811 }
812 if v > max_val {
813 max_val = v;
814 }
815 }
816 (min_val, max_val)
817 }
818}
819
820#[cfg(target_arch = "x86_64")]
821#[inline]
822fn sse2_dot_product(a: &[f64], b: &[f64]) -> f64 {
823 use std::arch::x86_64::*;
824 unsafe {
826 let mut acc0 = _mm_setzero_pd();
827 let mut acc1 = _mm_setzero_pd();
828 let mut acc2 = _mm_setzero_pd();
829 let mut acc3 = _mm_setzero_pd();
830 let chunks = a.len() / 8;
831 let pa = a.as_ptr();
832 let pb = b.as_ptr();
833 for i in 0..chunks {
834 let base = i * 8;
835 let va0 = _mm_loadu_pd(pa.add(base));
836 let vb0 = _mm_loadu_pd(pb.add(base));
837 let va1 = _mm_loadu_pd(pa.add(base + 2));
838 let vb1 = _mm_loadu_pd(pb.add(base + 2));
839 let va2 = _mm_loadu_pd(pa.add(base + 4));
840 let vb2 = _mm_loadu_pd(pb.add(base + 4));
841 let va3 = _mm_loadu_pd(pa.add(base + 6));
842 let vb3 = _mm_loadu_pd(pb.add(base + 6));
843 acc0 = _mm_add_pd(acc0, _mm_mul_pd(va0, vb0));
844 acc1 = _mm_add_pd(acc1, _mm_mul_pd(va1, vb1));
845 acc2 = _mm_add_pd(acc2, _mm_mul_pd(va2, vb2));
846 acc3 = _mm_add_pd(acc3, _mm_mul_pd(va3, vb3));
847 }
848 let sum = _mm_add_pd(_mm_add_pd(acc0, acc1), _mm_add_pd(acc2, acc3));
849 let mut total = _mm_cvtsd_f64(sum) + _mm_cvtsd_f64(_mm_unpackhi_pd(sum, sum));
850 for i in (chunks * 8)..a.len() {
851 total += a[i] * b[i];
852 }
853 total
854 }
855}
856
857#[cfg(target_arch = "x86_64")]
860#[inline]
861fn arch_simd_sum(data: &[f64]) -> f64 {
862 if is_x86_feature_detected!("avx512f") {
863 return unsafe { avx512_sum(data) };
865 }
866 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
867 return unsafe { avx2_sum(data) };
869 }
870 sse2_sum(data)
871}
872
873#[cfg(target_arch = "x86_64")]
874#[inline]
875fn arch_simd_variance(data: &[f64], mean: f64) -> f64 {
876 if is_x86_feature_detected!("avx512f") {
877 return unsafe { avx512_variance(data, mean) };
879 }
880 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
881 return unsafe { avx2_variance(data, mean) };
883 }
884 sse2_variance(data, mean)
885}
886
887#[cfg(target_arch = "x86_64")]
888#[inline]
889fn arch_simd_min_max(data: &[f64]) -> (f64, f64) {
890 if is_x86_feature_detected!("avx512f") {
891 return unsafe { avx512_min_max(data) };
893 }
894 if is_x86_feature_detected!("avx2") {
895 return unsafe { avx2_min_max(data) };
897 }
898 sse2_min_max(data)
899}
900
901#[cfg(target_arch = "x86_64")]
902#[inline]
903fn arch_simd_dot_product(a: &[f64], b: &[f64]) -> f64 {
904 if is_x86_feature_detected!("avx512f") {
905 return unsafe { avx512_dot_product(a, b) };
907 }
908 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
909 return unsafe { avx2_dot_product(a, b) };
911 }
912 sse2_dot_product(a, b)
913}
914
915#[cfg(target_arch = "aarch64")]
920#[inline]
921fn arch_simd_sum(data: &[f64]) -> f64 {
922 use std::arch::aarch64::*;
923 unsafe {
925 let mut acc0 = vdupq_n_f64(0.0);
926 let mut acc1 = vdupq_n_f64(0.0);
927 let mut acc2 = vdupq_n_f64(0.0);
928 let mut acc3 = vdupq_n_f64(0.0);
929 let mut comp0 = vdupq_n_f64(0.0);
931 let mut comp1 = vdupq_n_f64(0.0);
932 let mut comp2 = vdupq_n_f64(0.0);
933 let mut comp3 = vdupq_n_f64(0.0);
934 let chunks = data.len() / 8;
935 let ptr = data.as_ptr();
936 for i in 0..chunks {
937 let b = i * 8;
938 let v0 = vld1q_f64(ptr.add(b));
939 let y0 = vsubq_f64(v0, comp0);
940 let t0 = vaddq_f64(acc0, y0);
941 comp0 = vsubq_f64(vsubq_f64(t0, acc0), y0);
942 acc0 = t0;
943
944 let v1 = vld1q_f64(ptr.add(b + 2));
945 let y1 = vsubq_f64(v1, comp1);
946 let t1 = vaddq_f64(acc1, y1);
947 comp1 = vsubq_f64(vsubq_f64(t1, acc1), y1);
948 acc1 = t1;
949
950 let v2 = vld1q_f64(ptr.add(b + 4));
951 let y2 = vsubq_f64(v2, comp2);
952 let t2 = vaddq_f64(acc2, y2);
953 comp2 = vsubq_f64(vsubq_f64(t2, acc2), y2);
954 acc2 = t2;
955
956 let v3 = vld1q_f64(ptr.add(b + 6));
957 let y3 = vsubq_f64(v3, comp3);
958 let t3 = vaddq_f64(acc3, y3);
959 comp3 = vsubq_f64(vsubq_f64(t3, acc3), y3);
960 acc3 = t3;
961 }
962 let sum = vaddq_f64(vaddq_f64(acc0, acc1), vaddq_f64(acc2, acc3));
963 let mut total = vgetq_lane_f64(sum, 0) + vgetq_lane_f64(sum, 1);
964 let mut c = 0.0_f64;
966 for &v in &data[chunks * 8..] {
967 let y = v - c;
968 let t = total + y;
969 c = (t - total) - y;
970 total = t;
971 }
972 total
973 }
974}
975
976#[cfg(target_arch = "aarch64")]
977#[inline]
978fn arch_simd_variance(data: &[f64], mean: f64) -> f64 {
979 use std::arch::aarch64::*;
980 unsafe {
982 let vmean = vdupq_n_f64(mean);
983 let mut acc0 = vdupq_n_f64(0.0);
984 let mut acc1 = vdupq_n_f64(0.0);
985 let mut acc2 = vdupq_n_f64(0.0);
986 let mut acc3 = vdupq_n_f64(0.0);
987 let chunks = data.len() / 8;
988 let ptr = data.as_ptr();
989 for i in 0..chunks {
990 let b = i * 8;
991 let d0 = vsubq_f64(vld1q_f64(ptr.add(b)), vmean);
992 let d1 = vsubq_f64(vld1q_f64(ptr.add(b + 2)), vmean);
993 let d2 = vsubq_f64(vld1q_f64(ptr.add(b + 4)), vmean);
994 let d3 = vsubq_f64(vld1q_f64(ptr.add(b + 6)), vmean);
995 acc0 = vaddq_f64(acc0, vmulq_f64(d0, d0));
996 acc1 = vaddq_f64(acc1, vmulq_f64(d1, d1));
997 acc2 = vaddq_f64(acc2, vmulq_f64(d2, d2));
998 acc3 = vaddq_f64(acc3, vmulq_f64(d3, d3));
999 }
1000 let sum = vaddq_f64(vaddq_f64(acc0, acc1), vaddq_f64(acc2, acc3));
1001 let mut total = vgetq_lane_f64(sum, 0) + vgetq_lane_f64(sum, 1);
1002 for &v in &data[chunks * 8..] {
1003 let d = v - mean;
1004 total += d * d;
1005 }
1006 total / data.len() as f64
1007 }
1008}
1009
1010#[cfg(target_arch = "aarch64")]
1011#[inline]
1012fn arch_simd_min_max(data: &[f64]) -> (f64, f64) {
1013 use std::arch::aarch64::*;
1014 unsafe {
1016 let mut vmin0 = vdupq_n_f64(f64::INFINITY);
1019 let mut vmax0 = vdupq_n_f64(f64::NEG_INFINITY);
1020 let mut vmin1 = vmin0;
1021 let mut vmax1 = vmax0;
1022 let chunks = data.len() / 4;
1023 let ptr = data.as_ptr();
1024 for i in 0..chunks {
1025 let b = i * 4;
1026 let a = vld1q_f64(ptr.add(b));
1027 let c = vld1q_f64(ptr.add(b + 2));
1028 vmin0 = vminnmq_f64(vmin0, a);
1029 vmax0 = vmaxnmq_f64(vmax0, a);
1030 vmin1 = vminnmq_f64(vmin1, c);
1031 vmax1 = vmaxnmq_f64(vmax1, c);
1032 }
1033 let vmin = vminnmq_f64(vmin0, vmin1);
1034 let vmax = vmaxnmq_f64(vmax0, vmax1);
1035 let mut min_val = f64::min(vgetq_lane_f64(vmin, 0), vgetq_lane_f64(vmin, 1));
1036 let mut max_val = f64::max(vgetq_lane_f64(vmax, 0), vgetq_lane_f64(vmax, 1));
1037 for &v in &data[chunks * 4..] {
1038 if v < min_val {
1039 min_val = v;
1040 }
1041 if v > max_val {
1042 max_val = v;
1043 }
1044 }
1045 (min_val, max_val)
1046 }
1047}
1048
1049#[cfg(target_arch = "aarch64")]
1050#[inline]
1051fn arch_simd_dot_product(a: &[f64], b: &[f64]) -> f64 {
1052 use std::arch::aarch64::*;
1053 unsafe {
1055 let mut acc0 = vdupq_n_f64(0.0);
1056 let mut acc1 = vdupq_n_f64(0.0);
1057 let mut acc2 = vdupq_n_f64(0.0);
1058 let mut acc3 = vdupq_n_f64(0.0);
1059 let chunks = a.len() / 8;
1060 let pa = a.as_ptr();
1061 let pb = b.as_ptr();
1062 for i in 0..chunks {
1063 let base = i * 8;
1064 acc0 = vfmaq_f64(acc0, vld1q_f64(pa.add(base)), vld1q_f64(pb.add(base)));
1065 acc1 = vfmaq_f64(
1066 acc1,
1067 vld1q_f64(pa.add(base + 2)),
1068 vld1q_f64(pb.add(base + 2)),
1069 );
1070 acc2 = vfmaq_f64(
1071 acc2,
1072 vld1q_f64(pa.add(base + 4)),
1073 vld1q_f64(pb.add(base + 4)),
1074 );
1075 acc3 = vfmaq_f64(
1076 acc3,
1077 vld1q_f64(pa.add(base + 6)),
1078 vld1q_f64(pb.add(base + 6)),
1079 );
1080 }
1081 let sum = vaddq_f64(vaddq_f64(acc0, acc1), vaddq_f64(acc2, acc3));
1082 let mut total = vgetq_lane_f64(sum, 0) + vgetq_lane_f64(sum, 1);
1083 for i in (chunks * 8)..a.len() {
1084 total += a[i] * b[i];
1085 }
1086 total
1087 }
1088}
1089
1090#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1095#[inline]
1096fn arch_simd_sum(data: &[f64]) -> f64 {
1097 scalar_sum(data)
1098}
1099
1100#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1101#[inline]
1102fn arch_simd_variance(data: &[f64], mean: f64) -> f64 {
1103 scalar_variance(data, mean)
1104}
1105
1106#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1107#[inline]
1108fn arch_simd_min_max(data: &[f64]) -> (f64, f64) {
1109 scalar_min_max(data)
1110}
1111
1112#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1113#[inline]
1114fn arch_simd_dot_product(a: &[f64], b: &[f64]) -> f64 {
1115 scalar_dot_product(a, b)
1116}
1117
1118pub fn simd_sum(data: &[f64]) -> f64 {
1141 if data.is_empty() {
1142 return 0.0;
1143 }
1144 arch_simd_sum(data)
1145}
1146
1147pub fn simd_mean(data: &[f64]) -> f64 {
1159 if data.is_empty() {
1160 return f64::NAN;
1161 }
1162 simd_sum(data) / data.len() as f64
1163}
1164
1165pub fn simd_variance(data: &[f64], mean: f64) -> f64 {
1180 if data.len() < 2 {
1181 return f64::NAN;
1182 }
1183 let n = data.len() as f64;
1186 arch_simd_variance(data, mean) * n / (n - 1.0)
1187}
1188
1189pub fn simd_population_variance(data: &[f64], mean: f64) -> f64 {
1193 if data.is_empty() {
1194 return f64::NAN;
1195 }
1196 arch_simd_variance(data, mean)
1197}
1198
1199pub fn simd_min_max(data: &[f64]) -> (f64, f64) {
1227 if data.is_empty() {
1228 return (f64::INFINITY, f64::NEG_INFINITY);
1229 }
1230 arch_simd_min_max(data)
1231}
1232
1233pub fn simd_std_dev(data: &[f64]) -> f64 {
1238 if data.len() < 2 {
1239 return if data.is_empty() { f64::NAN } else { 0.0 };
1240 }
1241 let (_, var) = simd_mean_variance(data);
1242 var.sqrt()
1243}
1244
1245pub fn simd_mean_variance(data: &[f64]) -> (f64, f64) {
1258 if data.len() < 2 {
1259 let mean = if data.is_empty() { f64::NAN } else { data[0] };
1260 return (mean, f64::NAN);
1261 }
1262 let mean = simd_sum(data) / data.len() as f64;
1263 let pop_var = arch_simd_variance(data, mean);
1264 let n = data.len() as f64;
1265 (mean, pop_var * n / (n - 1.0))
1266}
1267
1268pub fn simd_range_filter_i64(timestamps: &[i64], min: i64, max: i64) -> Vec<u32> {
1287 let mut result = Vec::with_capacity(timestamps.len());
1288 let chunks = timestamps.chunks_exact(4);
1290 let remainder = chunks.remainder();
1291 let mut base_idx: u32 = 0;
1292 for chunk in chunks {
1293 if chunk[0] >= min && chunk[0] <= max {
1294 result.push(base_idx);
1295 }
1296 if chunk[1] >= min && chunk[1] <= max {
1297 result.push(base_idx + 1);
1298 }
1299 if chunk[2] >= min && chunk[2] <= max {
1300 result.push(base_idx + 2);
1301 }
1302 if chunk[3] >= min && chunk[3] <= max {
1303 result.push(base_idx + 3);
1304 }
1305 base_idx += 4;
1306 }
1307 for &ts in remainder {
1308 if ts >= min && ts <= max {
1309 result.push(base_idx);
1310 }
1311 base_idx += 1;
1312 }
1313 result
1314}
1315
1316pub fn simd_dot_product(a: &[f64], b: &[f64]) -> Result<f64, crate::compute::ComputeError> {
1334 if a.len() != b.len() {
1335 return Err(crate::compute::ComputeError::DimensionMismatch {
1336 expected: a.len(),
1337 got: b.len(),
1338 });
1339 }
1340 if a.is_empty() {
1341 return Ok(0.0);
1342 }
1343 Ok(arch_simd_dot_product(a, b))
1344}
1345
1346pub fn simd_tier() -> &'static str {
1350 #[cfg(target_arch = "x86_64")]
1351 {
1352 if is_x86_feature_detected!("avx512f") {
1353 return "AVX-512F (8×f64, 512-bit)";
1354 }
1355 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
1356 return "AVX2+FMA (4×f64, 256-bit)";
1357 }
1358 "SSE2 (2×f64, 128-bit)"
1360 }
1361 #[cfg(target_arch = "aarch64")]
1362 {
1363 "NEON (2×f64, 128-bit)"
1364 }
1365 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1366 {
1367 "Scalar (4-wide ILP unroll)"
1368 }
1369}
1370
1371#[cfg(test)]
1372mod tests {
1373 #[test]
1385 fn simd_min_max_skips_nan_at_every_position() {
1386 fn reference(data: &[f64]) -> (f64, f64) {
1388 let mut min = f64::INFINITY;
1389 let mut max = f64::NEG_INFINITY;
1390 for &v in data.iter().filter(|v| !v.is_nan()) {
1391 min = min.min(v);
1392 max = max.max(v);
1393 }
1394 (min, max)
1395 }
1396
1397 for len in [1_usize, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 33, 64, 129] {
1398 for nan_at in 0..len {
1399 let mut data: Vec<f64> = (0..len).map(|i| i as f64 - 10.0).collect();
1400 data[nan_at] = f64::NAN;
1401 let (min, max) = super::simd_min_max(&data);
1402 let (want_min, want_max) = reference(&data);
1403 assert_eq!(
1404 (min, max),
1405 (want_min, want_max),
1406 "len {len}, NaN at {nan_at}"
1407 );
1408 }
1409 }
1410 }
1411
1412 #[test]
1415 fn simd_min_max_of_all_nan_is_the_empty_answer() {
1416 for len in [1_usize, 4, 8, 16, 33] {
1417 let data = vec![f64::NAN; len];
1418 let (min, max) = super::simd_min_max(&data);
1419 assert_eq!((min, max), (f64::INFINITY, f64::NEG_INFINITY), "len {len}");
1420 }
1421 }
1422
1423 proptest::proptest! {
1424 #[test]
1426 fn simd_min_max_matches_a_nan_skipping_reference(
1427 data in proptest::collection::vec(
1428 proptest::prop_oneof![
1429 3 => -1e6_f64..1e6_f64,
1430 1 => proptest::strategy::Just(f64::NAN),
1431 1 => proptest::strategy::Just(f64::INFINITY),
1432 1 => proptest::strategy::Just(f64::NEG_INFINITY),
1433 ],
1434 0..200,
1435 )
1436 ) {
1437 let mut want_min = f64::INFINITY;
1438 let mut want_max = f64::NEG_INFINITY;
1439 for &v in data.iter().filter(|v| !v.is_nan()) {
1440 want_min = want_min.min(v);
1441 want_max = want_max.max(v);
1442 }
1443 let (min, max) = super::simd_min_max(&data);
1444 proptest::prop_assert_eq!(min, want_min);
1445 proptest::prop_assert_eq!(max, want_max);
1446 }
1447 }
1448
1449 use super::*;
1450
1451 #[test]
1454 fn sum_empty() {
1455 assert_eq!(simd_sum(&[]), 0.0);
1456 }
1457
1458 #[test]
1459 fn sum_single() {
1460 assert!((simd_sum(&[42.0]) - 42.0).abs() < 1e-10);
1461 }
1462
1463 #[test]
1464 fn sum_exact_chunk() {
1465 let data: Vec<f64> = (1..=8).map(|x| x as f64).collect();
1466 assert!((simd_sum(&data) - 36.0).abs() < 1e-10);
1467 }
1468
1469 #[test]
1470 fn sum_with_remainder() {
1471 let data: Vec<f64> = (1..=7).map(|x| x as f64).collect();
1472 assert!((simd_sum(&data) - 28.0).abs() < 1e-10);
1473 }
1474
1475 #[test]
1476 fn sum_large() {
1477 let data: Vec<f64> = (0..10_000).map(|i| i as f64).collect();
1478 let expected = (10_000.0 * 9999.0) / 2.0;
1479 assert!((simd_sum(&data) - expected).abs() < 1e-4);
1480 }
1481
1482 #[test]
1483 fn sum_large_avx2_range() {
1484 let data: Vec<f64> = (0..16).map(|i| i as f64).collect();
1486 assert!((simd_sum(&data) - 120.0).abs() < 1e-10);
1487 }
1488
1489 #[test]
1490 fn sum_large_avx512_range() {
1491 let data: Vec<f64> = (0..32).map(|i| i as f64).collect();
1493 assert!((simd_sum(&data) - 496.0).abs() < 1e-10);
1494 }
1495
1496 #[test]
1497 fn sum_100k() {
1498 let data: Vec<f64> = (0..100_000).map(|i| i as f64).collect();
1499 let expected = 100_000.0 * 99_999.0 / 2.0;
1500 assert!((simd_sum(&data) - expected).abs() / expected < 1e-10);
1501 }
1502
1503 #[test]
1506 fn mean_empty() {
1507 assert!(simd_mean(&[]).is_nan());
1508 }
1509
1510 #[test]
1511 fn mean_values() {
1512 assert!((simd_mean(&[1.0, 2.0, 3.0, 4.0, 5.0]) - 3.0).abs() < 1e-10);
1513 }
1514
1515 #[test]
1518 fn variance_known() {
1519 let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
1520 let mean = simd_mean(&data);
1521 let var = simd_variance(&data, mean);
1522 assert!((var - 32.0 / 7.0).abs() < 1e-10);
1524 }
1525
1526 #[test]
1527 fn variance_single_element() {
1528 assert!(simd_variance(&[42.0], 42.0).is_nan());
1530 }
1531
1532 #[test]
1533 fn variance_empty() {
1534 assert!(simd_variance(&[], 0.0).is_nan());
1535 }
1536
1537 #[test]
1538 fn variance_constant() {
1539 let data = [5.0; 100];
1540 let mean = simd_mean(&data);
1541 assert!(simd_variance(&data, mean).abs() < 1e-14);
1542 }
1543
1544 #[test]
1547 fn min_max_empty() {
1548 let (min, max) = simd_min_max(&[]);
1549 assert_eq!(min, f64::INFINITY);
1550 assert_eq!(max, f64::NEG_INFINITY);
1551 }
1552
1553 #[test]
1554 fn min_max_values() {
1555 let (min, max) = simd_min_max(&[5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0]);
1556 assert!((min - 1.0).abs() < 1e-10);
1557 assert!((max - 9.0).abs() < 1e-10);
1558 }
1559
1560 #[test]
1561 fn min_max_single() {
1562 let (min, max) = simd_min_max(&[42.0]);
1563 assert!((min - 42.0).abs() < 1e-10);
1564 assert!((max - 42.0).abs() < 1e-10);
1565 }
1566
1567 #[test]
1568 fn min_max_negative() {
1569 let (min, max) = simd_min_max(&[-10.0, -5.0, -1.0, -20.0, -3.0]);
1570 assert!((min - (-20.0)).abs() < 1e-10);
1571 assert!((max - (-1.0)).abs() < 1e-10);
1572 }
1573
1574 #[test]
1575 fn min_max_large() {
1576 let data: Vec<f64> = (0..10_000).map(|i| i as f64).collect();
1577 let (min, max) = simd_min_max(&data);
1578 assert!((min - 0.0).abs() < 1e-10);
1579 assert!((max - 9999.0).abs() < 1e-10);
1580 }
1581
1582 #[test]
1583 fn min_max_with_nan() {
1584 let data = [f64::NAN, 2.0, f64::NAN, 1.0, 5.0, f64::NAN, 3.0];
1587 let (min, max) = simd_min_max(&data);
1588 assert!((min - 1.0).abs() < 1e-10, "min should skip NaN, got {min}");
1589 assert!((max - 5.0).abs() < 1e-10, "max should skip NaN, got {max}");
1590 }
1591
1592 #[test]
1593 fn min_max_all_nan() {
1594 let (min, max) = simd_min_max(&[f64::NAN, f64::NAN]);
1596 assert!(
1597 min == f64::INFINITY || min.is_nan(),
1598 "unexpected min: {min}"
1599 );
1600 assert!(
1601 max == f64::NEG_INFINITY || max.is_nan(),
1602 "unexpected max: {max}"
1603 );
1604 }
1605
1606 #[test]
1609 fn std_dev_constant() {
1610 assert!((simd_std_dev(&[5.0, 5.0, 5.0, 5.0])).abs() < 1e-10);
1611 }
1612
1613 #[test]
1614 fn std_dev_known() {
1615 let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
1616 let sd = simd_std_dev(&data);
1617 assert!((sd - 2.138_089_935_299_395).abs() < 1e-6);
1618 }
1619
1620 #[test]
1623 fn dot_product_basic() {
1624 let a = [1.0, 2.0, 3.0, 4.0, 5.0];
1625 let b = [5.0, 4.0, 3.0, 2.0, 1.0];
1626 assert!((simd_dot_product(&a, &b).unwrap() - 35.0).abs() < 1e-10);
1627 }
1628
1629 #[test]
1630 fn dot_product_empty() {
1631 assert!((simd_dot_product(&[], &[]).unwrap()).abs() < 1e-10);
1632 }
1633
1634 #[test]
1635 fn dot_product_mismatch() {
1636 assert!(simd_dot_product(&[1.0], &[1.0, 2.0]).is_err());
1637 }
1638
1639 #[test]
1640 fn dot_product_identity() {
1641 let v: Vec<f64> = (1..=100).map(|i| i as f64).collect();
1643 let expected: f64 = v.iter().map(|x| x * x).sum();
1644 assert!((simd_dot_product(&v, &v).unwrap() - expected).abs() < 1e-6);
1645 }
1646
1647 #[test]
1648 fn dot_product_orthogonal() {
1649 let a = [1.0, 0.0, 1.0, 0.0];
1650 let b = [0.0, 1.0, 0.0, 1.0];
1651 assert!((simd_dot_product(&a, &b).unwrap()).abs() < 1e-10);
1652 }
1653
1654 #[test]
1655 fn dot_product_large() {
1656 let a: Vec<f64> = (0..10_000).map(|i| (i as f64) * 0.001).collect();
1658 let b: Vec<f64> = (0..10_000).map(|i| 1.0 - (i as f64) * 0.0001).collect();
1659 let expected: f64 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
1660 assert!(
1661 (simd_dot_product(&a, &b).unwrap() - expected).abs() / expected.abs().max(1.0) < 1e-8
1662 );
1663 }
1664
1665 #[test]
1668 fn simd_tier_not_empty() {
1669 let tier = simd_tier();
1670 assert!(!tier.is_empty());
1671 assert!(
1673 tier.contains("AVX-512")
1674 || tier.contains("AVX2")
1675 || tier.contains("SSE2")
1676 || tier.contains("NEON")
1677 || tier.contains("Scalar")
1678 );
1679 }
1680
1681 #[test]
1684 fn scalar_vs_arch_sum() {
1685 let data: Vec<f64> = (0..1_000).map(|i| (i as f64) * 0.1 + 0.7).collect();
1686 let arch_result = arch_simd_sum(&data);
1687 let scalar_result = scalar_sum(&data);
1688 assert!(
1689 (arch_result - scalar_result).abs() < 1e-6,
1690 "sum mismatch: arch={arch_result}, scalar={scalar_result}"
1691 );
1692 }
1693
1694 #[test]
1695 fn scalar_vs_arch_variance() {
1696 let data: Vec<f64> = (0..1_000).map(|i| (i as f64) * 0.1 + 0.7).collect();
1697 let mean = simd_mean(&data);
1698 let arch_result = arch_simd_variance(&data, mean);
1699 let scalar_result = scalar_variance(&data, mean);
1700 assert!(
1701 (arch_result - scalar_result).abs() < 1e-6,
1702 "variance mismatch: arch={arch_result}, scalar={scalar_result}"
1703 );
1704 }
1705
1706 #[test]
1707 fn scalar_vs_arch_min_max() {
1708 let data: Vec<f64> = (0..1_000)
1709 .map(|i| ((i * 7 + 3) % 500) as f64 - 250.0)
1710 .collect();
1711 let (amin, amax) = arch_simd_min_max(&data);
1712 let (smin, smax) = scalar_min_max(&data);
1713 assert!((amin - smin).abs() < 1e-10, "min mismatch");
1714 assert!((amax - smax).abs() < 1e-10, "max mismatch");
1715 }
1716
1717 #[test]
1718 fn scalar_vs_arch_dot_product() {
1719 let a: Vec<f64> = (0..1_000).map(|i| (i as f64) * 0.01).collect();
1720 let b: Vec<f64> = (0..1_000).map(|i| 1.0 - (i as f64) * 0.001).collect();
1721 let arch_result = arch_simd_dot_product(&a, &b);
1722 let scalar_result = scalar_dot_product(&a, &b);
1723 assert!(
1724 (arch_result - scalar_result).abs() / scalar_result.abs().max(1.0) < 1e-8,
1725 "dot product mismatch: arch={arch_result}, scalar={scalar_result}"
1726 );
1727 }
1728
1729 #[test]
1732 fn kahan_compensated_sum_precision() {
1733 let n = 100_000;
1737 let mut data = vec![1.0_f64; n];
1738 data.extend(vec![1e-8_f64; n]);
1739 let expected = n as f64 + (n as f64) * 1e-8;
1740 let result = simd_sum(&data);
1741 let rel_err = (result - expected).abs() / expected;
1742 assert!(
1743 rel_err < 1e-14,
1744 "Kahan sum relative error {rel_err:.2e} exceeds 1e-14 (result={result}, expected={expected})"
1745 );
1746 }
1747
1748 #[test]
1749 fn kahan_alternating_sign_precision() {
1750 let n = 50_000;
1753 let mut data = Vec::with_capacity(2 * n);
1754 for i in 0..n {
1755 data.push(1e15 + (i as f64));
1756 data.push(-(1e15 + (i as f64)));
1757 }
1758 let result = simd_sum(&data);
1759 assert!(
1760 result.abs() < 1e-3,
1761 "alternating-sign sum should be ~0, got {result}"
1762 );
1763 }
1764}
1765
1766#[cfg(all(test, target_arch = "x86_64", target_feature = "avx512f"))]
1773mod avx512_tests {
1774 use super::*;
1775
1776 #[test]
1777 fn avx512_sum_direct() {
1778 let data: Vec<f64> = (0..64).map(|i| i as f64).collect();
1779 let result = unsafe { avx512_sum(&data) };
1780 let expected: f64 = (0..64).map(|i| i as f64).sum();
1781 assert!(
1782 (result - expected).abs() < 1e-10,
1783 "avx512_sum mismatch: {result} vs {expected}"
1784 );
1785 }
1786
1787 #[test]
1788 fn avx512_variance_direct() {
1789 let data: Vec<f64> = (0..128).map(|i| i as f64).collect();
1790 let (var, mean) = unsafe { avx512_variance(&data) };
1791 assert!(
1792 (mean - 63.5).abs() < 1e-10,
1793 "avx512_variance mean mismatch: {mean}"
1794 );
1795 assert!(var > 0.0, "variance must be positive");
1796 }
1797
1798 #[test]
1799 fn avx512_min_max_direct() {
1800 let data: Vec<f64> = (0..64).map(|i| i as f64).collect();
1801 let (min, max) = unsafe { avx512_min_max(&data) };
1802 assert!((min - 0.0).abs() < 1e-10);
1803 assert!((max - 63.0).abs() < 1e-10);
1804 }
1805
1806 #[test]
1807 fn avx512_dot_product_direct() {
1808 let a: Vec<f64> = (0..64).map(|i| i as f64).collect();
1809 let b: Vec<f64> = (0..64).map(|i| (i * 2) as f64).collect();
1810 let result = unsafe { avx512_dot_product(&a, &b) };
1811 let expected: f64 = (0..64).map(|i| (i * i * 2) as f64).sum();
1812 assert!(
1813 (result - expected).abs() < 1e-6,
1814 "avx512_dot mismatch: {result} vs {expected}"
1815 );
1816 }
1817}