Skip to main content

shape_runtime/simd_rolling/
diff.rs

1//! Diff operations (diff, pct_change)
2
3use wide::f64x4;
4
5use super::SIMD_THRESHOLD;
6
7// ===== Public API - Feature-gated SIMD selection =====
8
9/// Diff operation (auto-selects SIMD or scalar)
10#[cfg(feature = "simd")]
11#[inline]
12pub fn diff(data: &[f64]) -> Vec<f64> {
13    diff_simd(data)
14}
15
16#[cfg(not(feature = "simd"))]
17#[inline]
18pub fn diff(data: &[f64]) -> Vec<f64> {
19    diff_scalar(data)
20}
21
22/// Percentage change (auto-selects SIMD or scalar)
23#[cfg(feature = "simd")]
24#[inline]
25pub fn pct_change(data: &[f64]) -> Vec<f64> {
26    pct_change_simd(data)
27}
28
29#[cfg(not(feature = "simd"))]
30#[inline]
31pub fn pct_change(data: &[f64]) -> Vec<f64> {
32    pct_change_scalar(data)
33}
34
35// ===== Internal implementations =====
36
37#[cfg(not(feature = "simd"))]
38fn diff_scalar(data: &[f64]) -> Vec<f64> {
39    let n = data.len();
40    if n < 2 {
41        return vec![f64::NAN; n];
42    }
43
44    let mut result = vec![f64::NAN; n];
45    for i in 1..n {
46        result[i] = data[i] - data[i - 1];
47    }
48    result
49}
50
51#[cfg(not(feature = "simd"))]
52fn pct_change_scalar(data: &[f64]) -> Vec<f64> {
53    let n = data.len();
54    if n < 2 {
55        return vec![f64::NAN; n];
56    }
57
58    let mut result = vec![f64::NAN; n];
59    for i in 1..n {
60        if data[i - 1] != 0.0 {
61            result[i] = (data[i] - data[i - 1]) / data[i - 1];
62        } else {
63            result[i] = f64::NAN;
64        }
65    }
66    result
67}
68
69/// SIMD-accelerated diff: result[i] = data[i] - data[i-1]
70///
71/// # Performance
72/// - Expected speedup: 3-4x over scalar
73/// - Complexity: O(n)
74fn diff_simd(data: &[f64]) -> Vec<f64> {
75    let n = data.len();
76    if n < 2 {
77        return vec![f64::NAN; n];
78    }
79
80    let mut result = vec![f64::NAN; n];
81
82    // Use scalar for small arrays
83    if n < SIMD_THRESHOLD {
84        for i in 1..n {
85            result[i] = data[i] - data[i - 1];
86        }
87        return result;
88    }
89
90    // Process 4 at a time
91    let chunks = (n - 1) / 4;
92
93    for chunk in 0..chunks {
94        let i = 1 + chunk * 4;
95
96        let curr = f64x4::new([data[i], data[i + 1], data[i + 2], data[i + 3]]);
97        let prev = f64x4::new([data[i - 1], data[i], data[i + 1], data[i + 2]]);
98        let diff = curr - prev;
99
100        let arr = diff.to_array();
101        result[i..i + 4].copy_from_slice(&arr);
102    }
103
104    // Remainder
105    for i in (1 + chunks * 4)..n {
106        result[i] = data[i] - data[i - 1];
107    }
108
109    result
110}
111
112/// SIMD-accelerated percentage change: result[i] = (data[i] - data[i-1]) / data[i-1]
113///
114/// # Performance
115/// - Expected speedup: 3-4x over scalar
116/// - Complexity: O(n)
117fn pct_change_simd(data: &[f64]) -> Vec<f64> {
118    let n = data.len();
119    if n < 2 {
120        return vec![f64::NAN; n];
121    }
122
123    let mut result = vec![f64::NAN; n];
124
125    if n < SIMD_THRESHOLD {
126        for i in 1..n {
127            result[i] = (data[i] - data[i - 1]) / data[i - 1];
128        }
129        return result;
130    }
131
132    let chunks = (n - 1) / 4;
133
134    for chunk in 0..chunks {
135        let i = 1 + chunk * 4;
136
137        let curr = f64x4::new([data[i], data[i + 1], data[i + 2], data[i + 3]]);
138        let prev = f64x4::new([data[i - 1], data[i], data[i + 1], data[i + 2]]);
139        let diff = curr - prev;
140        let pct = diff / prev;
141
142        let arr = pct.to_array();
143        result[i..i + 4].copy_from_slice(&arr);
144    }
145
146    // Remainder
147    for i in (1 + chunks * 4)..n {
148        result[i] = (data[i] - data[i - 1]) / data[i - 1];
149    }
150
151    result
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_diff_simd() {
160        let data = vec![100.0, 102.0, 101.0, 105.0, 103.0];
161        let result = diff(&data);
162
163        assert!(result[0].is_nan());
164        assert_eq!(result[1], 2.0);
165        assert_eq!(result[2], -1.0);
166        assert_eq!(result[3], 4.0);
167        assert_eq!(result[4], -2.0);
168    }
169
170    #[test]
171    fn test_pct_change_simd() {
172        let data = vec![100.0, 110.0, 99.0, 105.0];
173        let result = pct_change(&data);
174
175        assert!(result[0].is_nan());
176        assert!((result[1] - 0.10).abs() < 1e-10); // 10% increase
177        assert!((result[2] - (-0.1)).abs() < 1e-10); // ~-10% decrease
178        assert!((result[3] - 0.06060606).abs() < 1e-6); // ~6% increase
179    }
180}