1#![allow(clippy::type_complexity)]
10
11use oxifft::{Complex, Direction, Flags, Plan, Plan2D, R2rPlan, RealPlan, RealPlan2D};
12use oxifft::rdft::solvers::R2rKind;
14use std::collections::HashMap;
15use std::sync::Mutex;
16
17use crate::error::{FFTError, FFTResult};
18
19static R2C_CACHE: Mutex<Option<HashMap<usize, RealPlan<f64>>>> = Mutex::new(None);
25
26static C2C_FWD_CACHE: Mutex<Option<HashMap<usize, Plan<f64>>>> = Mutex::new(None);
28
29static C2C_BWD_CACHE: Mutex<Option<HashMap<usize, Plan<f64>>>> = Mutex::new(None);
31
32static C2R_CACHE: Mutex<Option<HashMap<usize, RealPlan<f64>>>> = Mutex::new(None);
34
35static DCT2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
37
38static IDCT2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
40
41static DST2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
43
44static IDST2_CACHE: Mutex<Option<HashMap<usize, R2rPlan<f64>>>> = Mutex::new(None);
46
47static R2C_2D_CACHE: Mutex<Option<HashMap<(usize, usize), RealPlan2D<f64>>>> = Mutex::new(None);
53
54static C2C_2D_FWD_CACHE: Mutex<Option<HashMap<(usize, usize), Plan2D<f64>>>> = Mutex::new(None);
56
57static C2C_2D_BWD_CACHE: Mutex<Option<HashMap<(usize, usize), Plan2D<f64>>>> = Mutex::new(None);
59
60static C2R_2D_CACHE: Mutex<Option<HashMap<(usize, usize), RealPlan2D<f64>>>> = Mutex::new(None);
62
63pub 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 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 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 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
100pub 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
148pub 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
176pub 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
206pub 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
236pub 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
266pub 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
296pub 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
338pub 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
391pub 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
429pub 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#[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
490pub 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}