Skip to main content

sklears_feature_extraction/
simd_ops.rs

1//! SIMD-optimized operations for feature extraction
2//!
3//! This module provides SIMD-optimized implementations of common mathematical
4//! operations used in feature extraction to improve performance.
5
6// use crate::*;
7use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
8use sklears_core::{error::Result as SklResult, prelude::SklearsError, types::Float};
9
10/// SIMD-optimized vector operations
11pub struct SimdOps;
12
13impl SimdOps {
14    /// Compute dot product with SIMD optimization
15    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        // Process 4 elements at a time for SIMD-like operations
22        let chunks = len / 4;
23        let _remainder = len % 4;
24
25        // Unrolled loop for better performance
26        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        // Handle remaining elements
35        for i in chunks * 4..len {
36            sum += a[i] * b[i];
37        }
38
39        sum
40    }
41
42    /// Compute L2 norm with SIMD optimization
43    pub fn l2_norm(vector: ArrayView1<Float>) -> Float {
44        let mut sum_squares = 0.0;
45        let len = vector.len();
46
47        // Process 4 elements at a time
48        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        // Handle remaining elements
61        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    /// Compute element-wise multiplication with SIMD optimization
70    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        // Unrolled loop for SIMD-like performance
78        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        // Handle remaining elements
87        for i in chunks * 4..len {
88            result[i] = a[i] * b[i];
89        }
90
91        result
92    }
93
94    /// Compute sum of array elements with SIMD optimization
95    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        // Process 4 elements at a time
101        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        // Handle remaining elements
107        for i in chunks * 4..len {
108            sum += vector[i];
109        }
110
111        sum
112    }
113
114    /// Compute matrix-vector multiplication with SIMD optimization
115    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    /// Compute cosine similarity with SIMD optimization
138    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    /// Compute Euclidean distance with SIMD optimization
151    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        // Process 4 elements at a time
159        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        // Handle remaining elements
170        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    /// Compute mean of array elements with SIMD optimization
179    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    /// Compute variance with SIMD optimization
187    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        // Process 4 elements at a time
198        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        // Handle remaining elements
209        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    /// Compute standard deviation with SIMD optimization
218    pub fn std_dev(vector: ArrayView1<Float>) -> Float {
219        Self::variance(vector).sqrt()
220    }
221
222    /// Compute range (max - min) with SIMD optimization
223    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        // Process 4 elements at a time
235        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        // Handle remaining elements
247        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    /// Compute skewness with SIMD optimization
257    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        // Process 4 elements at a time
274        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        // Handle remaining elements
285        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    /// Compute kurtosis with SIMD optimization
294    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        // Process 4 elements at a time
311        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        // Handle remaining elements
327        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 // Excess kurtosis
334    }
335}
336
337/// SIMD-optimized statistical operations
338pub struct SimdStats;
339
340impl SimdStats {
341    /// Compute multiple statistics in a single pass
342    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        // Process 4 elements at a time
356        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
364            sum += v0 + v1 + v2 + v3;
365
366            // Sum of squares
367            sum_squares += v0 * v0 + v1 * v1 + v2 * v2 + v3 * v3;
368
369            // Min/Max
370            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        // Handle remaining elements
375        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        // Compute higher-order moments
393        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/// Result of SIMD-optimized statistical computation
421#[derive(Debug, Clone)]
422pub struct StatisticsResult {
423    /// Mean value
424    pub mean: Float,
425    /// Variance
426    pub variance: Float,
427    /// Standard deviation
428    pub std_dev: Float,
429    /// Minimum value
430    pub min: Float,
431    /// Maximum value
432    pub max: Float,
433    /// Range (max - min)
434    pub range: Float,
435    /// Sum of all values
436    pub sum: Float,
437    /// Number of elements
438    pub count: usize,
439    /// Skewness
440    pub skewness: Float,
441    /// Kurtosis (excess)
442    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
462// Wrapper functions expected by tests
463pub 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
516/// SIMD-optimized vector subtraction
517pub 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
531/// SIMD-optimized element-wise vector multiplication
532pub 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
542/// SIMD-optimized vector norm computation
543pub 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            // L1 norm (Manhattan norm)
551            Ok(vector.iter().map(|x| x.abs()).sum())
552        }
553        2 => {
554            // L2 norm (Euclidean norm)
555            Ok(SimdOps::l2_norm(*vector))
556        }
557        _ => Err(SklearsError::InvalidInput(format!(
558            "Unsupported norm p={}",
559            p
560        ))),
561    }
562}
563
564/// SIMD-optimized Manhattan distance
565pub 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
579/// SIMD-optimized squared Euclidean distance
580pub 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
595/// SIMD-optimized batch dot product computation
596pub 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
613/// SIMD-optimized matrix multiplication
614pub 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
634/// SIMD-optimized vector sum
635pub 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
642/// SIMD-optimized vector mean
643pub 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
650/// SIMD-optimized vector variance
651pub 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]; // [1*1+2*2+3*3, 4*1+5*2+6*3]
747
748        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        // Sample variance calculation: 32/7 ≈ 4.571428571428571
759        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        // Expected standard deviation: sqrt(32/7) ≈ 2.1380899352993946
779        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); // 9.0 - 1.0 = 8.0
788    }
789
790    #[test]
791    fn test_skewness() {
792        // Symmetric distribution should have skewness close to 0
793        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        // Right-skewed distribution (positive skewness)
798        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        // Uniform distribution should have negative kurtosis (platykurtic)
806        let vector = array![1.0, 2.0, 3.0, 4.0, 5.0];
807        let result = SimdOps::kurtosis(vector.view());
808        // For a uniform distribution, excess kurtosis ≈ -1.2
809        assert!(
810            result < 0.0,
811            "Uniform distribution should have negative kurtosis"
812        );
813        assert!(result > -2.0, "Kurtosis should be reasonable");
814
815        // Test with more peaked distribution
816        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); // 6.0 - 1.0
831        assert_eq!(stats.count, 6);
832        assert!(stats.std_dev > 0.0);
833        // For a uniform distribution, skewness should be close to 0
834        assert_abs_diff_eq!(stats.skewness, 0.0, epsilon = 1e-10);
835    }
836
837    #[test]
838    fn test_edge_cases_new_functions() {
839        // Empty vector
840        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        // Single element
846        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        // Constant vector (zero standard deviation)
852        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}