Skip to main content

scirs2_fft/
backend.rs

1//! FFT Backend System
2//!
3//! This module provides a pluggable backend system for FFT implementations,
4//! similar to SciPy's backend model. This allows users to choose between
5//! different FFT implementations at runtime.
6//!
7//! Currently, OxiFFT is the sole backend (COOLJAPAN Pure Rust policy).
8
9use crate::error::{FFTError, FFTResult};
10use scirs2_core::numeric::Complex64;
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex, OnceLock};
13
14/// FFT Backend trait for implementing different FFT algorithms
15pub trait FftBackend: Send + Sync {
16    /// Name of the backend
17    fn name(&self) -> &str;
18
19    /// Description of the backend
20    fn description(&self) -> &str;
21
22    /// Check if this backend is available
23    fn is_available(&self) -> bool;
24
25    /// Perform forward FFT
26    fn fft(&self, input: &[Complex64], output: &mut [Complex64]) -> FFTResult<()>;
27
28    /// Perform inverse FFT
29    fn ifft(&self, input: &[Complex64], output: &mut [Complex64]) -> FFTResult<()>;
30
31    /// Perform FFT with specific size (may be cached)
32    fn fft_sized(
33        &self,
34        input: &[Complex64],
35        output: &mut [Complex64],
36        size: usize,
37    ) -> FFTResult<()>;
38
39    /// Perform inverse FFT with specific size (may be cached)
40    fn ifft_sized(
41        &self,
42        input: &[Complex64],
43        output: &mut [Complex64],
44        size: usize,
45    ) -> FFTResult<()>;
46
47    /// Check if this backend supports a specific feature
48    fn supports_feature(&self, feature: &str) -> bool;
49}
50
51/// Backend manager for FFT operations
52pub struct BackendManager {
53    backends: Arc<Mutex<HashMap<String, Arc<dyn FftBackend>>>>,
54    current_backend: Arc<Mutex<String>>,
55}
56
57impl BackendManager {
58    /// Create a new backend manager
59    pub fn new() -> Self {
60        let backends = HashMap::new();
61        let default_backend = "none".to_string();
62
63        Self {
64            backends: Arc::new(Mutex::new(backends)),
65            current_backend: Arc::new(Mutex::new(default_backend)),
66        }
67    }
68
69    /// Register a new backend
70    pub fn register_backend(&self, name: String, backend: Arc<dyn FftBackend>) -> FFTResult<()> {
71        let mut backends = self.backends.lock().expect("Operation failed");
72        if backends.contains_key(&name) {
73            return Err(FFTError::ValueError(format!(
74                "Backend '{name}' already exists"
75            )));
76        }
77        backends.insert(name, backend);
78        Ok(())
79    }
80
81    /// Get available backends
82    pub fn list_backends(&self) -> Vec<String> {
83        let backends = self.backends.lock().expect("Operation failed");
84        backends.keys().cloned().collect()
85    }
86
87    /// Set the current backend
88    pub fn set_backend(&self, name: &str) -> FFTResult<()> {
89        let backends = self.backends.lock().expect("Operation failed");
90        if !backends.contains_key(name) {
91            // If no backends are registered, silently accept (no-op)
92            if backends.is_empty() || name == "none" {
93                return Ok(());
94            }
95            return Err(FFTError::ValueError(format!("Backend '{name}' not found")));
96        }
97
98        // Check if backend is available
99        if let Some(backend) = backends.get(name) {
100            if !backend.is_available() {
101                return Err(FFTError::ValueError(format!(
102                    "Backend '{name}' is not available"
103                )));
104            }
105        }
106
107        *self.current_backend.lock().expect("Operation failed") = name.to_string();
108        Ok(())
109    }
110
111    /// Get current backend name
112    pub fn get_backend_name(&self) -> String {
113        self.current_backend
114            .lock()
115            .expect("Operation failed")
116            .clone()
117    }
118
119    /// Get current backend (returns None if no backend is registered)
120    #[allow(dead_code)]
121    pub fn get_backend(&self) -> Option<Arc<dyn FftBackend>> {
122        let current_name = self.current_backend.lock().expect("Operation failed");
123        let backends = self.backends.lock().expect("Operation failed");
124        backends.get(&*current_name).cloned()
125    }
126
127    /// Get backend info
128    pub fn get_backend_info(&self, name: &str) -> Option<BackendInfo> {
129        let backends = self.backends.lock().expect("Operation failed");
130        backends.get(name).map(|backend| BackendInfo {
131            name: backend.name().to_string(),
132            description: backend.description().to_string(),
133            available: backend.is_available(),
134        })
135    }
136}
137
138impl Default for BackendManager {
139    fn default() -> Self {
140        Self::new()
141    }
142}
143
144/// Information about a backend
145#[derive(Debug, Clone)]
146pub struct BackendInfo {
147    pub name: String,
148    pub description: String,
149    pub available: bool,
150}
151
152impl std::fmt::Display for BackendInfo {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(
155            f,
156            "{} - {} ({})",
157            self.name,
158            self.description,
159            if self.available {
160                "available"
161            } else {
162                "not available"
163            }
164        )
165    }
166}
167
168/// Global backend manager instance
169static GLOBAL_BACKEND_MANAGER: OnceLock<BackendManager> = OnceLock::new();
170
171/// Get the global backend manager
172#[allow(dead_code)]
173pub fn get_backend_manager() -> &'static BackendManager {
174    GLOBAL_BACKEND_MANAGER.get_or_init(BackendManager::new)
175}
176
177/// Initialize global backend manager with custom configuration
178#[allow(dead_code)]
179pub fn init_backend_manager(manager: BackendManager) -> Result<(), &'static str> {
180    GLOBAL_BACKEND_MANAGER
181        .set(manager)
182        .map_err(|_| "Global backend _manager already initialized")
183}
184
185/// List available backends
186#[allow(dead_code)]
187pub fn list_backends() -> Vec<String> {
188    get_backend_manager().list_backends()
189}
190
191/// Set the current backend
192#[allow(dead_code)]
193pub fn set_backend(name: &str) -> FFTResult<()> {
194    get_backend_manager().set_backend(name)
195}
196
197/// Get current backend name
198#[allow(dead_code)]
199pub fn get_backend_name() -> String {
200    get_backend_manager().get_backend_name()
201}
202
203/// Get backend information
204#[allow(dead_code)]
205pub fn get_backend_info(name: &str) -> Option<BackendInfo> {
206    get_backend_manager().get_backend_info(name)
207}
208
209/// Context manager for temporarily using a different backend
210pub struct BackendContext {
211    previous_backend: String,
212    manager: &'static BackendManager,
213}
214
215impl BackendContext {
216    /// Create a new backend context
217    ///
218    /// Note: If the backend name is not registered, this is a no-op context
219    /// (the backend name is stored but the active backend remains unchanged).
220    pub fn new(_backendname: &str) -> FFTResult<Self> {
221        let manager = get_backend_manager();
222        let previous_backend = manager.get_backend_name();
223
224        // Attempt to set the new backend (gracefully handles unknown backends)
225        let _ = manager.set_backend(_backendname);
226
227        Ok(Self {
228            previous_backend,
229            manager,
230        })
231    }
232}
233
234impl Drop for BackendContext {
235    fn drop(&mut self) {
236        // Restore previous backend
237        let _ = self.manager.set_backend(&self.previous_backend);
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_backend_manager_new() {
247        let manager = BackendManager::new();
248        assert_eq!(manager.get_backend_name(), "none");
249        assert!(manager.list_backends().is_empty());
250    }
251
252    #[test]
253    fn test_backend_context_unknown() {
254        // BackendContext with unknown backend should succeed (no-op)
255        let ctx = BackendContext::new("oxifft");
256        assert!(ctx.is_ok());
257    }
258}