ruvector_memopt/algorithms/
spectral.rs

1//! Spectral analysis for memory pattern classification
2//!
3//! Uses eigenvalue-based methods to classify memory usage patterns
4//! and identify anomalies.
5
6use std::collections::VecDeque;
7
8/// Spectral analyzer for memory pattern classification
9pub struct SpectralAnalyzer {
10    /// Rolling window of memory samples
11    samples: VecDeque<f64>,
12    /// Window size
13    window_size: usize,
14    /// FFT-like frequency bins (simplified)
15    frequency_bins: Vec<f64>,
16    /// Number of frequency bins
17    num_bins: usize,
18}
19
20impl SpectralAnalyzer {
21    pub fn new(window_size: usize) -> Self {
22        let num_bins = 8; // Simplified frequency analysis
23        Self {
24            samples: VecDeque::with_capacity(window_size),
25            window_size,
26            frequency_bins: vec![0.0; num_bins],
27            num_bins,
28        }
29    }
30
31    /// Add a memory usage sample (0.0 to 1.0)
32    pub fn add_sample(&mut self, usage: f64) {
33        if self.samples.len() >= self.window_size {
34            self.samples.pop_front();
35        }
36        self.samples.push_back(usage.clamp(0.0, 1.0));
37
38        if self.samples.len() >= self.window_size / 2 {
39            self.update_spectrum();
40        }
41    }
42
43    /// Update frequency spectrum using simplified DFT
44    fn update_spectrum(&mut self) {
45        let n = self.samples.len();
46        if n < 2 {
47            return;
48        }
49
50        let samples: Vec<f64> = self.samples.iter().copied().collect();
51
52        // Simplified frequency analysis (not full FFT, but captures patterns)
53        for k in 0..self.num_bins {
54            let mut real = 0.0;
55            let mut imag = 0.0;
56
57            for (i, &sample) in samples.iter().enumerate() {
58                let angle = 2.0 * std::f64::consts::PI * (k as f64) * (i as f64) / (n as f64);
59                real += sample * angle.cos();
60                imag += sample * angle.sin();
61            }
62
63            self.frequency_bins[k] = (real * real + imag * imag).sqrt() / (n as f64);
64        }
65    }
66
67    /// Classify the current memory pattern
68    pub fn classify(&self) -> MemoryPatternClass {
69        if self.samples.len() < self.window_size / 4 {
70            return MemoryPatternClass::Unknown;
71        }
72
73        let mean = self.mean();
74        let variance = self.variance();
75        let trend = self.trend();
76        let dominant_freq = self.dominant_frequency();
77
78        // Classification rules
79        if variance < 0.01 {
80            if mean > 0.8 {
81                MemoryPatternClass::ConstantHigh
82            } else if mean < 0.4 {
83                MemoryPatternClass::ConstantLow
84            } else {
85                MemoryPatternClass::Stable
86            }
87        } else if trend > 0.1 {
88            MemoryPatternClass::Increasing
89        } else if trend < -0.1 {
90            MemoryPatternClass::Decreasing
91        } else if dominant_freq > 0 && self.frequency_bins[dominant_freq] > 0.1 {
92            MemoryPatternClass::Oscillating
93        } else if variance > 0.1 {
94            MemoryPatternClass::Volatile
95        } else {
96            MemoryPatternClass::Normal
97        }
98    }
99
100    /// Calculate mean of samples
101    fn mean(&self) -> f64 {
102        if self.samples.is_empty() {
103            return 0.0;
104        }
105        self.samples.iter().sum::<f64>() / self.samples.len() as f64
106    }
107
108    /// Calculate variance of samples
109    fn variance(&self) -> f64 {
110        if self.samples.len() < 2 {
111            return 0.0;
112        }
113        let mean = self.mean();
114        self.samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / self.samples.len() as f64
115    }
116
117    /// Calculate linear trend (slope)
118    fn trend(&self) -> f64 {
119        let n = self.samples.len();
120        if n < 2 {
121            return 0.0;
122        }
123
124        let mut sum_x = 0.0;
125        let mut sum_y = 0.0;
126        let mut sum_xy = 0.0;
127        let mut sum_xx = 0.0;
128
129        for (i, &y) in self.samples.iter().enumerate() {
130            let x = i as f64;
131            sum_x += x;
132            sum_y += y;
133            sum_xy += x * y;
134            sum_xx += x * x;
135        }
136
137        let n = n as f64;
138        let denom = n * sum_xx - sum_x * sum_x;
139        if denom.abs() < 1e-10 {
140            return 0.0;
141        }
142
143        (n * sum_xy - sum_x * sum_y) / denom
144    }
145
146    /// Find dominant frequency bin
147    fn dominant_frequency(&self) -> usize {
148        self.frequency_bins
149            .iter()
150            .enumerate()
151            .skip(1) // Skip DC component
152            .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
153            .map(|(i, _)| i)
154            .unwrap_or(0)
155    }
156
157    /// Get recommendation based on pattern
158    pub fn get_recommendation(&self) -> SpectralRecommendation {
159        let class = self.classify();
160        let mean = self.mean();
161        let trend = self.trend();
162
163        match class {
164            MemoryPatternClass::ConstantHigh => SpectralRecommendation {
165                action: RecommendedAction::OptimizeNow,
166                confidence: 0.95,
167                reason: "Consistently high memory usage".into(),
168                predicted_relief_mb: (mean * 1000.0) as u64,
169            },
170            MemoryPatternClass::Increasing => SpectralRecommendation {
171                action: RecommendedAction::OptimizeSoon,
172                confidence: 0.85,
173                reason: format!("Memory usage trending up (slope: {:.3})", trend),
174                predicted_relief_mb: ((mean + trend * 10.0) * 500.0) as u64,
175            },
176            MemoryPatternClass::Volatile => SpectralRecommendation {
177                action: RecommendedAction::Monitor,
178                confidence: 0.7,
179                reason: "Volatile memory usage pattern".into(),
180                predicted_relief_mb: (mean * 300.0) as u64,
181            },
182            MemoryPatternClass::Oscillating => SpectralRecommendation {
183                action: RecommendedAction::ScheduleOptimization,
184                confidence: 0.75,
185                reason: "Cyclic memory pattern detected".into(),
186                predicted_relief_mb: (mean * 400.0) as u64,
187            },
188            MemoryPatternClass::Decreasing => SpectralRecommendation {
189                action: RecommendedAction::Wait,
190                confidence: 0.8,
191                reason: "Memory usage decreasing naturally".into(),
192                predicted_relief_mb: 0,
193            },
194            MemoryPatternClass::ConstantLow | MemoryPatternClass::Stable => SpectralRecommendation {
195                action: RecommendedAction::NoAction,
196                confidence: 0.9,
197                reason: "Memory usage is healthy".into(),
198                predicted_relief_mb: 0,
199            },
200            _ => SpectralRecommendation {
201                action: RecommendedAction::Monitor,
202                confidence: 0.5,
203                reason: "Insufficient data".into(),
204                predicted_relief_mb: 0,
205            },
206        }
207    }
208
209    /// Statistics for benchmarking
210    pub fn stats(&self) -> SpectralStats {
211        SpectralStats {
212            sample_count: self.samples.len(),
213            mean: self.mean(),
214            variance: self.variance(),
215            trend: self.trend(),
216            dominant_frequency: self.dominant_frequency(),
217            classification: self.classify(),
218        }
219    }
220}
221
222impl Default for SpectralAnalyzer {
223    fn default() -> Self {
224        Self::new(60) // 1 minute window at 1 sample/sec
225    }
226}
227
228/// Memory usage pattern classification
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum MemoryPatternClass {
231    Unknown,
232    Stable,
233    ConstantLow,
234    ConstantHigh,
235    Increasing,
236    Decreasing,
237    Oscillating,
238    Volatile,
239    Normal,
240}
241
242/// Recommended action based on spectral analysis
243#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum RecommendedAction {
245    NoAction,
246    Monitor,
247    Wait,
248    ScheduleOptimization,
249    OptimizeSoon,
250    OptimizeNow,
251}
252
253/// Spectral analysis recommendation
254#[derive(Debug, Clone)]
255pub struct SpectralRecommendation {
256    pub action: RecommendedAction,
257    pub confidence: f64,
258    pub reason: String,
259    pub predicted_relief_mb: u64,
260}
261
262#[derive(Debug, Clone)]
263pub struct SpectralStats {
264    pub sample_count: usize,
265    pub mean: f64,
266    pub variance: f64,
267    pub trend: f64,
268    pub dominant_frequency: usize,
269    pub classification: MemoryPatternClass,
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_spectral_stable() {
278        let mut analyzer = SpectralAnalyzer::new(20);
279
280        // Add stable samples around 0.5
281        for _ in 0..20 {
282            analyzer.add_sample(0.5);
283        }
284
285        let class = analyzer.classify();
286        assert!(matches!(class, MemoryPatternClass::Stable | MemoryPatternClass::Normal));
287    }
288
289    #[test]
290    fn test_spectral_increasing() {
291        let mut analyzer = SpectralAnalyzer::new(20);
292
293        // Add strongly increasing samples
294        for i in 0..20 {
295            analyzer.add_sample(0.2 + (i as f64) * 0.04);
296        }
297
298        let class = analyzer.classify();
299        // Should be increasing or normal with positive trend
300        let stats = analyzer.stats();
301        assert!(stats.trend > 0.0, "Trend should be positive: {}", stats.trend);
302    }
303
304    #[test]
305    fn test_recommendation() {
306        let mut analyzer = SpectralAnalyzer::new(20);
307
308        // High constant usage
309        for _ in 0..20 {
310            analyzer.add_sample(0.9);
311        }
312
313        let rec = analyzer.get_recommendation();
314        assert_eq!(rec.action, RecommendedAction::OptimizeNow);
315        assert!(rec.confidence > 0.9);
316    }
317}