Skip to main content

scirs2_fft/
oxifft_plan_cache.rs

1//! OxiFFT Plan Cache for performance optimization
2//!
3//! Caches OxiFFT plans to avoid expensive plan creation on repeated FFT calls.
4//! Plans are stored in global caches keyed by size (1D) or dimensions (2D).
5//!
6//! This replaces the FFTW plan cache while maintaining Pure Rust Policy compliance.
7
8// Allow complex type definitions for cache types - intentional for performance
9#![allow(clippy::type_complexity)]
10
11use oxifft::{Complex, Direction, Flags, Plan, Plan2D, R2rPlan, RealPlan, RealPlan2D};
12// Use the internal R2rKind which has FFTW-compatible naming (Redft10, Redft01, etc.)
13use oxifft::rdft::solvers::R2rKind;
14use std::collections::HashMap;
15use std::sync::Mutex;
16
17use crate::error::{FFTError, FFTResult};
18
19// ========================================
20// 1D PLAN CACHES
21// ========================================
22
23/// Global cache for R2C (real-to-complex) plans, keyed by input size
24static R2C_CACHE: Mutex<Option<HashMap<usize, RealPlan<f64>>>> = Mutex::new(None);
25
26/// Global cache for C2C forward plans, keyed by size
27static C2C_FWD_CACHE: Mutex<Option<HashMap<usize, Plan<f64>>>> = Mutex::new(None);
28
29/// Global cache for C2C backward plans, keyed by size
30static C2C_BWD_CACHE: Mutex<Option<HashMap<usize, Plan<f64>>>> = Mutex::new(None);
31
32/// Global cache for C2R (complex-to-real) plans, keyed by output size
33static C2R_CACHE: Mutex<Option<HashMap<usize, RealPlan<f64>>>> = Mutex::new(None);
34
35/// Global cache for R2R DCT-II plans
36static DCT2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
37
38/// Global cache for R2R IDCT-II (DCT-III) plans
39static IDCT2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
40
41/// Global cache for R2R DST-II plans
42static DST2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
43
44/// Global cache for R2R IDST-II (DST-III) plans
45static IDST2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
46
47// ========================================
48// 2D PLAN CACHES
49// ========================================
50
51/// Global cache for 2D R2C plans, keyed by (rows, cols)
52static R2C_2D_CACHE: Mutex<Option<HashMap<(usize, usize), RealPlan2D<f64>>>> = Mutex::new(None);
53
54/// Global cache for 2D C2C forward plans
55static C2C_2D_FWD_CACHE: Mutex<Option<HashMap<(usize, usize), Plan2D<f64>>>> = Mutex::new(None);
56
57/// Global cache for 2D C2C backward plans
58static C2C_2D_BWD_CACHE: Mutex<Option<HashMap<(usize, usize), Plan2D<f64>>>> = Mutex::new(None);
59
60/// Global cache for 2D C2R plans
61static C2R_2D_CACHE: Mutex<Option<HashMap<(usize, usize), RealPlan2D<f64>>>> = Mutex::new(None);
62
63// ========================================
64// 1D PLAN EXECUTION FUNCTIONS
65// ========================================
66
67/// Execute R2C FFT with cached plan
68pub fn execute_r2c(input: &[f64], output: &mut [Complex<f64>]) -> FFTResult<()> {
69    let n = input.len();
70
71    let mut cache = R2C_CACHE
72        .lock()
73        .map_err(|e| FFTError::ComputationError(format!("Failed to lock R2C cache: {}", e)))?;
74
75    // Initialize cache if needed
76    if cache.is_none() {
77        *cache = Some(HashMap::new());
78    }
79
80    let cache_map = cache
81        .as_mut()
82        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
83
84    // Create plan if not cached
85    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
86        let plan = RealPlan::r2c_1d(n, Flags::ESTIMATE).ok_or_else(|| {
87            FFTError::ComputationError(format!("Failed to create R2C plan for size {}", n))
88        })?;
89        e.insert(plan);
90    }
91
92    // Execute with cached plan
93    let plan = cache_map
94        .get(&n)
95        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
96    plan.execute_r2c(input, output);
97    Ok(())
98}
99
100/// Execute C2C FFT with cached plan
101pub fn execute_c2c(
102    input: &[Complex<f64>],
103    output: &mut [Complex<f64>],
104    direction: Direction,
105) -> FFTResult<()> {
106    let n = input.len();
107
108    let cache = match direction {
109        Direction::Forward => &C2C_FWD_CACHE,
110        Direction::Backward => &C2C_BWD_CACHE,
111        _ => {
112            return Err(FFTError::ComputationError(format!(
113                "Unsupported FFT direction: {:?}",
114                direction
115            )))
116        }
117    };
118
119    let mut cache_guard = cache.lock().map_err(|e| {
120        FFTError::ComputationError(format!("Failed to lock C2C {:?} cache: {}", direction, e))
121    })?;
122
123    if cache_guard.is_none() {
124        *cache_guard = Some(HashMap::new());
125    }
126
127    let cache_map = cache_guard
128        .as_mut()
129        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
130
131    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
132        let plan = Plan::dft_1d(n, direction, Flags::ESTIMATE).ok_or_else(|| {
133            FFTError::ComputationError(format!(
134                "Failed to create C2C {:?} plan for size {}",
135                direction, n
136            ))
137        })?;
138        e.insert(plan);
139    }
140
141    let plan = cache_map
142        .get(&n)
143        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
144    plan.execute(input, output);
145    Ok(())
146}
147
148/// Execute C2R (inverse real) FFT with cached plan
149pub fn execute_c2r(input: &[Complex<f64>], output: &mut [f64], n: usize) -> FFTResult<()> {
150    let mut cache = C2R_CACHE
151        .lock()
152        .map_err(|e| FFTError::ComputationError(format!("Failed to lock C2R cache: {}", e)))?;
153
154    if cache.is_none() {
155        *cache = Some(HashMap::new());
156    }
157
158    let cache_map = cache
159        .as_mut()
160        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
161
162    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
163        let plan = RealPlan::c2r_1d(n, Flags::ESTIMATE).ok_or_else(|| {
164            FFTError::ComputationError(format!("Failed to create C2R plan for size {}", n))
165        })?;
166        e.insert(plan);
167    }
168
169    let plan = cache_map
170        .get(&n)
171        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
172    plan.execute_c2r_unnormalized(input, output);
173    Ok(())
174}
175
176/// Execute DCT-II with cached plan
177pub fn execute_dct2(input: &[f64], output: &mut [f64]) -> FFTResult<()> {
178    let n = input.len();
179
180    let mut cache = DCT2_CACHE
181        .lock()
182        .map_err(|e| FFTError::ComputationError(format!("Failed to lock DCT2 cache: {}", e)))?;
183
184    if cache.is_none() {
185        *cache = Some(HashMap::new());
186    }
187
188    let cache_map = cache
189        .as_mut()
190        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
191
192    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
193        let plan = R2rPlan::r2r_1d(n, R2rKind::Redft10, Flags::ESTIMATE).ok_or_else(|| {
194            FFTError::ComputationError(format!("Failed to create DCT2 plan for size {}", n))
195        })?;
196        e.insert(plan);
197    }
198
199    let plan = cache_map
200        .get(&n)
201        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
202    plan.execute(input, output);
203    Ok(())
204}
205
206/// Execute IDCT-II (DCT-III) with cached plan
207pub fn execute_idct2(input: &[f64], output: &mut [f64]) -> FFTResult<()> {
208    let n = input.len();
209
210    let mut cache = IDCT2_CACHE
211        .lock()
212        .map_err(|e| FFTError::ComputationError(format!("Failed to lock IDCT2 cache: {}", e)))?;
213
214    if cache.is_none() {
215        *cache = Some(HashMap::new());
216    }
217
218    let cache_map = cache
219        .as_mut()
220        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
221
222    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
223        let plan = R2rPlan::r2r_1d(n, R2rKind::Redft01, Flags::ESTIMATE).ok_or_else(|| {
224            FFTError::ComputationError(format!("Failed to create IDCT2 plan for size {}", n))
225        })?;
226        e.insert(plan);
227    }
228
229    let plan = cache_map
230        .get(&n)
231        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
232    plan.execute(input, output);
233    Ok(())
234}
235
236/// Execute DST-II with cached plan
237pub fn execute_dst2(input: &[f64], output: &mut [f64]) -> FFTResult<()> {
238    let n = input.len();
239
240    let mut cache = DST2_CACHE
241        .lock()
242        .map_err(|e| FFTError::ComputationError(format!("Failed to lock DST2 cache: {}", e)))?;
243
244    if cache.is_none() {
245        *cache = Some(HashMap::new());
246    }
247
248    let cache_map = cache
249        .as_mut()
250        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
251
252    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
253        let plan = R2rPlan::r2r_1d(n, R2rKind::Rodft10, Flags::ESTIMATE).ok_or_else(|| {
254            FFTError::ComputationError(format!("Failed to create DST2 plan for size {}", n))
255        })?;
256        e.insert(plan);
257    }
258
259    let plan = cache_map
260        .get(&n)
261        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
262    plan.execute(input, output);
263    Ok(())
264}
265
266/// Execute IDST-II (DST-III) with cached plan
267pub fn execute_idst2(input: &[f64], output: &mut [f64]) -> FFTResult<()> {
268    let n = input.len();
269
270    let mut cache = IDST2_CACHE
271        .lock()
272        .map_err(|e| FFTError::ComputationError(format!("Failed to lock IDST2 cache: {}", e)))?;
273
274    if cache.is_none() {
275        *cache = Some(HashMap::new());
276    }
277
278    let cache_map = cache
279        .as_mut()
280        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
281
282    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(n) {
283        let plan = R2rPlan::r2r_1d(n, R2rKind::Rodft01, Flags::ESTIMATE).ok_or_else(|| {
284            FFTError::ComputationError(format!("Failed to create IDST2 plan for size {}", n))
285        })?;
286        e.insert(plan);
287    }
288
289    let plan = cache_map
290        .get(&n)
291        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
292    plan.execute(input, output);
293    Ok(())
294}
295
296// ========================================
297// 2D PLAN EXECUTION FUNCTIONS
298// ========================================
299
300/// Execute 2D R2C FFT with cached plan
301pub fn execute_r2c_2d(
302    input: &[f64],
303    output: &mut [Complex<f64>],
304    rows: usize,
305    cols: usize,
306) -> FFTResult<()> {
307    let key = (rows, cols);
308
309    let mut cache = R2C_2D_CACHE
310        .lock()
311        .map_err(|e| FFTError::ComputationError(format!("Failed to lock 2D R2C cache: {}", e)))?;
312
313    if cache.is_none() {
314        *cache = Some(HashMap::new());
315    }
316
317    let cache_map = cache
318        .as_mut()
319        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
320
321    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(key) {
322        let plan = RealPlan2D::r2c(rows, cols, Flags::ESTIMATE).ok_or_else(|| {
323            FFTError::ComputationError(format!(
324                "Failed to create 2D R2C plan for size {}x{}",
325                rows, cols
326            ))
327        })?;
328        e.insert(plan);
329    }
330
331    let plan = cache_map
332        .get(&key)
333        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
334    plan.execute_r2c(input, output);
335    Ok(())
336}
337
338/// Execute 2D C2C FFT with cached plan
339pub fn execute_c2c_2d(
340    input: &[Complex<f64>],
341    output: &mut [Complex<f64>],
342    rows: usize,
343    cols: usize,
344    direction: Direction,
345) -> FFTResult<()> {
346    let key = (rows, cols);
347
348    let cache = match direction {
349        Direction::Forward => &C2C_2D_FWD_CACHE,
350        Direction::Backward => &C2C_2D_BWD_CACHE,
351        _ => {
352            return Err(FFTError::ComputationError(format!(
353                "Unsupported FFT direction: {:?}",
354                direction
355            )))
356        }
357    };
358
359    let mut cache_guard = cache.lock().map_err(|e| {
360        FFTError::ComputationError(format!(
361            "Failed to lock 2D C2C {:?} cache: {}",
362            direction, e
363        ))
364    })?;
365
366    if cache_guard.is_none() {
367        *cache_guard = Some(HashMap::new());
368    }
369
370    let cache_map = cache_guard
371        .as_mut()
372        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
373
374    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(key) {
375        let plan = Plan2D::new(rows, cols, direction, Flags::ESTIMATE).ok_or_else(|| {
376            FFTError::ComputationError(format!(
377                "Failed to create 2D C2C {:?} plan for size {}x{}",
378                direction, rows, cols
379            ))
380        })?;
381        e.insert(plan);
382    }
383
384    let plan = cache_map
385        .get(&key)
386        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
387    plan.execute(input, output);
388    Ok(())
389}
390
391/// Execute 2D C2R FFT with cached plan
392pub fn execute_c2r_2d(
393    input: &[Complex<f64>],
394    output: &mut [f64],
395    rows: usize,
396    cols: usize,
397) -> FFTResult<()> {
398    let key = (rows, cols);
399
400    let mut cache = C2R_2D_CACHE
401        .lock()
402        .map_err(|e| FFTError::ComputationError(format!("Failed to lock 2D C2R cache: {}", e)))?;
403
404    if cache.is_none() {
405        *cache = Some(HashMap::new());
406    }
407
408    let cache_map = cache
409        .as_mut()
410        .ok_or_else(|| FFTError::ComputationError("Cache initialization failed".to_string()))?;
411
412    if let std::collections::hash_map::Entry::Vacant(e) = cache_map.entry(key) {
413        let plan = RealPlan2D::c2r(rows, cols, Flags::ESTIMATE).ok_or_else(|| {
414            FFTError::ComputationError(format!(
415                "Failed to create 2D C2R plan for size {}x{}",
416                rows, cols
417            ))
418        })?;
419        e.insert(plan);
420    }
421
422    let plan = cache_map
423        .get(&key)
424        .ok_or_else(|| FFTError::ComputationError("Failed to get cached plan".to_string()))?;
425    plan.execute_c2r(input, output);
426    Ok(())
427}
428
429// ========================================
430// CACHE MANAGEMENT
431// ========================================
432
433/// Clear all cached plans (useful for testing or memory management)
434pub fn clear_all_caches() {
435    if let Ok(mut cache) = R2C_CACHE.lock() {
436        *cache = None;
437    }
438    if let Ok(mut cache) = C2C_FWD_CACHE.lock() {
439        *cache = None;
440    }
441    if let Ok(mut cache) = C2C_BWD_CACHE.lock() {
442        *cache = None;
443    }
444    if let Ok(mut cache) = C2R_CACHE.lock() {
445        *cache = None;
446    }
447    if let Ok(mut cache) = DCT2_CACHE.lock() {
448        *cache = None;
449    }
450    if let Ok(mut cache) = IDCT2_CACHE.lock() {
451        *cache = None;
452    }
453    if let Ok(mut cache) = DST2_CACHE.lock() {
454        *cache = None;
455    }
456    if let Ok(mut cache) = IDST2_CACHE.lock() {
457        *cache = None;
458    }
459    if let Ok(mut cache) = R2C_2D_CACHE.lock() {
460        *cache = None;
461    }
462    if let Ok(mut cache) = C2C_2D_FWD_CACHE.lock() {
463        *cache = None;
464    }
465    if let Ok(mut cache) = C2C_2D_BWD_CACHE.lock() {
466        *cache = None;
467    }
468    if let Ok(mut cache) = C2R_2D_CACHE.lock() {
469        *cache = None;
470    }
471}
472
473/// Get cache statistics for debugging/monitoring
474#[derive(Debug, Clone, Default)]
475pub struct CacheStats {
476    pub r2c_count: usize,
477    pub c2c_fwd_count: usize,
478    pub c2c_bwd_count: usize,
479    pub c2r_count: usize,
480    pub dct2_count: usize,
481    pub idct2_count: usize,
482    pub dst2_count: usize,
483    pub idst2_count: usize,
484    pub r2c_2d_count: usize,
485    pub c2c_2d_fwd_count: usize,
486    pub c2c_2d_bwd_count: usize,
487    pub c2r_2d_count: usize,
488}
489
490/// Get current cache statistics
491pub fn get_cache_stats() -> CacheStats {
492    let mut stats = CacheStats::default();
493
494    if let Ok(cache) = R2C_CACHE.lock() {
495        if let Some(ref map) = *cache {
496            stats.r2c_count = map.len();
497        }
498    }
499    if let Ok(cache) = C2C_FWD_CACHE.lock() {
500        if let Some(ref map) = *cache {
501            stats.c2c_fwd_count = map.len();
502        }
503    }
504    if let Ok(cache) = C2C_BWD_CACHE.lock() {
505        if let Some(ref map) = *cache {
506            stats.c2c_bwd_count = map.len();
507        }
508    }
509    if let Ok(cache) = C2R_CACHE.lock() {
510        if let Some(ref map) = *cache {
511            stats.c2r_count = map.len();
512        }
513    }
514    if let Ok(cache) = DCT2_CACHE.lock() {
515        if let Some(ref map) = *cache {
516            stats.dct2_count = map.len();
517        }
518    }
519    if let Ok(cache) = IDCT2_CACHE.lock() {
520        if let Some(ref map) = *cache {
521            stats.idct2_count = map.len();
522        }
523    }
524    if let Ok(cache) = DST2_CACHE.lock() {
525        if let Some(ref map) = *cache {
526            stats.dst2_count = map.len();
527        }
528    }
529    if let Ok(cache) = IDST2_CACHE.lock() {
530        if let Some(ref map) = *cache {
531            stats.idst2_count = map.len();
532        }
533    }
534    if let Ok(cache) = R2C_2D_CACHE.lock() {
535        if let Some(ref map) = *cache {
536            stats.r2c_2d_count = map.len();
537        }
538    }
539    if let Ok(cache) = C2C_2D_FWD_CACHE.lock() {
540        if let Some(ref map) = *cache {
541            stats.c2c_2d_fwd_count = map.len();
542        }
543    }
544    if let Ok(cache) = C2C_2D_BWD_CACHE.lock() {
545        if let Some(ref map) = *cache {
546            stats.c2c_2d_bwd_count = map.len();
547        }
548    }
549    if let Ok(cache) = C2R_2D_CACHE.lock() {
550        if let Some(ref map) = *cache {
551            stats.c2r_2d_count = map.len();
552        }
553    }
554
555    stats
556}