1use std::collections::hash_map::DefaultHasher;
2use std::fmt;
3use std::hash::{Hash, Hasher};
4use std::num::NonZeroUsize;
5use std::sync::Arc;
6
7use num_traits::{Float, FromPrimitive};
8use realfft::{ComplexToReal, RealFftPlanner, RealToComplex};
9use rustfft::{Fft, FftNum, FftPlanner};
10use tenferro_runtime::{
11 ExtensionCacheKey, ExtensionCacheLimits, ExtensionCacheSelector, ExtensionCacheStore,
12};
13use tenferro_tensor::{CacheStats, RuntimeCacheControl};
14
15use crate::FFT_EXTENSION_FAMILY_ID;
16
17pub const FFT_PLAN_CACHE_NAME: &str = "rustfft-plans";
19
20pub const DEFAULT_FFT_PLAN_CACHE_CAPACITY: usize = 64;
22
23pub const fn fft_plan_cache_selector() -> ExtensionCacheSelector {
38 ExtensionCacheSelector::Cache {
39 family_id: FFT_EXTENSION_FAMILY_ID,
40 cache_name: FFT_PLAN_CACHE_NAME,
41 }
42}
43
44pub struct FftPlanCache {
68 store: ExtensionCacheStore,
69}
70
71impl fmt::Debug for FftPlanCache {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.debug_struct("FftPlanCache")
74 .field("capacity", &self.capacity())
75 .field("stats", &self.stats())
76 .finish_non_exhaustive()
77 }
78}
79
80impl FftPlanCache {
81 pub fn with_capacity(capacity: NonZeroUsize) -> Self {
83 Self {
84 store: ExtensionCacheStore::with_limits(ExtensionCacheLimits::new(capacity)),
85 }
86 }
87
88 pub fn capacity(&self) -> NonZeroUsize {
90 self.store.limits().max_entries()
91 }
92
93 pub fn limits(&self) -> ExtensionCacheLimits {
95 self.store.limits()
96 }
97
98 pub fn set_limits(&mut self, limits: ExtensionCacheLimits) {
100 self.store.set_limits(limits);
101 }
102
103 pub fn set_capacity(&mut self, capacity: NonZeroUsize) {
105 let mut limits = ExtensionCacheLimits::new(capacity);
106 if let Some(max_retained_bytes) = self.store.limits().max_retained_bytes() {
107 limits = limits.with_max_retained_bytes(max_retained_bytes);
108 }
109 self.store.set_limits(limits);
110 }
111
112 pub fn clear(&mut self) {
114 self.store.clear();
115 }
116
117 pub fn stats(&self) -> CacheStats {
119 self.store.stats(ExtensionCacheSelector::All)
120 }
121
122 pub(crate) fn store_mut(&mut self) -> &mut ExtensionCacheStore {
123 &mut self.store
124 }
125
126 pub(crate) fn plan_f32(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f32>> {
127 ExtensionFftPlanCache::new(&mut self.store).plan_f32(len, forward)
128 }
129
130 pub(crate) fn plan_f64(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f64>> {
131 ExtensionFftPlanCache::new(&mut self.store).plan_f64(len, forward)
132 }
133
134 pub(crate) fn real_plan_f32(&mut self, len: usize, forward: bool) -> RealPlan<f32> {
135 ExtensionFftPlanCache::new(&mut self.store).real_plan_f32(len, forward)
136 }
137
138 pub(crate) fn real_plan_f64(&mut self, len: usize, forward: bool) -> RealPlan<f64> {
139 ExtensionFftPlanCache::new(&mut self.store).real_plan_f64(len, forward)
140 }
141
142 #[cfg(test)]
143 pub(crate) fn contains_f64(&mut self, len: usize, forward: bool) -> bool {
144 let key = FftPlanKey {
145 len,
146 forward,
147 dtype: FftPlanDType::F64,
148 real: false,
149 };
150 self.store
151 .get::<ExtensionFftPlanEntry>(&extension_plan_key(key))
152 .is_some_and(|entry| entry.matches_f64(key))
153 }
154}
155
156impl Default for FftPlanCache {
157 fn default() -> Self {
158 Self::with_capacity(
159 NonZeroUsize::new(DEFAULT_FFT_PLAN_CACHE_CAPACITY).unwrap_or(NonZeroUsize::MIN),
160 )
161 }
162}
163
164impl RuntimeCacheControl for FftPlanCache {
165 fn clear(&mut self) {
166 Self::clear(self);
167 }
168
169 fn stats(&self) -> CacheStats {
170 Self::stats(self)
171 }
172}
173
174#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
175enum FftPlanDType {
176 F32,
177 F64,
178}
179
180#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
181struct FftPlanKey {
182 len: usize,
183 forward: bool,
184 dtype: FftPlanDType,
185 real: bool,
186}
187
188enum CachedFftPlan {
189 F32(Arc<dyn Fft<f32>>),
190 F64(Arc<dyn Fft<f64>>),
191 RealF32(Arc<dyn RealToComplex<f32>>),
192 RealF64(Arc<dyn RealToComplex<f64>>),
193 InverseF32(Arc<dyn ComplexToReal<f32>>),
194 InverseF64(Arc<dyn ComplexToReal<f64>>),
195}
196
197pub(crate) enum RealPlan<T> {
199 Forward(Arc<dyn RealToComplex<T>>),
200 Inverse(Arc<dyn ComplexToReal<T>>),
201}
202
203struct ExtensionFftPlanEntry {
204 key: FftPlanKey,
205 plan: CachedFftPlan,
206}
207
208impl ExtensionFftPlanEntry {
209 #[cfg(test)]
210 fn matches_f64(&self, key: FftPlanKey) -> bool {
211 self.key == key && matches!(self.plan, CachedFftPlan::F64(_))
212 }
213}
214
215pub(crate) trait FftPlanProvider: Send {
216 fn plan_f32(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f32>>;
217 fn plan_f64(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f64>>;
218 fn real_plan_f32(&mut self, len: usize, forward: bool) -> RealPlan<f32>;
219 fn real_plan_f64(&mut self, len: usize, forward: bool) -> RealPlan<f64>;
220}
221
222impl FftPlanProvider for FftPlanCache {
223 fn plan_f32(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f32>> {
224 Self::plan_f32(self, len, forward)
225 }
226
227 fn plan_f64(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f64>> {
228 Self::plan_f64(self, len, forward)
229 }
230
231 fn real_plan_f32(&mut self, len: usize, forward: bool) -> RealPlan<f32> {
232 Self::real_plan_f32(self, len, forward)
233 }
234
235 fn real_plan_f64(&mut self, len: usize, forward: bool) -> RealPlan<f64> {
236 Self::real_plan_f64(self, len, forward)
237 }
238}
239
240pub(crate) trait CachedFftPlanScalar: FftNum + Float + FromPrimitive + 'static {
241 fn plan<P: FftPlanProvider + ?Sized>(
242 plans: &mut P,
243 len: usize,
244 forward: bool,
245 ) -> Arc<dyn Fft<Self>>;
246
247 fn real_plan<P: FftPlanProvider + ?Sized>(
248 plans: &mut P,
249 len: usize,
250 forward: bool,
251 ) -> RealPlan<Self>;
252}
253
254impl CachedFftPlanScalar for f32 {
255 fn plan<P: FftPlanProvider + ?Sized>(
256 plans: &mut P,
257 len: usize,
258 forward: bool,
259 ) -> Arc<dyn Fft<Self>> {
260 plans.plan_f32(len, forward)
261 }
262
263 fn real_plan<P: FftPlanProvider + ?Sized>(
264 plans: &mut P,
265 len: usize,
266 forward: bool,
267 ) -> RealPlan<Self> {
268 plans.real_plan_f32(len, forward)
269 }
270}
271
272impl CachedFftPlanScalar for f64 {
273 fn plan<P: FftPlanProvider + ?Sized>(
274 plans: &mut P,
275 len: usize,
276 forward: bool,
277 ) -> Arc<dyn Fft<Self>> {
278 plans.plan_f64(len, forward)
279 }
280
281 fn real_plan<P: FftPlanProvider + ?Sized>(
282 plans: &mut P,
283 len: usize,
284 forward: bool,
285 ) -> RealPlan<Self> {
286 plans.real_plan_f64(len, forward)
287 }
288}
289
290pub(crate) fn cached_fft_plan<T: CachedFftPlanScalar, P: FftPlanProvider + ?Sized>(
291 plans: &mut P,
292 len: usize,
293 forward: bool,
294) -> Arc<dyn Fft<T>> {
295 T::plan(plans, len, forward)
296}
297
298pub(crate) fn cached_real_plan<T: CachedFftPlanScalar, P: FftPlanProvider + ?Sized>(
299 plans: &mut P,
300 len: usize,
301 forward: bool,
302) -> RealPlan<T> {
303 T::real_plan(plans, len, forward)
304}
305
306pub(crate) struct ExtensionFftPlanCache<'a> {
307 entries: &'a mut ExtensionCacheStore,
308}
309
310impl<'a> ExtensionFftPlanCache<'a> {
311 pub(crate) fn new(entries: &'a mut ExtensionCacheStore) -> Self {
312 Self { entries }
313 }
314}
315
316fn extension_plan_key(key: FftPlanKey) -> ExtensionCacheKey {
317 let mut hasher = DefaultHasher::new();
318 key.hash(&mut hasher);
319 ExtensionCacheKey::new(
320 FFT_EXTENSION_FAMILY_ID,
321 FFT_PLAN_CACHE_NAME,
322 hasher.finish(),
323 )
324}
325
326impl FftPlanProvider for ExtensionFftPlanCache<'_> {
327 fn plan_f32(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f32>> {
328 let key = FftPlanKey {
329 len,
330 forward,
331 dtype: FftPlanDType::F32,
332 real: false,
333 };
334 let cache_key = extension_plan_key(key);
335 if let Some(cached) = self.entries.get::<ExtensionFftPlanEntry>(&cache_key) {
336 if cached.key == key {
337 if let CachedFftPlan::F32(plan) = &cached.plan {
338 return Arc::clone(plan);
339 }
340 }
341 }
342 let plan = build_fft_plan::<f32>(len, forward);
343 self.entries.put(
344 cache_key,
345 ExtensionFftPlanEntry {
346 key,
347 plan: CachedFftPlan::F32(Arc::clone(&plan)),
348 },
349 fft_plan_retained_bytes(),
350 );
351 plan
352 }
353
354 fn plan_f64(&mut self, len: usize, forward: bool) -> Arc<dyn Fft<f64>> {
355 let key = FftPlanKey {
356 len,
357 forward,
358 dtype: FftPlanDType::F64,
359 real: false,
360 };
361 let cache_key = extension_plan_key(key);
362 if let Some(cached) = self.entries.get::<ExtensionFftPlanEntry>(&cache_key) {
363 if cached.key == key {
364 if let CachedFftPlan::F64(plan) = &cached.plan {
365 return Arc::clone(plan);
366 }
367 }
368 }
369 let plan = build_fft_plan::<f64>(len, forward);
370 self.entries.put(
371 cache_key,
372 ExtensionFftPlanEntry {
373 key,
374 plan: CachedFftPlan::F64(Arc::clone(&plan)),
375 },
376 fft_plan_retained_bytes(),
377 );
378 plan
379 }
380
381 fn real_plan_f32(&mut self, len: usize, forward: bool) -> RealPlan<f32> {
382 let key = FftPlanKey {
383 len,
384 forward,
385 dtype: FftPlanDType::F32,
386 real: true,
387 };
388 let cache_key = extension_plan_key(key);
389 if let Some(cached) = self.entries.get::<ExtensionFftPlanEntry>(&cache_key) {
390 if cached.key == key {
391 match (&cached.plan, forward) {
392 (CachedFftPlan::RealF32(plan), true) => {
393 return RealPlan::Forward(Arc::clone(plan));
394 }
395 (CachedFftPlan::InverseF32(plan), false) => {
396 return RealPlan::Inverse(Arc::clone(plan));
397 }
398 _ => {}
399 }
400 }
401 }
402 let plan = build_real_plan::<f32>(len, forward);
403 let cached = match &plan {
404 RealPlan::Forward(plan) => CachedFftPlan::RealF32(Arc::clone(plan)),
405 RealPlan::Inverse(plan) => CachedFftPlan::InverseF32(Arc::clone(plan)),
406 };
407 self.entries.put(
408 cache_key,
409 ExtensionFftPlanEntry { key, plan: cached },
410 fft_plan_retained_bytes(),
411 );
412 plan
413 }
414
415 fn real_plan_f64(&mut self, len: usize, forward: bool) -> RealPlan<f64> {
416 let key = FftPlanKey {
417 len,
418 forward,
419 dtype: FftPlanDType::F64,
420 real: true,
421 };
422 let cache_key = extension_plan_key(key);
423 if let Some(cached) = self.entries.get::<ExtensionFftPlanEntry>(&cache_key) {
424 if cached.key == key {
425 match (&cached.plan, forward) {
426 (CachedFftPlan::RealF64(plan), true) => {
427 return RealPlan::Forward(Arc::clone(plan));
428 }
429 (CachedFftPlan::InverseF64(plan), false) => {
430 return RealPlan::Inverse(Arc::clone(plan));
431 }
432 _ => {}
433 }
434 }
435 }
436 let plan = build_real_plan::<f64>(len, forward);
437 let cached = match &plan {
438 RealPlan::Forward(plan) => CachedFftPlan::RealF64(Arc::clone(plan)),
439 RealPlan::Inverse(plan) => CachedFftPlan::InverseF64(Arc::clone(plan)),
440 };
441 self.entries.put(
442 cache_key,
443 ExtensionFftPlanEntry { key, plan: cached },
444 fft_plan_retained_bytes(),
445 );
446 plan
447 }
448}
449
450fn build_real_plan<T: FftNum + 'static>(len: usize, forward: bool) -> RealPlan<T> {
451 let mut planner = RealFftPlanner::<T>::new();
452 if forward {
453 RealPlan::Forward(planner.plan_fft_forward(len))
454 } else {
455 RealPlan::Inverse(planner.plan_fft_inverse(len))
456 }
457}
458
459fn build_fft_plan<T: FftNum + 'static>(len: usize, forward: bool) -> Arc<dyn Fft<T>> {
460 let mut planner = FftPlanner::<T>::new();
461 if forward {
462 planner.plan_fft_forward(len)
463 } else {
464 planner.plan_fft_inverse(len)
465 }
466}
467
468const fn fft_plan_retained_bytes() -> usize {
469 std::mem::size_of::<FftPlanKey>() + std::mem::size_of::<CachedFftPlan>()
470}