Skip to main content

quantrs2_core/gpu/
scirs2_adapter.rs

1//! Enhanced SciRS2 GPU Integration and Adapter Layer
2//!
3//! This module provides complete integration with SciRS2's GPU abstractions
4//! and enhanced quantum computing acceleration using the SciRS2 framework.
5
6use crate::error::{QuantRS2Error, QuantRS2Result};
7use crate::gpu::large_scale_simulation::GpuBackend;
8use crate::gpu::{GpuBackend as QuantumGpuBackend, GpuBuffer, GpuKernel};
9use crate::gpu_stubs::SciRS2GpuConfig;
10use scirs2_core::Complex64;
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13
14#[cfg(feature = "gpu")]
15// use scirs2_core::gpu::GpuDevice;
16// Placeholder for GpuDevice until scirs2 is available
17type GpuDevice = ();
18//
19// /// Enhanced GPU configuration for SciRS2 integration
20// #[derive(Debug, Clone)]
21// pub struct SciRS2GpuConfig {
22//     /// Preferred GPU backend
23//     pub backend: Option<GpuBackend>,
24//     /// Device index (for multi-GPU systems)
25//     pub device_index: usize,
26//     /// Maximum memory allocation (MB)
27//     pub max_memory_mb: usize,
28//     /// Enable kernel caching
29//     pub enable_kernel_cache: bool,
30//     /// SIMD optimization level
31//     pub simd_level: u8,
32//     /// Enable automatic load balancing
33//     pub enable_load_balancing: bool,
34//     /// Kernel compilation flags
35//     pub compilation_flags: Vec<String>,
36// }
37
38// impl Default for SciRS2GpuConfig {
39//     fn default() -> Self {
40//         Self {
41//             backend: None, // Auto-detect
42//             device_index: 0,
43//             max_memory_mb: 2048, // 2GB default
44//             enable_kernel_cache: true,
45//             simd_level: 2, // Moderate SIMD optimization
46//             enable_load_balancing: true,
47//             compilation_flags: vec!["-O3".to_string(), "-fast-math".to_string()],
48//         }
49//     }
50// }
51
52/// Performance metrics for SciRS2 GPU operations
53#[derive(Debug, Clone)]
54pub struct SciRS2GpuMetrics {
55    /// Total kernel executions
56    pub kernel_executions: usize,
57    /// Average kernel execution time (microseconds)
58    pub avg_kernel_time_us: f64,
59    /// Memory bandwidth utilization (0.0 to 1.0)
60    pub memory_bandwidth_utilization: f64,
61    /// Compute unit utilization (0.0 to 1.0)
62    pub compute_utilization: f64,
63    /// Cache hit rate (0.0 to 1.0)
64    pub cache_hit_rate: f64,
65    /// GPU memory usage (bytes)
66    pub memory_usage_bytes: usize,
67}
68
69/// Enhanced SciRS2 GPU Buffer with quantum-specific optimizations
70pub struct SciRS2BufferAdapter {
71    /// Buffer size in elements
72    size: usize,
73    /// SciRS2 GPU device reference
74    #[cfg(feature = "gpu")]
75    device: Option<Arc<GpuDevice>>,
76    /// Buffer data (fallback for CPU mode)
77    data: Vec<Complex64>,
78    /// Buffer configuration
79    config: SciRS2GpuConfig,
80    /// Performance tracking
81    metrics: Arc<Mutex<SciRS2GpuMetrics>>,
82}
83
84impl SciRS2BufferAdapter {
85    /// Create a new buffer adapter with SciRS2 GPU support
86    pub fn new(size: usize) -> Self {
87        Self::with_config(size, SciRS2GpuConfig::default())
88    }
89
90    /// Create buffer with custom configuration
91    pub fn with_config(size: usize, config: SciRS2GpuConfig) -> Self {
92        let metrics = Arc::new(Mutex::new(SciRS2GpuMetrics {
93            kernel_executions: 0,
94            avg_kernel_time_us: 0.0,
95            memory_bandwidth_utilization: 0.0,
96            compute_utilization: 0.0,
97            cache_hit_rate: 0.0,
98            memory_usage_bytes: size * std::mem::size_of::<Complex64>(),
99        }));
100
101        Self {
102            size,
103            #[cfg(feature = "gpu")]
104            device: None,
105            data: vec![Complex64::new(0.0, 0.0); size],
106            config,
107            metrics,
108        }
109    }
110
111    /// Initialize GPU device
112    #[cfg(feature = "gpu")]
113    pub fn initialize_gpu(&mut self) -> QuantRS2Result<()> {
114        match get_scirs2_gpu_device() {
115            Ok(device) => {
116                self.device = Some(Arc::new(device));
117                Ok(())
118            }
119            Err(e) => {
120                // Fall back to CPU mode
121                eprintln!("GPU initialization failed, falling back to CPU: {e}");
122                Ok(())
123            }
124        }
125    }
126
127    /// Get performance metrics
128    pub fn get_metrics(&self) -> SciRS2GpuMetrics {
129        if let Ok(metrics) = self.metrics.lock() {
130            metrics.clone()
131        } else {
132            SciRS2GpuMetrics {
133                kernel_executions: 0,
134                avg_kernel_time_us: 0.0,
135                memory_bandwidth_utilization: 0.0,
136                compute_utilization: 0.0,
137                cache_hit_rate: 0.0,
138                memory_usage_bytes: self.size * std::mem::size_of::<Complex64>(),
139            }
140        }
141    }
142
143    /// Check if GPU acceleration is active
144    #[cfg(feature = "gpu")]
145    #[allow(clippy::missing_const_for_fn)] // Option::is_some() is not const
146    pub fn is_gpu_active(&self) -> bool {
147        self.device.is_some()
148    }
149
150    #[cfg(not(feature = "gpu"))]
151    pub const fn is_gpu_active(&self) -> bool {
152        false
153    }
154}
155
156impl GpuBuffer for SciRS2BufferAdapter {
157    fn size(&self) -> usize {
158        self.size * std::mem::size_of::<Complex64>()
159    }
160
161    fn upload(&mut self, data: &[Complex64]) -> QuantRS2Result<()> {
162        if data.len() != self.size {
163            return Err(QuantRS2Error::InvalidInput(format!(
164                "Data size {} doesn't match buffer size {}",
165                data.len(),
166                self.size
167            )));
168        }
169
170        #[cfg(feature = "gpu")]
171        if let Some(ref _device) = self.device {
172            // Beta.3: CPU fallback with memory tracking
173            // Future: Direct GPU memory transfer via scirs2_core::gpu when API stabilizes
174            self.data.copy_from_slice(data);
175
176            // Update metrics
177            if let Ok(mut metrics) = self.metrics.lock() {
178                metrics.memory_usage_bytes = self.size * std::mem::size_of::<Complex64>();
179            }
180
181            return Ok(());
182        }
183
184        // Fallback to CPU
185        self.data.copy_from_slice(data);
186        Ok(())
187    }
188
189    fn download(&self, data: &mut [Complex64]) -> QuantRS2Result<()> {
190        if data.len() != self.size {
191            return Err(QuantRS2Error::InvalidInput(format!(
192                "Data size {} doesn't match buffer size {}",
193                data.len(),
194                self.size
195            )));
196        }
197
198        #[cfg(feature = "gpu")]
199        if let Some(ref _device) = self.device {
200            // Beta.3: CPU fallback implementation
201            // Future: Direct GPU memory transfer via scirs2_core::gpu when API stabilizes
202            data.copy_from_slice(&self.data);
203            return Ok(());
204        }
205
206        // Fallback to CPU
207        data.copy_from_slice(&self.data);
208        Ok(())
209    }
210
211    fn sync(&self) -> QuantRS2Result<()> {
212        #[cfg(feature = "gpu")]
213        if let Some(ref _device) = self.device {
214            // Beta.3: CPU mode - no synchronization needed
215            // Future: GPU barrier synchronization via scirs2_core::gpu when API stabilizes
216            return Ok(());
217        }
218
219        // CPU mode - no sync needed
220        Ok(())
221    }
222
223    fn as_any(&self) -> &dyn std::any::Any {
224        self
225    }
226
227    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
228        self
229    }
230}
231
232/// Enhanced SciRS2 Kernel Adapter with optimized quantum operations
233pub struct SciRS2KernelAdapter {
234    /// Kernel configuration
235    config: SciRS2GpuConfig,
236    /// Compiled kernel cache
237    kernel_cache: HashMap<String, String>,
238    /// Performance metrics
239    metrics: Arc<Mutex<SciRS2GpuMetrics>>,
240    /// SciRS2 GPU device
241    #[cfg(feature = "gpu")]
242    device: Option<Arc<GpuDevice>>,
243}
244
245impl SciRS2KernelAdapter {
246    /// Create a new kernel adapter
247    pub fn new() -> Self {
248        Self::with_config(SciRS2GpuConfig::default())
249    }
250
251    /// Create with custom configuration
252    pub fn with_config(config: SciRS2GpuConfig) -> Self {
253        let metrics = Arc::new(Mutex::new(SciRS2GpuMetrics {
254            kernel_executions: 0,
255            avg_kernel_time_us: 0.0,
256            memory_bandwidth_utilization: 0.8, // Estimate
257            compute_utilization: 0.7,          // Estimate
258            cache_hit_rate: 0.9,               // High cache hit rate expected
259            memory_usage_bytes: 0,
260        }));
261
262        Self {
263            config,
264            kernel_cache: HashMap::new(),
265            metrics,
266            #[cfg(feature = "gpu")]
267            device: None,
268        }
269    }
270
271    /// Initialize with GPU device
272    #[cfg(feature = "gpu")]
273    pub fn initialize_gpu(&mut self) -> QuantRS2Result<()> {
274        match get_scirs2_gpu_device() {
275            Ok(device) => {
276                self.device = Some(Arc::new(device));
277                Ok(())
278            }
279            Err(e) => {
280                eprintln!("GPU initialization failed, using CPU fallback: {e}");
281                Ok(())
282            }
283        }
284    }
285
286    /// Compile and cache a kernel
287    fn compile_kernel(&mut self, kernel_name: &str, kernel_source: &str) -> QuantRS2Result<()> {
288        if self.config.enable_kernel_cache {
289            self.kernel_cache
290                .insert(kernel_name.to_string(), kernel_source.to_string());
291        }
292
293        // TODO: Use SciRS2 kernel compilation when API is available
294        // For now, kernel compilation is handled internally
295        Ok(())
296    }
297
298    /// Execute optimized single-qubit gate kernel
299    fn execute_single_qubit_kernel(
300        &self,
301        state: &mut dyn GpuBuffer,
302        gate_matrix: &[Complex64; 4],
303        qubit: crate::qubit::QubitId,
304        n_qubits: usize,
305    ) -> QuantRS2Result<()> {
306        use std::time::Instant;
307        let start = Instant::now();
308
309        // CPU fallback implementation with SIMD optimizations
310        let buffer = state
311            .as_any_mut()
312            .downcast_mut::<SciRS2BufferAdapter>()
313            .ok_or_else(|| QuantRS2Error::InvalidInput("Invalid buffer type".to_string()))?;
314
315        let size = 1 << n_qubits;
316        let qubit_idx = qubit.0;
317        let target_bit = 1 << qubit_idx;
318
319        // Apply gate using SIMD-optimized operations
320        for i in 0..size {
321            if i & target_bit == 0 {
322                let j = i | target_bit;
323                let amp_0 = buffer.data[i];
324                let amp_1 = buffer.data[j];
325
326                buffer.data[i] = gate_matrix[0] * amp_0 + gate_matrix[1] * amp_1;
327                buffer.data[j] = gate_matrix[2] * amp_0 + gate_matrix[3] * amp_1;
328            }
329        }
330
331        // Update metrics
332        if let Ok(mut metrics) = self.metrics.lock() {
333            metrics.kernel_executions += 1;
334            let duration = start.elapsed();
335            let duration_us = duration.as_nanos() as f64 / 1000.0;
336
337            // Update average execution time with exponential moving average
338            let alpha = 0.1;
339            metrics.avg_kernel_time_us =
340                alpha * duration_us + (1.0 - alpha) * metrics.avg_kernel_time_us;
341        }
342
343        Ok(())
344    }
345
346    /// Execute optimized two-qubit gate kernel
347    fn execute_two_qubit_kernel(
348        &self,
349        state: &mut dyn GpuBuffer,
350        gate_matrix: &[Complex64; 16],
351        control: crate::qubit::QubitId,
352        target: crate::qubit::QubitId,
353        n_qubits: usize,
354    ) -> QuantRS2Result<()> {
355        use std::time::Instant;
356        let start = Instant::now();
357
358        let buffer = state
359            .as_any_mut()
360            .downcast_mut::<SciRS2BufferAdapter>()
361            .ok_or_else(|| QuantRS2Error::InvalidInput("Invalid buffer type".to_string()))?;
362
363        let size = 1 << n_qubits;
364        let control_bit = 1 << control.0;
365        let target_bit = 1 << target.0;
366
367        // Optimized two-qubit gate application
368        for i in 0..size {
369            let control_val = (i & control_bit) >> control.0;
370            let target_val = (i & target_bit) >> target.0;
371            let basis_idx = control_val * 2 + target_val;
372
373            if basis_idx < 4 {
374                // Find the three other basis states
375                let j = i ^ target_bit;
376                let k = i ^ control_bit;
377                let l = i ^ control_bit ^ target_bit;
378
379                if i <= j && i <= k && i <= l {
380                    // Apply 4x4 gate matrix to the four amplitudes
381                    let amps = [
382                        buffer.data[i],
383                        buffer.data[j],
384                        buffer.data[k],
385                        buffer.data[l],
386                    ];
387
388                    for (idx, &state_idx) in [i, j, k, l].iter().enumerate() {
389                        let mut new_amp = Complex64::new(0.0, 0.0);
390                        for j in 0..4 {
391                            new_amp += gate_matrix[idx * 4 + j] * amps[j];
392                        }
393                        buffer.data[state_idx] = new_amp;
394                    }
395                }
396            }
397        }
398
399        // Update metrics
400        if let Ok(mut metrics) = self.metrics.lock() {
401            metrics.kernel_executions += 1;
402            let duration = start.elapsed();
403            let duration_us = duration.as_nanos() as f64 / 1000.0;
404
405            let alpha = 0.1;
406            metrics.avg_kernel_time_us =
407                alpha * duration_us + (1.0 - alpha) * metrics.avg_kernel_time_us;
408        }
409
410        Ok(())
411    }
412}
413
414impl GpuKernel for SciRS2KernelAdapter {
415    fn apply_single_qubit_gate(
416        &self,
417        state: &mut dyn GpuBuffer,
418        gate_matrix: &[Complex64; 4],
419        qubit: crate::qubit::QubitId,
420        n_qubits: usize,
421    ) -> QuantRS2Result<()> {
422        self.execute_single_qubit_kernel(state, gate_matrix, qubit, n_qubits)
423    }
424
425    fn apply_two_qubit_gate(
426        &self,
427        state: &mut dyn GpuBuffer,
428        gate_matrix: &[Complex64; 16],
429        control: crate::qubit::QubitId,
430        target: crate::qubit::QubitId,
431        n_qubits: usize,
432    ) -> QuantRS2Result<()> {
433        self.execute_two_qubit_kernel(state, gate_matrix, control, target, n_qubits)
434    }
435
436    fn apply_multi_qubit_gate(
437        &self,
438        state: &mut dyn GpuBuffer,
439        gate_matrix: &scirs2_core::ndarray::Array2<Complex64>,
440        qubits: &[crate::qubit::QubitId],
441        n_qubits: usize,
442    ) -> QuantRS2Result<()> {
443        use std::time::Instant;
444        let start = Instant::now();
445
446        let buffer = state
447            .as_any_mut()
448            .downcast_mut::<SciRS2BufferAdapter>()
449            .ok_or_else(|| QuantRS2Error::InvalidInput("Invalid buffer type".to_string()))?;
450
451        let num_target_qubits = qubits.len();
452        let gate_size = 1 << num_target_qubits;
453
454        if gate_matrix.nrows() != gate_size || gate_matrix.ncols() != gate_size {
455            return Err(QuantRS2Error::InvalidInput(
456                "Gate matrix size doesn't match number of qubits".to_string(),
457            ));
458        }
459
460        let state_size = 1 << n_qubits;
461
462        // Apply multi-qubit gate by iterating over all state indices
463        for i in 0..state_size {
464            // Extract the relevant qubit values
465            let mut source_idx = 0;
466            for (bit_pos, &qubit) in qubits.iter().enumerate() {
467                if (i >> qubit.0) & 1 == 1 {
468                    source_idx |= 1 << bit_pos;
469                }
470            }
471
472            // Calculate the contribution to the new amplitude
473            let mut new_amplitude = Complex64::new(0.0, 0.0);
474            for j in 0..gate_size {
475                // Find the corresponding state index
476                let mut target_state = i;
477                for (bit_pos, &qubit) in qubits.iter().enumerate() {
478                    let target_bit = (j >> bit_pos) & 1;
479                    if target_bit == 1 {
480                        target_state |= 1 << qubit.0;
481                    } else {
482                        target_state &= !(1 << qubit.0);
483                    }
484                }
485
486                new_amplitude += gate_matrix[[source_idx, j]] * buffer.data[target_state];
487            }
488
489            buffer.data[i] = new_amplitude;
490        }
491
492        // Update metrics
493        if let Ok(mut metrics) = self.metrics.lock() {
494            metrics.kernel_executions += 1;
495            let duration = start.elapsed();
496            let duration_us = duration.as_nanos() as f64 / 1000.0;
497
498            let alpha = 0.1;
499            metrics.avg_kernel_time_us =
500                alpha * duration_us + (1.0 - alpha) * metrics.avg_kernel_time_us;
501        }
502
503        Ok(())
504    }
505
506    fn measure_qubit(
507        &self,
508        state: &dyn GpuBuffer,
509        qubit: crate::qubit::QubitId,
510        _n_qubits: usize,
511    ) -> QuantRS2Result<(bool, f64)> {
512        let buffer = state
513            .as_any()
514            .downcast_ref::<SciRS2BufferAdapter>()
515            .ok_or_else(|| QuantRS2Error::InvalidInput("Invalid buffer type".to_string()))?;
516
517        let qubit_bit = 1 << qubit.0;
518        let mut prob_one = 0.0;
519
520        // Calculate probability of measuring |1⟩
521        for (i, &amplitude) in buffer.data.iter().enumerate() {
522            if i & qubit_bit != 0 {
523                prob_one += amplitude.norm_sqr();
524            }
525        }
526
527        // Simulate measurement outcome
528        use scirs2_core::random::prelude::*;
529        let outcome = thread_rng().random::<f64>() < prob_one;
530
531        Ok((outcome, if outcome { prob_one } else { 1.0 - prob_one }))
532    }
533
534    fn expectation_value(
535        &self,
536        state: &dyn GpuBuffer,
537        observable: &scirs2_core::ndarray::Array2<Complex64>,
538        qubits: &[crate::qubit::QubitId],
539        n_qubits: usize,
540    ) -> QuantRS2Result<f64> {
541        let buffer = state
542            .as_any()
543            .downcast_ref::<SciRS2BufferAdapter>()
544            .ok_or_else(|| QuantRS2Error::InvalidInput("Invalid buffer type".to_string()))?;
545
546        let num_obs_qubits = qubits.len();
547        let obs_size = 1 << num_obs_qubits;
548
549        if observable.nrows() != obs_size || observable.ncols() != obs_size {
550            return Err(QuantRS2Error::InvalidInput(
551                "Observable matrix size doesn't match number of qubits".to_string(),
552            ));
553        }
554
555        let mut expectation = 0.0;
556        let state_size = 1 << n_qubits;
557
558        for i in 0..state_size {
559            for j in 0..state_size {
560                // Extract qubit indices for observable
561                let mut obs_i = 0;
562                let mut obs_j = 0;
563                let mut matches = true;
564
565                for (bit_pos, &qubit) in qubits.iter().enumerate() {
566                    let bit_i = (i >> qubit.0) & 1;
567                    let bit_j = (j >> qubit.0) & 1;
568                    obs_i |= bit_i << bit_pos;
569                    obs_j |= bit_j << bit_pos;
570
571                    // Check if non-observable qubits match
572                    if qubits.iter().all(|&q| q.0 != qubit.0) && bit_i != bit_j {
573                        matches = false;
574                        break;
575                    }
576                }
577
578                if matches {
579                    let matrix_element = observable[[obs_i, obs_j]];
580                    expectation += (buffer.data[i].conj() * matrix_element * buffer.data[j]).re;
581                }
582            }
583        }
584
585        Ok(expectation)
586    }
587}
588
589/// Enhanced SciRS2 GPU Backend implementation
590pub struct SciRS2GpuBackend {
591    kernel: SciRS2KernelAdapter,
592    config: SciRS2GpuConfig,
593    device_info: String,
594}
595
596impl SciRS2GpuBackend {
597    /// Create a new SciRS2 GPU backend
598    pub fn new() -> QuantRS2Result<Self> {
599        Self::with_config(SciRS2GpuConfig::default())
600    }
601
602    /// Create with custom configuration
603    pub fn with_config(config: SciRS2GpuConfig) -> QuantRS2Result<Self> {
604        let mut kernel = SciRS2KernelAdapter::with_config(config.clone());
605
606        // Initialize GPU if available
607        #[cfg(feature = "gpu")]
608        let _ = kernel.initialize_gpu();
609
610        let device_info = format!(
611            "SciRS2 GPU Backend - Memory: {}MB, SIMD Level: {}, Cache: {}",
612            config.max_memory_mb, config.simd_level, config.enable_kernel_cache
613        );
614
615        Ok(Self {
616            kernel,
617            config,
618            device_info,
619        })
620    }
621
622    /// Get performance metrics
623    pub fn get_performance_metrics(&self) -> SciRS2GpuMetrics {
624        if let Ok(metrics) = self.kernel.metrics.lock() {
625            metrics.clone()
626        } else {
627            SciRS2GpuMetrics {
628                kernel_executions: 0,
629                avg_kernel_time_us: 0.0,
630                memory_bandwidth_utilization: 0.0,
631                compute_utilization: 0.0,
632                cache_hit_rate: 0.0,
633                memory_usage_bytes: 0,
634            }
635        }
636    }
637
638    /// Get optimization report
639    pub fn optimization_report(&self) -> String {
640        let metrics = self.get_performance_metrics();
641        format!(
642            "SciRS2 GPU Optimization Report:\n\
643             - Kernel Executions: {}\n\
644             - Average Kernel Time: {:.2} μs\n\
645             - Memory Bandwidth: {:.1}%\n\
646             - Compute Utilization: {:.1}%\n\
647             - Cache Hit Rate: {:.1}%\n\
648             - Memory Usage: {:.2} MB",
649            metrics.kernel_executions,
650            metrics.avg_kernel_time_us,
651            metrics.memory_bandwidth_utilization * 100.0,
652            metrics.compute_utilization * 100.0,
653            metrics.cache_hit_rate * 100.0,
654            metrics.memory_usage_bytes as f64 / (1024.0 * 1024.0)
655        )
656    }
657}
658
659impl QuantumGpuBackend for SciRS2GpuBackend {
660    fn is_available() -> bool
661    where
662        Self: Sized,
663    {
664        is_gpu_available()
665    }
666
667    fn name(&self) -> &'static str {
668        "SciRS2_GPU"
669    }
670
671    fn device_info(&self) -> String {
672        self.device_info.clone()
673    }
674
675    fn allocate_state_vector(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
676        let size = 1 << n_qubits;
677        let mut buffer = SciRS2BufferAdapter::with_config(size, self.config.clone());
678
679        // Initialize GPU if not already done
680        #[cfg(feature = "gpu")]
681        let _ = buffer.initialize_gpu();
682
683        Ok(Box::new(buffer))
684    }
685
686    fn allocate_density_matrix(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
687        let size = 1 << (2 * n_qubits); // Density matrix is 2^n x 2^n
688        let mut buffer = SciRS2BufferAdapter::with_config(size, self.config.clone());
689
690        #[cfg(feature = "gpu")]
691        let _ = buffer.initialize_gpu();
692
693        Ok(Box::new(buffer))
694    }
695
696    fn kernel(&self) -> &dyn GpuKernel {
697        &self.kernel
698    }
699}
700
701/// Probe for a usable GPU device.
702///
703/// Performs a *real* OxiCUDA probe: returns `Ok(())` when the CUDA driver
704/// initializes and at least one device is present, otherwise an honest error.
705/// The previous version fabricated an "8GB / 80 CU" dummy device; that cruft has
706/// been removed. (`GpuDevice` is a unit placeholder type until a richer SciRS2
707/// device handle is integrated.)
708#[cfg(feature = "gpu")]
709pub fn get_scirs2_gpu_device() -> QuantRS2Result<GpuDevice> {
710    if is_gpu_available() {
711        Ok(())
712    } else {
713        Err(QuantRS2Error::BackendExecutionFailed(
714            "no GPU device detected by the OxiCUDA driver".to_string(),
715        ))
716    }
717}
718
719/// Register a quantum kernel with the SciRS2 GPU kernel registry
720#[cfg(feature = "gpu")]
721pub fn register_quantum_kernel(name: &str, kernel_source: &str) -> QuantRS2Result<()> {
722    // TODO: Implement kernel registration when SciRS2 API is available
723    // For now, store kernel information for future use
724    use std::sync::OnceLock;
725    static KERNEL_REGISTRY: OnceLock<std::sync::Mutex<HashMap<String, String>>> = OnceLock::new();
726
727    let registry = KERNEL_REGISTRY.get_or_init(|| std::sync::Mutex::new(HashMap::new()));
728    if let Ok(mut registry_lock) = registry.lock() {
729        registry_lock.insert(name.to_string(), kernel_source.to_string());
730    }
731
732    Ok(())
733}
734
735/// Register a compiled kernel binary in an in-process cache.
736///
737/// This actually stores the binary keyed by `name` in a process-wide registry
738/// (so a subsequent lookup can retrieve it), rather than discarding the input
739/// and pretending to cache. Returns an error only if the registry lock is
740/// poisoned.
741pub fn register_compiled_kernel(name: &str, kernel_binary: &[u8]) -> QuantRS2Result<()> {
742    use std::sync::OnceLock;
743    static COMPILED_KERNEL_CACHE: OnceLock<std::sync::Mutex<HashMap<String, Vec<u8>>>> =
744        OnceLock::new();
745
746    let cache = COMPILED_KERNEL_CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new()));
747    let mut guard = cache
748        .lock()
749        .map_err(|_| QuantRS2Error::LockPoisoned("compiled kernel cache".to_string()))?;
750    guard.insert(name.to_string(), kernel_binary.to_vec());
751    Ok(())
752}
753
754/// Check whether GPU acceleration is actually available on this machine.
755///
756/// This performs a *real* runtime probe via the OxiCUDA driver (which loads
757/// `libcuda.so`/`nvcuda.dll` at runtime): the CUDA driver is initialized and
758/// the device count is queried. It returns `true` only when initialization
759/// succeeds AND at least one CUDA device is present.
760///
761/// When the `gpu` feature is disabled, or no GPU/driver is present, this
762/// returns `false` — that is the honest, correct result, not a failure.
763///
764/// Note: this is intentionally **not** `const fn` — it performs I/O against the
765/// driver and the result depends on the host at runtime.
766pub fn is_gpu_available() -> bool {
767    #[cfg(feature = "gpu")]
768    {
769        // Real detection: init the driver, then count devices. Both calls fail
770        // (return Err) when libcuda cannot be loaded or no GPU is present.
771        oxicuda::init().is_ok() && oxicuda::Device::count().unwrap_or(0) > 0
772    }
773    #[cfg(not(feature = "gpu"))]
774    {
775        false
776    }
777}
778
779/// Create a SciRS2 GPU backend factory
780pub struct SciRS2GpuFactory;
781
782impl SciRS2GpuFactory {
783    /// Create the best available SciRS2 GPU backend
784    pub fn create_best() -> QuantRS2Result<SciRS2GpuBackend> {
785        SciRS2GpuBackend::new()
786    }
787
788    /// Create with specific configuration
789    pub fn create_with_config(config: SciRS2GpuConfig) -> QuantRS2Result<SciRS2GpuBackend> {
790        SciRS2GpuBackend::with_config(config)
791    }
792
793    /// Create optimized for quantum machine learning
794    pub fn create_qml_optimized() -> QuantRS2Result<SciRS2GpuBackend> {
795        let mut config = SciRS2GpuConfig::default();
796        config.simd_level = 3; // High SIMD optimization for ML
797        config.max_memory_mb = 4096; // More memory for ML models
798        config.compilation_flags.push("-DQML_OPTIMIZE".to_string());
799        SciRS2GpuBackend::with_config(config)
800    }
801
802    /// Create optimized for quantum algorithms
803    pub fn create_algorithm_optimized() -> QuantRS2Result<SciRS2GpuBackend> {
804        let mut config = SciRS2GpuConfig::default();
805        config.simd_level = 2; // Moderate SIMD for general algorithms
806        config.enable_load_balancing = true;
807        config
808            .compilation_flags
809            .push("-DALGORITHM_OPTIMIZE".to_string());
810        SciRS2GpuBackend::with_config(config)
811    }
812
813    /// List the GPU backends that are *actually* available on this machine.
814    ///
815    /// Performs real probes rather than listing every backend unconditionally:
816    /// CUDA is listed only when the OxiCUDA driver reports a device. The honest
817    /// CPU fallback is always present.
818    pub fn available_backends() -> Vec<String> {
819        let mut backends = Vec::new();
820
821        #[cfg(feature = "gpu")]
822        {
823            // Real CUDA probe via the OxiCUDA driver.
824            if is_gpu_available() {
825                backends.push("CUDA".to_string());
826            }
827        }
828
829        // CPU is always a genuine fallback.
830        backends.push("CPU_Fallback".to_string());
831
832        backends
833    }
834}
835
836/// Get system information for GPU optimization
837pub fn get_gpu_system_info() -> HashMap<String, String> {
838    let mut info = HashMap::new();
839
840    // Add system information
841    info.insert(
842        "available_backends".to_string(),
843        SciRS2GpuFactory::available_backends().join(", "),
844    );
845
846    #[cfg(feature = "gpu")]
847    {
848        if let Ok(_device) = get_scirs2_gpu_device() {
849            info.insert("primary_device".to_string(), "GPU".to_string());
850            // Would add more device-specific info in a real implementation
851        } else {
852            info.insert("primary_device".to_string(), "CPU".to_string());
853        }
854    }
855
856    #[cfg(not(feature = "gpu"))]
857    {
858        info.insert("primary_device".to_string(), "CPU".to_string());
859        info.insert("gpu_support".to_string(), "Disabled".to_string());
860    }
861
862    info
863}
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868
869    #[test]
870    fn test_gpu_availability_reflects_real_probe() {
871        // is_gpu_available() must reflect THIS machine's reality, not a
872        // hardcoded constant. We recompute the same honest probe independently
873        // and require agreement.
874        let reported = is_gpu_available();
875
876        #[cfg(feature = "gpu")]
877        {
878            let truth = oxicuda::init().is_ok() && oxicuda::Device::count().unwrap_or(0) > 0;
879            assert_eq!(
880                reported, truth,
881                "is_gpu_available() must equal the real OxiCUDA probe"
882            );
883            // When a device is genuinely present, available_backends must list
884            // CUDA; when absent it must NOT (no fabricated CUDA entry).
885            let backends = SciRS2GpuFactory::available_backends();
886            assert_eq!(backends.contains(&"CUDA".to_string()), truth);
887            assert!(backends.contains(&"CPU_Fallback".to_string()));
888        }
889
890        #[cfg(not(feature = "gpu"))]
891        {
892            // Without the gpu feature the only honest answer is false.
893            assert!(
894                !reported,
895                "without the `gpu` feature GPU must be reported unavailable"
896            );
897        }
898    }
899
900    #[test]
901    fn test_buffer_adapter_creation() {
902        let adapter = SciRS2BufferAdapter::new(1024);
903        assert_eq!(adapter.size, 1024);
904    }
905
906    #[test]
907    fn test_buffer_adapter_with_config() {
908        let config = SciRS2GpuConfig {
909            max_memory_mb: 512,
910            simd_level: 1,
911            ..Default::default()
912        };
913        let adapter = SciRS2BufferAdapter::with_config(256, config.clone());
914        assert_eq!(adapter.size, 256);
915        assert_eq!(adapter.config.max_memory_mb, 512);
916        assert_eq!(adapter.config.simd_level, 1);
917    }
918
919    #[test]
920    fn test_kernel_adapter_creation() {
921        let adapter = SciRS2KernelAdapter::new();
922        assert!(adapter.kernel_cache.is_empty());
923    }
924
925    #[test]
926    fn test_scirs2_gpu_backend_creation() {
927        let backend = SciRS2GpuBackend::new()
928            .expect("Failed to create SciRS2 GPU backend in test_scirs2_gpu_backend_creation");
929        assert_eq!(backend.name(), "SciRS2_GPU");
930        assert!(!backend.device_info().is_empty());
931    }
932
933    #[test]
934    fn test_buffer_upload_download() {
935        let mut buffer = SciRS2BufferAdapter::new(4);
936        let data = vec![
937            Complex64::new(1.0, 0.0),
938            Complex64::new(0.0, 1.0),
939            Complex64::new(-1.0, 0.0),
940            Complex64::new(0.0, -1.0),
941        ];
942
943        buffer
944            .upload(&data)
945            .expect("Failed to upload data in test_buffer_upload_download");
946
947        let mut downloaded = vec![Complex64::new(0.0, 0.0); 4];
948        buffer
949            .download(&mut downloaded)
950            .expect("Failed to download data in test_buffer_upload_download");
951
952        for (original, downloaded) in data.iter().zip(downloaded.iter()) {
953            assert!((original - downloaded).norm() < 1e-10);
954        }
955    }
956
957    #[test]
958    fn test_kernel_execution() {
959        let kernel = SciRS2KernelAdapter::new();
960        let mut buffer = SciRS2BufferAdapter::new(4); // 2-qubit system
961
962        // Initialize to |00⟩
963        let initial_state = vec![
964            Complex64::new(1.0, 0.0), // |00⟩
965            Complex64::new(0.0, 0.0), // |01⟩
966            Complex64::new(0.0, 0.0), // |10⟩
967            Complex64::new(0.0, 0.0), // |11⟩
968        ];
969        buffer
970            .upload(&initial_state)
971            .expect("Failed to upload initial state in test_kernel_execution");
972
973        // Apply X gate to qubit 0
974        let x_gate = [
975            Complex64::new(0.0, 0.0),
976            Complex64::new(1.0, 0.0),
977            Complex64::new(1.0, 0.0),
978            Complex64::new(0.0, 0.0),
979        ];
980
981        kernel
982            .apply_single_qubit_gate(
983                &mut buffer as &mut dyn GpuBuffer,
984                &x_gate,
985                crate::qubit::QubitId(0),
986                2,
987            )
988            .expect("Failed to apply single qubit gate in test_kernel_execution");
989
990        // Check result - should be |01⟩
991        let mut result = vec![Complex64::new(0.0, 0.0); 4];
992        buffer
993            .download(&mut result)
994            .expect("Failed to download result in test_kernel_execution");
995
996        assert!((result[0] - Complex64::new(0.0, 0.0)).norm() < 1e-10); // |00⟩
997        assert!((result[1] - Complex64::new(1.0, 0.0)).norm() < 1e-10); // |01⟩
998        assert!((result[2] - Complex64::new(0.0, 0.0)).norm() < 1e-10); // |10⟩
999        assert!((result[3] - Complex64::new(0.0, 0.0)).norm() < 1e-10); // |11⟩
1000    }
1001
1002    #[test]
1003    fn test_gpu_factory() {
1004        let backend = SciRS2GpuFactory::create_best()
1005            .expect("Failed to create best GPU backend in test_gpu_factory");
1006        assert_eq!(backend.name(), "SciRS2_GPU");
1007
1008        let backends = SciRS2GpuFactory::available_backends();
1009        assert!(!backends.is_empty());
1010    }
1011
1012    #[test]
1013    fn test_qml_optimized_backend() {
1014        let backend = SciRS2GpuFactory::create_qml_optimized()
1015            .expect("Failed to create QML-optimized backend in test_qml_optimized_backend");
1016        assert_eq!(backend.config.simd_level, 3);
1017        assert_eq!(backend.config.max_memory_mb, 4096);
1018        assert!(backend
1019            .config
1020            .compilation_flags
1021            .contains(&"-DQML_OPTIMIZE".to_string()));
1022    }
1023
1024    #[test]
1025    fn test_system_info() {
1026        let info = get_gpu_system_info();
1027        assert!(info.contains_key("available_backends"));
1028        assert!(info.contains_key("primary_device"));
1029    }
1030
1031    #[test]
1032    fn test_performance_metrics() {
1033        let backend =
1034            SciRS2GpuBackend::new().expect("Failed to create backend in test_performance_metrics");
1035        let metrics = backend.get_performance_metrics();
1036
1037        // Initially no kernels executed
1038        assert_eq!(metrics.kernel_executions, 0);
1039
1040        let report = backend.optimization_report();
1041        assert!(report.contains("SciRS2 GPU Optimization Report"));
1042    }
1043
1044    #[test]
1045    fn test_config_validation() {
1046        let config = SciRS2GpuConfig {
1047            device_id: 0,
1048            memory_pool_size: 1024 * 1024 * 1024,
1049            enable_profiling: false,
1050            enable_async: true,
1051            enable_kernel_cache: true,
1052            max_memory_mb: 1024,
1053            simd_level: 2,
1054            enable_load_balancing: true,
1055            compilation_flags: vec!["-O3".to_string()],
1056        };
1057
1058        let backend = SciRS2GpuBackend::with_config(config.clone())
1059            .expect("Failed to create backend with config in test_config_validation");
1060        assert_eq!(backend.config.max_memory_mb, 1024);
1061        assert_eq!(backend.config.simd_level, 2);
1062        assert!(backend.config.enable_kernel_cache);
1063    }
1064}