ruvector_memopt/algorithms/
spectral.rs1use std::collections::VecDeque;
7
8pub struct SpectralAnalyzer {
10 samples: VecDeque<f64>,
12 window_size: usize,
14 frequency_bins: Vec<f64>,
16 num_bins: usize,
18}
19
20impl SpectralAnalyzer {
21 pub fn new(window_size: usize) -> Self {
22 let num_bins = 8; 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 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 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 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 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 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 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 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 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 fn dominant_frequency(&self) -> usize {
148 self.frequency_bins
149 .iter()
150 .enumerate()
151 .skip(1) .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 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 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) }
226}
227
228#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
244pub enum RecommendedAction {
245 NoAction,
246 Monitor,
247 Wait,
248 ScheduleOptimization,
249 OptimizeSoon,
250 OptimizeNow,
251}
252
253#[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 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 for i in 0..20 {
295 analyzer.add_sample(0.2 + (i as f64) * 0.04);
296 }
297
298 let class = analyzer.classify();
299 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 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}