quantrs2_ml/time_series/
decomposition.rs

1//! Seasonal and trend decomposition with quantum enhancement
2
3use super::config::*;
4use crate::error::{MLError, Result};
5use ndarray::{s, Array1, Array2, Axis};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::f64::consts::PI;
9
10/// Quantum seasonal decomposer for time series
11#[derive(Debug, Clone)]
12pub struct QuantumSeasonalDecomposer {
13    /// Seasonality configuration
14    config: SeasonalityConfig,
15
16    /// Quantum circuits for seasonal extraction
17    seasonal_circuits: HashMap<String, Vec<f64>>,
18
19    /// Trend extractor
20    trend_extractor: QuantumTrendExtractor,
21
22    /// Residual analyzer
23    residual_analyzer: QuantumResidualAnalyzer,
24
25    /// Decomposition results cache
26    decomposition_cache: DecompositionCache,
27}
28
29/// Quantum trend extractor
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct QuantumTrendExtractor {
32    /// Trend smoothing parameter
33    smoothing_param: f64,
34
35    /// Quantum circuit for trend extraction
36    trend_circuit: Vec<f64>,
37
38    /// Changepoint detector
39    changepoint_detector: Option<QuantumChangepointDetector>,
40
41    /// Trend model parameters
42    trend_parameters: TrendParameters,
43}
44
45/// Quantum changepoint detector
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct QuantumChangepointDetector {
48    /// Detection threshold
49    threshold: f64,
50
51    /// Quantum circuit for detection
52    detection_circuit: Vec<f64>,
53
54    /// Detected changepoints
55    changepoints: Vec<ChangePoint>,
56
57    /// Detection algorithm
58    algorithm: ChangepointAlgorithm,
59}
60
61/// Changepoint information
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ChangePoint {
64    /// Time index of changepoint
65    pub time_index: usize,
66
67    /// Confidence score
68    pub confidence: f64,
69
70    /// Type of change
71    pub change_type: ChangeType,
72
73    /// Magnitude of change
74    pub magnitude: f64,
75}
76
77/// Types of changes detected
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub enum ChangeType {
80    TrendChange,
81    LevelShift,
82    VarianceChange,
83    SeasonalityChange,
84    QuantumPhaseTransition,
85}
86
87/// Changepoint detection algorithms
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub enum ChangepointAlgorithm {
90    CUSUM,
91    PELT,
92    BinarySegmentation,
93    QuantumDetection,
94    HybridQuantumClassical,
95}
96
97/// Trend model parameters
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct TrendParameters {
100    /// Linear trend coefficient
101    pub linear_coeff: f64,
102
103    /// Quadratic trend coefficient
104    pub quadratic_coeff: f64,
105
106    /// Exponential growth rate
107    pub exp_growth_rate: f64,
108
109    /// Quantum enhancement parameters
110    pub quantum_params: Array1<f64>,
111}
112
113/// Quantum residual analyzer
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct QuantumResidualAnalyzer {
116    /// Quantum circuit for residual analysis
117    analysis_circuit: Vec<f64>,
118
119    /// Anomaly detection threshold
120    anomaly_threshold: f64,
121
122    /// Detected anomalies
123    anomalies: Vec<AnomalyPoint>,
124
125    /// Residual statistics
126    residual_stats: ResidualStatistics,
127}
128
129/// Anomaly point in residuals
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct AnomalyPoint {
132    /// Time index
133    pub timestamp: usize,
134
135    /// Anomalous value
136    pub value: f64,
137
138    /// Anomaly score
139    pub anomaly_score: f64,
140
141    /// Type of anomaly
142    pub anomaly_type: AnomalyType,
143}
144
145/// Residual statistics
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct ResidualStatistics {
148    /// Mean of residuals
149    pub mean: f64,
150
151    /// Standard deviation of residuals
152    pub std: f64,
153
154    /// Skewness
155    pub skewness: f64,
156
157    /// Kurtosis
158    pub kurtosis: f64,
159
160    /// Autocorrelation function
161    pub autocorrelation: Array1<f64>,
162
163    /// Quantum coherence measure
164    pub quantum_coherence: f64,
165}
166
167/// Decomposition cache for performance
168#[derive(Debug, Clone)]
169pub struct DecompositionCache {
170    /// Cached decompositions
171    cache: HashMap<String, DecompositionResult>,
172
173    /// Cache hit count
174    hits: usize,
175
176    /// Cache miss count
177    misses: usize,
178
179    /// Maximum cache size
180    max_size: usize,
181}
182
183/// Decomposition result
184#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct DecompositionResult {
186    /// Original time series
187    pub original: Array2<f64>,
188
189    /// Trend component
190    pub trend: Array1<f64>,
191
192    /// Seasonal components
193    pub seasonal: HashMap<String, Array1<f64>>,
194
195    /// Residual component
196    pub residual: Array1<f64>,
197
198    /// Decomposition quality metrics
199    pub quality_metrics: DecompositionQuality,
200}
201
202/// Quality metrics for decomposition
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct DecompositionQuality {
205    /// Explained variance ratio
206    pub explained_variance: f64,
207
208    /// Reconstruction error
209    pub reconstruction_error: f64,
210
211    /// Seasonal strength
212    pub seasonal_strength: HashMap<String, f64>,
213
214    /// Trend strength
215    pub trend_strength: f64,
216
217    /// Quantum enhancement effectiveness
218    pub quantum_effectiveness: f64,
219}
220
221/// Seasonal component extractor
222#[derive(Debug, Clone)]
223pub struct SeasonalComponentExtractor {
224    /// Period length
225    period: usize,
226
227    /// Component name
228    name: String,
229
230    /// Quantum circuit parameters
231    quantum_circuit: Vec<f64>,
232
233    /// Extraction method
234    method: SeasonalExtractionMethod,
235}
236
237/// Methods for seasonal extraction
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub enum SeasonalExtractionMethod {
240    MovingAverage,
241    FourierDecomposition,
242    STL,
243    QuantumFourier,
244    WaveletDecomposition,
245    HybridQuantumClassical,
246}
247
248impl QuantumSeasonalDecomposer {
249    /// Create new quantum seasonal decomposer
250    pub fn new(config: SeasonalityConfig, num_qubits: usize) -> Result<Self> {
251        let mut seasonal_circuits = HashMap::new();
252
253        // Create circuits for each seasonality component
254        if let Some(daily) = config.daily {
255            seasonal_circuits.insert(
256                "daily".to_string(),
257                Self::create_seasonal_circuit(daily, num_qubits)?,
258            );
259        }
260
261        if let Some(weekly) = config.weekly {
262            seasonal_circuits.insert(
263                "weekly".to_string(),
264                Self::create_seasonal_circuit(weekly, num_qubits)?,
265            );
266        }
267
268        if let Some(monthly) = config.monthly {
269            seasonal_circuits.insert(
270                "monthly".to_string(),
271                Self::create_seasonal_circuit(monthly, num_qubits)?,
272            );
273        }
274
275        if let Some(yearly) = config.yearly {
276            seasonal_circuits.insert(
277                "yearly".to_string(),
278                Self::create_seasonal_circuit(yearly, num_qubits)?,
279            );
280        }
281
282        // Create circuits for custom periods
283        for (i, &period) in config.custom_periods.iter().enumerate() {
284            let name = format!("custom_{}", i);
285            seasonal_circuits.insert(name, Self::create_seasonal_circuit(period, num_qubits)?);
286        }
287
288        let trend_extractor = QuantumTrendExtractor::new(0.1, num_qubits)?;
289        let residual_analyzer = QuantumResidualAnalyzer::new(num_qubits)?;
290        let decomposition_cache = DecompositionCache::new(100);
291
292        Ok(Self {
293            config,
294            seasonal_circuits,
295            trend_extractor,
296            residual_analyzer,
297            decomposition_cache,
298        })
299    }
300
301    /// Create quantum circuit for seasonal extraction
302    fn create_seasonal_circuit(period: usize, num_qubits: usize) -> Result<Vec<f64>> {
303        let mut circuit = Vec::new();
304
305        // Frequency encoding for seasonal pattern
306        for i in 0..num_qubits {
307            let frequency = 2.0 * PI * i as f64 / period as f64;
308            circuit.push(frequency);
309        }
310
311        // Phase adjustments for quantum enhancement
312        for i in 0..num_qubits {
313            let phase = PI * i as f64 / num_qubits as f64;
314            circuit.push(phase);
315        }
316
317        Ok(circuit)
318    }
319
320    /// Decompose time series into components
321    pub fn decompose(
322        &mut self,
323        data: &Array2<f64>,
324    ) -> Result<(Array2<f64>, Option<Array1<f64>>, Option<Array1<f64>>)> {
325        // Check cache first
326        let cache_key = self.generate_cache_key(data);
327        if let Some(cached_result) = self.decomposition_cache.get(&cache_key) {
328            let cached_result = cached_result.clone();
329            return self.convert_cached_result(&cached_result);
330        }
331
332        // Perform full decomposition
333        let decomposition = self.full_decomposition(data)?;
334
335        // Cache the result
336        self.decomposition_cache
337            .insert(cache_key, decomposition.clone());
338
339        // Convert to expected format
340        let detrended = &decomposition.original - &decomposition.trend.clone().insert_axis(Axis(1));
341        let deseasonalized =
342            self.remove_seasonal_components(&detrended, &decomposition.seasonal)?;
343
344        Ok((
345            deseasonalized,
346            Some(decomposition.trend),
347            self.combine_seasonal_components(&decomposition.seasonal),
348        ))
349    }
350
351    /// Perform full decomposition analysis
352    fn full_decomposition(&mut self, data: &Array2<f64>) -> Result<DecompositionResult> {
353        // Extract trend component
354        let trend = self.trend_extractor.extract_trend(data)?;
355
356        // Remove trend
357        let detrended = data - &trend.clone().insert_axis(Axis(1));
358
359        // Extract seasonal components
360        let seasonal = self.extract_all_seasonal_components(&detrended)?;
361
362        // Calculate residual
363        let mut residual_data = detrended.clone();
364        for (_, seasonal_component) in &seasonal {
365            residual_data = &residual_data - &seasonal_component.clone().insert_axis(Axis(1));
366        }
367        let residual = residual_data.column(0).to_owned();
368
369        // Analyze residuals
370        self.residual_analyzer.analyze_residuals(&residual)?;
371
372        // Calculate quality metrics
373        let quality_metrics = self.calculate_quality_metrics(data, &trend, &seasonal, &residual)?;
374
375        Ok(DecompositionResult {
376            original: data.clone(),
377            trend,
378            seasonal,
379            residual,
380            quality_metrics,
381        })
382    }
383
384    /// Extract all configured seasonal components
385    fn extract_all_seasonal_components(
386        &self,
387        data: &Array2<f64>,
388    ) -> Result<HashMap<String, Array1<f64>>> {
389        let mut seasonal_components = HashMap::new();
390
391        for (component_name, circuit) in &self.seasonal_circuits {
392            let component = self.extract_seasonal_component(data, component_name, circuit)?;
393            seasonal_components.insert(component_name.clone(), component);
394        }
395
396        Ok(seasonal_components)
397    }
398
399    /// Extract specific seasonal component
400    fn extract_seasonal_component(
401        &self,
402        data: &Array2<f64>,
403        name: &str,
404        circuit: &[f64],
405    ) -> Result<Array1<f64>> {
406        let n_samples = data.nrows();
407        let mut seasonal_component = Array1::zeros(n_samples);
408
409        // Determine period from component name and config
410        let period = self.get_period_for_component(name);
411
412        if let Some(period) = period {
413            // Apply quantum-enhanced seasonal extraction
414            for t in 0..n_samples {
415                let mut seasonal_value = 0.0;
416
417                // Classical seasonal extraction
418                let phase = 2.0 * PI * (t % period) as f64 / period as f64;
419                seasonal_value += phase.sin();
420
421                // Quantum enhancement
422                if !circuit.is_empty() {
423                    let circuit_idx = t % circuit.len();
424                    let quantum_phase = circuit[circuit_idx] * phase;
425                    seasonal_value += 0.1 * quantum_phase.cos(); // Quantum correction
426                }
427
428                seasonal_component[t] = seasonal_value;
429            }
430
431            // Normalize component
432            self.normalize_seasonal_component(&mut seasonal_component);
433        }
434
435        Ok(seasonal_component)
436    }
437
438    /// Get period for component name
439    fn get_period_for_component(&self, name: &str) -> Option<usize> {
440        match name {
441            "daily" => self.config.daily,
442            "weekly" => self.config.weekly,
443            "monthly" => self.config.monthly,
444            "yearly" => self.config.yearly,
445            custom_name if custom_name.starts_with("custom_") => {
446                if let Ok(index) = custom_name[7..].parse::<usize>() {
447                    self.config.custom_periods.get(index).copied()
448                } else {
449                    None
450                }
451            }
452            _ => None,
453        }
454    }
455
456    /// Normalize seasonal component
457    fn normalize_seasonal_component(&self, component: &mut Array1<f64>) {
458        let mean = component.mean().unwrap_or(0.0);
459        for value in component.iter_mut() {
460            *value -= mean; // Center around zero
461        }
462    }
463
464    /// Remove seasonal components from data
465    fn remove_seasonal_components(
466        &self,
467        data: &Array2<f64>,
468        seasonal: &HashMap<String, Array1<f64>>,
469    ) -> Result<Array2<f64>> {
470        let mut result = data.clone();
471
472        for (_, component) in seasonal {
473            result = &result - &component.clone().insert_axis(Axis(1));
474        }
475
476        Ok(result)
477    }
478
479    /// Combine seasonal components
480    fn combine_seasonal_components(
481        &self,
482        seasonal: &HashMap<String, Array1<f64>>,
483    ) -> Option<Array1<f64>> {
484        if seasonal.is_empty() {
485            return None;
486        }
487
488        let first_component = seasonal.values().next()?;
489        let mut combined = Array1::zeros(first_component.len());
490
491        for component in seasonal.values() {
492            combined = combined + component;
493        }
494
495        Some(combined)
496    }
497
498    /// Calculate decomposition quality metrics
499    fn calculate_quality_metrics(
500        &self,
501        original: &Array2<f64>,
502        trend: &Array1<f64>,
503        seasonal: &HashMap<String, Array1<f64>>,
504        residual: &Array1<f64>,
505    ) -> Result<DecompositionQuality> {
506        // Calculate explained variance
507        let original_var = original.var(0.0);
508        let residual_var = residual.var(0.0);
509        let explained_variance = 1.0 - (residual_var / original_var.max(1e-10));
510
511        // Calculate reconstruction error
512        let mut reconstructed = trend.clone().insert_axis(Axis(1));
513        for component in seasonal.values() {
514            reconstructed = reconstructed + component.clone().insert_axis(Axis(1));
515        }
516        reconstructed = reconstructed + residual.clone().insert_axis(Axis(1));
517
518        let reconstruction_error = (original - &reconstructed)
519            .mapv(|x| x * x)
520            .mean()
521            .unwrap_or(0.0)
522            .sqrt();
523
524        // Calculate seasonal strengths
525        let mut seasonal_strength = HashMap::new();
526        for (name, component) in seasonal {
527            let strength = component.var(0.0) / original_var.max(1e-10);
528            seasonal_strength.insert(name.clone(), strength);
529        }
530
531        // Calculate trend strength
532        let trend_strength = trend.var(0.0) / original_var.max(1e-10);
533
534        // Simplified quantum effectiveness measure
535        let quantum_effectiveness = 0.8; // Placeholder
536
537        Ok(DecompositionQuality {
538            explained_variance,
539            reconstruction_error,
540            seasonal_strength,
541            trend_strength,
542            quantum_effectiveness,
543        })
544    }
545
546    /// Generate cache key for data
547    fn generate_cache_key(&self, data: &Array2<f64>) -> String {
548        // Simple hash-based key (in practice would use proper hashing)
549        format!("{}x{}_{:.3}", data.nrows(), data.ncols(), data.sum())
550    }
551
552    /// Convert cached result to expected format
553    fn convert_cached_result(
554        &self,
555        result: &DecompositionResult,
556    ) -> Result<(Array2<f64>, Option<Array1<f64>>, Option<Array1<f64>>)> {
557        let detrended = &result.original - &result.trend.clone().insert_axis(Axis(1));
558        let deseasonalized = self.remove_seasonal_components(&detrended, &result.seasonal)?;
559        let combined_seasonal = self.combine_seasonal_components(&result.seasonal);
560
561        Ok((
562            deseasonalized,
563            Some(result.trend.clone()),
564            combined_seasonal,
565        ))
566    }
567
568    /// Get decomposition quality metrics
569    pub fn get_quality_metrics(&self) -> Option<&DecompositionQuality> {
570        // Return metrics from last decomposition (simplified)
571        None
572    }
573
574    /// Get detected changepoints
575    pub fn get_changepoints(&self) -> Vec<&ChangePoint> {
576        if let Some(ref detector) = self.trend_extractor.changepoint_detector {
577            detector.changepoints.iter().collect()
578        } else {
579            Vec::new()
580        }
581    }
582
583    /// Get detected anomalies
584    pub fn get_anomalies(&self) -> &[AnomalyPoint] {
585        &self.residual_analyzer.anomalies
586    }
587}
588
589impl QuantumTrendExtractor {
590    /// Create new quantum trend extractor
591    pub fn new(smoothing_param: f64, num_qubits: usize) -> Result<Self> {
592        let mut trend_circuit = Vec::new();
593
594        // Smoothing gates
595        for i in 0..num_qubits {
596            trend_circuit.push(smoothing_param * (i + 1) as f64);
597        }
598
599        // Phase rotation parameters
600        for i in 0..num_qubits {
601            trend_circuit.push(PI * i as f64 / num_qubits as f64);
602        }
603
604        let changepoint_detector = Some(QuantumChangepointDetector::new(0.05, num_qubits)?);
605
606        let trend_parameters = TrendParameters {
607            linear_coeff: 0.0,
608            quadratic_coeff: 0.0,
609            exp_growth_rate: 0.0,
610            quantum_params: Array1::zeros(num_qubits),
611        };
612
613        Ok(Self {
614            smoothing_param,
615            trend_circuit,
616            changepoint_detector,
617            trend_parameters,
618        })
619    }
620
621    /// Extract trend from time series data
622    pub fn extract_trend(&mut self, data: &Array2<f64>) -> Result<Array1<f64>> {
623        let n_samples = data.nrows();
624        let mut trend = Array1::zeros(n_samples);
625
626        // Apply quantum-enhanced trend extraction
627        for t in 0..n_samples {
628            let mut trend_value = 0.0;
629
630            // Classical trend estimation (moving average)
631            let window_size = (n_samples / 10).max(3).min(21); // Adaptive window
632            let start = t.saturating_sub(window_size / 2);
633            let end = (t + window_size / 2 + 1).min(n_samples);
634
635            let window_sum: f64 = data.slice(s![start..end, 0]).sum();
636            trend_value = window_sum / (end - start) as f64;
637
638            // Quantum enhancement
639            if !self.trend_circuit.is_empty() {
640                let circuit_idx = t % self.trend_circuit.len();
641                let quantum_factor = self.trend_circuit[circuit_idx];
642                let quantum_phase = quantum_factor * trend_value * PI;
643                trend_value += 0.05 * quantum_phase.sin(); // Small quantum correction
644            }
645
646            trend[t] = trend_value;
647        }
648
649        // Detect changepoints if detector is available
650        if let Some(ref mut detector) = self.changepoint_detector {
651            detector.detect_changepoints(&trend)?;
652        }
653
654        // Fit trend parameters
655        self.fit_trend_parameters(&trend)?;
656
657        Ok(trend)
658    }
659
660    /// Fit parametric trend model
661    fn fit_trend_parameters(&mut self, trend: &Array1<f64>) -> Result<()> {
662        let n = trend.len();
663        if n < 3 {
664            return Ok(()); // Not enough data for fitting
665        }
666
667        // Simple linear trend fitting
668        let mut sum_t = 0.0;
669        let mut sum_y = 0.0;
670        let mut sum_ty = 0.0;
671        let mut sum_t2 = 0.0;
672
673        for (t, &y) in trend.iter().enumerate() {
674            let t_val = t as f64;
675            sum_t += t_val;
676            sum_y += y;
677            sum_ty += t_val * y;
678            sum_t2 += t_val * t_val;
679        }
680
681        let n_f = n as f64;
682        let denominator = n_f * sum_t2 - sum_t * sum_t;
683
684        if denominator.abs() > 1e-10 {
685            self.trend_parameters.linear_coeff = (n_f * sum_ty - sum_t * sum_y) / denominator;
686            // Intercept would be (sum_y - linear_coeff * sum_t) / n_f
687        }
688
689        Ok(())
690    }
691
692    /// Get trend parameters
693    pub fn get_trend_parameters(&self) -> &TrendParameters {
694        &self.trend_parameters
695    }
696}
697
698impl QuantumChangepointDetector {
699    /// Create new changepoint detector
700    pub fn new(threshold: f64, num_qubits: usize) -> Result<Self> {
701        let mut detection_circuit = Vec::new();
702
703        // Detection gates
704        for i in 0..num_qubits {
705            detection_circuit.push(1.0); // H gate marker
706            detection_circuit.push(PI * i as f64 / num_qubits as f64); // Phase
707        }
708
709        Ok(Self {
710            threshold,
711            detection_circuit,
712            changepoints: Vec::new(),
713            algorithm: ChangepointAlgorithm::QuantumDetection,
714        })
715    }
716
717    /// Detect changepoints in time series
718    pub fn detect_changepoints(&mut self, data: &Array1<f64>) -> Result<()> {
719        self.changepoints.clear();
720
721        match self.algorithm {
722            ChangepointAlgorithm::QuantumDetection => self.quantum_detection(data)?,
723            ChangepointAlgorithm::CUSUM => self.cusum_detection(data)?,
724            _ => {
725                // Default to quantum detection
726                self.quantum_detection(data)?
727            }
728        }
729
730        Ok(())
731    }
732
733    /// Quantum-enhanced changepoint detection
734    fn quantum_detection(&mut self, data: &Array1<f64>) -> Result<()> {
735        let n = data.len();
736        if n < 10 {
737            return Ok(()); // Not enough data
738        }
739
740        let window_size = n / 10;
741
742        for t in window_size..(n - window_size) {
743            // Calculate local statistics
744            let before_window = data.slice(s![t.saturating_sub(window_size)..t]);
745            let after_window = data.slice(s![t..t + window_size]);
746
747            let mean_before = before_window.mean().unwrap_or(0.0);
748            let mean_after = after_window.mean().unwrap_or(0.0);
749            let std_before = before_window.std(1.0);
750            let std_after = after_window.std(1.0);
751
752            // Classical change detection
753            let mean_change = (mean_after - mean_before).abs() / (std_before + std_after + 1e-10);
754            let var_change = (std_after - std_before).abs() / (std_before + 1e-10);
755
756            // Quantum enhancement
757            let quantum_factor = if !self.detection_circuit.is_empty() {
758                let circuit_idx = t % self.detection_circuit.len();
759                let phase = self.detection_circuit[circuit_idx] * mean_change;
760                1.0 + 0.1 * phase.sin()
761            } else {
762                1.0
763            };
764
765            let change_score = (mean_change + var_change) * quantum_factor;
766
767            if change_score > self.threshold {
768                let changepoint = ChangePoint {
769                    time_index: t,
770                    confidence: change_score.min(1.0),
771                    change_type: if mean_change > var_change {
772                        ChangeType::LevelShift
773                    } else {
774                        ChangeType::VarianceChange
775                    },
776                    magnitude: change_score,
777                };
778
779                self.changepoints.push(changepoint);
780            }
781        }
782
783        Ok(())
784    }
785
786    /// CUSUM-based changepoint detection
787    fn cusum_detection(&mut self, data: &Array1<f64>) -> Result<()> {
788        let n = data.len();
789        let mean = data.mean().unwrap_or(0.0);
790        let std = data.std(1.0);
791
792        let mut cusum_pos = 0.0;
793        let mut cusum_neg = 0.0;
794        let threshold = 4.0 * std; // CUSUM threshold
795
796        for (t, &value) in data.iter().enumerate() {
797            let deviation = value - mean;
798
799            cusum_pos = (cusum_pos + deviation).max(0.0);
800            cusum_neg = (cusum_neg - deviation).max(0.0);
801
802            if cusum_pos > threshold || cusum_neg > threshold {
803                let changepoint = ChangePoint {
804                    time_index: t,
805                    confidence: (cusum_pos.max(cusum_neg) / threshold).min(1.0),
806                    change_type: ChangeType::TrendChange,
807                    magnitude: cusum_pos.max(cusum_neg),
808                };
809
810                self.changepoints.push(changepoint);
811
812                // Reset CUSUM
813                cusum_pos = 0.0;
814                cusum_neg = 0.0;
815            }
816        }
817
818        Ok(())
819    }
820
821    /// Get detected changepoints
822    pub fn get_changepoints(&self) -> &[ChangePoint] {
823        &self.changepoints
824    }
825}
826
827impl QuantumResidualAnalyzer {
828    /// Create new residual analyzer
829    pub fn new(num_qubits: usize) -> Result<Self> {
830        let mut analysis_circuit = Vec::new();
831
832        // Analysis gates
833        for i in 0..num_qubits {
834            analysis_circuit.push(1.0); // H gate marker
835            analysis_circuit.push(PI * i as f64 / num_qubits as f64); // Phase
836        }
837
838        let residual_stats = ResidualStatistics {
839            mean: 0.0,
840            std: 1.0,
841            skewness: 0.0,
842            kurtosis: 3.0,
843            autocorrelation: Array1::zeros(10),
844            quantum_coherence: 0.0,
845        };
846
847        Ok(Self {
848            analysis_circuit,
849            anomaly_threshold: 2.0,
850            anomalies: Vec::new(),
851            residual_stats,
852        })
853    }
854
855    /// Analyze residuals for anomalies and patterns
856    pub fn analyze_residuals(&mut self, residuals: &Array1<f64>) -> Result<()> {
857        // Update residual statistics
858        self.update_residual_statistics(residuals)?;
859
860        // Detect anomalies
861        self.detect_anomalies(residuals)?;
862
863        Ok(())
864    }
865
866    /// Update comprehensive residual statistics
867    fn update_residual_statistics(&mut self, residuals: &Array1<f64>) -> Result<()> {
868        let n = residuals.len() as f64;
869
870        // Basic statistics
871        self.residual_stats.mean = residuals.mean().unwrap_or(0.0);
872        self.residual_stats.std = residuals.std(1.0);
873
874        // Higher order moments
875        let mean = self.residual_stats.mean;
876        let mut skewness_sum = 0.0;
877        let mut kurtosis_sum = 0.0;
878
879        for &value in residuals.iter() {
880            let deviation = value - mean;
881            skewness_sum += deviation.powi(3);
882            kurtosis_sum += deviation.powi(4);
883        }
884
885        let std_cubed = self.residual_stats.std.powi(3);
886        let std_fourth = self.residual_stats.std.powi(4);
887
888        if std_cubed > 1e-10 {
889            self.residual_stats.skewness = skewness_sum / (n * std_cubed);
890        }
891
892        if std_fourth > 1e-10 {
893            self.residual_stats.kurtosis = kurtosis_sum / (n * std_fourth);
894        }
895
896        // Autocorrelation function
897        self.calculate_autocorrelation(residuals)?;
898
899        // Quantum coherence measure
900        self.residual_stats.quantum_coherence = self.calculate_quantum_coherence(residuals)?;
901
902        Ok(())
903    }
904
905    /// Calculate autocorrelation function
906    fn calculate_autocorrelation(&mut self, residuals: &Array1<f64>) -> Result<()> {
907        let n = residuals.len();
908        let max_lag = 10.min(n / 4);
909        let mut autocorr = Array1::zeros(max_lag);
910
911        let mean = residuals.mean().unwrap_or(0.0);
912        let variance = residuals.var(1.0);
913
914        for lag in 0..max_lag {
915            let mut sum = 0.0;
916            let mut count = 0;
917
918            for t in lag..n {
919                sum += (residuals[t] - mean) * (residuals[t - lag] - mean);
920                count += 1;
921            }
922
923            if count > 0 && variance > 1e-10 {
924                autocorr[lag] = sum / (count as f64 * variance);
925            }
926        }
927
928        self.residual_stats.autocorrelation = autocorr;
929        Ok(())
930    }
931
932    /// Calculate quantum coherence measure
933    fn calculate_quantum_coherence(&self, residuals: &Array1<f64>) -> Result<f64> {
934        // Simplified quantum coherence based on entropy
935        let n = residuals.len();
936        let n_bins = 10;
937
938        let min_val = residuals.iter().fold(f64::INFINITY, |a, &b| a.min(b));
939        let max_val = residuals.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
940        let range = max_val - min_val;
941
942        if range < 1e-10 {
943            return Ok(0.0);
944        }
945
946        let mut bin_counts = vec![0; n_bins];
947        for &value in residuals.iter() {
948            let bin_idx = ((value - min_val) / range * (n_bins - 1) as f64) as usize;
949            let bin_idx = bin_idx.min(n_bins - 1);
950            bin_counts[bin_idx] += 1;
951        }
952
953        let mut entropy = 0.0;
954        for &count in &bin_counts {
955            if count > 0 {
956                let prob = count as f64 / n as f64;
957                entropy -= prob * prob.ln();
958            }
959        }
960
961        // Normalize and convert to coherence measure
962        let max_entropy = (n_bins as f64).ln();
963        let normalized_entropy = entropy / max_entropy;
964
965        Ok(1.0 - normalized_entropy) // High coherence = low entropy
966    }
967
968    /// Detect anomalies in residuals
969    fn detect_anomalies(&mut self, residuals: &Array1<f64>) -> Result<()> {
970        self.anomalies.clear();
971
972        let mean = self.residual_stats.mean;
973        let std = self.residual_stats.std;
974        let threshold = self.anomaly_threshold * std;
975
976        for (t, &value) in residuals.iter().enumerate() {
977            let deviation = (value - mean).abs();
978
979            if deviation > threshold {
980                let anomaly_score = deviation / std;
981
982                // Determine anomaly type based on context
983                let anomaly_type = if anomaly_score > 4.0 {
984                    AnomalyType::Point
985                } else if self.is_contextual_anomaly(t, residuals) {
986                    AnomalyType::Contextual
987                } else {
988                    AnomalyType::Point
989                };
990
991                let anomaly = AnomalyPoint {
992                    timestamp: t,
993                    value,
994                    anomaly_score,
995                    anomaly_type,
996                };
997
998                self.anomalies.push(anomaly);
999            }
1000        }
1001
1002        Ok(())
1003    }
1004
1005    /// Check if anomaly is contextual
1006    fn is_contextual_anomaly(&self, index: usize, residuals: &Array1<f64>) -> bool {
1007        let window_size = 5;
1008        let start = index.saturating_sub(window_size);
1009        let end = (index + window_size + 1).min(residuals.len());
1010
1011        if end - start < 3 {
1012            return false;
1013        }
1014
1015        let window = residuals.slice(s![start..end]);
1016        let local_mean = window.mean().unwrap_or(0.0);
1017        let local_std = window.std(1.0);
1018
1019        let global_mean = self.residual_stats.mean;
1020        let global_std = self.residual_stats.std;
1021
1022        // Check if local statistics differ significantly from global
1023        let mean_diff = (local_mean - global_mean).abs() / global_std.max(1e-10);
1024        let std_ratio = local_std / global_std.max(1e-10);
1025
1026        mean_diff > 1.0 || std_ratio > 2.0 || std_ratio < 0.5
1027    }
1028
1029    /// Get residual statistics
1030    pub fn get_statistics(&self) -> &ResidualStatistics {
1031        &self.residual_stats
1032    }
1033
1034    /// Get detected anomalies
1035    pub fn get_anomalies(&self) -> &[AnomalyPoint] {
1036        &self.anomalies
1037    }
1038}
1039
1040impl DecompositionCache {
1041    /// Create new decomposition cache
1042    pub fn new(max_size: usize) -> Self {
1043        Self {
1044            cache: HashMap::new(),
1045            hits: 0,
1046            misses: 0,
1047            max_size,
1048        }
1049    }
1050
1051    /// Get cached decomposition result
1052    pub fn get(&mut self, key: &str) -> Option<&DecompositionResult> {
1053        if let Some(result) = self.cache.get(key) {
1054            self.hits += 1;
1055            Some(result)
1056        } else {
1057            self.misses += 1;
1058            None
1059        }
1060    }
1061
1062    /// Insert decomposition result into cache
1063    pub fn insert(&mut self, key: String, result: DecompositionResult) {
1064        if self.cache.len() >= self.max_size {
1065            // Simple eviction: remove a random entry
1066            if let Some(random_key) = self.cache.keys().next().cloned() {
1067                self.cache.remove(&random_key);
1068            }
1069        }
1070
1071        self.cache.insert(key, result);
1072    }
1073
1074    /// Get cache statistics
1075    pub fn get_stats(&self) -> (usize, usize, f64) {
1076        let total = self.hits + self.misses;
1077        let hit_rate = if total > 0 {
1078            self.hits as f64 / total as f64
1079        } else {
1080            0.0
1081        };
1082        (self.hits, self.misses, hit_rate)
1083    }
1084}