1use crate::error::{FFTError, FFTResult};
9use crate::sparse_fft::{
10 SparseFFT, SparseFFTAlgorithm, SparseFFTConfig, SparsityEstimationMethod, WindowFunction,
11};
12use scirs2_core::numeric::Complex64;
13use scirs2_core::numeric::NumCast;
14use scirs2_core::simd_ops::PlatformCapabilities;
15use std::fmt::Debug;
16
17#[derive(Debug, Clone)]
19pub struct KernelConfig {
20 pub block_size: usize,
22 pub grid_size: usize,
24 pub shared_memory_size: usize,
26 pub use_mixed_precision: bool,
28 pub registers_per_thread: usize,
30 pub use_tensor_cores: bool,
32}
33
34impl Default for KernelConfig {
35 fn default() -> Self {
36 Self {
37 block_size: 256,
38 grid_size: 0, shared_memory_size: 16 * 1024, use_mixed_precision: false,
41 registers_per_thread: 32,
42 use_tensor_cores: false,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum KernelImplementation {
50 Throughput,
52 Latency,
54 MemoryEfficient,
56 HighAccuracy,
58 PowerEfficient,
60}
61
62#[derive(Debug, Clone)]
81pub struct KernelStats {
82 pub execution_time_ms: f64,
84 pub memory_bandwidth_gb_s: Option<f64>,
87 pub estimated_compute_throughput_gflops: Option<f64>,
90 pub bytes_transferred_to_device: usize,
92 pub bytes_transferred_from_device: usize,
94 pub estimated_occupancy_percent: f64,
98}
99
100#[derive(Debug, Clone, Copy)]
107pub struct KernelPerformanceEstimate {
108 pub flop_count: f64,
111 pub occupancy_percent: f64,
113}
114
115pub fn estimate_kernel_performance(
127 config: &KernelConfig,
128 input_size: usize,
129 max_threads_per_block: usize,
130) -> KernelPerformanceEstimate {
131 let flop_count = if input_size >= 2 {
133 5.0 * input_size as f64 * (input_size as f64).log2()
134 } else {
135 0.0
136 };
137
138 const MAX_REGISTERS_PER_BLOCK: f64 = 65536.0;
142 let max_threads = max_threads_per_block.max(1) as f64;
143 let block = config.block_size.max(1) as f64;
144
145 let thread_limited = (block / max_threads).min(1.0);
147
148 let regs_per_thread = config.registers_per_thread.max(1) as f64;
151 let reg_hosted_threads = MAX_REGISTERS_PER_BLOCK / regs_per_thread;
152 let register_limited = (reg_hosted_threads / max_threads).min(1.0);
153
154 let occupancy = thread_limited.min(register_limited).clamp(0.01, 1.0);
155
156 KernelPerformanceEstimate {
157 flop_count,
158 occupancy_percent: occupancy * 100.0,
159 }
160}
161
162fn analytical_time_estimate_ms(flop_count: f64, config: &KernelConfig) -> f64 {
170 const REFERENCE_MS_PER_FLOP: f64 = 1.0e-7;
174
175 let mut cost = flop_count * REFERENCE_MS_PER_FLOP;
176
177 if config.use_mixed_precision {
179 cost *= 0.6;
180 }
181 if config.use_tensor_cores {
182 cost *= 0.5;
183 }
184
185 cost.max(f64::MIN_POSITIVE)
186}
187
188fn throughput_from_estimate(flop_count: f64, time_ms: f64) -> Option<f64> {
193 if time_ms > 0.0 && flop_count > 0.0 {
194 Some(flop_count / (time_ms * 1.0e6))
196 } else {
197 None
198 }
199}
200
201pub trait GPUKernel {
203 fn name(&self) -> &str;
205
206 fn config(&self) -> &KernelConfig;
208
209 fn set_config(&mut self, config: KernelConfig);
211
212 fn execute(&self) -> FFTResult<KernelStats>;
214}
215
216#[derive(Debug)]
218pub struct FFTKernel {
219 config: KernelConfig,
221 input_size: usize,
223 #[allow(dead_code)]
225 input_address: usize,
226 #[allow(dead_code)]
228 output_address: usize,
229}
230
231impl FFTKernel {
232 pub fn new(input_size: usize, input_address: usize, outputaddress: usize) -> Self {
234 let mut config = KernelConfig::default();
235 config.grid_size = input_size.div_ceil(config.block_size);
237
238 Self {
239 config,
240 input_size,
241 input_address,
242 output_address: outputaddress,
243 }
244 }
245}
246
247impl GPUKernel for FFTKernel {
248 fn name(&self) -> &str {
249 "FFT_Kernel"
250 }
251
252 fn config(&self) -> &KernelConfig {
253 &self.config
254 }
255
256 fn set_config(&mut self, config: KernelConfig) {
257 self.config = config;
258 }
259
260 fn execute(&self) -> FFTResult<KernelStats> {
261 let bytes_in = self.input_size * std::mem::size_of::<Complex64>();
270 let bytes_out = self.input_size * std::mem::size_of::<Complex64>();
271
272 let estimate =
273 estimate_kernel_performance(&self.config, self.input_size, self.config.block_size);
274
275 let execution_time_ms = analytical_time_estimate_ms(estimate.flop_count, &self.config);
279 let estimated_compute_throughput_gflops =
280 throughput_from_estimate(estimate.flop_count, execution_time_ms);
281
282 Ok(KernelStats {
283 execution_time_ms,
284 memory_bandwidth_gb_s: None,
285 estimated_compute_throughput_gflops,
286 bytes_transferred_to_device: bytes_in,
287 bytes_transferred_from_device: bytes_out,
288 estimated_occupancy_percent: estimate.occupancy_percent,
289 })
290 }
291}
292
293#[derive(Debug)]
295pub struct SparseFFTKernel {
296 config: KernelConfig,
298 input_size: usize,
300 sparsity: usize,
302 #[allow(dead_code)]
304 input_address: usize,
305 #[allow(dead_code)]
307 output_values_address: usize,
308 #[allow(dead_code)]
310 output_indices_address: usize,
311 algorithm: SparseFFTAlgorithm,
313 window_function: WindowFunction,
315}
316
317impl SparseFFTKernel {
318 #[allow(clippy::too_many_arguments)]
320 pub fn new(
321 input_size: usize,
322 sparsity: usize,
323 input_address: usize,
324 output_values_address: usize,
325 output_indices_address: usize,
326 algorithm: SparseFFTAlgorithm,
327 window_function: WindowFunction,
328 ) -> Self {
329 let mut config = KernelConfig::default();
330 config.grid_size = input_size.div_ceil(config.block_size);
332
333 Self {
334 config,
335 input_size,
336 sparsity,
337 input_address,
338 output_values_address,
339 output_indices_address,
340 algorithm,
341 window_function,
342 }
343 }
344
345 pub fn apply_window(&self) -> FFTResult<KernelStats> {
352 let estimate =
353 estimate_kernel_performance(&self.config, self.input_size, self.config.block_size);
354
355 let window_flops = self.input_size as f64;
357 let execution_time_ms = analytical_time_estimate_ms(window_flops, &self.config);
358 let estimated_compute_throughput_gflops =
359 throughput_from_estimate(window_flops, execution_time_ms);
360
361 Ok(KernelStats {
362 execution_time_ms,
363 memory_bandwidth_gb_s: None,
364 estimated_compute_throughput_gflops,
365 bytes_transferred_to_device: 0,
366 bytes_transferred_from_device: 0,
367 estimated_occupancy_percent: estimate.occupancy_percent,
368 })
369 }
370
371 pub fn get_algorithm_implementation(&self) -> FFTResult<KernelImplementation> {
373 match self.algorithm {
375 SparseFFTAlgorithm::Sublinear => Ok(KernelImplementation::Throughput),
376 SparseFFTAlgorithm::CompressedSensing => Ok(KernelImplementation::HighAccuracy),
377 SparseFFTAlgorithm::Iterative => Ok(KernelImplementation::Latency),
378 SparseFFTAlgorithm::Deterministic => Ok(KernelImplementation::Throughput),
379 SparseFFTAlgorithm::FrequencyPruning => Ok(KernelImplementation::MemoryEfficient),
380 SparseFFTAlgorithm::SpectralFlatness => Ok(KernelImplementation::HighAccuracy),
381 }
382 }
383}
384
385impl GPUKernel for SparseFFTKernel {
386 fn name(&self) -> &str {
387 "SparseFFT_Kernel"
388 }
389
390 fn config(&self) -> &KernelConfig {
391 &self.config
392 }
393
394 fn set_config(&mut self, config: KernelConfig) {
395 self.config = config;
396 }
397
398 fn execute(&self) -> FFTResult<KernelStats> {
399 let algorithm_factor = match self.algorithm {
408 SparseFFTAlgorithm::Sublinear => 0.8,
409 SparseFFTAlgorithm::CompressedSensing => 1.5,
410 SparseFFTAlgorithm::Iterative => 1.2,
411 SparseFFTAlgorithm::Deterministic => 1.0,
412 SparseFFTAlgorithm::FrequencyPruning => 0.9,
413 SparseFFTAlgorithm::SpectralFlatness => 1.3,
414 };
415 let window_factor = match self.window_function {
416 WindowFunction::None => 1.0,
417 WindowFunction::Hann => 1.1,
418 WindowFunction::Hamming => 1.1,
419 WindowFunction::Blackman => 1.2,
420 WindowFunction::FlatTop => 1.3,
421 WindowFunction::Kaiser => 1.4,
422 };
423
424 let estimate =
425 estimate_kernel_performance(&self.config, self.input_size, self.config.block_size);
426 let effective_flops = estimate.flop_count * algorithm_factor * window_factor;
427 let execution_time_ms = analytical_time_estimate_ms(effective_flops, &self.config);
428 let estimated_compute_throughput_gflops =
429 throughput_from_estimate(effective_flops, execution_time_ms);
430
431 Ok(KernelStats {
432 execution_time_ms,
433 memory_bandwidth_gb_s: None,
434 estimated_compute_throughput_gflops,
435 bytes_transferred_to_device: self.input_size * std::mem::size_of::<Complex64>(),
436 bytes_transferred_from_device: (self.sparsity * 2) * std::mem::size_of::<Complex64>(),
437 estimated_occupancy_percent: estimate.occupancy_percent,
438 })
439 }
440}
441
442#[derive(Debug, Clone)]
444pub struct KernelFactory {
445 #[allow(dead_code)]
447 arch: String,
448 compute_capabilities: Vec<(i32, i32)>,
450 available_memory: usize,
452 shared_memory_per_block: usize,
454 max_threads_per_block: usize,
456}
457
458impl KernelFactory {
459 pub(crate) fn compute_capabilities(&self) -> &[(i32, i32)] {
464 &self.compute_capabilities
465 }
466
467 pub(crate) fn max_threads_per_block(&self) -> usize {
470 self.max_threads_per_block
471 }
472
473 pub(crate) fn shared_memory_per_block(&self) -> usize {
476 self.shared_memory_per_block
477 }
478
479 pub fn new(
481 arch: String,
482 compute_capabilities: Vec<(i32, i32)>,
483 available_memory: usize,
484 shared_memory_per_block: usize,
485 max_threads_per_block: usize,
486 ) -> Self {
487 Self {
488 arch,
489 compute_capabilities,
490 available_memory,
491 shared_memory_per_block,
492 max_threads_per_block,
493 }
494 }
495
496 pub fn create_fft_kernel(
498 &self,
499 input_size: usize,
500 input_address: usize,
501 output_address: usize,
502 ) -> FFTResult<FFTKernel> {
503 let mut kernel = FFTKernel::new(input_size, input_address, output_address);
504
505 let mut config = KernelConfig::default();
507
508 config.block_size = if self.max_threads_per_block >= 1024 {
510 1024
511 } else if self.max_threads_per_block >= 512 {
512 512
513 } else {
514 256
515 };
516
517 config.grid_size = input_size.div_ceil(config.block_size);
519
520 config.shared_memory_size = std::cmp::min(
522 self.shared_memory_per_block,
523 16 * 1024, );
525
526 if !self.compute_capabilities.is_empty()
528 && (self.compute_capabilities[0].0 >= 7
529 || (self.compute_capabilities[0].0 == 6 && self.compute_capabilities[0].1 >= 1))
530 {
531 config.use_mixed_precision = true;
532 }
533
534 if !self.compute_capabilities.is_empty() && self.compute_capabilities[0].0 >= 7 {
536 config.use_tensor_cores = true;
537 }
538
539 kernel.set_config(config);
540 Ok(kernel)
541 }
542
543 #[allow(clippy::too_many_arguments)]
545 pub fn create_sparse_fft_kernel(
546 &self,
547 input_size: usize,
548 sparsity: usize,
549 input_address: usize,
550 output_values_address: usize,
551 output_indices_address: usize,
552 algorithm: SparseFFTAlgorithm,
553 window_function: WindowFunction,
554 ) -> FFTResult<SparseFFTKernel> {
555 let mut kernel = SparseFFTKernel::new(
556 input_size,
557 sparsity,
558 input_address,
559 output_values_address,
560 output_indices_address,
561 algorithm,
562 window_function,
563 );
564
565 let mut config = KernelConfig::default();
567
568 config.block_size = match algorithm {
570 SparseFFTAlgorithm::Sublinear => 256,
571 SparseFFTAlgorithm::CompressedSensing => 512,
572 SparseFFTAlgorithm::Iterative => 128,
573 SparseFFTAlgorithm::Deterministic => 256,
574 SparseFFTAlgorithm::FrequencyPruning => 256,
575 SparseFFTAlgorithm::SpectralFlatness => 512,
576 };
577
578 config.block_size = std::cmp::min(config.block_size, self.max_threads_per_block);
580
581 config.grid_size = input_size.div_ceil(config.block_size);
583
584 config.shared_memory_size = match algorithm {
586 SparseFFTAlgorithm::Sublinear => 16 * 1024,
587 SparseFFTAlgorithm::CompressedSensing => 32 * 1024,
588 SparseFFTAlgorithm::Iterative => 8 * 1024,
589 SparseFFTAlgorithm::Deterministic => 16 * 1024,
590 SparseFFTAlgorithm::FrequencyPruning => 16 * 1024,
591 SparseFFTAlgorithm::SpectralFlatness => 32 * 1024,
592 };
593
594 config.shared_memory_size =
596 std::cmp::min(config.shared_memory_size, self.shared_memory_per_block);
597
598 if !self.compute_capabilities.is_empty()
600 && (self.compute_capabilities[0].0 >= 7
601 || (self.compute_capabilities[0].0 == 6 && self.compute_capabilities[0].1 >= 1))
602 {
603 match algorithm {
605 SparseFFTAlgorithm::Sublinear
606 | SparseFFTAlgorithm::Deterministic
607 | SparseFFTAlgorithm::FrequencyPruning => {
608 config.use_mixed_precision = true;
609 }
610 _ => {
611 config.use_mixed_precision = false;
612 }
613 }
614 }
615
616 if !self.compute_capabilities.is_empty() && self.compute_capabilities[0].0 >= 7 {
618 match algorithm {
620 SparseFFTAlgorithm::CompressedSensing | SparseFFTAlgorithm::SpectralFlatness => {
621 config.use_tensor_cores = true;
622 }
623 _ => {
624 config.use_tensor_cores = false;
625 }
626 }
627 }
628
629 kernel.set_config(config);
630 Ok(kernel)
631 }
632
633 pub fn check_memory_requirements(&self, total_bytesneeded: usize) -> FFTResult<()> {
635 if total_bytesneeded > self.available_memory {
636 return Err(FFTError::MemoryError(format!(
637 "Not enough GPU memory: need {} bytes, available {} bytes",
638 total_bytesneeded, self.available_memory
639 )));
640 }
641
642 Ok(())
643 }
644}
645
646pub struct KernelLauncher {
648 factory: KernelFactory,
650 active_kernels: Vec<Box<dyn GPUKernel>>,
652 total_memory_allocated: usize,
654}
655
656impl KernelLauncher {
657 pub fn new(factory: KernelFactory) -> Self {
659 Self {
660 factory,
661 active_kernels: Vec::new(),
662 total_memory_allocated: 0,
663 }
664 }
665
666 pub fn allocate_fft_memory(&mut self, inputsize: usize) -> FFTResult<(usize, usize)> {
668 let element_size = std::mem::size_of::<Complex64>();
669 let input_bytes = inputsize * element_size;
670 let output_bytes = inputsize * element_size;
671
672 let total_bytes = input_bytes + output_bytes;
673 self.factory.check_memory_requirements(total_bytes)?;
674
675 const HANDLE_BASE: usize = 0x1_0000;
681 let input_address = HANDLE_BASE + self.total_memory_allocated;
682 let output_address = input_address + input_bytes;
683
684 self.total_memory_allocated += total_bytes;
685
686 Ok((input_address, output_address))
687 }
688
689 pub fn allocate_sparse_fft_memory(
691 &mut self,
692 input_size: usize,
693 sparsity: usize,
694 ) -> FFTResult<(usize, usize, usize)> {
695 let element_size = std::mem::size_of::<Complex64>();
696 let index_size = std::mem::size_of::<usize>();
697
698 let input_bytes = input_size * element_size;
699 let output_values_bytes = sparsity * element_size;
700 let output_indices_bytes = sparsity * index_size;
701
702 let total_bytes = input_bytes + output_values_bytes + output_indices_bytes;
703 self.factory.check_memory_requirements(total_bytes)?;
704
705 const HANDLE_BASE: usize = 0x1_0000;
710 let input_address = HANDLE_BASE + self.total_memory_allocated;
711 let output_values_address = input_address + input_bytes;
712 let output_indices_address = output_values_address + output_values_bytes;
713
714 self.total_memory_allocated += total_bytes;
715
716 Ok((input_address, output_values_address, output_indices_address))
717 }
718
719 pub fn launch_fft_kernel(
721 &mut self,
722 input_size: usize,
723 input_address: usize,
724 output_address: usize,
725 ) -> FFTResult<KernelStats> {
726 let kernel = self
727 .factory
728 .create_fft_kernel(input_size, input_address, output_address)?;
729
730 let stats = kernel.execute()?;
731
732 Ok(stats)
736 }
737
738 #[allow(clippy::too_many_arguments)]
740 pub fn launch_sparse_fft_kernel(
741 &mut self,
742 input_size: usize,
743 sparsity: usize,
744 input_address: usize,
745 output_values_address: usize,
746 output_indices_address: usize,
747 algorithm: SparseFFTAlgorithm,
748 window_function: WindowFunction,
749 ) -> FFTResult<KernelStats> {
750 let kernel = self.factory.create_sparse_fft_kernel(
751 input_size,
752 sparsity,
753 input_address,
754 output_values_address,
755 output_indices_address,
756 algorithm,
757 window_function,
758 )?;
759
760 if window_function != WindowFunction::None {
762 kernel.apply_window()?;
764 }
765
766 let stats = kernel.execute()?;
767
768 Ok(stats)
772 }
773
774 pub fn get_total_memory_allocated(&self) -> usize {
776 self.total_memory_allocated
777 }
778
779 pub fn free_all_memory(&mut self) {
781 self.active_kernels.clear();
783 self.total_memory_allocated = 0;
784 }
785}
786
787#[allow(clippy::too_many_arguments)]
806#[allow(dead_code)]
807pub fn execute_sparse_fft_kernel<T>(
808 signal: &[T],
809 sparsity: usize,
810 algorithm: SparseFFTAlgorithm,
811 window_function: WindowFunction,
812 gpu_arch: &str,
813 compute_capability: (i32, i32),
814 available_memory: usize,
815) -> FFTResult<(Vec<Complex64>, Vec<usize>, KernelStats)>
816where
817 T: NumCast + Copy + Debug + 'static,
818{
819 let factory = KernelFactory::new(
821 gpu_arch.to_string(),
822 vec![compute_capability],
823 available_memory,
824 48 * 1024, 1024, );
827
828 let mut launcher = KernelLauncher::new(factory);
830
831 let (input_address, output_values_address, output_indices_address) =
834 launcher.allocate_sparse_fft_memory(signal.len(), sparsity)?;
835
836 let mut stats = launcher.launch_sparse_fft_kernel(
838 signal.len(),
839 sparsity,
840 input_address,
841 output_values_address,
842 output_indices_address,
843 algorithm,
844 window_function,
845 )?;
846
847 let config = SparseFFTConfig {
851 estimation_method: SparsityEstimationMethod::Manual,
852 sparsity,
853 algorithm,
854 window_function,
855 ..SparseFFTConfig::default()
856 };
857 let mut processor = SparseFFT::new(config);
858
859 let compute_start = std::time::Instant::now();
860 let result = processor.sparse_fft(signal)?;
861 stats.execution_time_ms = compute_start.elapsed().as_secs_f64() * 1.0e3;
864
865 launcher.free_all_memory();
867
868 Ok((result.values, result.indices, stats))
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874 use std::f64::consts::PI;
875
876 fn create_sparse_signal(n: usize, frequencies: &[(usize, f64)]) -> Vec<f64> {
878 let mut signal = vec![0.0; n];
879
880 for i in 0..n {
881 let t = 2.0 * PI * (i as f64) / (n as f64);
882 for &(freq, amp) in frequencies {
883 signal[i] += amp * (freq as f64 * t).sin();
884 }
885 }
886
887 signal
888 }
889
890 #[test]
891 fn test_kernel_factory() {
892 let caps = PlatformCapabilities::detect();
894 if !caps.cuda_available && !caps.gpu_available {
895 eprintln!("GPU not available, using mock kernel factory test");
897 let factory = KernelFactory::new(
899 "Mock Device".to_string(),
900 vec![(1, 1)],
901 1024 * 1024, 16 * 1024, 32, );
905 assert!(factory.arch.contains("Mock"));
906 return;
907 }
908
909 let factory = KernelFactory::new(
910 "NVIDIA GeForce RTX 3080".to_string(),
911 vec![(8, 6)],
912 10 * 1024 * 1024 * 1024, 48 * 1024, 1024, );
916
917 let kernel = factory
919 .create_fft_kernel(1024, 0x10000, 0x20000)
920 .expect("Operation failed");
921
922 let config = kernel.config();
924 assert_eq!(config.block_size, 1024);
925 assert!(config.use_mixed_precision);
926 assert!(config.use_tensor_cores);
927
928 let kernel = factory
930 .create_sparse_fft_kernel(
931 1024,
932 10,
933 0x10000,
934 0x20000,
935 0x30000,
936 SparseFFTAlgorithm::Sublinear,
937 WindowFunction::Hann,
938 )
939 .expect("Operation failed");
940
941 let config = kernel.config();
943 assert_eq!(config.block_size, 256);
944 assert!(config.use_mixed_precision);
945 }
946
947 #[test]
948 fn test_kernel_launcher() {
949 let caps = PlatformCapabilities::detect();
951 if !caps.cuda_available && !caps.gpu_available {
952 eprintln!("GPU not available, using mock kernel launcher test");
954 let factory = KernelFactory::new(
955 "Mock Device".to_string(),
956 vec![(1, 1)],
957 1024 * 1024,
958 16 * 1024,
959 32,
960 );
961 let launcher = KernelLauncher::new(factory);
962 assert_eq!(launcher.get_total_memory_allocated(), 0);
964 return;
965 }
966
967 let factory = KernelFactory::new(
968 "NVIDIA GeForce RTX 3080".to_string(),
969 vec![(8, 6)],
970 10 * 1024 * 1024 * 1024, 48 * 1024, 1024, );
974
975 let mut launcher = KernelLauncher::new(factory);
976
977 let (input_address, output_address) = launcher
979 .allocate_fft_memory(1024)
980 .expect("Operation failed");
981 assert_ne!(input_address, 0);
982 assert_ne!(output_address, 0);
983
984 let stats = launcher
986 .launch_fft_kernel(1024, input_address, output_address)
987 .expect("Operation failed");
988
989 assert!(stats.execution_time_ms > 0.0);
994 assert!(stats.estimated_occupancy_percent > 0.0);
995 assert!(stats.estimated_occupancy_percent <= 100.0);
996 assert!(stats.memory_bandwidth_gb_s.is_none());
997 assert!(matches!(
998 stats.estimated_compute_throughput_gflops,
999 Some(gflops) if gflops > 0.0
1000 ));
1001
1002 launcher.free_all_memory();
1004 assert_eq!(launcher.get_total_memory_allocated(), 0);
1005 }
1006
1007 #[test]
1008 fn test_execute_sparse_fft_kernel() {
1009 let n = 1024;
1011 let frequencies = vec![(3, 1.0), (7, 0.5), (15, 0.25)];
1012 let signal = create_sparse_signal(n, &frequencies);
1013
1014 let caps = PlatformCapabilities::detect();
1016 if !caps.cuda_available && !caps.gpu_available {
1017 eprintln!("GPU not available, using mock sparse FFT kernel test");
1019 let result = execute_sparse_fft_kernel(
1021 &signal,
1022 6,
1023 SparseFFTAlgorithm::Sublinear,
1024 WindowFunction::Hann,
1025 "Mock Device",
1026 (1, 1),
1027 1024 * 1024, );
1029 let (values, indices, stats) = result.expect("Operation failed");
1034 assert_eq!(values.len(), 6);
1035 assert_eq!(indices.len(), 6);
1036 assert!(
1037 indices.contains(&3),
1038 "real strongest tone (bin 3) not found"
1039 );
1040 assert!(stats.execution_time_ms >= 0.0);
1041 return;
1042 }
1043
1044 let (values, indices, stats) = execute_sparse_fft_kernel(
1046 &signal,
1047 6,
1048 SparseFFTAlgorithm::Sublinear,
1049 WindowFunction::Hann,
1050 "NVIDIA GeForce RTX 3080",
1051 (8, 6),
1052 10 * 1024 * 1024 * 1024, )
1054 .expect("Operation failed");
1055
1056 assert_eq!(values.len(), 6);
1058 assert_eq!(indices.len(), 6);
1059 assert!(
1060 indices.contains(&3),
1061 "real strongest tone (bin 3) not found"
1062 );
1063 assert!(stats.execution_time_ms > 0.0);
1064 }
1065}