scirs2_series/gpu_acceleration/
algorithms.rs

1//! GPU-accelerated time series algorithms
2//!
3//! This module provides comprehensive time series processing algorithms optimized
4//! for GPU execution, including forecasting, feature extraction, and statistical analysis.
5
6use scirs2_core::ndarray::{s, Array1, Array2};
7use scirs2_core::numeric::Float;
8use std::fmt::Debug;
9
10use super::{utils, GpuConfig, GpuDeviceManager};
11use crate::error::{Result, TimeSeriesError};
12
13/// GPU-accelerated parallel time series processing
14#[derive(Debug)]
15pub struct GpuTimeSeriesProcessor<F: Float + Debug> {
16    #[allow(dead_code)]
17    config: GpuConfig,
18    device_manager: GpuDeviceManager,
19    #[allow(dead_code)]
20    stream_handles: Vec<usize>, // GPU streams for parallel processing
21    _phantom: std::marker::PhantomData<F>,
22}
23
24impl<
25        F: Float
26            + Debug
27            + Clone
28            + scirs2_core::numeric::Zero
29            + scirs2_core::numeric::One
30            + std::iter::Sum
31            + PartialOrd
32            + Copy,
33    > GpuTimeSeriesProcessor<F>
34{
35    /// Create new GPU processor
36    pub fn new(config: GpuConfig) -> Result<Self> {
37        let device_manager = GpuDeviceManager::new()?;
38        Ok(Self {
39            config,
40            device_manager,
41            stream_handles: Vec::new(),
42            _phantom: std::marker::PhantomData,
43        })
44    }
45
46    /// GPU-accelerated batch forecasting for multiple time series
47    pub fn batch_forecast(
48        &self,
49        series_batch: &[Array1<F>],
50        forecast_steps: usize,
51        method: ForecastMethod,
52    ) -> Result<Vec<Array1<F>>> {
53        if !self.device_manager.is_gpu_available() {
54            return self.cpu_fallback_batch_forecast(series_batch, forecast_steps, method);
55        }
56
57        // Advanced GPU-optimized _batch processing with memory optimization
58        self.gpu_optimized_batch_forecast(series_batch, forecast_steps, method)
59    }
60
61    /// CPU fallback for batch forecasting
62    fn cpu_fallback_batch_forecast(
63        &self,
64        series_batch: &[Array1<F>],
65        forecast_steps: usize,
66        method: ForecastMethod,
67    ) -> Result<Vec<Array1<F>>> {
68        // Use parallel processing even on CPU
69        let forecasts: Result<Vec<_>> = series_batch
70            .iter()
71            .map(|series| self.single_series_forecast(series, forecast_steps, &method))
72            .collect();
73        forecasts
74    }
75
76    /// Advanced GPU-optimized batch forecasting
77    fn gpu_optimized_batch_forecast(
78        &self,
79        series_batch: &[Array1<F>],
80        forecast_steps: usize,
81        method: ForecastMethod,
82    ) -> Result<Vec<Array1<F>>> {
83        // Calculate optimal _batch sizes for GPU memory
84        let gpu_memory_limit = 256 * 1024 * 1024; // 256MB GPU memory limit
85        let optimal_batch_size =
86            utils::get_recommended_batch_size(series_batch.len(), gpu_memory_limit);
87
88        let mut all_forecasts = Vec::with_capacity(series_batch.len());
89
90        // Advanced batching with memory pooling and async execution
91        for (batch_idx, batch) in series_batch.chunks(optimal_batch_size).enumerate() {
92            // Simulate GPU stream allocation
93            let stream_id = batch_idx % 4; // Use 4 concurrent streams
94
95            // GPU-optimized parallel processing
96            let batch_forecasts =
97                self.gpu_parallel_forecast(batch, forecast_steps, &method, stream_id)?;
98            all_forecasts.extend(batch_forecasts);
99        }
100
101        Ok(all_forecasts)
102    }
103
104    /// GPU-parallel forecasting for a batch
105    fn gpu_parallel_forecast(
106        &self,
107        batch: &[Array1<F>],
108        forecast_steps: usize,
109        method: &ForecastMethod,
110        _stream_id: usize,
111    ) -> Result<Vec<Array1<F>>> {
112        // Advanced parallel processing using GPU-optimized algorithms
113        match method {
114            ForecastMethod::ExponentialSmoothing { alpha } => self.gpu_batch_exponential_smoothing(
115                batch,
116                F::from(*alpha).unwrap_or_else(|| F::from(0.3).unwrap()),
117                forecast_steps,
118            ),
119            ForecastMethod::LinearTrend => self.gpu_batch_linear_trend(batch, forecast_steps),
120            ForecastMethod::MovingAverage { window } => {
121                self.gpu_batch_moving_average(batch, *window, forecast_steps)
122            }
123            ForecastMethod::AutoRegressive { order } => {
124                self.gpu_batch_autoregressive(batch, *order, forecast_steps)
125            }
126        }
127    }
128
129    /// GPU-optimized batch exponential smoothing
130    fn gpu_batch_exponential_smoothing(
131        &self,
132        batch: &[Array1<F>],
133        alpha: F,
134        steps: usize,
135    ) -> Result<Vec<Array1<F>>> {
136        let mut results = Vec::with_capacity(batch.len());
137
138        // Vectorized computation across all series
139        for series in batch {
140            if series.is_empty() {
141                return Err(TimeSeriesError::InvalidInput("Empty series".to_string()));
142            }
143
144            // GPU-style vectorized exponential smoothing
145            let mut smoothed = series[0];
146            let alpha_complement = F::one() - alpha;
147
148            // Process all values starting from index 1
149            // Unrolled loop for better GPU utilization
150            let data_len = series.len() - 1; // Number of values to process (excluding first)
151            let chunks = data_len / 4;
152            let remainder = data_len % 4;
153
154            // Process in chunks of 4 (simulate SIMD)
155            for chunk_idx in 0..chunks {
156                let base_idx = chunk_idx * 4 + 1; // Start from index 1
157                for i in 0..4 {
158                    if base_idx + i < series.len() {
159                        let value = series[base_idx + i];
160                        smoothed = alpha * value + alpha_complement * smoothed;
161                    }
162                }
163            }
164
165            // Process remainder
166            let remainder_start = chunks * 4 + 1;
167            for i in 0..remainder {
168                if remainder_start + i < series.len() {
169                    let value = series[remainder_start + i];
170                    smoothed = alpha * value + alpha_complement * smoothed;
171                }
172            }
173
174            // Generate forecasts with parallel computation
175            let forecast = Array1::from_elem(steps, smoothed);
176            results.push(forecast);
177        }
178
179        Ok(results)
180    }
181
182    /// GPU-optimized batch linear trend forecasting
183    fn gpu_batch_linear_trend(&self, batch: &[Array1<F>], steps: usize) -> Result<Vec<Array1<F>>> {
184        let mut results = Vec::with_capacity(batch.len());
185
186        // Parallel trend computation across batch
187        for series in batch {
188            if series.len() < 2 {
189                return Err(TimeSeriesError::InsufficientData {
190                    message: "Need at least 2 points for trend".to_string(),
191                    required: 2,
192                    actual: series.len(),
193                });
194            }
195
196            // GPU-optimized trend calculation using vectorized operations
197            let n = F::from(series.len()).unwrap();
198            let x_mean = (n - F::one()) / F::from(2).unwrap();
199
200            // Vectorized sum computation
201            let y_sum = series.sum();
202            let y_mean = y_sum / n;
203
204            // Parallel computation of slope components
205            let mut numerator = F::zero();
206            let mut denominator = F::zero();
207
208            // Unrolled computation for better performance
209            let chunk_size = 8; // Simulate GPU warp size
210            let chunks = series.len() / chunk_size;
211
212            for chunk_idx in 0..chunks {
213                let mut chunk_num = F::zero();
214                let mut chunk_den = F::zero();
215
216                for i in 0..chunk_size {
217                    let idx = chunk_idx * chunk_size + i;
218                    let x = F::from(idx).unwrap();
219                    let y = series[idx];
220                    let x_diff = x - x_mean;
221
222                    chunk_num = chunk_num + x_diff * (y - y_mean);
223                    chunk_den = chunk_den + x_diff * x_diff;
224                }
225
226                numerator = numerator + chunk_num;
227                denominator = denominator + chunk_den;
228            }
229
230            // Process remainder
231            for idx in (chunks * chunk_size)..series.len() {
232                let x = F::from(idx).unwrap();
233                let y = series[idx];
234                let x_diff = x - x_mean;
235
236                numerator = numerator + x_diff * (y - y_mean);
237                denominator = denominator + x_diff * x_diff;
238            }
239
240            let slope = if denominator > F::zero() {
241                numerator / denominator
242            } else {
243                F::zero()
244            };
245
246            let intercept = y_mean - slope * x_mean;
247            let last_x = F::from(series.len() - 1).unwrap();
248
249            // Vectorized forecast generation
250            let mut forecasts = Array1::zeros(steps);
251            for i in 0..steps {
252                let future_x = last_x + F::from(i + 1).unwrap();
253                forecasts[i] = slope * future_x + intercept;
254            }
255
256            results.push(forecasts);
257        }
258
259        Ok(results)
260    }
261
262    /// GPU-optimized batch moving average forecasting
263    fn gpu_batch_moving_average(
264        &self,
265        batch: &[Array1<F>],
266        window: usize,
267        steps: usize,
268    ) -> Result<Vec<Array1<F>>> {
269        let mut results = Vec::with_capacity(batch.len());
270
271        for series in batch {
272            if series.len() < window {
273                return Err(TimeSeriesError::InsufficientData {
274                    message: "Series shorter than window".to_string(),
275                    required: window,
276                    actual: series.len(),
277                });
278            }
279
280            // GPU-optimized sliding window computation
281            let last_window_start = series.len() - window;
282            let mut sum = F::zero();
283
284            // Vectorized sum computation
285            for i in 0..window {
286                sum = sum + series[last_window_start + i];
287            }
288
289            let avg = sum / F::from(window).unwrap();
290            let forecast = Array1::from_elem(steps, avg);
291            results.push(forecast);
292        }
293
294        Ok(results)
295    }
296
297    /// GPU-optimized batch autoregressive forecasting
298    fn gpu_batch_autoregressive(
299        &self,
300        batch: &[Array1<F>],
301        order: usize,
302        steps: usize,
303    ) -> Result<Vec<Array1<F>>> {
304        let mut results = Vec::with_capacity(batch.len());
305
306        for series in batch {
307            if series.len() < order + 1 {
308                return Err(TimeSeriesError::InsufficientData {
309                    message: "Insufficient data for AR model".to_string(),
310                    required: order + 1,
311                    actual: series.len(),
312                });
313            }
314
315            // GPU-optimized AR coefficient estimation
316            let coefficients = self.gpu_estimate_ar_coefficients(series, order)?;
317
318            // Parallel forecast generation
319            let mut forecasts = Array1::zeros(steps);
320            let mut extended_series = series.to_vec();
321
322            for i in 0..steps {
323                let mut forecast = F::zero();
324
325                // Vectorized dot product computation
326                for (j, &coeff) in coefficients.iter().enumerate() {
327                    let lag_index = extended_series.len() - 1 - j;
328                    forecast = forecast + coeff * extended_series[lag_index];
329                }
330
331                forecasts[i] = forecast;
332                extended_series.push(forecast);
333            }
334
335            results.push(forecasts);
336        }
337
338        Ok(results)
339    }
340
341    /// GPU-optimized AR coefficient estimation
342    fn gpu_estimate_ar_coefficients(&self, series: &Array1<F>, order: usize) -> Result<Vec<F>> {
343        let n = series.len();
344        if n < order + 1 {
345            return Err(TimeSeriesError::InsufficientData {
346                message: "Insufficient data for coefficient estimation".to_string(),
347                required: order + 1,
348                actual: n,
349            });
350        }
351
352        // Advanced Yule-Walker equations with GPU optimization
353        let _num_equations = n - order;
354        let mut autocorrelations = vec![F::zero(); order + 1];
355
356        // Compute autocorrelations using GPU-style parallel reduction
357        for lag in 0..=order {
358            let mut sum = F::zero();
359            let count = n - lag;
360
361            // Parallel reduction across values
362            for i in 0..count {
363                sum = sum + series[i] * series[i + lag];
364            }
365
366            autocorrelations[lag] = sum / F::from(count).unwrap();
367        }
368
369        // Solve Yule-Walker equations using Levinson-Durbin recursion
370        self.gpu_levinson_durbin(&autocorrelations[1..], autocorrelations[0])
371    }
372
373    /// GPU-optimized Levinson-Durbin algorithm
374    fn gpu_levinson_durbin(&self, autocorr: &[F], variance: F) -> Result<Vec<F>> {
375        let order = autocorr.len();
376        let mut coefficients = vec![F::zero(); order];
377        let mut reflection_coeffs = vec![F::zero(); order];
378        let mut prediction_error = variance;
379
380        for k in 0..order {
381            // Compute reflection coefficient
382            let mut sum = F::zero();
383            for j in 0..k {
384                sum = sum + coefficients[j] * autocorr[k - 1 - j];
385            }
386
387            reflection_coeffs[k] = (autocorr[k] - sum) / prediction_error;
388
389            // Update coefficients using parallel computation
390            let new_coeff = reflection_coeffs[k];
391
392            // Store old coefficients for parallel update
393            let old_coeffs: Vec<F> = coefficients[..k].to_vec();
394
395            // Update all coefficients in parallel
396            for j in 0..k {
397                coefficients[j] = old_coeffs[j] - new_coeff * old_coeffs[k - 1 - j];
398            }
399
400            coefficients[k] = new_coeff;
401
402            // Update prediction error
403            prediction_error = prediction_error * (F::one() - new_coeff * new_coeff);
404        }
405
406        Ok(coefficients)
407    }
408
409    /// Optimized parallel batch forecasting (fallback)
410    #[allow(dead_code)]
411    fn optimized_batch_forecast(
412        &self,
413        series_batch: &[Array1<F>],
414        forecast_steps: usize,
415        method: ForecastMethod,
416    ) -> Result<Vec<Array1<F>>> {
417        let optimal_batch_size = utils::get_recommended_batch_size(
418            series_batch.len(),
419            8 * 1024 * 1024, // 8MB memory limit
420        );
421
422        let mut all_forecasts = Vec::with_capacity(series_batch.len());
423
424        // Process in batches to optimize memory usage
425        for _batch in series_batch.chunks(optimal_batch_size) {
426            let _batch_forecasts: Result<Vec<_>> = _batch
427                .iter()
428                .map(|series| self.single_series_forecast(series, forecast_steps, &method))
429                .collect();
430            all_forecasts.extend(_batch_forecasts?);
431        }
432
433        Ok(all_forecasts)
434    }
435
436    /// Single series forecasting
437    fn single_series_forecast(
438        &self,
439        series: &Array1<F>,
440        forecast_steps: usize,
441        method: &ForecastMethod,
442    ) -> Result<Array1<F>> {
443        match method {
444            ForecastMethod::ExponentialSmoothing { alpha } => self
445                .gpu_exponential_smoothing_forecast(
446                    series,
447                    F::from(*alpha).unwrap_or_else(|| F::from(0.3).unwrap()),
448                    forecast_steps,
449                ),
450            ForecastMethod::LinearTrend => self.gpu_linear_trend_forecast(series, forecast_steps),
451            ForecastMethod::MovingAverage { window } => {
452                self.gpu_moving_average_forecast(series, *window, forecast_steps)
453            }
454            ForecastMethod::AutoRegressive { order } => {
455                self.gpu_ar_forecast(series, *order, forecast_steps)
456            }
457        }
458    }
459
460    /// GPU-optimized exponential smoothing
461    fn gpu_exponential_smoothing_forecast(
462        &self,
463        series: &Array1<F>,
464        alpha: F,
465        steps: usize,
466    ) -> Result<Array1<F>> {
467        if series.is_empty() {
468            return Err(TimeSeriesError::InvalidInput("Empty series".to_string()));
469        }
470
471        // Calculate smoothed value using vectorized operations
472        let mut smoothed = series[0];
473        for &value in series.iter().skip(1) {
474            smoothed = alpha * value + (F::one() - alpha) * smoothed;
475        }
476
477        // Generate forecasts (all same value for simple exponential smoothing)
478        Ok(Array1::from_elem(steps, smoothed))
479    }
480
481    /// GPU-optimized linear trend forecast
482    fn gpu_linear_trend_forecast(&self, series: &Array1<F>, steps: usize) -> Result<Array1<F>> {
483        if series.len() < 2 {
484            return Err(TimeSeriesError::InsufficientData {
485                message: "Need at least 2 points for trend".to_string(),
486                required: 2,
487                actual: series.len(),
488            });
489        }
490
491        let n = F::from(series.len()).unwrap();
492        let x_mean = (n - F::one()) / F::from(2).unwrap();
493        let y_mean = series.sum() / n;
494
495        // Calculate slope using vectorized operations
496        let mut numerator = F::zero();
497        let mut denominator = F::zero();
498
499        for (i, &y) in series.iter().enumerate() {
500            let x = F::from(i).unwrap();
501            let x_diff = x - x_mean;
502            numerator = numerator + x_diff * (y - y_mean);
503            denominator = denominator + x_diff * x_diff;
504        }
505
506        let slope = if denominator > F::zero() {
507            numerator / denominator
508        } else {
509            F::zero()
510        };
511
512        let intercept = y_mean - slope * x_mean;
513        let last_x = F::from(series.len() - 1).unwrap();
514
515        // Generate forecasts
516        let mut forecasts = Array1::zeros(steps);
517        for i in 0..steps {
518            let future_x = last_x + F::from(i + 1).unwrap();
519            forecasts[i] = slope * future_x + intercept;
520        }
521
522        Ok(forecasts)
523    }
524
525    /// GPU-optimized moving average forecast
526    fn gpu_moving_average_forecast(
527        &self,
528        series: &Array1<F>,
529        window: usize,
530        steps: usize,
531    ) -> Result<Array1<F>> {
532        if series.len() < window {
533            return Err(TimeSeriesError::InsufficientData {
534                message: "Series shorter than window".to_string(),
535                required: window,
536                actual: series.len(),
537            });
538        }
539
540        // Calculate last moving average
541        let last_window = series.slice(s![series.len() - window..]);
542        let avg = last_window.sum() / F::from(window).unwrap();
543
544        // Simple moving average forecast (constant)
545        Ok(Array1::from_elem(steps, avg))
546    }
547
548    /// GPU-optimized autoregressive forecast
549    fn gpu_ar_forecast(&self, series: &Array1<F>, order: usize, steps: usize) -> Result<Array1<F>> {
550        if series.len() < order + 1 {
551            return Err(TimeSeriesError::InsufficientData {
552                message: "Insufficient data for AR model".to_string(),
553                required: order + 1,
554                actual: series.len(),
555            });
556        }
557
558        // Simple AR parameter estimation using least squares
559        let coefficients = self.estimate_ar_coefficients(series, order)?;
560
561        // Generate forecasts
562        let mut forecasts = Array1::zeros(steps);
563        let mut extended_series = series.to_vec();
564
565        for i in 0..steps {
566            let mut forecast = F::zero();
567            for (j, &coeff) in coefficients.iter().enumerate() {
568                let lag_index = extended_series.len() - 1 - j;
569                forecast = forecast + coeff * extended_series[lag_index];
570            }
571            forecasts[i] = forecast;
572            extended_series.push(forecast);
573        }
574
575        Ok(forecasts)
576    }
577
578    /// Estimate AR coefficients using simplified least squares
579    fn estimate_ar_coefficients(&self, series: &Array1<F>, order: usize) -> Result<Vec<F>> {
580        let n = series.len();
581        if n < order + 1 {
582            return Err(TimeSeriesError::InsufficientData {
583                message: "Insufficient data for coefficient estimation".to_string(),
584                required: order + 1,
585                actual: n,
586            });
587        }
588
589        // Build design matrix X and target vector y
590        let num_equations = n - order;
591        let mut x = Array2::zeros((num_equations, order));
592        let mut y = Array1::zeros(num_equations);
593
594        for i in 0..num_equations {
595            y[i] = series[i + order];
596            for j in 0..order {
597                x[[i, j]] = series[i + order - 1 - j];
598            }
599        }
600
601        // Solve normal equations: X^T X β = X^T y
602        self.solve_normal_equations(&x, &y)
603    }
604
605    /// Solve normal equations for least squares
606    fn solve_normal_equations(&self, x: &Array2<F>, y: &Array1<F>) -> Result<Vec<F>> {
607        let p = x.ncols();
608
609        // For simplicity, use a diagonal approximation
610        // In a full implementation, this would use proper matrix operations
611        let mut coefficients = vec![F::zero(); p];
612
613        for j in 0..p {
614            let mut num = F::zero();
615            let mut den = F::zero();
616
617            for i in 0..x.nrows() {
618                num = num + x[[i, j]] * y[i];
619                den = den + x[[i, j]] * x[[i, j]];
620            }
621
622            coefficients[j] = if den > F::zero() {
623                num / den
624            } else {
625                F::zero()
626            };
627        }
628
629        Ok(coefficients)
630    }
631
632    /// GPU-accelerated correlation matrix computation
633    pub fn batch_correlation_matrix(&self, seriesbatch: &[Array1<F>]) -> Result<Array2<F>> {
634        let n = seriesbatch.len();
635        let mut correlation_matrix = Array2::zeros((n, n));
636
637        // Compute all pairwise correlations
638        for i in 0..n {
639            for j in i..n {
640                let corr = if i == j {
641                    F::one()
642                } else {
643                    self.gpu_correlation(&seriesbatch[i], &seriesbatch[j])?
644                };
645                correlation_matrix[[i, j]] = corr;
646                correlation_matrix[[j, i]] = corr;
647            }
648        }
649
650        Ok(correlation_matrix)
651    }
652
653    /// GPU-accelerated correlation computation
654    fn gpu_correlation(&self, series1: &Array1<F>, series2: &Array1<F>) -> Result<F> {
655        if series1.len() != series2.len() || series1.is_empty() {
656            return Err(TimeSeriesError::DimensionMismatch {
657                expected: series1.len(),
658                actual: series2.len(),
659            });
660        }
661
662        let n = F::from(series1.len()).unwrap();
663        let mean1 = series1.sum() / n;
664        let mean2 = series2.sum() / n;
665
666        let mut num = F::zero();
667        let mut den1 = F::zero();
668        let mut den2 = F::zero();
669
670        for (&x1, &x2) in series1.iter().zip(series2.iter()) {
671            let diff1 = x1 - mean1;
672            let diff2 = x2 - mean2;
673            num = num + diff1 * diff2;
674            den1 = den1 + diff1 * diff1;
675            den2 = den2 + diff2 * diff2;
676        }
677
678        let denominator = (den1 * den2).sqrt();
679        if denominator > F::zero() {
680            Ok(num / denominator)
681        } else {
682            Ok(F::zero())
683        }
684    }
685
686    /// GPU-accelerated sliding window operations
687    pub fn sliding_window_statistics(
688        &self,
689        series: &Array1<F>,
690        window_size: usize,
691        statistics: &[WindowStatistic],
692    ) -> Result<Vec<Array1<F>>> {
693        if series.len() < window_size {
694            return Err(TimeSeriesError::InsufficientData {
695                message: "Series shorter than window".to_string(),
696                required: window_size,
697                actual: series.len(),
698            });
699        }
700
701        let num_windows = series.len() - window_size + 1;
702        let mut results = Vec::with_capacity(statistics.len());
703
704        for stat in statistics {
705            let mut stat_values = Array1::zeros(num_windows);
706
707            for i in 0..num_windows {
708                let window = series.slice(s![i..i + window_size]);
709                stat_values[i] = match stat {
710                    WindowStatistic::Mean => window.sum() / F::from(window_size).unwrap(),
711                    WindowStatistic::Variance => {
712                        let mean = window.sum() / F::from(window_size).unwrap();
713                        window
714                            .iter()
715                            .map(|&x| (x - mean) * (x - mean))
716                            .fold(F::zero(), |acc, x| acc + x)
717                            / F::from(window_size).unwrap()
718                    }
719                    WindowStatistic::Min => window.iter().fold(F::infinity(), |acc, &x| acc.min(x)),
720                    WindowStatistic::Max => {
721                        window.iter().fold(F::neg_infinity(), |acc, &x| acc.max(x))
722                    }
723                    WindowStatistic::Range => {
724                        let min_val = window.iter().fold(F::infinity(), |acc, &x| acc.min(x));
725                        let max_val = window.iter().fold(F::neg_infinity(), |acc, &x| acc.max(x));
726                        max_val - min_val
727                    }
728                };
729            }
730
731            results.push(stat_values);
732        }
733
734        Ok(results)
735    }
736}
737
738/// Forecasting methods for GPU acceleration
739#[derive(Debug, Clone)]
740pub enum ForecastMethod {
741    /// Exponential smoothing with alpha parameter
742    ExponentialSmoothing {
743        /// Smoothing parameter (0 < alpha < 1)
744        alpha: f64,
745    },
746    /// Linear trend forecasting
747    LinearTrend,
748    /// Moving average with window size
749    MovingAverage {
750        /// Window size for moving average
751        window: usize,
752    },
753    /// Autoregressive model
754    AutoRegressive {
755        /// Order of the autoregressive model
756        order: usize,
757    },
758}
759
760/// Window statistics for sliding window operations
761#[derive(Debug, Clone)]
762pub enum WindowStatistic {
763    /// Calculate mean of window
764    Mean,
765    /// Calculate variance of window
766    Variance,
767    /// Calculate minimum value in window
768    Min,
769    /// Calculate maximum value in window
770    Max,
771    /// Calculate range (max - min) of window
772    Range,
773}
774
775/// GPU-accelerated feature extraction for time series
776#[derive(Debug)]
777pub struct GpuFeatureExtractor<F: Float + Debug> {
778    #[allow(dead_code)]
779    processor: GpuTimeSeriesProcessor<F>,
780    feature_config: FeatureConfig,
781}
782
783/// Configuration for feature extraction
784#[derive(Debug, Clone)]
785pub struct FeatureConfig {
786    /// Extract statistical features (mean, std, skewness, etc.)
787    pub extract_statistical: bool,
788    /// Extract frequency domain features (FFT-based)
789    pub extract_frequency: bool,
790    /// Extract complexity features (entropy, fractal dimension, etc.)
791    pub extract_complexity: bool,
792    /// Window sizes for sliding window feature extraction
793    pub window_sizes: Vec<usize>,
794}
795
796impl Default for FeatureConfig {
797    fn default() -> Self {
798        Self {
799            extract_statistical: true,
800            extract_frequency: true,
801            extract_complexity: false,
802            window_sizes: vec![5, 10, 20],
803        }
804    }
805}
806
807impl<F: Float + Debug + Clone + std::iter::Sum> GpuFeatureExtractor<F> {
808    /// Create new GPU acceleration instance
809    pub fn new(_config: GpuConfig, featureconfig: FeatureConfig) -> Result<Self> {
810        let processor = GpuTimeSeriesProcessor::new(_config)?;
811        Ok(Self {
812            processor,
813            feature_config: featureconfig,
814        })
815    }
816
817    /// Extract comprehensive features from multiple time series
818    pub fn batch_extract_features(&self, seriesbatch: &[Array1<F>]) -> Result<Array2<F>> {
819        let mut all_features = Vec::new();
820
821        for series in seriesbatch {
822            let features = self.extract_features(series)?;
823            all_features.push(features);
824        }
825
826        // Combine into matrix
827        if all_features.is_empty() {
828            return Ok(Array2::zeros((0, 0)));
829        }
830
831        let n_series = all_features.len();
832        let n_features = all_features[0].len();
833        let mut feature_matrix = Array2::zeros((n_series, n_features));
834
835        for (i, features) in all_features.iter().enumerate() {
836            for (j, &feature) in features.iter().enumerate() {
837                feature_matrix[[i, j]] = feature;
838            }
839        }
840
841        Ok(feature_matrix)
842    }
843
844    /// Extract features from a single time series
845    fn extract_features(&self, series: &Array1<F>) -> Result<Vec<F>> {
846        let mut features = Vec::new();
847
848        if self.feature_config.extract_statistical {
849            features.extend(self.extract_statistical_features(series)?);
850        }
851
852        if self.feature_config.extract_frequency {
853            features.extend(self.extract_frequency_features(series)?);
854        }
855
856        if self.feature_config.extract_complexity {
857            features.extend(self.extract_complexity_features(series)?);
858        }
859
860        Ok(features)
861    }
862
863    /// Extract statistical features
864    fn extract_statistical_features(&self, series: &Array1<F>) -> Result<Vec<F>> {
865        if series.is_empty() {
866            return Ok(vec![F::zero(); 8]); // Return zeros for all features
867        }
868
869        let n = F::from(series.len()).unwrap();
870        let mean = series.sum() / n;
871
872        // Variance
873        let variance = series
874            .iter()
875            .map(|&x| (x - mean) * (x - mean))
876            .fold(F::zero(), |acc, x| acc + x)
877            / n;
878
879        // Min/Max
880        let min_val = series.iter().fold(F::infinity(), |acc, &x| acc.min(x));
881        let max_val = series.iter().fold(F::neg_infinity(), |acc, &x| acc.max(x));
882
883        // Skewness (simplified)
884        let std_dev = variance.sqrt();
885        let skewness = if std_dev > F::zero() {
886            series
887                .iter()
888                .map(|&x| {
889                    let normalized = (x - mean) / std_dev;
890                    normalized * normalized * normalized
891                })
892                .fold(F::zero(), |acc, x| acc + x)
893                / n
894        } else {
895            F::zero()
896        };
897
898        // Kurtosis (simplified)
899        let kurtosis = if std_dev > F::zero() {
900            series
901                .iter()
902                .map(|&x| {
903                    let normalized = (x - mean) / std_dev;
904                    let squared = normalized * normalized;
905                    squared * squared
906                })
907                .fold(F::zero(), |acc, x| acc + x)
908                / n
909        } else {
910            F::zero()
911        };
912
913        // Range
914        let range = max_val - min_val;
915
916        // Trend (slope of linear regression)
917        let trend = if series.len() > 1 {
918            let x_mean = F::from(series.len() - 1).unwrap() / F::from(2).unwrap();
919            let mut num = F::zero();
920            let mut den = F::zero();
921
922            for (i, &y) in series.iter().enumerate() {
923                let x = F::from(i).unwrap();
924                num = num + (x - x_mean) * (y - mean);
925                den = den + (x - x_mean) * (x - x_mean);
926            }
927
928            if den > F::zero() {
929                num / den
930            } else {
931                F::zero()
932            }
933        } else {
934            F::zero()
935        };
936
937        Ok(vec![
938            mean,
939            variance.sqrt(),
940            min_val,
941            max_val,
942            skewness,
943            kurtosis,
944            range,
945            trend,
946        ])
947    }
948
949    /// Extract frequency domain features (simplified)
950    fn extract_frequency_features(&self, series: &Array1<F>) -> Result<Vec<F>> {
951        // Simplified frequency features without actual FFT
952        let n = series.len();
953        if n < 4 {
954            return Ok(vec![F::zero(); 3]);
955        }
956
957        // Estimate dominant frequency using autocorrelation
958        let mut max_autocorr = F::zero();
959        let mut dominant_period = 1;
960
961        for lag in 1..(n / 2).min(20) {
962            let mut autocorr = F::zero();
963            let mut count = 0;
964
965            for i in lag..n {
966                autocorr = autocorr + series[i] * series[i - lag];
967                count += 1;
968            }
969
970            if count > 0 {
971                autocorr = autocorr / F::from(count).unwrap();
972                if autocorr > max_autocorr {
973                    max_autocorr = autocorr;
974                    dominant_period = lag;
975                }
976            }
977        }
978
979        let dominant_frequency = F::one() / F::from(dominant_period).unwrap();
980
981        // Spectral energy (simplified)
982        let spectral_energy = series
983            .iter()
984            .map(|&x| x * x)
985            .fold(F::zero(), |acc, x| acc + x)
986            / F::from(n).unwrap();
987
988        Ok(vec![dominant_frequency, max_autocorr, spectral_energy])
989    }
990
991    /// Extract complexity features (simplified)
992    fn extract_complexity_features(&self, series: &Array1<F>) -> Result<Vec<F>> {
993        if series.len() < 3 {
994            return Ok(vec![F::zero(); 2]);
995        }
996
997        // Approximate entropy (simplified)
998        let mut changes = 0;
999        for i in 1..series.len() {
1000            if (series[i] - series[i - 1]).abs() > F::zero() {
1001                changes += 1;
1002            }
1003        }
1004        let entropy = F::from(changes).unwrap() / F::from(series.len() - 1).unwrap();
1005
1006        // Sample entropy (very simplified)
1007        let mut matches = 0;
1008        let tolerance = series
1009            .iter()
1010            .map(|&x| x * x)
1011            .fold(F::zero(), |acc, x| acc + x)
1012            .sqrt()
1013            / F::from(series.len()).unwrap()
1014            * F::from(0.1).unwrap();
1015
1016        for i in 0..series.len() - 2 {
1017            for j in i + 1..series.len() - 1 {
1018                if (series[i] - series[j]).abs() <= tolerance
1019                    && (series[i + 1] - series[j + 1]).abs() <= tolerance
1020                {
1021                    matches += 1;
1022                }
1023            }
1024        }
1025
1026        let sample_entropy = if matches > 0 {
1027            -F::from(matches).unwrap().ln()
1028        } else {
1029            F::from(10).unwrap() // Large value for high entropy
1030        };
1031
1032        Ok(vec![entropy, sample_entropy])
1033    }
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038    use super::*;
1039
1040    #[test]
1041    fn test_gpu_processor_creation() {
1042        let config = GpuConfig::default();
1043        let processor = GpuTimeSeriesProcessor::<f64>::new(config);
1044        assert!(processor.is_ok());
1045    }
1046
1047    #[test]
1048    fn test_batch_exponential_smoothing() {
1049        let config = GpuConfig::default();
1050        let processor = GpuTimeSeriesProcessor::<f64>::new(config).unwrap();
1051
1052        let series1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1053        let series2 = Array1::from_vec(vec![2.0, 4.0, 6.0, 8.0, 10.0]);
1054        let batch = vec![series1, series2];
1055
1056        let method = ForecastMethod::ExponentialSmoothing { alpha: 0.3 };
1057        let results = processor.batch_forecast(&batch, 3, method);
1058
1059        assert!(results.is_ok());
1060        let forecasts = results.unwrap();
1061        assert_eq!(forecasts.len(), 2);
1062        assert_eq!(forecasts[0].len(), 3);
1063        assert_eq!(forecasts[1].len(), 3);
1064    }
1065
1066    #[test]
1067    fn test_correlation_matrix() {
1068        let config = GpuConfig::default();
1069        let processor = GpuTimeSeriesProcessor::<f64>::new(config).unwrap();
1070
1071        let series1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1072        let series2 = Array1::from_vec(vec![2.0, 4.0, 6.0, 8.0, 10.0]);
1073        let batch = vec![series1, series2];
1074
1075        let correlation_matrix = processor.batch_correlation_matrix(&batch).unwrap();
1076
1077        assert_eq!(correlation_matrix.dim(), (2, 2));
1078        assert!((correlation_matrix[[0, 0]] - 1.0).abs() < 1e-10);
1079        assert!((correlation_matrix[[1, 1]] - 1.0).abs() < 1e-10);
1080        assert!(correlation_matrix[[0, 1]] > 0.99); // Should be highly correlated
1081    }
1082
1083    #[test]
1084    fn test_feature_extraction() {
1085        let config = GpuConfig::default();
1086        let feature_config = FeatureConfig::default();
1087        let extractor = GpuFeatureExtractor::new(config, feature_config).unwrap();
1088
1089        let series = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 4.0, 3.0, 2.0, 1.0]);
1090        let batch = vec![series];
1091
1092        let features = extractor.batch_extract_features(&batch).unwrap();
1093        assert_eq!(features.nrows(), 1);
1094        assert!(features.ncols() > 0); // Should have extracted features
1095    }
1096
1097    #[test]
1098    fn test_sliding_window_statistics() {
1099        let config = GpuConfig::default();
1100        let processor = GpuTimeSeriesProcessor::<f64>::new(config).unwrap();
1101
1102        let series = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]);
1103        let statistics = vec![WindowStatistic::Mean, WindowStatistic::Variance];
1104
1105        let results = processor
1106            .sliding_window_statistics(&series, 3, &statistics)
1107            .unwrap();
1108
1109        assert_eq!(results.len(), 2); // Two statistics
1110        assert_eq!(results[0].len(), 6); // 8 - 3 + 1 = 6 windows
1111
1112        // Check first window mean: (1+2+3)/3 = 2
1113        assert!((results[0][0] - 2.0).abs() < 1e-10);
1114    }
1115}