Skip to main content

quantrs2_core/gpu/
specialized_kernels.rs

1//! Enhanced GPU kernel optimization for specialized quantum gates
2//!
3//! This module provides high-performance GPU kernels optimized for specialized quantum gates
4//! including holonomic gates, post-quantum cryptography gates, and quantum ML gates.
5//! It leverages tensor cores, optimized memory access patterns, and gate fusion for maximum performance.
6
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::GateOp,
10    qubit::QubitId,
11};
12use scirs2_core::Complex64;
13use std::collections::HashMap;
14use std::sync::{Arc, Mutex};
15
16/// Map an OxiCUDA driver error into a `QuantRS2Error` without losing detail.
17#[cfg(feature = "gpu")]
18fn map_cuda_err(err: oxicuda::CudaError) -> QuantRS2Error {
19    QuantRS2Error::BackendExecutionFailed(format!("OxiCUDA driver error: {err:?}"))
20}
21
22/// Honest error for a specialized CUDA kernel whose device source is not yet authored.
23fn uncompiled_kernel_err(name: &str) -> QuantRS2Error {
24    QuantRS2Error::UnsupportedOperation(format!(
25        "specialized CUDA kernel `{name}` is not available: real PTX device code has not been \
26         authored yet (no fabricated kernel is returned)"
27    ))
28}
29
30/// Honest error for a specialized WebGPU shader whose WGSL source is not yet authored.
31fn uncompiled_shader_err(name: &str) -> QuantRS2Error {
32    QuantRS2Error::UnsupportedOperation(format!(
33        "specialized WebGPU shader `{name}` is not available: real WGSL device code has not been \
34         authored yet (no fabricated shader is returned)"
35    ))
36}
37
38/// Query real WebGPU adapter limits via wgpu.
39///
40/// Requests the first available adapter (any backend) and reports its genuine
41/// `max_compute_workgroup_size_x`. Returns an honest error when no adapter is
42/// present — never a hardcoded limit.
43#[cfg(feature = "gpu")]
44fn query_webgpu_limits() -> QuantRS2Result<WebGpuLimits> {
45    // wgpu 29: `InstanceDescriptor` has no `Default`; use the explicit ctor.
46    let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
47    let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
48        power_preference: wgpu::PowerPreference::HighPerformance,
49        force_fallback_adapter: false,
50        compatible_surface: None,
51        // wgpu 30: `apply_limit_buckets` is a new anti-fingerprinting knob (relevant to
52        // untrusted/browser contexts); native driver-limits queries want the real limits,
53        // so leave it at its `Default` (`false`). `..Default::default()` also keeps this
54        // call forward-compatible with any future fields wgpu adds to this struct.
55        ..Default::default()
56    }))
57    .map_err(|e| {
58        QuantRS2Error::BackendExecutionFailed(format!("no WebGPU adapter available: {e}"))
59    })?;
60
61    let limits = adapter.limits();
62    Ok(WebGpuLimits {
63        max_compute_workgroup_size: limits.max_compute_workgroup_size_x,
64    })
65}
66
67/// Apply a dense `d x d` unitary (`d = 2^k`, row-major) to the `k` targeted
68/// qubits of a state vector, in place, on the CPU.
69///
70/// This is a genuine matrix-vector application over each targeted-qubit
71/// subspace (LSB qubit ordering, matching the rest of the crate): for every
72/// fixed configuration of the untargeted qubits, the `d` amplitudes selected by
73/// the targeted qubits are gathered and updated as `out[i] = Σ_j M[i*d+j]·in[j]`.
74///
75/// Returns an error on dimension mismatch or out-of-range qubit — it never
76/// silently skips work.
77fn apply_dense_gate_cpu(
78    state: &mut [Complex64],
79    matrix: &[Complex64],
80    target_qubits: &[QubitId],
81) -> QuantRS2Result<()> {
82    let state_len = state.len();
83    if state_len == 0 || !state_len.is_power_of_two() {
84        return Err(QuantRS2Error::InvalidInput(format!(
85            "state vector length must be a non-zero power of two, got {state_len}"
86        )));
87    }
88    let n_qubits = state_len.trailing_zeros() as usize;
89
90    let gate_qubits = target_qubits.len();
91    if gate_qubits == 0 {
92        return Err(QuantRS2Error::InvalidInput(
93            "holonomic/dense gate requires at least one target qubit".to_string(),
94        ));
95    }
96    if gate_qubits > n_qubits {
97        return Err(QuantRS2Error::InvalidInput(format!(
98            "gate acts on {gate_qubits} qubits but the state only has {n_qubits}"
99        )));
100    }
101
102    let gate_dim = 1usize << gate_qubits;
103    if matrix.len() != gate_dim * gate_dim {
104        return Err(QuantRS2Error::InvalidInput(format!(
105            "gate matrix has {} entries but a {gate_dim}x{gate_dim} ({}) matrix was expected",
106            matrix.len(),
107            gate_dim * gate_dim
108        )));
109    }
110
111    // Sorted, de-duplicated, in-range target bit positions (LSB ordering).
112    let mut qubit_indices: Vec<usize> = target_qubits.iter().map(|q| q.0 as usize).collect();
113    qubit_indices.sort_unstable();
114    for &idx in &qubit_indices {
115        if idx >= n_qubits {
116            return Err(QuantRS2Error::InvalidInput(format!(
117                "target qubit {idx} out of range for a {n_qubits}-qubit state"
118            )));
119        }
120    }
121    qubit_indices.dedup();
122    if qubit_indices.len() != gate_qubits {
123        return Err(QuantRS2Error::InvalidInput(
124            "duplicate target qubits supplied to dense gate".to_string(),
125        ));
126    }
127
128    let unaffected_qubits = n_qubits - gate_qubits;
129    let iterations = 1usize << unaffected_qubits;
130
131    let mut indices = vec![0usize; gate_dim];
132    let mut temp = vec![Complex64::new(0.0, 0.0); gate_dim];
133
134    for i in 0..iterations {
135        // Scatter the `unaffected_qubits` index bits of `i` into the positions
136        // NOT occupied by target qubits to form the base index.
137        let mut base = 0usize;
138        let mut remaining = i;
139        let mut qubit_pos = 0usize;
140        for bit in 0..n_qubits {
141            if qubit_pos < gate_qubits && bit == qubit_indices[qubit_pos] {
142                qubit_pos += 1;
143            } else {
144                if remaining & 1 == 1 {
145                    base |= 1 << bit;
146                }
147                remaining >>= 1;
148            }
149        }
150
151        // Enumerate the `gate_dim` amplitudes of this subspace.
152        for (j, slot) in indices.iter_mut().enumerate() {
153            let mut idx = base;
154            for (k, &qubit_idx) in qubit_indices.iter().enumerate() {
155                if (j >> k) & 1 == 1 {
156                    idx |= 1 << qubit_idx;
157                }
158            }
159            *slot = idx;
160        }
161
162        // Gather, multiply by the dense matrix, scatter back.
163        for (t, &idx) in indices.iter().enumerate() {
164            temp[t] = state[idx];
165        }
166        for (row, &idx) in indices.iter().enumerate() {
167            let mut sum = Complex64::new(0.0, 0.0);
168            let row_off = row * gate_dim;
169            for (col, &amp) in temp.iter().enumerate() {
170                sum += matrix[row_off + col] * amp;
171            }
172            state[idx] = sum;
173        }
174    }
175
176    Ok(())
177}
178
179/// Enhanced GPU kernel manager for specialized gates
180pub struct SpecializedGpuKernels {
181    /// CUDA context for kernel execution
182    cuda_context: Option<CudaSpecializedContext>,
183    /// WebGPU context for cross-platform support
184    webgpu_context: Option<WebGpuSpecializedContext>,
185    /// Kernel cache for compiled kernels
186    kernel_cache: Arc<Mutex<KernelCache>>,
187    /// Performance statistics
188    performance_stats: Arc<Mutex<PerformanceStats>>,
189    /// Optimization configuration
190    config: OptimizationConfig,
191}
192
193/// CUDA context specialized for quantum gates
194pub struct CudaSpecializedContext {
195    /// Device compute capability
196    #[allow(dead_code)]
197    compute_capability: (i32, i32),
198    /// Tensor core availability
199    has_tensor_cores: bool,
200    /// Maximum shared memory per block
201    #[allow(dead_code)]
202    max_shared_memory: usize,
203    /// Warp size
204    #[allow(dead_code)]
205    warp_size: usize,
206    /// Compiled kernels
207    kernels: HashMap<String, CompiledKernel>,
208}
209
210/// WebGPU context for cross-platform support
211pub struct WebGpuSpecializedContext {
212    /// Device limits
213    #[allow(dead_code)]
214    device_limits: WebGpuLimits,
215    /// Compiled shaders
216    #[allow(dead_code)]
217    shaders: HashMap<String, CompiledShader>,
218    /// Buffer pools for efficient memory management
219    #[allow(dead_code)]
220    buffer_pools: HashMap<String, BufferPool>,
221}
222
223/// Kernel cache for compiled GPU kernels
224pub struct KernelCache {
225    /// Cached CUDA kernels
226    #[allow(dead_code)]
227    cuda_kernels: HashMap<String, CachedCudaKernel>,
228    /// Cached WebGPU shaders
229    #[allow(dead_code)]
230    webgpu_shaders: HashMap<String, CachedWebGpuShader>,
231    /// Cache hit statistics
232    cache_stats: CacheStatistics,
233}
234
235/// Performance statistics for optimization analysis
236pub struct PerformanceStats {
237    /// Kernel execution times
238    kernel_times: HashMap<String, Vec<f64>>,
239    /// Memory bandwidth utilization
240    memory_bandwidth: HashMap<String, f64>,
241    /// Tensor core utilization
242    tensor_core_utilization: f64,
243    /// Cache hit rates
244    #[allow(dead_code)]
245    cache_hit_rates: HashMap<String, f64>,
246}
247
248/// GPU optimization configuration
249#[derive(Debug, Clone)]
250pub struct OptimizationConfig {
251    /// Enable tensor core optimization
252    pub use_tensor_cores: bool,
253    /// Enable memory access optimization
254    pub optimize_memory_access: bool,
255    /// Enable gate fusion
256    pub enable_gate_fusion: bool,
257    /// Maximum fusion chain length
258    pub max_fusion_length: usize,
259    /// Memory coalescing threshold
260    pub coalescing_threshold: usize,
261    /// Use mixed precision
262    pub use_mixed_precision: bool,
263}
264
265impl Default for OptimizationConfig {
266    fn default() -> Self {
267        Self {
268            use_tensor_cores: true,
269            optimize_memory_access: true,
270            enable_gate_fusion: true,
271            max_fusion_length: 8,
272            coalescing_threshold: 32,
273            use_mixed_precision: true,
274        }
275    }
276}
277
278impl SpecializedGpuKernels {
279    /// Create a new specialized GPU kernel manager
280    pub fn new(config: OptimizationConfig) -> QuantRS2Result<Self> {
281        let cuda_context = Self::initialize_cuda_context(&config)?;
282        let webgpu_context = Self::initialize_webgpu_context(&config)?;
283
284        Ok(Self {
285            cuda_context,
286            webgpu_context,
287            kernel_cache: Arc::new(Mutex::new(KernelCache::new())),
288            performance_stats: Arc::new(Mutex::new(PerformanceStats::new())),
289            config,
290        })
291    }
292
293    /// Initialize a CUDA context for specialized kernels.
294    ///
295    /// Returns `Ok(None)` when no CUDA device is present (the honest result on a
296    /// GPU-less host) *or* when the specialized-gate device kernels are not yet
297    /// authored. A `CudaSpecializedContext` is only constructed when a real
298    /// device is queried successfully AND every required kernel compiles; since
299    /// the PTX gate kernels are still DEFERRED, this currently yields `None`
300    /// even on a CUDA host rather than fabricating compiled kernels.
301    fn initialize_cuda_context(
302        config: &OptimizationConfig,
303    ) -> QuantRS2Result<Option<CudaSpecializedContext>> {
304        // Real availability probe.
305        if !Self::is_cuda_available() {
306            return Ok(None);
307        }
308
309        // Real device queries — genuine values from the driver.
310        let compute_capability = Self::get_compute_capability()?;
311        let has_tensor_cores = compute_capability.0 >= 7; // Volta and later
312        let device_props = Self::get_device_properties()?;
313
314        // Attempt to compile the specialized gate kernels. These currently
315        // return an honest error (no PTX source yet); we therefore treat a
316        // compilation failure as "specialized CUDA path unavailable" and return
317        // None instead of fabricating a usable context.
318        let kernel_specs: [(
319            &str,
320            fn(&OptimizationConfig) -> QuantRS2Result<CompiledKernel>,
321        ); 5] = [
322            ("holonomic_gate", Self::compile_holonomic_kernel),
323            ("post_quantum_hash", Self::compile_post_quantum_kernel),
324            ("quantum_ml_attention", Self::compile_qml_attention_kernel),
325            (
326                "fused_rotation_sequence",
327                Self::compile_fused_rotation_kernel,
328            ),
329            ("tensor_core_matmul", Self::compile_tensor_core_kernel),
330        ];
331
332        let mut kernels = HashMap::with_capacity(kernel_specs.len());
333        for (name, compile) in kernel_specs {
334            match compile(config) {
335                Ok(kernel) => {
336                    kernels.insert(name.to_string(), kernel);
337                }
338                // Specialized kernels not yet authored — honest "unavailable".
339                Err(_) => return Ok(None),
340            }
341        }
342
343        Ok(Some(CudaSpecializedContext {
344            compute_capability,
345            has_tensor_cores,
346            max_shared_memory: device_props.max_shared_memory,
347            warp_size: device_props.warp_size,
348            kernels,
349        }))
350    }
351
352    /// Initialize a WebGPU context for specialized shaders.
353    ///
354    /// Returns `Ok(None)` when no WebGPU adapter is present or when the
355    /// specialized-gate WGSL shaders are not yet authored. A context is only
356    /// built when a real adapter is queried AND every shader compiles; since the
357    /// WGSL gate shaders are still DEFERRED, this currently yields `None` rather
358    /// than fabricating compiled shaders.
359    fn initialize_webgpu_context(
360        config: &OptimizationConfig,
361    ) -> QuantRS2Result<Option<WebGpuSpecializedContext>> {
362        // Real adapter limits; absence of an adapter is an honest "unavailable".
363        let device_limits = match Self::get_webgpu_limits() {
364            Ok(limits) => limits,
365            Err(_) => return Ok(None),
366        };
367
368        let shader_specs: [(
369            &str,
370            fn(&OptimizationConfig) -> QuantRS2Result<CompiledShader>,
371        ); 3] = [
372            ("holonomic_gate", Self::compile_holonomic_shader),
373            ("post_quantum_hash", Self::compile_post_quantum_shader),
374            ("quantum_ml_attention", Self::compile_qml_attention_shader),
375        ];
376
377        let mut shaders = HashMap::with_capacity(shader_specs.len());
378        for (name, compile) in shader_specs {
379            match compile(config) {
380                Ok(shader) => {
381                    shaders.insert(name.to_string(), shader);
382                }
383                // Specialized shaders not yet authored — honest "unavailable".
384                Err(_) => return Ok(None),
385            }
386        }
387
388        let mut buffer_pools = HashMap::new();
389        buffer_pools.insert("state_vectors".to_string(), BufferPool::new(1024 * 1024)); // 1MB initial
390        buffer_pools.insert("gate_matrices".to_string(), BufferPool::new(512 * 1024)); // 512KB initial
391        buffer_pools.insert("temporary_buffers".to_string(), BufferPool::new(256 * 1024)); // 256KB initial
392
393        Ok(Some(WebGpuSpecializedContext {
394            device_limits,
395            shaders,
396            buffer_pools,
397        }))
398    }
399
400    /// Apply a holonomic (unitary) gate to the targeted qubits.
401    ///
402    /// Dispatches to a GPU kernel when a specialized GPU context is available;
403    /// otherwise computes the result on the CPU. The CPU path is a *real*
404    /// matrix-vector application over the targeted-qubit subspace (it is not a
405    /// no-op). Because the specialized GPU gate kernels are still DEFERRED, the
406    /// GPU contexts are currently `None`, so this resolves to the CPU path —
407    /// which performs the genuine computation.
408    pub fn apply_holonomic_gate(
409        &self,
410        state: &mut [Complex64],
411        holonomy_matrix: &[Complex64],
412        target_qubits: &[QubitId],
413    ) -> QuantRS2Result<()> {
414        let state_size = state.len();
415
416        // Choose execution path based on size and available hardware contexts.
417        if state_size > 1024 && self.cuda_context.is_some() {
418            self.apply_holonomic_gate_cuda(state, holonomy_matrix, target_qubits)
419        } else if self.webgpu_context.is_some() {
420            self.apply_holonomic_gate_webgpu(state, holonomy_matrix, target_qubits)
421        } else {
422            // CPU fallback — real computation, honestly labeled.
423            apply_dense_gate_cpu(state, holonomy_matrix, target_qubits)
424        }
425    }
426
427    /// Apply a holonomic gate using a CUDA kernel.
428    ///
429    /// This path is only taken when a real CUDA specialized context exists. The
430    /// PTX device kernel for holonomic gates is DEFERRED, so this returns an
431    /// honest error rather than pretending a kernel ran. (In practice the
432    /// dispatcher never reaches here because `cuda_context` is `None`.)
433    fn apply_holonomic_gate_cuda(
434        &self,
435        _state: &mut [Complex64],
436        _holonomy_matrix: &[Complex64],
437        _target_qubits: &[QubitId],
438    ) -> QuantRS2Result<()> {
439        Err(uncompiled_kernel_err("holonomic_gate"))
440    }
441
442    /// Apply a post-quantum cryptographic hash gate.
443    ///
444    /// These compression schemes (quantum sponge / Merkle tree / Grover) are
445    /// implemented as GPU device kernels that are not yet authored, and they
446    /// have no defined CPU reference here. Rather than silently returning
447    /// success without touching `state` (the previous fabricated behavior), this
448    /// returns an honest error. DEFERRED until the real kernels exist.
449    pub fn apply_post_quantum_hash_gate(
450        &self,
451        _state: &mut [Complex64],
452        _hash_circuit: &[Complex64],
453        compression_type: PostQuantumCompressionType,
454    ) -> QuantRS2Result<()> {
455        let scheme = match compression_type {
456            PostQuantumCompressionType::QuantumSponge { .. } => "quantum_sponge",
457            PostQuantumCompressionType::QuantumMerkleTree { .. } => "quantum_merkle_tree",
458            PostQuantumCompressionType::QuantumGrover { .. } => "quantum_grover",
459        };
460        Err(QuantRS2Error::UnsupportedOperation(format!(
461            "post-quantum hash gate `{scheme}` is not implemented: the GPU kernel is DEFERRED and \
462             there is no CPU reference (refusing to fabricate a no-op success)"
463        )))
464    }
465
466    /// Apply a quantum ML attention mechanism.
467    ///
468    /// The specialized attention kernels (CUDA/WebGPU) are not yet authored and
469    /// there is no defined CPU reference for the previously no-op fallback.
470    /// Returns an honest error rather than silently returning success without
471    /// transforming `state`. DEFERRED until the real kernels exist.
472    pub fn apply_quantum_ml_attention(
473        &self,
474        _state: &mut [Complex64],
475        _query_params: &[Complex64],
476        _key_params: &[Complex64],
477        _value_params: &[Complex64],
478        _num_heads: usize,
479    ) -> QuantRS2Result<()> {
480        Err(QuantRS2Error::UnsupportedOperation(
481            "quantum ML attention is not implemented: the specialized GPU kernels are DEFERRED and \
482             there is no CPU reference (refusing to fabricate a no-op success)"
483                .to_string(),
484        ))
485    }
486
487    /// Apply a sequence of gates to the state vector.
488    ///
489    /// Each gate is applied with a *real* matrix-vector update on the CPU via
490    /// [`apply_single_gate_optimized`]. Gate *fusion* (merging adjacent gates
491    /// into a single combined matrix for fewer passes) is DEFERRED:
492    /// [`analyze_gate_fusion_opportunities`] currently finds no chains, so this
493    /// reduces to honest per-gate application. The result is numerically
494    /// correct; only the fused-pass optimization is missing.
495    pub fn apply_fused_gate_sequence(
496        &self,
497        state: &mut [Complex64],
498        gates: &[Box<dyn GateOp>],
499    ) -> QuantRS2Result<()> {
500        // Try to discover fusion chains (currently always empty — DEFERRED).
501        let fusion_chains = if self.config.enable_gate_fusion && gates.len() >= 2 {
502            self.analyze_gate_fusion_opportunities(gates)?
503        } else {
504            Vec::new()
505        };
506
507        if fusion_chains.is_empty() {
508            // No fusion available: apply each gate individually (real compute).
509            for gate in gates {
510                self.apply_single_gate_optimized(state, gate.as_ref())?;
511            }
512            return Ok(());
513        }
514
515        // Fusion path is not yet implemented; the only honest action for a
516        // non-empty chain is per-gate application of its members.
517        for chain in fusion_chains {
518            for gate in &chain.gates {
519                self.apply_single_gate_optimized(state, gate.as_ref())?;
520            }
521        }
522
523        Ok(())
524    }
525
526    /// Record a real kernel execution time (milliseconds) for reporting.
527    ///
528    /// Only invoked from paths that actually executed a kernel; it is never fed
529    /// a fabricated constant.
530    fn update_performance_stats(&self, kernel_name: &str, execution_time: f64) {
531        if let Ok(mut stats) = self.performance_stats.lock() {
532            stats
533                .kernel_times
534                .entry(kernel_name.to_string())
535                .or_default()
536                .push(execution_time);
537        }
538        // Silently ignore lock poisoning for performance stats update
539    }
540
541    /// Get performance report
542    pub fn get_performance_report(&self) -> PerformanceReport {
543        let stats = self
544            .performance_stats
545            .lock()
546            .unwrap_or_else(|e| e.into_inner());
547        let cache = self.kernel_cache.lock().unwrap_or_else(|e| e.into_inner());
548
549        PerformanceReport {
550            average_kernel_times: stats
551                .kernel_times
552                .iter()
553                .map(|(k, v)| (k.clone(), v.iter().sum::<f64>() / v.len() as f64))
554                .collect(),
555            cache_hit_rate: cache.cache_stats.overall_hit_rate(),
556            tensor_core_utilization: stats.tensor_core_utilization,
557            memory_bandwidth_utilization: stats.memory_bandwidth.values().sum::<f64>()
558                / stats.memory_bandwidth.len() as f64,
559        }
560    }
561
562    /// Real CUDA availability probe via the OxiCUDA driver.
563    ///
564    /// Loads `libcuda.so`/`nvcuda.dll` at runtime and counts devices. Returns
565    /// `true` only when the driver initializes AND at least one device exists.
566    /// Without the `gpu` feature, or with no device/driver, returns `false`
567    /// (the honest result).
568    fn is_cuda_available() -> bool {
569        crate::gpu::is_gpu_available()
570    }
571
572    /// Query the real compute capability of CUDA device 0.
573    ///
574    /// Returns the genuine `(major, minor)` reported by the driver. Returns an
575    /// honest error when GPU support is not compiled in or no device exists —
576    /// it never fabricates a capability such as `(7, 5)`.
577    fn get_compute_capability() -> QuantRS2Result<(i32, i32)> {
578        #[cfg(feature = "gpu")]
579        {
580            oxicuda::init().map_err(map_cuda_err)?;
581            let device = oxicuda::Device::get(0).map_err(map_cuda_err)?;
582            device.compute_capability().map_err(map_cuda_err)
583        }
584        #[cfg(not(feature = "gpu"))]
585        {
586            Err(crate::error::QuantRS2Error::UnsupportedOperation(
587                "compute capability unavailable: GPU backend not linked (enable the `gpu` feature)"
588                    .to_string(),
589            ))
590        }
591    }
592
593    /// Query real device properties (warp size, shared memory) from device 0.
594    ///
595    /// Returns genuine values from the OxiCUDA driver, or an honest error when
596    /// no GPU is available — never the previously hardcoded `49152`/`32`.
597    fn get_device_properties() -> QuantRS2Result<DeviceProperties> {
598        #[cfg(feature = "gpu")]
599        {
600            oxicuda::init().map_err(map_cuda_err)?;
601            let device = oxicuda::Device::get(0).map_err(map_cuda_err)?;
602            let warp_size = device.warp_size().map_err(map_cuda_err)? as usize;
603            let max_shared_memory =
604                device.max_shared_memory_per_block().map_err(map_cuda_err)? as usize;
605            Ok(DeviceProperties {
606                max_shared_memory,
607                warp_size,
608            })
609        }
610        #[cfg(not(feature = "gpu"))]
611        {
612            Err(crate::error::QuantRS2Error::UnsupportedOperation(
613                "device properties unavailable: GPU backend not linked (enable the `gpu` feature)"
614                    .to_string(),
615            ))
616        }
617    }
618
619    /// Query real WebGPU device limits via a wgpu adapter request.
620    ///
621    /// Returns the genuine `max_compute_workgroup_size_x` from the first
622    /// available adapter, or an honest error when no WebGPU adapter is present —
623    /// never the previously hardcoded `256`.
624    fn get_webgpu_limits() -> QuantRS2Result<WebGpuLimits> {
625        #[cfg(feature = "gpu")]
626        {
627            query_webgpu_limits()
628        }
629        #[cfg(not(feature = "gpu"))]
630        {
631            Err(crate::error::QuantRS2Error::UnsupportedOperation(
632                "WebGPU limits unavailable: GPU backend not linked (enable the `gpu` feature)"
633                    .to_string(),
634            ))
635        }
636    }
637
638    // NOTE on kernel compilation: the specialized quantum-gate kernels
639    // (holonomic, post-quantum hash, QML attention, fused rotations,
640    // tensor-core matmul) require hand-authored PTX/WGSL device code that does
641    // NOT yet exist in this crate. Rather than fabricate a "compiled kernel"
642    // with a fake `last_execution_time`, these compile entry points return an
643    // honest error so a caller can never mistake an unbuilt kernel for a real
644    // one. See the module-level DEFERRED note. When real device source is added,
645    // wire `oxicuda::Module::from_ptx` / WGSL compilation here.
646    fn compile_holonomic_kernel(_config: &OptimizationConfig) -> QuantRS2Result<CompiledKernel> {
647        Err(uncompiled_kernel_err("holonomic_gate"))
648    }
649    fn compile_post_quantum_kernel(_config: &OptimizationConfig) -> QuantRS2Result<CompiledKernel> {
650        Err(uncompiled_kernel_err("post_quantum_hash"))
651    }
652    fn compile_qml_attention_kernel(
653        _config: &OptimizationConfig,
654    ) -> QuantRS2Result<CompiledKernel> {
655        Err(uncompiled_kernel_err("quantum_ml_attention"))
656    }
657    fn compile_fused_rotation_kernel(
658        _config: &OptimizationConfig,
659    ) -> QuantRS2Result<CompiledKernel> {
660        Err(uncompiled_kernel_err("fused_rotation_sequence"))
661    }
662    fn compile_tensor_core_kernel(_config: &OptimizationConfig) -> QuantRS2Result<CompiledKernel> {
663        Err(uncompiled_kernel_err("tensor_core_matmul"))
664    }
665
666    fn compile_holonomic_shader(_config: &OptimizationConfig) -> QuantRS2Result<CompiledShader> {
667        Err(uncompiled_shader_err("holonomic_gate"))
668    }
669    fn compile_post_quantum_shader(_config: &OptimizationConfig) -> QuantRS2Result<CompiledShader> {
670        Err(uncompiled_shader_err("post_quantum_hash"))
671    }
672    fn compile_qml_attention_shader(
673        _config: &OptimizationConfig,
674    ) -> QuantRS2Result<CompiledShader> {
675        Err(uncompiled_shader_err("quantum_ml_attention"))
676    }
677
678    /// Apply a holonomic gate via a WebGPU compute shader.
679    ///
680    /// The WGSL shader is DEFERRED, so this returns an honest error instead of
681    /// silently returning success. (Unreachable while `webgpu_context` is
682    /// `None`, but kept honest.)
683    fn apply_holonomic_gate_webgpu(
684        &self,
685        _state: &mut [Complex64],
686        _matrix: &[Complex64],
687        _qubits: &[QubitId],
688    ) -> QuantRS2Result<()> {
689        Err(uncompiled_shader_err("holonomic_gate"))
690    }
691
692    /// Apply a single gate to the state vector on the CPU.
693    ///
694    /// This is a *real* application: the gate's unitary matrix is fetched and
695    /// applied over the targeted-qubit subspace. It is the fallback used by
696    /// [`apply_fused_gate_sequence`] when no fusion is performed.
697    fn apply_single_gate_optimized(
698        &self,
699        state: &mut [Complex64],
700        gate: &dyn GateOp,
701    ) -> QuantRS2Result<()> {
702        let matrix = gate.matrix()?;
703        let qubits = gate.qubits();
704        apply_dense_gate_cpu(state, &matrix, &qubits)
705    }
706
707    /// Analyze a gate list for fusion opportunities.
708    ///
709    /// Gate fusion (merging adjacent rotations / Pauli strings / controlled
710    /// sequences into a single combined matrix) is not yet implemented. Rather
711    /// than fabricate fusion chains, this honestly reports *no* fusion
712    /// opportunities, so callers correctly fall back to applying each gate
713    /// individually via [`apply_single_gate_optimized`]. DEFERRED.
714    fn analyze_gate_fusion_opportunities(
715        &self,
716        _gates: &[Box<dyn GateOp>],
717    ) -> QuantRS2Result<Vec<FusionChain>> {
718        Ok(Vec::new())
719    }
720}
721
722/// Supporting types and structures
723
724#[derive(Debug, Clone)]
725pub enum PostQuantumCompressionType {
726    QuantumSponge { rate: usize, capacity: usize },
727    QuantumMerkleTree { depth: usize, arity: usize },
728    QuantumGrover { iterations: usize },
729}
730
731#[derive(Debug, Clone)]
732pub enum FusionType {
733    RotationSequence,
734    PauliString,
735    ControlledSequence,
736    None,
737}
738
739pub struct FusionChain {
740    pub gates: Vec<Box<dyn GateOp>>,
741    pub fusion_type: FusionType,
742}
743
744pub struct CompiledKernel {
745    pub name: String,
746    pub last_execution_time: f64,
747}
748
749pub struct CompiledShader {
750    pub name: String,
751}
752
753pub struct CachedCudaKernel {
754    pub kernel: CompiledKernel,
755    pub compilation_time: f64,
756}
757
758pub struct CachedWebGpuShader {
759    pub shader: CompiledShader,
760    pub compilation_time: f64,
761}
762
763pub struct CacheStatistics {
764    pub hits: usize,
765    pub misses: usize,
766}
767
768impl CacheStatistics {
769    pub fn overall_hit_rate(&self) -> f64 {
770        if self.hits + self.misses == 0 {
771            0.0
772        } else {
773            self.hits as f64 / (self.hits + self.misses) as f64
774        }
775    }
776}
777
778pub struct BufferPool {
779    pub initial_size: usize,
780}
781
782impl BufferPool {
783    pub const fn new(initial_size: usize) -> Self {
784        Self { initial_size }
785    }
786}
787
788pub struct DeviceProperties {
789    pub max_shared_memory: usize,
790    pub warp_size: usize,
791}
792
793pub struct WebGpuLimits {
794    pub max_compute_workgroup_size: u32,
795}
796
797pub struct PerformanceReport {
798    pub average_kernel_times: HashMap<String, f64>,
799    pub cache_hit_rate: f64,
800    pub tensor_core_utilization: f64,
801    pub memory_bandwidth_utilization: f64,
802}
803
804impl KernelCache {
805    pub fn new() -> Self {
806        Self {
807            cuda_kernels: HashMap::new(),
808            webgpu_shaders: HashMap::new(),
809            cache_stats: CacheStatistics { hits: 0, misses: 0 },
810        }
811    }
812}
813
814impl Default for KernelCache {
815    fn default() -> Self {
816        Self::new()
817    }
818}
819
820impl PerformanceStats {
821    pub fn new() -> Self {
822        Self {
823            kernel_times: HashMap::new(),
824            memory_bandwidth: HashMap::new(),
825            tensor_core_utilization: 0.0,
826            cache_hit_rates: HashMap::new(),
827        }
828    }
829}
830
831impl Default for PerformanceStats {
832    fn default() -> Self {
833        Self::new()
834    }
835}
836
837#[cfg(test)]
838mod tests {
839    use super::*;
840    use std::f64::consts::FRAC_1_SQRT_2;
841
842    #[test]
843    fn test_specialized_gpu_kernels_creation() {
844        let config = OptimizationConfig::default();
845        let kernels = SpecializedGpuKernels::new(config);
846        assert!(kernels.is_ok());
847    }
848
849    #[test]
850    fn test_holonomic_identity_preserves_state() {
851        let config = OptimizationConfig::default();
852        let kernels =
853            SpecializedGpuKernels::new(config).expect("Failed to create specialized GPU kernels");
854
855        let mut state = vec![
856            Complex64::new(0.6, 0.0),
857            Complex64::new(0.0, 0.8),
858            Complex64::new(0.0, 0.0),
859            Complex64::new(0.0, 0.0),
860        ];
861        let original = state.clone();
862        // 2x2 identity on qubit 0.
863        let identity = vec![
864            Complex64::new(1.0, 0.0),
865            Complex64::new(0.0, 0.0),
866            Complex64::new(0.0, 0.0),
867            Complex64::new(1.0, 0.0),
868        ];
869        kernels
870            .apply_holonomic_gate(&mut state, &identity, &[QubitId(0)])
871            .expect("identity holonomic gate should succeed");
872
873        for (a, b) in state.iter().zip(original.iter()) {
874            assert!((a - b).norm() < 1e-12, "identity must not change the state");
875        }
876    }
877
878    #[test]
879    fn test_holonomic_gate_is_real_not_noop() {
880        // A Hadamard-like unitary on qubit 0 must actually transform |0> into an
881        // equal superposition. A no-op fabrication would leave the state at |0>.
882        let config = OptimizationConfig::default();
883        let kernels =
884            SpecializedGpuKernels::new(config).expect("Failed to create specialized GPU kernels");
885
886        let mut state = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
887        let h = vec![
888            Complex64::new(FRAC_1_SQRT_2, 0.0),
889            Complex64::new(FRAC_1_SQRT_2, 0.0),
890            Complex64::new(FRAC_1_SQRT_2, 0.0),
891            Complex64::new(-FRAC_1_SQRT_2, 0.0),
892        ];
893        kernels
894            .apply_holonomic_gate(&mut state, &h, &[QubitId(0)])
895            .expect("hadamard holonomic gate should succeed");
896
897        // Both amplitudes must be 1/sqrt(2) — proving real computation occurred.
898        assert!((state[0].re - FRAC_1_SQRT_2).abs() < 1e-12);
899        assert!((state[1].re - FRAC_1_SQRT_2).abs() < 1e-12);
900        assert!(
901            (state[1].norm() - FRAC_1_SQRT_2).abs() < 1e-12,
902            "second amplitude must be populated; a no-op would leave it at 0"
903        );
904    }
905
906    #[test]
907    fn test_dense_gate_on_high_qubit_index() {
908        // Apply X to qubit 1 of a 2-qubit state |00> -> |10> (LSB ordering, so
909        // bit 1 set => basis index 2). Verifies correct subspace indexing.
910        let mut state = vec![
911            Complex64::new(1.0, 0.0),
912            Complex64::new(0.0, 0.0),
913            Complex64::new(0.0, 0.0),
914            Complex64::new(0.0, 0.0),
915        ];
916        let x = vec![
917            Complex64::new(0.0, 0.0),
918            Complex64::new(1.0, 0.0),
919            Complex64::new(1.0, 0.0),
920            Complex64::new(0.0, 0.0),
921        ];
922        apply_dense_gate_cpu(&mut state, &x, &[QubitId(1)]).expect("X on qubit 1 should succeed");
923        assert!(
924            (state[2].re - 1.0).abs() < 1e-12,
925            "amplitude should move to index 2"
926        );
927        assert!(state[0].norm() < 1e-12);
928    }
929
930    #[test]
931    fn test_dense_gate_dimension_mismatch_errors() {
932        let mut state = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
933        // 3-entry matrix is not a valid 2x2 — must error, not silently skip.
934        let bad = vec![Complex64::new(1.0, 0.0); 3];
935        assert!(apply_dense_gate_cpu(&mut state, &bad, &[QubitId(0)]).is_err());
936    }
937
938    #[test]
939    fn test_post_quantum_and_attention_are_honest_errors() {
940        // These specialized GPU ops are DEFERRED; they must return an honest
941        // error rather than a silent no-op success.
942        let kernels = SpecializedGpuKernels::new(OptimizationConfig::default())
943            .expect("kernel manager creation");
944        let mut state = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
945
946        let pq = kernels.apply_post_quantum_hash_gate(
947            &mut state,
948            &[Complex64::new(1.0, 0.0)],
949            PostQuantumCompressionType::QuantumGrover { iterations: 1 },
950        );
951        assert!(
952            pq.is_err(),
953            "post-quantum hash gate must be an honest error"
954        );
955
956        let att = kernels.apply_quantum_ml_attention(
957            &mut state,
958            &[Complex64::new(1.0, 0.0)],
959            &[Complex64::new(1.0, 0.0)],
960            &[Complex64::new(1.0, 0.0)],
961            1,
962        );
963        assert!(att.is_err(), "quantum ML attention must be an honest error");
964    }
965
966    #[test]
967    fn test_compute_capability_is_not_hardcoded_75() {
968        // Real query: either a genuine capability (NOT the old fabricated (7,5))
969        // or an honest error when no device / no `gpu` feature. It must never
970        // silently return the fabricated (7, 5) constant.
971        match SpecializedGpuKernels::get_compute_capability() {
972            Ok(cc) => {
973                assert_ne!(
974                    cc,
975                    (7, 5),
976                    "compute capability must be a real probe, not the fabricated (7,5)"
977                );
978                assert!(cc.0 >= 1, "real compute capability major must be >= 1");
979            }
980            Err(_) => {
981                // Honest error is acceptable when no GPU is present.
982            }
983        }
984    }
985
986    #[test]
987    fn test_performance_reporting() {
988        let config = OptimizationConfig::default();
989        let kernels = SpecializedGpuKernels::new(config)
990            .expect("Failed to create specialized GPU kernels for performance reporting");
991
992        let report = kernels.get_performance_report();
993        assert!(report.cache_hit_rate >= 0.0 && report.cache_hit_rate <= 1.0);
994    }
995}