scirs2_fft/
planning_adaptive.rs1use crate::error::FFTResult;
8use crate::planning::{FftPlan, PlannerBackend, PlanningStrategy};
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, Instant};
11
12#[derive(Debug, Clone)]
14pub struct AdaptivePlanningConfig {
15 pub enabled: bool,
17
18 pub min_samples: usize,
20
21 pub evaluation_interval: Duration,
23
24 pub max_strategy_switches: usize,
26
27 pub enable_backend_switching: bool,
29
30 pub improvement_threshold: f64,
32}
33
34impl Default for AdaptivePlanningConfig {
35 fn default() -> Self {
36 Self {
37 enabled: true,
38 min_samples: 5,
39 evaluation_interval: Duration::from_secs(10),
40 max_strategy_switches: 3,
41 enable_backend_switching: true,
42 improvement_threshold: 1.1, }
44 }
45}
46
47#[derive(Debug, Clone)]
49struct StrategyMetrics {
50 total_time: Duration,
52
53 count: usize,
55
56 avg_time: Duration,
58
59 #[allow(dead_code)]
62 last_evaluated: Instant,
63}
64
65impl StrategyMetrics {
66 fn new() -> Self {
68 Self {
69 total_time: Duration::from_nanos(0),
70 count: 0,
71 avg_time: Duration::from_nanos(0),
72 last_evaluated: Instant::now(),
73 }
74 }
75
76 fn record(&mut self, time: Duration) {
78 self.total_time += time;
79 self.count += 1;
80 self.avg_time =
81 Duration::from_nanos((self.total_time.as_nanos() / self.count as u128) as u64);
82 }
83}
84
85pub struct AdaptivePlanner {
87 size: Vec<usize>,
89
90 forward: bool,
92
93 current_strategy: PlanningStrategy,
95
96 current_backend: PlannerBackend,
98
99 metrics: HashMap<PlanningStrategy, StrategyMetrics>,
101
102 last_strategy_switch: Instant,
104
105 strategy_switches: usize,
107
108 config: AdaptivePlanningConfig,
110
111 current_plan: Option<Arc<FftPlan>>,
113}
114
115use std::collections::HashMap;
116
117impl AdaptivePlanner {
118 pub fn new(size: &[usize], forward: bool, config: Option<AdaptivePlanningConfig>) -> Self {
120 let config = config.unwrap_or_default();
121 let mut metrics = HashMap::new();
122
123 metrics.insert(PlanningStrategy::AlwaysNew, StrategyMetrics::new());
125 metrics.insert(PlanningStrategy::CacheFirst, StrategyMetrics::new());
126 metrics.insert(PlanningStrategy::SerializedFirst, StrategyMetrics::new());
127 metrics.insert(PlanningStrategy::AutoTuned, StrategyMetrics::new());
128
129 Self {
130 size: size.to_vec(),
131 forward,
132 current_strategy: PlanningStrategy::CacheFirst, current_backend: PlannerBackend::default(),
134 metrics,
135 last_strategy_switch: Instant::now(),
136 strategy_switches: 0,
137 config,
138 current_plan: None,
139 }
140 }
141
142 pub fn current_strategy(&self) -> PlanningStrategy {
144 self.current_strategy
145 }
146
147 pub fn current_backend(&self) -> PlannerBackend {
149 self.current_backend.clone()
150 }
151
152 pub fn get_plan(&mut self) -> FFTResult<Arc<FftPlan>> {
154 if let Some(plan) = &self.current_plan {
156 return Ok(plan.clone());
157 }
158
159 use crate::planning::{AdvancedFftPlanner, PlanningConfig};
161
162 let config = PlanningConfig {
163 strategy: self.current_strategy,
164 ..Default::default()
165 };
166
167 let mut planner = AdvancedFftPlanner::with_config(config);
168 let plan = planner.plan_fft(&self.size, self.forward, self.current_backend.clone())?;
169
170 self.current_plan = Some(plan.clone());
171 Ok(plan)
172 }
173
174 pub fn record_execution(&mut self, executiontime: Duration) -> FFTResult<()> {
176 if !self.config.enabled {
177 return Ok(());
178 }
179
180 if let Some(metrics) = self.metrics.get_mut(&self.current_strategy) {
182 metrics.record(executiontime);
183 }
184
185 let should_evaluate =
187 self.metrics[&self.current_strategy].count >= self.config.min_samples &&
189 self.last_strategy_switch.elapsed() >= self.config.evaluation_interval &&
191 self.strategy_switches < self.config.max_strategy_switches;
193
194 if should_evaluate {
195 self.evaluate_strategies()?;
196 }
197
198 Ok(())
199 }
200
201 fn evaluate_strategies(&mut self) -> FFTResult<()> {
203 let mut best_strategy = self.current_strategy;
205 let mut best_time = self.metrics[&self.current_strategy].avg_time;
206
207 for (strategy, metrics) in &self.metrics {
208 if metrics.count == 0 {
210 continue;
211 }
212
213 let improvement_ratio =
215 best_time.as_nanos() as f64 / metrics.avg_time.as_nanos() as f64;
216 if improvement_ratio > self.config.improvement_threshold {
217 best_strategy = *strategy;
218 best_time = metrics.avg_time;
219 }
220 }
221
222 if best_strategy != self.current_strategy {
224 self.current_strategy = best_strategy;
225 self.last_strategy_switch = Instant::now();
226 self.strategy_switches += 1;
227
228 self.current_plan = None;
230 }
231
232 if self.config.enable_backend_switching {
234 }
237
238 Ok(())
239 }
240
241 pub fn get_statistics(&self) -> HashMap<PlanningStrategy, (Duration, usize)> {
243 let mut stats = HashMap::new();
244
245 for (strategy, metrics) in &self.metrics {
246 stats.insert(*strategy, (metrics.avg_time, metrics.count));
247 }
248
249 stats
250 }
251}
252
253pub struct AdaptiveExecutor {
255 planner: Arc<Mutex<AdaptivePlanner>>,
257}
258
259impl AdaptiveExecutor {
260 pub fn new(size: &[usize], forward: bool, config: Option<AdaptivePlanningConfig>) -> Self {
262 let planner = AdaptivePlanner::new(size, forward, config);
263
264 Self {
265 planner: Arc::new(Mutex::new(planner)),
266 }
267 }
268
269 pub fn execute(
271 &self,
272 input: &[scirs2_core::numeric::Complex64],
273 output: &mut [scirs2_core::numeric::Complex64],
274 ) -> FFTResult<()> {
275 let start = Instant::now();
276
277 let plan = {
279 let mut planner = self.planner.lock().unwrap();
280 planner.get_plan()?
281 };
282
283 let executor = crate::planning::FftPlanExecutor::new(plan);
285
286 executor.execute(input, output)?;
288
289 let execution_time = start.elapsed();
291
292 {
293 let mut planner = self.planner.lock().unwrap();
294 planner.record_execution(execution_time)?;
295 }
296
297 Ok(())
298 }
299
300 pub fn current_strategy(&self) -> PlanningStrategy {
302 let planner = self.planner.lock().unwrap();
303 planner.current_strategy()
304 }
305
306 pub fn get_statistics(&self) -> HashMap<PlanningStrategy, (Duration, usize)> {
308 let planner = self.planner.lock().unwrap();
309 planner.get_statistics()
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use scirs2_core::numeric::Complex64;
317
318 #[test]
319 fn test_adaptive_planner_basics() {
320 let mut planner = AdaptivePlanner::new(&[16], true, None);
321
322 assert_eq!(planner.current_strategy(), PlanningStrategy::CacheFirst);
324
325 for _ in 0..10 {
327 planner
328 .record_execution(Duration::from_micros(100))
329 .unwrap();
330 }
331
332 let stats = planner.get_statistics();
334 assert_eq!(stats[&PlanningStrategy::CacheFirst].1, 10);
335 }
336
337 #[test]
338 fn test_adaptive_executor() {
339 let executor = AdaptiveExecutor::new(&[16], true, None);
340
341 let input = vec![Complex64::new(1.0, 0.0); 16];
343 let mut output = vec![Complex64::default(); 16];
344
345 for _ in 0..5 {
347 executor.execute(&input, &mut output).unwrap();
348 }
349
350 let stats = executor.get_statistics();
352 assert!(stats[&executor.current_strategy()].1 >= 5);
353 }
354}