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 let hi = _mm256_extractf128_pd(v, 1);
58 let lo = _mm256_castpd256_pd128(v);
59 let sum128 = _mm_add_pd(lo, hi); let hi64 = _mm_unpackhi_pd(sum128, sum128);
61 _mm_cvtsd_f64(_mm_add_sd(sum128, hi64))
62}
63
64#[cfg(target_arch = "x86_64")]
71#[target_feature(enable = "avx512f")]
72#[inline]
73unsafe fn hsum_512(v: std::arch::x86_64::__m512d) -> f64 {
74 use std::arch::x86_64::*;
75 let lo = _mm512_castpd512_pd256(v);
76 let hi = _mm512_extractf64x4_pd(v, 1);
77 let sum256 = _mm256_add_pd(lo, hi);
78 hsum_256(sum256)
79}
80
81#[cfg(target_arch = "x86_64")]
87#[inline(always)]
88unsafe fn hmin_256(v: std::arch::x86_64::__m256d) -> f64 {
89 use std::arch::x86_64::*;
90 let hi = _mm256_extractf128_pd(v, 1);
91 let lo = _mm256_castpd256_pd128(v);
92 let m = _mm_min_pd(lo, hi);
93 let hi64 = _mm_unpackhi_pd(m, m);
94 _mm_cvtsd_f64(_mm_min_sd(m, hi64))
95}
96
97#[cfg(target_arch = "x86_64")]
103#[inline(always)]
104unsafe fn hmax_256(v: std::arch::x86_64::__m256d) -> f64 {
105 use std::arch::x86_64::*;
106 let hi = _mm256_extractf128_pd(v, 1);
107 let lo = _mm256_castpd256_pd128(v);
108 let m = _mm_max_pd(lo, hi);
109 let hi64 = _mm_unpackhi_pd(m, m);
110 _mm_cvtsd_f64(_mm_max_sd(m, hi64))
111}
112
113#[cfg(target_arch = "x86_64")]
119#[target_feature(enable = "avx512f")]
120#[inline]
121unsafe fn hmin_512(v: std::arch::x86_64::__m512d) -> f64 {
122 use std::arch::x86_64::*;
123 let lo = _mm512_castpd512_pd256(v);
124 let hi = _mm512_extractf64x4_pd(v, 1);
125 let m = _mm256_min_pd(lo, hi);
126 hmin_256(m)
127}
128
129#[cfg(target_arch = "x86_64")]
135#[target_feature(enable = "avx512f")]
136#[inline]
137unsafe fn hmax_512(v: std::arch::x86_64::__m512d) -> f64 {
138 use std::arch::x86_64::*;
139 let lo = _mm512_castpd512_pd256(v);
140 let hi = _mm512_extractf64x4_pd(v, 1);
141 let m = _mm256_max_pd(lo, hi);
142 hmax_256(m)
143}
144
145#[inline]
151#[allow(dead_code)]
152fn scalar_sum(data: &[f64]) -> f64 {
153 let chunks = data.chunks_exact(4);
154 let remainder = chunks.remainder();
155 let (mut a0, mut a1, mut a2, mut a3) = (0.0_f64, 0.0, 0.0, 0.0);
156 let (mut c0, mut c1, mut c2, mut c3) = (0.0_f64, 0.0, 0.0, 0.0);
157 for c in chunks {
158 let y0 = c[0] - c0;
159 let t0 = a0 + y0;
160 c0 = (t0 - a0) - y0;
161 a0 = t0;
162 let y1 = c[1] - c1;
163 let t1 = a1 + y1;
164 c1 = (t1 - a1) - y1;
165 a1 = t1;
166 let y2 = c[2] - c2;
167 let t2 = a2 + y2;
168 c2 = (t2 - a2) - y2;
169 a2 = t2;
170 let y3 = c[3] - c3;
171 let t3 = a3 + y3;
172 c3 = (t3 - a3) - y3;
173 a3 = t3;
174 }
175 let mut sum = 0.0_f64;
177 let mut c = 0.0_f64;
178 for &v in &[a0, a1, a2, a3] {
179 let y = v - c;
180 let t = sum + y;
181 c = (t - sum) - y;
182 sum = t;
183 }
184 for &v in remainder {
185 let y = v - c;
186 let t = sum + y;
187 c = (t - sum) - y;
188 sum = t;
189 }
190 sum
191}
192
193#[inline]
195#[allow(dead_code)]
196fn scalar_variance(data: &[f64], mean: f64) -> f64 {
197 let chunks = data.chunks_exact(4);
198 let remainder = chunks.remainder();
199 let (mut a0, mut a1, mut a2, mut a3) = (0.0_f64, 0.0, 0.0, 0.0);
200 let (mut c0, mut c1, mut c2, mut c3) = (0.0_f64, 0.0, 0.0, 0.0);
202 for c in chunks {
203 let (d0, d1, d2, d3) = (c[0] - mean, c[1] - mean, c[2] - mean, c[3] - mean);
204 let y0 = d0 * d0 - c0;
205 let t0 = a0 + y0;
206 c0 = (t0 - a0) - y0;
207 a0 = t0;
208 let y1 = d1 * d1 - c1;
209 let t1 = a1 + y1;
210 c1 = (t1 - a1) - y1;
211 a1 = t1;
212 let y2 = d2 * d2 - c2;
213 let t2 = a2 + y2;
214 c2 = (t2 - a2) - y2;
215 a2 = t2;
216 let y3 = d3 * d3 - c3;
217 let t3 = a3 + y3;
218 c3 = (t3 - a3) - y3;
219 a3 = t3;
220 }
221 let mut s = (a0 + a1) + (a2 + a3);
222 let mut cs = (c0 + c1) + (c2 + c3);
223 for &v in remainder {
224 let d = v - mean;
225 let y = d * d - cs;
226 let t = s + y;
227 cs = (t - s) - y;
228 s = t;
229 }
230 s / data.len() as f64
231}
232
233#[inline]
235#[allow(dead_code)]
236fn scalar_min_max(data: &[f64]) -> (f64, f64) {
237 let mut min_val = f64::INFINITY;
238 let mut max_val = f64::NEG_INFINITY;
239 for &v in data {
240 if v < min_val {
241 min_val = v;
242 }
243 if v > max_val {
244 max_val = v;
245 }
246 }
247 (min_val, max_val)
248}
249
250#[inline]
252#[allow(dead_code)]
253fn scalar_dot_product(a: &[f64], b: &[f64]) -> f64 {
254 let chunks = a.len() / 4;
255 let (mut a0, mut a1, mut a2, mut a3) = (0.0_f64, 0.0, 0.0, 0.0);
256 for i in 0..chunks {
257 let base = i * 4;
258 a0 += a[base] * b[base];
259 a1 += a[base + 1] * b[base + 1];
260 a2 += a[base + 2] * b[base + 2];
261 a3 += a[base + 3] * b[base + 3];
262 }
263 let mut t = (a0 + a1) + (a2 + a3);
264 for i in (chunks * 4)..a.len() {
265 t += a[i] * b[i];
266 }
267 t
268}
269
270#[cfg(target_arch = "x86_64")]
288#[target_feature(enable = "avx512f")]
289unsafe fn avx512_sum(data: &[f64]) -> f64 {
290 use std::arch::x86_64::*;
291 let mut acc0 = _mm512_setzero_pd();
292 let mut acc1 = _mm512_setzero_pd();
293 let mut acc2 = _mm512_setzero_pd();
294 let mut acc3 = _mm512_setzero_pd();
295 let mut comp0 = _mm512_setzero_pd();
297 let mut comp1 = _mm512_setzero_pd();
298 let mut comp2 = _mm512_setzero_pd();
299 let mut comp3 = _mm512_setzero_pd();
300 let chunks = data.len() / 32;
301 let ptr = data.as_ptr();
302 for i in 0..chunks {
303 let b = i * 32;
304 let v0 = _mm512_loadu_pd(ptr.add(b));
305 let y0 = _mm512_sub_pd(v0, comp0);
306 let t0 = _mm512_add_pd(acc0, y0);
307 comp0 = _mm512_sub_pd(_mm512_sub_pd(t0, acc0), y0);
308 acc0 = t0;
309
310 let v1 = _mm512_loadu_pd(ptr.add(b + 8));
311 let y1 = _mm512_sub_pd(v1, comp1);
312 let t1 = _mm512_add_pd(acc1, y1);
313 comp1 = _mm512_sub_pd(_mm512_sub_pd(t1, acc1), y1);
314 acc1 = t1;
315
316 let v2 = _mm512_loadu_pd(ptr.add(b + 16));
317 let y2 = _mm512_sub_pd(v2, comp2);
318 let t2 = _mm512_add_pd(acc2, y2);
319 comp2 = _mm512_sub_pd(_mm512_sub_pd(t2, acc2), y2);
320 acc2 = t2;
321
322 let v3 = _mm512_loadu_pd(ptr.add(b + 24));
323 let y3 = _mm512_sub_pd(v3, comp3);
324 let t3 = _mm512_add_pd(acc3, y3);
325 comp3 = _mm512_sub_pd(_mm512_sub_pd(t3, acc3), y3);
326 acc3 = t3;
327 }
328 let sum = _mm512_add_pd(_mm512_add_pd(acc0, acc1), _mm512_add_pd(acc2, acc3));
329 let mut total = hsum_512(sum);
330 let mut c = 0.0_f64;
332 for &v in &data[chunks * 32..] {
333 let y = v - c;
334 let t = total + y;
335 c = (t - total) - y;
336 total = t;
337 }
338 total
339}
340
341#[cfg(target_arch = "x86_64")]
342#[target_feature(enable = "avx512f")]
343unsafe fn avx512_variance(data: &[f64], mean: f64) -> f64 {
344 use std::arch::x86_64::*;
345 let vmean = _mm512_set1_pd(mean);
346 let mut acc0 = _mm512_setzero_pd();
347 let mut acc1 = _mm512_setzero_pd();
348 let mut acc2 = _mm512_setzero_pd();
349 let mut acc3 = _mm512_setzero_pd();
350 let chunks = data.len() / 32;
351 let ptr = data.as_ptr();
352 for i in 0..chunks {
353 let b = i * 32;
354 let d0 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b)), vmean);
355 let d1 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b + 8)), vmean);
356 let d2 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b + 16)), vmean);
357 let d3 = _mm512_sub_pd(_mm512_loadu_pd(ptr.add(b + 24)), vmean);
358 acc0 = _mm512_fmadd_pd(d0, d0, acc0);
359 acc1 = _mm512_fmadd_pd(d1, d1, acc1);
360 acc2 = _mm512_fmadd_pd(d2, d2, acc2);
361 acc3 = _mm512_fmadd_pd(d3, d3, acc3);
362 }
363 let sum = _mm512_add_pd(_mm512_add_pd(acc0, acc1), _mm512_add_pd(acc2, acc3));
364 let mut total = hsum_512(sum);
365 for &v in &data[chunks * 32..] {
366 let d = v - mean;
367 total += d * d;
368 }
369 total / data.len() as f64
370}
371
372#[cfg(target_arch = "x86_64")]
373#[target_feature(enable = "avx512f")]
374unsafe fn avx512_min_max(data: &[f64]) -> (f64, f64) {
375 use std::arch::x86_64::*;
376 let mut vmin0 = _mm512_set1_pd(f64::INFINITY);
377 let mut vmax0 = _mm512_set1_pd(f64::NEG_INFINITY);
378 let mut vmin1 = vmin0;
379 let mut vmax1 = vmax0;
380 let chunks = data.len() / 16;
381 let ptr = data.as_ptr();
382 for i in 0..chunks {
383 let b = i * 16;
384 let a = _mm512_loadu_pd(ptr.add(b));
385 let c = _mm512_loadu_pd(ptr.add(b + 8));
386 vmin0 = _mm512_min_pd(a, vmin0);
387 vmax0 = _mm512_max_pd(a, vmax0);
388 vmin1 = _mm512_min_pd(c, vmin1);
389 vmax1 = _mm512_max_pd(c, vmax1);
390 }
391 let vmin = _mm512_min_pd(vmin0, vmin1);
392 let vmax = _mm512_max_pd(vmax0, vmax1);
393 let mut min_val = hmin_512(vmin);
394 let mut max_val = hmax_512(vmax);
395 for &v in &data[chunks * 16..] {
396 if v < min_val {
397 min_val = v;
398 }
399 if v > max_val {
400 max_val = v;
401 }
402 }
403 (min_val, max_val)
404}
405
406#[cfg(target_arch = "x86_64")]
407#[target_feature(enable = "avx512f")]
408unsafe fn avx512_dot_product(a: &[f64], b: &[f64]) -> f64 {
409 use std::arch::x86_64::*;
410 let mut acc0 = _mm512_setzero_pd();
411 let mut acc1 = _mm512_setzero_pd();
412 let mut acc2 = _mm512_setzero_pd();
413 let mut acc3 = _mm512_setzero_pd();
414 let chunks = a.len() / 32;
415 let pa = a.as_ptr();
416 let pb = b.as_ptr();
417 for i in 0..chunks {
418 let base = i * 32;
419 acc0 = _mm512_fmadd_pd(
420 _mm512_loadu_pd(pa.add(base)),
421 _mm512_loadu_pd(pb.add(base)),
422 acc0,
423 );
424 acc1 = _mm512_fmadd_pd(
425 _mm512_loadu_pd(pa.add(base + 8)),
426 _mm512_loadu_pd(pb.add(base + 8)),
427 acc1,
428 );
429 acc2 = _mm512_fmadd_pd(
430 _mm512_loadu_pd(pa.add(base + 16)),
431 _mm512_loadu_pd(pb.add(base + 16)),
432 acc2,
433 );
434 acc3 = _mm512_fmadd_pd(
435 _mm512_loadu_pd(pa.add(base + 24)),
436 _mm512_loadu_pd(pb.add(base + 24)),
437 acc3,
438 );
439 }
440 let sum = _mm512_add_pd(_mm512_add_pd(acc0, acc1), _mm512_add_pd(acc2, acc3));
441 let mut total = hsum_512(sum);
442 for i in (chunks * 32)..a.len() {
443 total += a[i] * b[i];
444 }
445 total
446}
447
448#[cfg(target_arch = "x86_64")]
460#[target_feature(enable = "avx2,fma")]
461unsafe fn avx2_sum(data: &[f64]) -> f64 {
462 use std::arch::x86_64::*;
463 let mut acc0 = _mm256_setzero_pd();
464 let mut acc1 = _mm256_setzero_pd();
465 let mut acc2 = _mm256_setzero_pd();
466 let mut acc3 = _mm256_setzero_pd();
467 let mut comp0 = _mm256_setzero_pd();
469 let mut comp1 = _mm256_setzero_pd();
470 let mut comp2 = _mm256_setzero_pd();
471 let mut comp3 = _mm256_setzero_pd();
472 let chunks = data.len() / 16;
473 let ptr = data.as_ptr();
474 for i in 0..chunks {
475 let b = i * 16;
476 let v0 = _mm256_loadu_pd(ptr.add(b));
477 let y0 = _mm256_sub_pd(v0, comp0);
478 let t0 = _mm256_add_pd(acc0, y0);
479 comp0 = _mm256_sub_pd(_mm256_sub_pd(t0, acc0), y0);
480 acc0 = t0;
481
482 let v1 = _mm256_loadu_pd(ptr.add(b + 4));
483 let y1 = _mm256_sub_pd(v1, comp1);
484 let t1 = _mm256_add_pd(acc1, y1);
485 comp1 = _mm256_sub_pd(_mm256_sub_pd(t1, acc1), y1);
486 acc1 = t1;
487
488 let v2 = _mm256_loadu_pd(ptr.add(b + 8));
489 let y2 = _mm256_sub_pd(v2, comp2);
490 let t2 = _mm256_add_pd(acc2, y2);
491 comp2 = _mm256_sub_pd(_mm256_sub_pd(t2, acc2), y2);
492 acc2 = t2;
493
494 let v3 = _mm256_loadu_pd(ptr.add(b + 12));
495 let y3 = _mm256_sub_pd(v3, comp3);
496 let t3 = _mm256_add_pd(acc3, y3);
497 comp3 = _mm256_sub_pd(_mm256_sub_pd(t3, acc3), y3);
498 acc3 = t3;
499 }
500 let sum = _mm256_add_pd(_mm256_add_pd(acc0, acc1), _mm256_add_pd(acc2, acc3));
501 let mut total = hsum_256(sum);
502 let mut c = 0.0_f64;
504 for &v in &data[chunks * 16..] {
505 let y = v - c;
506 let t = total + y;
507 c = (t - total) - y;
508 total = t;
509 }
510 total
511}
512
513#[cfg(target_arch = "x86_64")]
514#[target_feature(enable = "avx2,fma")]
515unsafe fn avx2_variance(data: &[f64], mean: f64) -> f64 {
516 use std::arch::x86_64::*;
517 let vmean = _mm256_set1_pd(mean);
518 let mut acc0 = _mm256_setzero_pd();
519 let mut acc1 = _mm256_setzero_pd();
520 let mut acc2 = _mm256_setzero_pd();
521 let mut acc3 = _mm256_setzero_pd();
522 let chunks = data.len() / 16;
523 let ptr = data.as_ptr();
524 for i in 0..chunks {
525 let b = i * 16;
526 let d0 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b)), vmean);
527 let d1 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b + 4)), vmean);
528 let d2 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b + 8)), vmean);
529 let d3 = _mm256_sub_pd(_mm256_loadu_pd(ptr.add(b + 12)), vmean);
530 acc0 = _mm256_fmadd_pd(d0, d0, acc0);
531 acc1 = _mm256_fmadd_pd(d1, d1, acc1);
532 acc2 = _mm256_fmadd_pd(d2, d2, acc2);
533 acc3 = _mm256_fmadd_pd(d3, d3, acc3);
534 }
535 let sum = _mm256_add_pd(_mm256_add_pd(acc0, acc1), _mm256_add_pd(acc2, acc3));
536 let mut total = hsum_256(sum);
537 for &v in &data[chunks * 16..] {
538 let d = v - mean;
539 total += d * d;
540 }
541 total / data.len() as f64
542}
543
544#[cfg(target_arch = "x86_64")]
545#[target_feature(enable = "avx2")]
546unsafe fn avx2_min_max(data: &[f64]) -> (f64, f64) {
547 use std::arch::x86_64::*;
548 let mut vmin0 = _mm256_set1_pd(f64::INFINITY);
549 let mut vmax0 = _mm256_set1_pd(f64::NEG_INFINITY);
550 let mut vmin1 = vmin0;
551 let mut vmax1 = vmax0;
552 let chunks = data.len() / 8;
553 let ptr = data.as_ptr();
554 for i in 0..chunks {
555 let b = i * 8;
556 let a = _mm256_loadu_pd(ptr.add(b));
557 let c = _mm256_loadu_pd(ptr.add(b + 4));
558 vmin0 = _mm256_min_pd(a, vmin0);
559 vmax0 = _mm256_max_pd(a, vmax0);
560 vmin1 = _mm256_min_pd(c, vmin1);
561 vmax1 = _mm256_max_pd(c, vmax1);
562 }
563 let vmin = _mm256_min_pd(vmin0, vmin1);
564 let vmax = _mm256_max_pd(vmax0, vmax1);
565 let mut min_val = hmin_256(vmin);
566 let mut max_val = hmax_256(vmax);
567 for &v in &data[chunks * 8..] {
568 if v < min_val {
569 min_val = v;
570 }
571 if v > max_val {
572 max_val = v;
573 }
574 }
575 (min_val, max_val)
576}
577
578#[cfg(target_arch = "x86_64")]
579#[target_feature(enable = "avx2,fma")]
580unsafe fn avx2_dot_product(a: &[f64], b: &[f64]) -> f64 {
581 use std::arch::x86_64::*;
582 let mut acc0 = _mm256_setzero_pd();
583 let mut acc1 = _mm256_setzero_pd();
584 let mut acc2 = _mm256_setzero_pd();
585 let mut acc3 = _mm256_setzero_pd();
586 let chunks = a.len() / 16;
587 let pa = a.as_ptr();
588 let pb = b.as_ptr();
589 for i in 0..chunks {
590 let base = i * 16;
591 acc0 = _mm256_fmadd_pd(
592 _mm256_loadu_pd(pa.add(base)),
593 _mm256_loadu_pd(pb.add(base)),
594 acc0,
595 );
596 acc1 = _mm256_fmadd_pd(
597 _mm256_loadu_pd(pa.add(base + 4)),
598 _mm256_loadu_pd(pb.add(base + 4)),
599 acc1,
600 );
601 acc2 = _mm256_fmadd_pd(
602 _mm256_loadu_pd(pa.add(base + 8)),
603 _mm256_loadu_pd(pb.add(base + 8)),
604 acc2,
605 );
606 acc3 = _mm256_fmadd_pd(
607 _mm256_loadu_pd(pa.add(base + 12)),
608 _mm256_loadu_pd(pb.add(base + 12)),
609 acc3,
610 );
611 }
612 let sum = _mm256_add_pd(_mm256_add_pd(acc0, acc1), _mm256_add_pd(acc2, acc3));
613 let mut total = hsum_256(sum);
614 for i in (chunks * 16)..a.len() {
615 total += a[i] * b[i];
616 }
617 total
618}
619
620#[cfg(target_arch = "x86_64")]
623#[inline]
624fn sse2_sum(data: &[f64]) -> f64 {
625 use std::arch::x86_64::*;
626 unsafe {
628 let mut acc0 = _mm_setzero_pd();
629 let mut acc1 = _mm_setzero_pd();
630 let mut acc2 = _mm_setzero_pd();
631 let mut acc3 = _mm_setzero_pd();
632 let mut comp0 = _mm_setzero_pd();
634 let mut comp1 = _mm_setzero_pd();
635 let mut comp2 = _mm_setzero_pd();
636 let mut comp3 = _mm_setzero_pd();
637 let chunks = data.len() / 8;
638 let ptr = data.as_ptr();
639 for i in 0..chunks {
640 let b = i * 8;
641 let v0 = _mm_loadu_pd(ptr.add(b));
642 let y0 = _mm_sub_pd(v0, comp0);
643 let t0 = _mm_add_pd(acc0, y0);
644 comp0 = _mm_sub_pd(_mm_sub_pd(t0, acc0), y0);
645 acc0 = t0;
646
647 let v1 = _mm_loadu_pd(ptr.add(b + 2));
648 let y1 = _mm_sub_pd(v1, comp1);
649 let t1 = _mm_add_pd(acc1, y1);
650 comp1 = _mm_sub_pd(_mm_sub_pd(t1, acc1), y1);
651 acc1 = t1;
652
653 let v2 = _mm_loadu_pd(ptr.add(b + 4));
654 let y2 = _mm_sub_pd(v2, comp2);
655 let t2 = _mm_add_pd(acc2, y2);
656 comp2 = _mm_sub_pd(_mm_sub_pd(t2, acc2), y2);
657 acc2 = t2;
658
659 let v3 = _mm_loadu_pd(ptr.add(b + 6));
660 let y3 = _mm_sub_pd(v3, comp3);
661 let t3 = _mm_add_pd(acc3, y3);
662 comp3 = _mm_sub_pd(_mm_sub_pd(t3, acc3), y3);
663 acc3 = t3;
664 }
665 let sum = _mm_add_pd(_mm_add_pd(acc0, acc1), _mm_add_pd(acc2, acc3));
666 let mut total = _mm_cvtsd_f64(sum) + _mm_cvtsd_f64(_mm_unpackhi_pd(sum, sum));
667 let mut c = 0.0_f64;
669 for &v in &data[chunks * 8..] {
670 let y = v - c;
671 let t = total + y;
672 c = (t - total) - y;
673 total = t;
674 }
675 total
676 }
677}
678
679#[cfg(target_arch = "x86_64")]
680#[inline]
681fn sse2_variance(data: &[f64], mean: f64) -> f64 {
682 use std::arch::x86_64::*;
683 unsafe {
685 let vmean = _mm_set1_pd(mean);
686 let mut acc0 = _mm_setzero_pd();
687 let mut acc1 = _mm_setzero_pd();
688 let mut acc2 = _mm_setzero_pd();
689 let mut acc3 = _mm_setzero_pd();
690 let chunks = data.len() / 8;
691 let ptr = data.as_ptr();
692 for i in 0..chunks {
693 let b = i * 8;
694 let d0 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b)), vmean);
695 let d1 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b + 2)), vmean);
696 let d2 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b + 4)), vmean);
697 let d3 = _mm_sub_pd(_mm_loadu_pd(ptr.add(b + 6)), vmean);
698 acc0 = _mm_add_pd(acc0, _mm_mul_pd(d0, d0));
699 acc1 = _mm_add_pd(acc1, _mm_mul_pd(d1, d1));
700 acc2 = _mm_add_pd(acc2, _mm_mul_pd(d2, d2));
701 acc3 = _mm_add_pd(acc3, _mm_mul_pd(d3, d3));
702 }
703 let sum = _mm_add_pd(_mm_add_pd(acc0, acc1), _mm_add_pd(acc2, acc3));
704 let mut total = _mm_cvtsd_f64(sum) + _mm_cvtsd_f64(_mm_unpackhi_pd(sum, sum));
705 for &v in &data[chunks * 8..] {
706 let d = v - mean;
707 total += d * d;
708 }
709 total / data.len() as f64
710 }
711}
712
713#[cfg(target_arch = "x86_64")]
714#[inline]
715fn sse2_min_max(data: &[f64]) -> (f64, f64) {
716 use std::arch::x86_64::*;
717 unsafe {
719 let mut vmin0 = _mm_set1_pd(f64::INFINITY);
720 let mut vmax0 = _mm_set1_pd(f64::NEG_INFINITY);
721 let mut vmin1 = vmin0;
722 let mut vmax1 = vmax0;
723 let chunks = data.len() / 4;
724 let ptr = data.as_ptr();
725 for i in 0..chunks {
726 let b = i * 4;
727 let a = _mm_loadu_pd(ptr.add(b));
728 let c = _mm_loadu_pd(ptr.add(b + 2));
729 vmin0 = _mm_min_pd(a, vmin0);
730 vmax0 = _mm_max_pd(a, vmax0);
731 vmin1 = _mm_min_pd(c, vmin1);
732 vmax1 = _mm_max_pd(c, vmax1);
733 }
734 let vmin = _mm_min_pd(vmin0, vmin1);
735 let vmax = _mm_max_pd(vmax0, vmax1);
736 let mut min_val = f64::min(
737 _mm_cvtsd_f64(vmin),
738 _mm_cvtsd_f64(_mm_unpackhi_pd(vmin, vmin)),
739 );
740 let mut max_val = f64::max(
741 _mm_cvtsd_f64(vmax),
742 _mm_cvtsd_f64(_mm_unpackhi_pd(vmax, vmax)),
743 );
744 for &v in &data[chunks * 4..] {
745 if v < min_val {
746 min_val = v;
747 }
748 if v > max_val {
749 max_val = v;
750 }
751 }
752 (min_val, max_val)
753 }
754}
755
756#[cfg(target_arch = "x86_64")]
757#[inline]
758fn sse2_dot_product(a: &[f64], b: &[f64]) -> f64 {
759 use std::arch::x86_64::*;
760 unsafe {
762 let mut acc0 = _mm_setzero_pd();
763 let mut acc1 = _mm_setzero_pd();
764 let mut acc2 = _mm_setzero_pd();
765 let mut acc3 = _mm_setzero_pd();
766 let chunks = a.len() / 8;
767 let pa = a.as_ptr();
768 let pb = b.as_ptr();
769 for i in 0..chunks {
770 let base = i * 8;
771 let va0 = _mm_loadu_pd(pa.add(base));
772 let vb0 = _mm_loadu_pd(pb.add(base));
773 let va1 = _mm_loadu_pd(pa.add(base + 2));
774 let vb1 = _mm_loadu_pd(pb.add(base + 2));
775 let va2 = _mm_loadu_pd(pa.add(base + 4));
776 let vb2 = _mm_loadu_pd(pb.add(base + 4));
777 let va3 = _mm_loadu_pd(pa.add(base + 6));
778 let vb3 = _mm_loadu_pd(pb.add(base + 6));
779 acc0 = _mm_add_pd(acc0, _mm_mul_pd(va0, vb0));
780 acc1 = _mm_add_pd(acc1, _mm_mul_pd(va1, vb1));
781 acc2 = _mm_add_pd(acc2, _mm_mul_pd(va2, vb2));
782 acc3 = _mm_add_pd(acc3, _mm_mul_pd(va3, vb3));
783 }
784 let sum = _mm_add_pd(_mm_add_pd(acc0, acc1), _mm_add_pd(acc2, acc3));
785 let mut total = _mm_cvtsd_f64(sum) + _mm_cvtsd_f64(_mm_unpackhi_pd(sum, sum));
786 for i in (chunks * 8)..a.len() {
787 total += a[i] * b[i];
788 }
789 total
790 }
791}
792
793#[cfg(target_arch = "x86_64")]
796#[inline]
797fn arch_simd_sum(data: &[f64]) -> f64 {
798 if is_x86_feature_detected!("avx512f") {
799 return unsafe { avx512_sum(data) };
801 }
802 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
803 return unsafe { avx2_sum(data) };
805 }
806 sse2_sum(data)
807}
808
809#[cfg(target_arch = "x86_64")]
810#[inline]
811fn arch_simd_variance(data: &[f64], mean: f64) -> f64 {
812 if is_x86_feature_detected!("avx512f") {
813 return unsafe { avx512_variance(data, mean) };
815 }
816 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
817 return unsafe { avx2_variance(data, mean) };
819 }
820 sse2_variance(data, mean)
821}
822
823#[cfg(target_arch = "x86_64")]
824#[inline]
825fn arch_simd_min_max(data: &[f64]) -> (f64, f64) {
826 if is_x86_feature_detected!("avx512f") {
827 return unsafe { avx512_min_max(data) };
829 }
830 if is_x86_feature_detected!("avx2") {
831 return unsafe { avx2_min_max(data) };
833 }
834 sse2_min_max(data)
835}
836
837#[cfg(target_arch = "x86_64")]
838#[inline]
839fn arch_simd_dot_product(a: &[f64], b: &[f64]) -> f64 {
840 if is_x86_feature_detected!("avx512f") {
841 return unsafe { avx512_dot_product(a, b) };
843 }
844 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
845 return unsafe { avx2_dot_product(a, b) };
847 }
848 sse2_dot_product(a, b)
849}
850
851#[cfg(target_arch = "aarch64")]
856#[inline]
857fn arch_simd_sum(data: &[f64]) -> f64 {
858 use std::arch::aarch64::*;
859 unsafe {
861 let mut acc0 = vdupq_n_f64(0.0);
862 let mut acc1 = vdupq_n_f64(0.0);
863 let mut acc2 = vdupq_n_f64(0.0);
864 let mut acc3 = vdupq_n_f64(0.0);
865 let mut comp0 = vdupq_n_f64(0.0);
867 let mut comp1 = vdupq_n_f64(0.0);
868 let mut comp2 = vdupq_n_f64(0.0);
869 let mut comp3 = vdupq_n_f64(0.0);
870 let chunks = data.len() / 8;
871 let ptr = data.as_ptr();
872 for i in 0..chunks {
873 let b = i * 8;
874 let v0 = vld1q_f64(ptr.add(b));
875 let y0 = vsubq_f64(v0, comp0);
876 let t0 = vaddq_f64(acc0, y0);
877 comp0 = vsubq_f64(vsubq_f64(t0, acc0), y0);
878 acc0 = t0;
879
880 let v1 = vld1q_f64(ptr.add(b + 2));
881 let y1 = vsubq_f64(v1, comp1);
882 let t1 = vaddq_f64(acc1, y1);
883 comp1 = vsubq_f64(vsubq_f64(t1, acc1), y1);
884 acc1 = t1;
885
886 let v2 = vld1q_f64(ptr.add(b + 4));
887 let y2 = vsubq_f64(v2, comp2);
888 let t2 = vaddq_f64(acc2, y2);
889 comp2 = vsubq_f64(vsubq_f64(t2, acc2), y2);
890 acc2 = t2;
891
892 let v3 = vld1q_f64(ptr.add(b + 6));
893 let y3 = vsubq_f64(v3, comp3);
894 let t3 = vaddq_f64(acc3, y3);
895 comp3 = vsubq_f64(vsubq_f64(t3, acc3), y3);
896 acc3 = t3;
897 }
898 let sum = vaddq_f64(vaddq_f64(acc0, acc1), vaddq_f64(acc2, acc3));
899 let mut total = vgetq_lane_f64(sum, 0) + vgetq_lane_f64(sum, 1);
900 let mut c = 0.0_f64;
902 for &v in &data[chunks * 8..] {
903 let y = v - c;
904 let t = total + y;
905 c = (t - total) - y;
906 total = t;
907 }
908 total
909 }
910}
911
912#[cfg(target_arch = "aarch64")]
913#[inline]
914fn arch_simd_variance(data: &[f64], mean: f64) -> f64 {
915 use std::arch::aarch64::*;
916 unsafe {
918 let vmean = vdupq_n_f64(mean);
919 let mut acc0 = vdupq_n_f64(0.0);
920 let mut acc1 = vdupq_n_f64(0.0);
921 let mut acc2 = vdupq_n_f64(0.0);
922 let mut acc3 = vdupq_n_f64(0.0);
923 let chunks = data.len() / 8;
924 let ptr = data.as_ptr();
925 for i in 0..chunks {
926 let b = i * 8;
927 let d0 = vsubq_f64(vld1q_f64(ptr.add(b)), vmean);
928 let d1 = vsubq_f64(vld1q_f64(ptr.add(b + 2)), vmean);
929 let d2 = vsubq_f64(vld1q_f64(ptr.add(b + 4)), vmean);
930 let d3 = vsubq_f64(vld1q_f64(ptr.add(b + 6)), vmean);
931 acc0 = vaddq_f64(acc0, vmulq_f64(d0, d0));
932 acc1 = vaddq_f64(acc1, vmulq_f64(d1, d1));
933 acc2 = vaddq_f64(acc2, vmulq_f64(d2, d2));
934 acc3 = vaddq_f64(acc3, vmulq_f64(d3, d3));
935 }
936 let sum = vaddq_f64(vaddq_f64(acc0, acc1), vaddq_f64(acc2, acc3));
937 let mut total = vgetq_lane_f64(sum, 0) + vgetq_lane_f64(sum, 1);
938 for &v in &data[chunks * 8..] {
939 let d = v - mean;
940 total += d * d;
941 }
942 total / data.len() as f64
943 }
944}
945
946#[cfg(target_arch = "aarch64")]
947#[inline]
948fn arch_simd_min_max(data: &[f64]) -> (f64, f64) {
949 use std::arch::aarch64::*;
950 unsafe {
952 let mut vmin0 = vdupq_n_f64(f64::INFINITY);
955 let mut vmax0 = vdupq_n_f64(f64::NEG_INFINITY);
956 let mut vmin1 = vmin0;
957 let mut vmax1 = vmax0;
958 let chunks = data.len() / 4;
959 let ptr = data.as_ptr();
960 for i in 0..chunks {
961 let b = i * 4;
962 let a = vld1q_f64(ptr.add(b));
963 let c = vld1q_f64(ptr.add(b + 2));
964 vmin0 = vminnmq_f64(vmin0, a);
965 vmax0 = vmaxnmq_f64(vmax0, a);
966 vmin1 = vminnmq_f64(vmin1, c);
967 vmax1 = vmaxnmq_f64(vmax1, c);
968 }
969 let vmin = vminnmq_f64(vmin0, vmin1);
970 let vmax = vmaxnmq_f64(vmax0, vmax1);
971 let mut min_val = f64::min(vgetq_lane_f64(vmin, 0), vgetq_lane_f64(vmin, 1));
972 let mut max_val = f64::max(vgetq_lane_f64(vmax, 0), vgetq_lane_f64(vmax, 1));
973 for &v in &data[chunks * 4..] {
974 if v < min_val {
975 min_val = v;
976 }
977 if v > max_val {
978 max_val = v;
979 }
980 }
981 (min_val, max_val)
982 }
983}
984
985#[cfg(target_arch = "aarch64")]
986#[inline]
987fn arch_simd_dot_product(a: &[f64], b: &[f64]) -> f64 {
988 use std::arch::aarch64::*;
989 unsafe {
991 let mut acc0 = vdupq_n_f64(0.0);
992 let mut acc1 = vdupq_n_f64(0.0);
993 let mut acc2 = vdupq_n_f64(0.0);
994 let mut acc3 = vdupq_n_f64(0.0);
995 let chunks = a.len() / 8;
996 let pa = a.as_ptr();
997 let pb = b.as_ptr();
998 for i in 0..chunks {
999 let base = i * 8;
1000 acc0 = vfmaq_f64(acc0, vld1q_f64(pa.add(base)), vld1q_f64(pb.add(base)));
1001 acc1 = vfmaq_f64(
1002 acc1,
1003 vld1q_f64(pa.add(base + 2)),
1004 vld1q_f64(pb.add(base + 2)),
1005 );
1006 acc2 = vfmaq_f64(
1007 acc2,
1008 vld1q_f64(pa.add(base + 4)),
1009 vld1q_f64(pb.add(base + 4)),
1010 );
1011 acc3 = vfmaq_f64(
1012 acc3,
1013 vld1q_f64(pa.add(base + 6)),
1014 vld1q_f64(pb.add(base + 6)),
1015 );
1016 }
1017 let sum = vaddq_f64(vaddq_f64(acc0, acc1), vaddq_f64(acc2, acc3));
1018 let mut total = vgetq_lane_f64(sum, 0) + vgetq_lane_f64(sum, 1);
1019 for i in (chunks * 8)..a.len() {
1020 total += a[i] * b[i];
1021 }
1022 total
1023 }
1024}
1025
1026#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1031#[inline]
1032fn arch_simd_sum(data: &[f64]) -> f64 {
1033 scalar_sum(data)
1034}
1035
1036#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1037#[inline]
1038fn arch_simd_variance(data: &[f64], mean: f64) -> f64 {
1039 scalar_variance(data, mean)
1040}
1041
1042#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1043#[inline]
1044fn arch_simd_min_max(data: &[f64]) -> (f64, f64) {
1045 scalar_min_max(data)
1046}
1047
1048#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1049#[inline]
1050fn arch_simd_dot_product(a: &[f64], b: &[f64]) -> f64 {
1051 scalar_dot_product(a, b)
1052}
1053
1054pub fn simd_sum(data: &[f64]) -> f64 {
1077 if data.is_empty() {
1078 return 0.0;
1079 }
1080 arch_simd_sum(data)
1081}
1082
1083pub fn simd_mean(data: &[f64]) -> f64 {
1095 if data.is_empty() {
1096 return f64::NAN;
1097 }
1098 simd_sum(data) / data.len() as f64
1099}
1100
1101pub fn simd_variance(data: &[f64], mean: f64) -> f64 {
1116 if data.len() < 2 {
1117 return f64::NAN;
1118 }
1119 let n = data.len() as f64;
1122 arch_simd_variance(data, mean) * n / (n - 1.0)
1123}
1124
1125pub fn simd_population_variance(data: &[f64], mean: f64) -> f64 {
1129 if data.is_empty() {
1130 return f64::NAN;
1131 }
1132 arch_simd_variance(data, mean)
1133}
1134
1135pub fn simd_min_max(data: &[f64]) -> (f64, f64) {
1163 if data.is_empty() {
1164 return (f64::INFINITY, f64::NEG_INFINITY);
1165 }
1166 arch_simd_min_max(data)
1167}
1168
1169pub fn simd_std_dev(data: &[f64]) -> f64 {
1174 if data.len() < 2 {
1175 return if data.is_empty() { f64::NAN } else { 0.0 };
1176 }
1177 let (_, var) = simd_mean_variance(data);
1178 var.sqrt()
1179}
1180
1181pub fn simd_mean_variance(data: &[f64]) -> (f64, f64) {
1194 if data.len() < 2 {
1195 let mean = if data.is_empty() { f64::NAN } else { data[0] };
1196 return (mean, f64::NAN);
1197 }
1198 let mean = simd_sum(data) / data.len() as f64;
1199 let pop_var = arch_simd_variance(data, mean);
1200 let n = data.len() as f64;
1201 (mean, pop_var * n / (n - 1.0))
1202}
1203
1204pub fn simd_range_filter_i64(timestamps: &[i64], min: i64, max: i64) -> Vec<u32> {
1223 let mut result = Vec::with_capacity(timestamps.len());
1224 let chunks = timestamps.chunks_exact(4);
1226 let remainder = chunks.remainder();
1227 let mut base_idx: u32 = 0;
1228 for chunk in chunks {
1229 if chunk[0] >= min && chunk[0] <= max {
1230 result.push(base_idx);
1231 }
1232 if chunk[1] >= min && chunk[1] <= max {
1233 result.push(base_idx + 1);
1234 }
1235 if chunk[2] >= min && chunk[2] <= max {
1236 result.push(base_idx + 2);
1237 }
1238 if chunk[3] >= min && chunk[3] <= max {
1239 result.push(base_idx + 3);
1240 }
1241 base_idx += 4;
1242 }
1243 for &ts in remainder {
1244 if ts >= min && ts <= max {
1245 result.push(base_idx);
1246 }
1247 base_idx += 1;
1248 }
1249 result
1250}
1251
1252pub fn simd_dot_product(a: &[f64], b: &[f64]) -> Result<f64, crate::compute::ComputeError> {
1270 if a.len() != b.len() {
1271 return Err(crate::compute::ComputeError::DimensionMismatch {
1272 expected: a.len(),
1273 got: b.len(),
1274 });
1275 }
1276 if a.is_empty() {
1277 return Ok(0.0);
1278 }
1279 Ok(arch_simd_dot_product(a, b))
1280}
1281
1282pub fn simd_tier() -> &'static str {
1286 #[cfg(target_arch = "x86_64")]
1287 {
1288 if is_x86_feature_detected!("avx512f") {
1289 return "AVX-512F (8×f64, 512-bit)";
1290 }
1291 if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
1292 return "AVX2+FMA (4×f64, 256-bit)";
1293 }
1294 "SSE2 (2×f64, 128-bit)"
1296 }
1297 #[cfg(target_arch = "aarch64")]
1298 {
1299 "NEON (2×f64, 128-bit)"
1300 }
1301 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1302 {
1303 "Scalar (4-wide ILP unroll)"
1304 }
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309 #[test]
1321 fn simd_min_max_skips_nan_at_every_position() {
1322 fn reference(data: &[f64]) -> (f64, f64) {
1324 let mut min = f64::INFINITY;
1325 let mut max = f64::NEG_INFINITY;
1326 for &v in data.iter().filter(|v| !v.is_nan()) {
1327 min = min.min(v);
1328 max = max.max(v);
1329 }
1330 (min, max)
1331 }
1332
1333 for len in [1_usize, 2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 33, 64, 129] {
1334 for nan_at in 0..len {
1335 let mut data: Vec<f64> = (0..len).map(|i| i as f64 - 10.0).collect();
1336 data[nan_at] = f64::NAN;
1337 let (min, max) = super::simd_min_max(&data);
1338 let (want_min, want_max) = reference(&data);
1339 assert_eq!(
1340 (min, max),
1341 (want_min, want_max),
1342 "len {len}, NaN at {nan_at}"
1343 );
1344 }
1345 }
1346 }
1347
1348 #[test]
1351 fn simd_min_max_of_all_nan_is_the_empty_answer() {
1352 for len in [1_usize, 4, 8, 16, 33] {
1353 let data = vec![f64::NAN; len];
1354 let (min, max) = super::simd_min_max(&data);
1355 assert_eq!((min, max), (f64::INFINITY, f64::NEG_INFINITY), "len {len}");
1356 }
1357 }
1358
1359 proptest::proptest! {
1360 #[test]
1362 fn simd_min_max_matches_a_nan_skipping_reference(
1363 data in proptest::collection::vec(
1364 proptest::prop_oneof![
1365 3 => -1e6_f64..1e6_f64,
1366 1 => proptest::strategy::Just(f64::NAN),
1367 1 => proptest::strategy::Just(f64::INFINITY),
1368 1 => proptest::strategy::Just(f64::NEG_INFINITY),
1369 ],
1370 0..200,
1371 )
1372 ) {
1373 let mut want_min = f64::INFINITY;
1374 let mut want_max = f64::NEG_INFINITY;
1375 for &v in data.iter().filter(|v| !v.is_nan()) {
1376 want_min = want_min.min(v);
1377 want_max = want_max.max(v);
1378 }
1379 let (min, max) = super::simd_min_max(&data);
1380 proptest::prop_assert_eq!(min, want_min);
1381 proptest::prop_assert_eq!(max, want_max);
1382 }
1383 }
1384
1385 use super::*;
1386
1387 #[test]
1390 fn sum_empty() {
1391 assert_eq!(simd_sum(&[]), 0.0);
1392 }
1393
1394 #[test]
1395 fn sum_single() {
1396 assert!((simd_sum(&[42.0]) - 42.0).abs() < 1e-10);
1397 }
1398
1399 #[test]
1400 fn sum_exact_chunk() {
1401 let data: Vec<f64> = (1..=8).map(|x| x as f64).collect();
1402 assert!((simd_sum(&data) - 36.0).abs() < 1e-10);
1403 }
1404
1405 #[test]
1406 fn sum_with_remainder() {
1407 let data: Vec<f64> = (1..=7).map(|x| x as f64).collect();
1408 assert!((simd_sum(&data) - 28.0).abs() < 1e-10);
1409 }
1410
1411 #[test]
1412 fn sum_large() {
1413 let data: Vec<f64> = (0..10_000).map(|i| i as f64).collect();
1414 let expected = (10_000.0 * 9999.0) / 2.0;
1415 assert!((simd_sum(&data) - expected).abs() < 1e-4);
1416 }
1417
1418 #[test]
1419 fn sum_large_avx2_range() {
1420 let data: Vec<f64> = (0..16).map(|i| i as f64).collect();
1422 assert!((simd_sum(&data) - 120.0).abs() < 1e-10);
1423 }
1424
1425 #[test]
1426 fn sum_large_avx512_range() {
1427 let data: Vec<f64> = (0..32).map(|i| i as f64).collect();
1429 assert!((simd_sum(&data) - 496.0).abs() < 1e-10);
1430 }
1431
1432 #[test]
1433 fn sum_100k() {
1434 let data: Vec<f64> = (0..100_000).map(|i| i as f64).collect();
1435 let expected = 100_000.0 * 99_999.0 / 2.0;
1436 assert!((simd_sum(&data) - expected).abs() / expected < 1e-10);
1437 }
1438
1439 #[test]
1442 fn mean_empty() {
1443 assert!(simd_mean(&[]).is_nan());
1444 }
1445
1446 #[test]
1447 fn mean_values() {
1448 assert!((simd_mean(&[1.0, 2.0, 3.0, 4.0, 5.0]) - 3.0).abs() < 1e-10);
1449 }
1450
1451 #[test]
1454 fn variance_known() {
1455 let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
1456 let mean = simd_mean(&data);
1457 let var = simd_variance(&data, mean);
1458 assert!((var - 32.0 / 7.0).abs() < 1e-10);
1460 }
1461
1462 #[test]
1463 fn variance_single_element() {
1464 assert!(simd_variance(&[42.0], 42.0).is_nan());
1466 }
1467
1468 #[test]
1469 fn variance_empty() {
1470 assert!(simd_variance(&[], 0.0).is_nan());
1471 }
1472
1473 #[test]
1474 fn variance_constant() {
1475 let data = [5.0; 100];
1476 let mean = simd_mean(&data);
1477 assert!(simd_variance(&data, mean).abs() < 1e-14);
1478 }
1479
1480 #[test]
1483 fn min_max_empty() {
1484 let (min, max) = simd_min_max(&[]);
1485 assert_eq!(min, f64::INFINITY);
1486 assert_eq!(max, f64::NEG_INFINITY);
1487 }
1488
1489 #[test]
1490 fn min_max_values() {
1491 let (min, max) = simd_min_max(&[5.0, 2.0, 8.0, 1.0, 9.0, 3.0, 7.0]);
1492 assert!((min - 1.0).abs() < 1e-10);
1493 assert!((max - 9.0).abs() < 1e-10);
1494 }
1495
1496 #[test]
1497 fn min_max_single() {
1498 let (min, max) = simd_min_max(&[42.0]);
1499 assert!((min - 42.0).abs() < 1e-10);
1500 assert!((max - 42.0).abs() < 1e-10);
1501 }
1502
1503 #[test]
1504 fn min_max_negative() {
1505 let (min, max) = simd_min_max(&[-10.0, -5.0, -1.0, -20.0, -3.0]);
1506 assert!((min - (-20.0)).abs() < 1e-10);
1507 assert!((max - (-1.0)).abs() < 1e-10);
1508 }
1509
1510 #[test]
1511 fn min_max_large() {
1512 let data: Vec<f64> = (0..10_000).map(|i| i as f64).collect();
1513 let (min, max) = simd_min_max(&data);
1514 assert!((min - 0.0).abs() < 1e-10);
1515 assert!((max - 9999.0).abs() < 1e-10);
1516 }
1517
1518 #[test]
1519 fn min_max_with_nan() {
1520 let data = [f64::NAN, 2.0, f64::NAN, 1.0, 5.0, f64::NAN, 3.0];
1523 let (min, max) = simd_min_max(&data);
1524 assert!((min - 1.0).abs() < 1e-10, "min should skip NaN, got {min}");
1525 assert!((max - 5.0).abs() < 1e-10, "max should skip NaN, got {max}");
1526 }
1527
1528 #[test]
1529 fn min_max_all_nan() {
1530 let (min, max) = simd_min_max(&[f64::NAN, f64::NAN]);
1532 assert!(
1533 min == f64::INFINITY || min.is_nan(),
1534 "unexpected min: {min}"
1535 );
1536 assert!(
1537 max == f64::NEG_INFINITY || max.is_nan(),
1538 "unexpected max: {max}"
1539 );
1540 }
1541
1542 #[test]
1545 fn std_dev_constant() {
1546 assert!((simd_std_dev(&[5.0, 5.0, 5.0, 5.0])).abs() < 1e-10);
1547 }
1548
1549 #[test]
1550 fn std_dev_known() {
1551 let data = [2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
1552 let sd = simd_std_dev(&data);
1553 assert!((sd - 2.138_089_935_299_395).abs() < 1e-6);
1554 }
1555
1556 #[test]
1559 fn dot_product_basic() {
1560 let a = [1.0, 2.0, 3.0, 4.0, 5.0];
1561 let b = [5.0, 4.0, 3.0, 2.0, 1.0];
1562 assert!((simd_dot_product(&a, &b).unwrap() - 35.0).abs() < 1e-10);
1563 }
1564
1565 #[test]
1566 fn dot_product_empty() {
1567 assert!((simd_dot_product(&[], &[]).unwrap()).abs() < 1e-10);
1568 }
1569
1570 #[test]
1571 fn dot_product_mismatch() {
1572 assert!(simd_dot_product(&[1.0], &[1.0, 2.0]).is_err());
1573 }
1574
1575 #[test]
1576 fn dot_product_identity() {
1577 let v: Vec<f64> = (1..=100).map(|i| i as f64).collect();
1579 let expected: f64 = v.iter().map(|x| x * x).sum();
1580 assert!((simd_dot_product(&v, &v).unwrap() - expected).abs() < 1e-6);
1581 }
1582
1583 #[test]
1584 fn dot_product_orthogonal() {
1585 let a = [1.0, 0.0, 1.0, 0.0];
1586 let b = [0.0, 1.0, 0.0, 1.0];
1587 assert!((simd_dot_product(&a, &b).unwrap()).abs() < 1e-10);
1588 }
1589
1590 #[test]
1591 fn dot_product_large() {
1592 let a: Vec<f64> = (0..10_000).map(|i| (i as f64) * 0.001).collect();
1594 let b: Vec<f64> = (0..10_000).map(|i| 1.0 - (i as f64) * 0.0001).collect();
1595 let expected: f64 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
1596 assert!(
1597 (simd_dot_product(&a, &b).unwrap() - expected).abs() / expected.abs().max(1.0) < 1e-8
1598 );
1599 }
1600
1601 #[test]
1604 fn simd_tier_not_empty() {
1605 let tier = simd_tier();
1606 assert!(!tier.is_empty());
1607 assert!(
1609 tier.contains("AVX-512")
1610 || tier.contains("AVX2")
1611 || tier.contains("SSE2")
1612 || tier.contains("NEON")
1613 || tier.contains("Scalar")
1614 );
1615 }
1616
1617 #[test]
1620 fn scalar_vs_arch_sum() {
1621 let data: Vec<f64> = (0..1_000).map(|i| (i as f64) * 0.1 + 0.7).collect();
1622 let arch_result = arch_simd_sum(&data);
1623 let scalar_result = scalar_sum(&data);
1624 assert!(
1625 (arch_result - scalar_result).abs() < 1e-6,
1626 "sum mismatch: arch={arch_result}, scalar={scalar_result}"
1627 );
1628 }
1629
1630 #[test]
1631 fn scalar_vs_arch_variance() {
1632 let data: Vec<f64> = (0..1_000).map(|i| (i as f64) * 0.1 + 0.7).collect();
1633 let mean = simd_mean(&data);
1634 let arch_result = arch_simd_variance(&data, mean);
1635 let scalar_result = scalar_variance(&data, mean);
1636 assert!(
1637 (arch_result - scalar_result).abs() < 1e-6,
1638 "variance mismatch: arch={arch_result}, scalar={scalar_result}"
1639 );
1640 }
1641
1642 #[test]
1643 fn scalar_vs_arch_min_max() {
1644 let data: Vec<f64> = (0..1_000)
1645 .map(|i| ((i * 7 + 3) % 500) as f64 - 250.0)
1646 .collect();
1647 let (amin, amax) = arch_simd_min_max(&data);
1648 let (smin, smax) = scalar_min_max(&data);
1649 assert!((amin - smin).abs() < 1e-10, "min mismatch");
1650 assert!((amax - smax).abs() < 1e-10, "max mismatch");
1651 }
1652
1653 #[test]
1654 fn scalar_vs_arch_dot_product() {
1655 let a: Vec<f64> = (0..1_000).map(|i| (i as f64) * 0.01).collect();
1656 let b: Vec<f64> = (0..1_000).map(|i| 1.0 - (i as f64) * 0.001).collect();
1657 let arch_result = arch_simd_dot_product(&a, &b);
1658 let scalar_result = scalar_dot_product(&a, &b);
1659 assert!(
1660 (arch_result - scalar_result).abs() / scalar_result.abs().max(1.0) < 1e-8,
1661 "dot product mismatch: arch={arch_result}, scalar={scalar_result}"
1662 );
1663 }
1664
1665 #[test]
1668 fn kahan_compensated_sum_precision() {
1669 let n = 100_000;
1673 let mut data = vec![1.0_f64; n];
1674 data.extend(vec![1e-8_f64; n]);
1675 let expected = n as f64 + (n as f64) * 1e-8;
1676 let result = simd_sum(&data);
1677 let rel_err = (result - expected).abs() / expected;
1678 assert!(
1679 rel_err < 1e-14,
1680 "Kahan sum relative error {rel_err:.2e} exceeds 1e-14 (result={result}, expected={expected})"
1681 );
1682 }
1683
1684 #[test]
1685 fn kahan_alternating_sign_precision() {
1686 let n = 50_000;
1689 let mut data = Vec::with_capacity(2 * n);
1690 for i in 0..n {
1691 data.push(1e15 + (i as f64));
1692 data.push(-(1e15 + (i as f64)));
1693 }
1694 let result = simd_sum(&data);
1695 assert!(
1696 result.abs() < 1e-3,
1697 "alternating-sign sum should be ~0, got {result}"
1698 );
1699 }
1700}
1701
1702#[cfg(all(test, target_arch = "x86_64", target_feature = "avx512f"))]
1709mod avx512_tests {
1710 use super::*;
1711
1712 #[test]
1713 fn avx512_sum_direct() {
1714 let data: Vec<f64> = (0..64).map(|i| i as f64).collect();
1715 let result = unsafe { avx512_sum(&data) };
1716 let expected: f64 = (0..64).map(|i| i as f64).sum();
1717 assert!(
1718 (result - expected).abs() < 1e-10,
1719 "avx512_sum mismatch: {result} vs {expected}"
1720 );
1721 }
1722
1723 #[test]
1724 fn avx512_variance_direct() {
1725 let data: Vec<f64> = (0..128).map(|i| i as f64).collect();
1726 let (var, mean) = unsafe { avx512_variance(&data) };
1727 assert!(
1728 (mean - 63.5).abs() < 1e-10,
1729 "avx512_variance mean mismatch: {mean}"
1730 );
1731 assert!(var > 0.0, "variance must be positive");
1732 }
1733
1734 #[test]
1735 fn avx512_min_max_direct() {
1736 let data: Vec<f64> = (0..64).map(|i| i as f64).collect();
1737 let (min, max) = unsafe { avx512_min_max(&data) };
1738 assert!((min - 0.0).abs() < 1e-10);
1739 assert!((max - 63.0).abs() < 1e-10);
1740 }
1741
1742 #[test]
1743 fn avx512_dot_product_direct() {
1744 let a: Vec<f64> = (0..64).map(|i| i as f64).collect();
1745 let b: Vec<f64> = (0..64).map(|i| (i * 2) as f64).collect();
1746 let result = unsafe { avx512_dot_product(&a, &b) };
1747 let expected: f64 = (0..64).map(|i| (i * i * 2) as f64).sum();
1748 assert!(
1749 (result - expected).abs() < 1e-6,
1750 "avx512_dot mismatch: {result} vs {expected}"
1751 );
1752 }
1753}