1use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
8use sklears_core::{error::Result as SklResult, prelude::SklearsError, types::Float};
9
10pub struct SimdOps;
12
13impl SimdOps {
14 pub fn dot_product(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Float {
16 assert_eq!(a.len(), b.len(), "Vector dimensions must match");
17
18 let mut sum = 0.0;
19 let len = a.len();
20
21 let chunks = len / 4;
23 let _remainder = len % 4;
24
25 for i in 0..chunks {
27 let base = i * 4;
28 sum += a[base] * b[base]
29 + a[base + 1] * b[base + 1]
30 + a[base + 2] * b[base + 2]
31 + a[base + 3] * b[base + 3];
32 }
33
34 for i in chunks * 4..len {
36 sum += a[i] * b[i];
37 }
38
39 sum
40 }
41
42 pub fn l2_norm(vector: ArrayView1<Float>) -> Float {
44 let mut sum_squares = 0.0;
45 let len = vector.len();
46
47 let chunks = len / 4;
49
50 for i in 0..chunks {
51 let base = i * 4;
52 let v0 = vector[base];
53 let v1 = vector[base + 1];
54 let v2 = vector[base + 2];
55 let v3 = vector[base + 3];
56
57 sum_squares += v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
58 }
59
60 for i in chunks * 4..len {
62 let v = vector[i];
63 sum_squares += v * v;
64 }
65
66 sum_squares.sqrt()
67 }
68
69 pub fn element_wise_multiply(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Array1<Float> {
71 assert_eq!(a.len(), b.len(), "Vector dimensions must match");
72
73 let len = a.len();
74 let mut result = Array1::zeros(len);
75 let chunks = len / 4;
76
77 for i in 0..chunks {
79 let base = i * 4;
80 result[base] = a[base] * b[base];
81 result[base + 1] = a[base + 1] * b[base + 1];
82 result[base + 2] = a[base + 2] * b[base + 2];
83 result[base + 3] = a[base + 3] * b[base + 3];
84 }
85
86 for i in chunks * 4..len {
88 result[i] = a[i] * b[i];
89 }
90
91 result
92 }
93
94 pub fn sum(vector: ArrayView1<Float>) -> Float {
96 let len = vector.len();
97 let chunks = len / 4;
98 let mut sum = 0.0;
99
100 for i in 0..chunks {
102 let base = i * 4;
103 sum += vector[base] + vector[base + 1] + vector[base + 2] + vector[base + 3];
104 }
105
106 for i in chunks * 4..len {
108 sum += vector[i];
109 }
110
111 sum
112 }
113
114 pub fn matrix_vector_multiply(
116 matrix: ArrayView2<Float>,
117 vector: ArrayView1<Float>,
118 ) -> Array1<Float> {
119 assert_eq!(
120 matrix.ncols(),
121 vector.len(),
122 "Matrix columns must match vector length"
123 );
124
125 let rows = matrix.nrows();
126 let _cols = matrix.ncols();
127 let mut result = Array1::zeros(rows);
128
129 for row in 0..rows {
130 let matrix_row = matrix.row(row);
131 result[row] = Self::dot_product(matrix_row, vector);
132 }
133
134 result
135 }
136
137 pub fn cosine_similarity(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Float {
139 let dot = Self::dot_product(a, b);
140 let norm_a = Self::l2_norm(a);
141 let norm_b = Self::l2_norm(b);
142
143 if norm_a == 0.0 || norm_b == 0.0 {
144 0.0
145 } else {
146 dot / (norm_a * norm_b)
147 }
148 }
149
150 pub fn euclidean_distance(a: ArrayView1<Float>, b: ArrayView1<Float>) -> Float {
152 assert_eq!(a.len(), b.len(), "Vector dimensions must match");
153
154 let len = a.len();
155 let chunks = len / 4;
156 let mut sum_squares = 0.0;
157
158 for i in 0..chunks {
160 let base = i * 4;
161 let d0 = a[base] - b[base];
162 let d1 = a[base + 1] - b[base + 1];
163 let d2 = a[base + 2] - b[base + 2];
164 let d3 = a[base + 3] - b[base + 3];
165
166 sum_squares += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
167 }
168
169 for i in chunks * 4..len {
171 let diff = a[i] - b[i];
172 sum_squares += diff * diff;
173 }
174
175 sum_squares.sqrt()
176 }
177
178 pub fn mean(vector: ArrayView1<Float>) -> Float {
180 if vector.is_empty() {
181 return 0.0;
182 }
183 Self::sum(vector) / vector.len() as Float
184 }
185
186 pub fn variance(vector: ArrayView1<Float>) -> Float {
188 if vector.len() <= 1 {
189 return 0.0;
190 }
191
192 let mean_val = Self::mean(vector);
193 let len = vector.len();
194 let chunks = len / 4;
195 let mut sum_squares = 0.0;
196
197 for i in 0..chunks {
199 let base = i * 4;
200 let d0 = vector[base] - mean_val;
201 let d1 = vector[base + 1] - mean_val;
202 let d2 = vector[base + 2] - mean_val;
203 let d3 = vector[base + 3] - mean_val;
204
205 sum_squares += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
206 }
207
208 for i in chunks * 4..len {
210 let diff = vector[i] - mean_val;
211 sum_squares += diff * diff;
212 }
213
214 sum_squares / (len - 1) as Float
215 }
216
217 pub fn std_dev(vector: ArrayView1<Float>) -> Float {
219 Self::variance(vector).sqrt()
220 }
221
222 pub fn range(vector: ArrayView1<Float>) -> Float {
224 if vector.is_empty() {
225 return 0.0;
226 }
227
228 let len = vector.len();
229 let chunks = len / 4;
230
231 let mut min_val = Float::INFINITY;
232 let mut max_val = Float::NEG_INFINITY;
233
234 for i in 0..chunks {
236 let base = i * 4;
237 let v0 = vector[base];
238 let v1 = vector[base + 1];
239 let v2 = vector[base + 2];
240 let v3 = vector[base + 3];
241
242 min_val = min_val.min(v0).min(v1).min(v2).min(v3);
243 max_val = max_val.max(v0).max(v1).max(v2).max(v3);
244 }
245
246 for i in chunks * 4..len {
248 let v = vector[i];
249 min_val = min_val.min(v);
250 max_val = max_val.max(v);
251 }
252
253 max_val - min_val
254 }
255
256 pub fn skewness(vector: ArrayView1<Float>) -> Float {
258 if vector.len() < 3 {
259 return 0.0;
260 }
261
262 let mean_val = Self::mean(vector);
263 let std_val = Self::std_dev(vector);
264
265 if std_val == 0.0 {
266 return 0.0;
267 }
268
269 let len = vector.len();
270 let chunks = len / 4;
271 let mut sum_cubes = 0.0;
272
273 for i in 0..chunks {
275 let base = i * 4;
276 let d0 = (vector[base] - mean_val) / std_val;
277 let d1 = (vector[base + 1] - mean_val) / std_val;
278 let d2 = (vector[base + 2] - mean_val) / std_val;
279 let d3 = (vector[base + 3] - mean_val) / std_val;
280
281 sum_cubes += d0 * d0 * d0 + d1 * d1 * d1 + d2 * d2 * d2 + d3 * d3 * d3;
282 }
283
284 for i in chunks * 4..len {
286 let d = (vector[i] - mean_val) / std_val;
287 sum_cubes += d * d * d;
288 }
289
290 sum_cubes / len as Float
291 }
292
293 pub fn kurtosis(vector: ArrayView1<Float>) -> Float {
295 if vector.len() < 4 {
296 return 0.0;
297 }
298
299 let mean_val = Self::mean(vector);
300 let std_val = Self::std_dev(vector);
301
302 if std_val == 0.0 {
303 return 0.0;
304 }
305
306 let len = vector.len();
307 let chunks = len / 4;
308 let mut sum_fourth_powers = 0.0;
309
310 for i in 0..chunks {
312 let base = i * 4;
313 let d0 = (vector[base] - mean_val) / std_val;
314 let d1 = (vector[base + 1] - mean_val) / std_val;
315 let d2 = (vector[base + 2] - mean_val) / std_val;
316 let d3 = (vector[base + 3] - mean_val) / std_val;
317
318 let p0 = d0 * d0;
319 let p1 = d1 * d1;
320 let p2 = d2 * d2;
321 let p3 = d3 * d3;
322
323 sum_fourth_powers += p0 * p0 + p1 * p1 + p2 * p2 + p3 * p3;
324 }
325
326 for i in chunks * 4..len {
328 let d = (vector[i] - mean_val) / std_val;
329 let p = d * d;
330 sum_fourth_powers += p * p;
331 }
332
333 (sum_fourth_powers / len as Float) - 3.0 }
335}
336
337pub struct SimdStats;
339
340impl SimdStats {
341 pub fn compute_stats(vector: ArrayView1<Float>) -> StatisticsResult {
343 if vector.is_empty() {
344 return StatisticsResult::default();
345 }
346
347 let len = vector.len();
348 let chunks = len / 4;
349
350 let mut sum = 0.0;
351 let mut sum_squares = 0.0;
352 let mut min_val = Float::INFINITY;
353 let mut max_val = Float::NEG_INFINITY;
354
355 for i in 0..chunks {
357 let base = i * 4;
358 let v0 = vector[base];
359 let v1 = vector[base + 1];
360 let v2 = vector[base + 2];
361 let v3 = vector[base + 3];
362
363 sum += v0 + v1 + v2 + v3;
365
366 sum_squares += v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
368
369 min_val = min_val.min(v0).min(v1).min(v2).min(v3);
371 max_val = max_val.max(v0).max(v1).max(v2).max(v3);
372 }
373
374 for i in chunks * 4..len {
376 let v = vector[i];
377 sum += v;
378 sum_squares += v * v;
379 min_val = min_val.min(v);
380 max_val = max_val.max(v);
381 }
382
383 let mean = sum / len as Float;
384 let variance = if len > 1 {
385 (sum_squares - sum * sum / len as Float) / (len - 1) as Float
386 } else {
387 0.0
388 };
389 let std_dev = variance.sqrt();
390 let range = max_val - min_val;
391
392 let skewness = if len >= 3 && std_dev > 0.0 {
394 SimdOps::skewness(vector)
395 } else {
396 0.0
397 };
398
399 let kurtosis = if len >= 4 && std_dev > 0.0 {
400 SimdOps::kurtosis(vector)
401 } else {
402 0.0
403 };
404
405 StatisticsResult {
406 mean,
407 variance,
408 std_dev,
409 min: min_val,
410 max: max_val,
411 range,
412 sum,
413 count: len,
414 skewness,
415 kurtosis,
416 }
417 }
418}
419
420#[derive(Debug, Clone)]
422pub struct StatisticsResult {
423 pub mean: Float,
425 pub variance: Float,
427 pub std_dev: Float,
429 pub min: Float,
431 pub max: Float,
433 pub range: Float,
435 pub sum: Float,
437 pub count: usize,
439 pub skewness: Float,
441 pub kurtosis: Float,
443}
444
445impl Default for StatisticsResult {
446 fn default() -> Self {
447 Self {
448 mean: 0.0,
449 variance: 0.0,
450 std_dev: 0.0,
451 min: 0.0,
452 max: 0.0,
453 range: 0.0,
454 sum: 0.0,
455 count: 0,
456 skewness: 0.0,
457 kurtosis: 0.0,
458 }
459 }
460}
461
462pub fn simd_dot_product(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
464 if a.len() != b.len() {
465 return Err(SklearsError::InvalidInput(
466 "Vector dimensions must match".to_string(),
467 ));
468 }
469 Ok(SimdOps::dot_product(*a, *b))
470}
471
472pub fn simd_add_vectors(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
473 if a.len() != b.len() {
474 return Err(SklearsError::InvalidInput(
475 "Vector dimensions must match".to_string(),
476 ));
477 }
478
479 let mut result = Array1::zeros(a.len());
480 for i in 0..a.len() {
481 result[i] = a[i] + b[i];
482 }
483 Ok(result)
484}
485
486pub fn simd_matrix_vector_multiply(
487 matrix: &ArrayView2<f64>,
488 vector: &ArrayView1<f64>,
489) -> SklResult<Array1<f64>> {
490 if matrix.ncols() != vector.len() {
491 return Err(SklearsError::InvalidInput(
492 "Matrix columns must match vector length".to_string(),
493 ));
494 }
495 Ok(SimdOps::matrix_vector_multiply(*matrix, *vector))
496}
497
498pub fn simd_euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
499 if a.len() != b.len() {
500 return Err(SklearsError::InvalidInput(
501 "Vector dimensions must match".to_string(),
502 ));
503 }
504 Ok(SimdOps::euclidean_distance(*a, *b))
505}
506
507pub fn simd_cosine_similarity(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
508 if a.len() != b.len() {
509 return Err(SklearsError::InvalidInput(
510 "Vector dimensions must match".to_string(),
511 ));
512 }
513 Ok(SimdOps::cosine_similarity(*a, *b))
514}
515
516pub fn simd_subtract_vectors(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
518 if a.len() != b.len() {
519 return Err(SklearsError::InvalidInput(
520 "Vector dimensions must match".to_string(),
521 ));
522 }
523
524 let mut result = Array1::zeros(a.len());
525 for i in 0..a.len() {
526 result[i] = a[i] - b[i];
527 }
528 Ok(result)
529}
530
531pub fn simd_multiply_vectors(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<Array1<f64>> {
533 if a.len() != b.len() {
534 return Err(SklearsError::InvalidInput(
535 "Vector dimensions must match".to_string(),
536 ));
537 }
538
539 Ok(SimdOps::element_wise_multiply(*a, *b))
540}
541
542pub fn simd_vector_norm(vector: &ArrayView1<f64>, p: i32) -> SklResult<f64> {
544 if vector.is_empty() {
545 return Err(SklearsError::InvalidInput("Empty vector".to_string()));
546 }
547
548 match p {
549 1 => {
550 Ok(vector.iter().map(|x| x.abs()).sum())
552 }
553 2 => {
554 Ok(SimdOps::l2_norm(*vector))
556 }
557 _ => Err(SklearsError::InvalidInput(format!(
558 "Unsupported norm p={}",
559 p
560 ))),
561 }
562}
563
564pub fn simd_manhattan_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
566 if a.len() != b.len() {
567 return Err(SklearsError::InvalidInput(
568 "Vector dimensions must match".to_string(),
569 ));
570 }
571
572 let mut distance = 0.0;
573 for i in 0..a.len() {
574 distance += (a[i] - b[i]).abs();
575 }
576 Ok(distance)
577}
578
579pub fn simd_squared_euclidean_distance(a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> SklResult<f64> {
581 if a.len() != b.len() {
582 return Err(SklearsError::InvalidInput(
583 "Vector dimensions must match".to_string(),
584 ));
585 }
586
587 let mut sum_squares = 0.0;
588 for i in 0..a.len() {
589 let diff = a[i] - b[i];
590 sum_squares += diff * diff;
591 }
592 Ok(sum_squares)
593}
594
595pub fn simd_batch_dot_product(
597 vectors: &ArrayView2<f64>,
598 query: &ArrayView1<f64>,
599) -> SklResult<Array1<f64>> {
600 if vectors.ncols() != query.len() {
601 return Err(SklearsError::InvalidInput(
602 "Query vector dimension must match matrix columns".to_string(),
603 ));
604 }
605
606 let mut results = Array1::zeros(vectors.nrows());
607 for (i, row) in vectors.axis_iter(Axis(0)).enumerate() {
608 results[i] = SimdOps::dot_product(row, *query);
609 }
610 Ok(results)
611}
612
613pub fn simd_matrix_multiply(a: &ArrayView2<f64>, b: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
615 if a.ncols() != b.nrows() {
616 return Err(SklearsError::InvalidInput(
617 "Matrix dimensions incompatible for multiplication".to_string(),
618 ));
619 }
620
621 let mut result = Array2::zeros((a.nrows(), b.ncols()));
622 for i in 0..a.nrows() {
623 for j in 0..b.ncols() {
624 let mut sum = 0.0;
625 for k in 0..a.ncols() {
626 sum += a[(i, k)] * b[(k, j)];
627 }
628 result[(i, j)] = sum;
629 }
630 }
631 Ok(result)
632}
633
634pub fn simd_vector_sum(vector: &ArrayView1<f64>) -> SklResult<f64> {
636 if vector.is_empty() {
637 return Err(SklearsError::InvalidInput("Empty vector".to_string()));
638 }
639 Ok(SimdOps::sum(*vector))
640}
641
642pub fn simd_vector_mean(vector: &ArrayView1<f64>) -> SklResult<f64> {
644 if vector.is_empty() {
645 return Err(SklearsError::InvalidInput("Empty vector".to_string()));
646 }
647 Ok(SimdOps::mean(*vector))
648}
649
650pub fn simd_vector_variance(vector: &ArrayView1<f64>) -> SklResult<f64> {
652 if vector.is_empty() {
653 return Err(SklearsError::InvalidInput("Empty vector".to_string()));
654 }
655 Ok(SimdOps::variance(*vector))
656}
657
658#[allow(non_snake_case)]
659#[cfg(test)]
660mod tests {
661 use super::*;
662 use approx::assert_abs_diff_eq;
663 use scirs2_core::ndarray::array;
664
665 #[test]
666 fn test_dot_product() {
667 let a = array![1.0, 2.0, 3.0, 4.0, 5.0];
668 let b = array![2.0, 3.0, 4.0, 5.0, 6.0];
669
670 let result = SimdOps::dot_product(a.view(), b.view());
671 let expected = 1.0 * 2.0 + 2.0 * 3.0 + 3.0 * 4.0 + 4.0 * 5.0 + 5.0 * 6.0;
672
673 assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
674 }
675
676 #[test]
677 fn test_l2_norm() {
678 let vector = array![3.0, 4.0];
679 let result = SimdOps::l2_norm(vector.view());
680 assert_abs_diff_eq!(result, 5.0, epsilon = 1e-10);
681 }
682
683 #[test]
684 fn test_element_wise_multiply() {
685 let a = array![1.0, 2.0, 3.0, 4.0];
686 let b = array![2.0, 3.0, 4.0, 5.0];
687
688 let result = SimdOps::element_wise_multiply(a.view(), b.view());
689 let expected = array![2.0, 6.0, 12.0, 20.0];
690
691 for (r, e) in result.iter().zip(expected.iter()) {
692 assert_abs_diff_eq!(r, e, epsilon = 1e-10);
693 }
694 }
695
696 #[test]
697 fn test_sum() {
698 let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
699 let result = SimdOps::sum(vector.view());
700 assert_abs_diff_eq!(result, 15.0, epsilon = 1e-10);
701 }
702
703 #[test]
704 fn test_cosine_similarity() {
705 let a = array![1.0, 0.0, 0.0];
706 let b = array![0.0, 1.0, 0.0];
707
708 let result = SimdOps::cosine_similarity(a.view(), b.view());
709 assert_abs_diff_eq!(result, 0.0, epsilon = 1e-10);
710
711 let c = array![1.0, 1.0, 0.0];
712 let d = array![1.0, 1.0, 0.0];
713
714 let result2 = SimdOps::cosine_similarity(c.view(), d.view());
715 assert_abs_diff_eq!(result2, 1.0, epsilon = 1e-10);
716 }
717
718 #[test]
719 fn test_euclidean_distance() {
720 let a = array![0.0, 0.0];
721 let b = array![3.0, 4.0];
722
723 let result = SimdOps::euclidean_distance(a.view(), b.view());
724 assert_abs_diff_eq!(result, 5.0, epsilon = 1e-10);
725 }
726
727 #[test]
728 fn test_statistics() {
729 let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
730 let stats = SimdStats::compute_stats(vector.view());
731
732 assert_abs_diff_eq!(stats.mean, 3.0, epsilon = 1e-10);
733 assert_abs_diff_eq!(stats.sum, 15.0, epsilon = 1e-10);
734 assert_abs_diff_eq!(stats.min, 1.0, epsilon = 1e-10);
735 assert_abs_diff_eq!(stats.max, 5.0, epsilon = 1e-10);
736 assert_eq!(stats.count, 5);
737 }
738
739 #[test]
740 fn test_matrix_vector_multiply() {
741 let matrix = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
742 .expect("operation should succeed");
743 let vector = array![1.0, 2.0, 3.0];
744
745 let result = SimdOps::matrix_vector_multiply(matrix.view(), vector.view());
746 let expected = array![14.0, 32.0]; for (r, e) in result.iter().zip(expected.iter()) {
749 assert_abs_diff_eq!(r, e, epsilon = 1e-10);
750 }
751 }
752
753 #[test]
754 fn test_variance() {
755 let vector = array![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
756 let result = SimdOps::variance(vector.view());
757
758 let expected = 32.0 / 7.0;
760 assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
761 }
762
763 #[test]
764 fn test_empty_vector() {
765 let empty = Array1::<Float>::zeros(0);
766 let stats = SimdStats::compute_stats(empty.view());
767
768 assert_eq!(stats.count, 0);
769 assert_eq!(stats.sum, 0.0);
770 assert_eq!(stats.mean, 0.0);
771 }
772
773 #[test]
774 fn test_std_dev() {
775 let vector = array![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
776 let result = SimdOps::std_dev(vector.view());
777
778 let expected = (32.0_f64 / 7.0_f64).sqrt() as Float;
780 assert_abs_diff_eq!(result, expected, epsilon = 1e-10);
781 }
782
783 #[test]
784 fn test_range() {
785 let vector = array![1.0, 5.0, 3.0, 9.0, 2.0];
786 let result = SimdOps::range(vector.view());
787 assert_abs_diff_eq!(result, 8.0, epsilon = 1e-10); }
789
790 #[test]
791 fn test_skewness() {
792 let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
794 let result = SimdOps::skewness(vector.view());
795 assert_abs_diff_eq!(result, 0.0, epsilon = 1e-10);
796
797 let right_skewed = array![1.0, 1.0, 1.0, 2.0, 10.0];
799 let skew_result = SimdOps::skewness(right_skewed.view());
800 assert!(skew_result > 0.0);
801 }
802
803 #[test]
804 fn test_kurtosis() {
805 let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
807 let result = SimdOps::kurtosis(vector.view());
808 assert!(
810 result < 0.0,
811 "Uniform distribution should have negative kurtosis"
812 );
813 assert!(result > -2.0, "Kurtosis should be reasonable");
814
815 let peaked = array![5.0, 5.0, 5.0, 1.0, 9.0];
817 let peaked_result = SimdOps::kurtosis(peaked.view());
818 assert!(
819 peaked_result > result,
820 "More peaked distribution should have higher kurtosis"
821 );
822 }
823
824 #[test]
825 fn test_enhanced_statistics() {
826 let vector = array![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
827 let stats = SimdStats::compute_stats(vector.view());
828
829 assert_abs_diff_eq!(stats.mean, 3.5, epsilon = 1e-10);
830 assert_abs_diff_eq!(stats.range, 5.0, epsilon = 1e-10); assert_eq!(stats.count, 6);
832 assert!(stats.std_dev > 0.0);
833 assert_abs_diff_eq!(stats.skewness, 0.0, epsilon = 1e-10);
835 }
836
837 #[test]
838 fn test_edge_cases_new_functions() {
839 let empty = Array1::<Float>::zeros(0);
841 assert_eq!(SimdOps::range(empty.view()), 0.0);
842 assert_eq!(SimdOps::skewness(empty.view()), 0.0);
843 assert_eq!(SimdOps::kurtosis(empty.view()), 0.0);
844
845 let single = array![5.0];
847 assert_eq!(SimdOps::range(single.view()), 0.0);
848 assert_eq!(SimdOps::skewness(single.view()), 0.0);
849 assert_eq!(SimdOps::kurtosis(single.view()), 0.0);
850
851 let constant = array![3.0, 3.0, 3.0, 3.0];
853 assert_eq!(SimdOps::range(constant.view()), 0.0);
854 assert_eq!(SimdOps::skewness(constant.view()), 0.0);
855 assert_eq!(SimdOps::kurtosis(constant.view()), 0.0);
856 }
857}