Skip to main content

quantrs2_sim/cuquantum/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::error::{Result, SimulatorError};
6use quantrs2_circuit::prelude::Circuit;
7use scirs2_core::ndarray::{Array1, Array2};
8use scirs2_core::random::RngExt;
9use scirs2_core::Complex64;
10use std::collections::HashMap;
11use thiserror::Error;
12
13/// cuQuantum simulation configuration
14#[derive(Debug, Clone)]
15pub struct CuQuantumConfig {
16    /// Device ID to use (-1 for auto-select)
17    pub device_id: i32,
18    /// Enable multi-GPU execution
19    pub multi_gpu: bool,
20    /// Number of GPUs to use (0 for all available)
21    pub num_gpus: usize,
22    /// Memory pool size in bytes (0 for auto)
23    pub memory_pool_size: usize,
24    /// Enable asynchronous execution
25    pub async_execution: bool,
26    /// Enable memory optimization (may reduce peak memory)
27    pub memory_optimization: bool,
28    /// Computation precision
29    pub precision: ComputePrecision,
30    /// Gate fusion level
31    pub gate_fusion_level: GateFusionLevel,
32    /// Enable profiling
33    pub enable_profiling: bool,
34    /// Maximum number of qubits for state vector simulation
35    pub max_statevec_qubits: usize,
36    /// Tensor network contraction algorithm
37    pub tensor_contraction: TensorContractionAlgorithm,
38    /// Enable TF32 tensor core mode (NVIDIA Ampere and newer)
39    /// When enabled, FP32 matrix operations use 19-bit TensorFloat-32 format
40    /// providing near-FP32 accuracy with ~8x speedup on tensor cores
41    /// Only effective when device has tensor cores (compute capability ≥ 8.0)
42    pub enable_tf32: bool,
43}
44impl CuQuantumConfig {
45    /// Create configuration optimized for large circuits
46    pub fn large_circuit() -> Self {
47        Self {
48            memory_optimization: true,
49            gate_fusion_level: GateFusionLevel::Aggressive,
50            tensor_contraction: TensorContractionAlgorithm::OptimalWithSlicing,
51            enable_tf32: true, // Enable TF32 for performance
52            ..Default::default()
53        }
54    }
55    /// Create configuration optimized for variational algorithms (VQE/QAOA)
56    pub fn variational() -> Self {
57        Self {
58            async_execution: true,
59            gate_fusion_level: GateFusionLevel::Moderate,
60            enable_profiling: false,
61            enable_tf32: true, // Enable TF32 for VQE/QAOA speedup
62            ..Default::default()
63        }
64    }
65    /// Create configuration for multi-GPU execution
66    pub fn multi_gpu(num_gpus: usize) -> Self {
67        Self {
68            multi_gpu: true,
69            num_gpus,
70            memory_optimization: true,
71            enable_tf32: true, // Enable TF32 on all GPUs
72            ..Default::default()
73        }
74    }
75
76    /// Create configuration with TF32 explicitly enabled/disabled
77    pub fn with_tf32(mut self, enable: bool) -> Self {
78        self.enable_tf32 = enable;
79        self
80    }
81
82    /// Check if TF32 should be used based on device capabilities
83    pub fn should_use_tf32(&self, device_info: &CudaDeviceInfo) -> bool {
84        self.enable_tf32
85            && device_info.has_tensor_cores
86            && device_info.compute_capability >= (8, 0) // Ampere and newer
87            && matches!(
88                self.precision,
89                ComputePrecision::Single | ComputePrecision::Mixed
90            )
91    }
92}
93/// CUDA device information
94#[derive(Debug, Clone)]
95pub struct CudaDeviceInfo {
96    /// Device ID
97    pub device_id: i32,
98    /// Device name
99    pub name: String,
100    /// Total global memory in bytes
101    pub total_memory: usize,
102    /// Free memory in bytes
103    pub free_memory: usize,
104    /// Compute capability (major, minor)
105    pub compute_capability: (i32, i32),
106    /// Number of streaming multiprocessors
107    pub sm_count: i32,
108    /// Maximum threads per block
109    pub max_threads_per_block: i32,
110    /// Warp size
111    pub warp_size: i32,
112    /// Whether tensor cores are available
113    pub has_tensor_cores: bool,
114}
115impl CudaDeviceInfo {
116    /// Get maximum qubits supportable for state vector simulation
117    pub fn max_statevec_qubits(&self) -> usize {
118        let available_memory = (self.free_memory as f64 * 0.8) as usize;
119        let bytes_per_amplitude = 16;
120        let max_amplitudes = available_memory / bytes_per_amplitude;
121        (max_amplitudes as f64).log2().floor() as usize
122    }
123}
124/// Recommended simulation backend
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum RecommendedBackend {
127    /// Use state vector simulation (smaller circuits)
128    StateVector,
129    /// Use tensor network simulation (larger circuits)
130    TensorNetwork,
131    /// Hybrid approach
132    Hybrid,
133    /// Cannot simulate (too large)
134    NotFeasible,
135}
136/// Tensor network state representation
137#[derive(Debug, Clone)]
138pub struct TensorNetworkState {
139    /// Tensors in the network
140    tensors: Vec<Tensor>,
141    /// Connections between tensors
142    edges: Vec<TensorEdge>,
143    /// Open indices (not contracted)
144    open_indices: Vec<usize>,
145}
146impl TensorNetworkState {
147    /// Create from a quantum circuit
148    pub fn from_circuit<const N: usize>(circuit: &Circuit<N>) -> Result<Self> {
149        let mut tensors = Vec::new();
150        let mut edges = Vec::new();
151        for qubit in 0..N {
152            tensors.push(Tensor::initial_state(qubit));
153        }
154        for (gate_idx, gate) in circuit.gates().iter().enumerate() {
155            let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
156            tensors.push(Tensor::from_gate(gate_idx, &qubits));
157            for &qubit in &qubits {
158                edges.push(TensorEdge {
159                    tensor_a: qubit,
160                    tensor_b: N + gate_idx,
161                    index: qubit,
162                });
163            }
164        }
165        Ok(Self {
166            tensors,
167            edges,
168            open_indices: (0..N).collect(),
169        })
170    }
171    /// Get number of tensors
172    pub fn num_tensors(&self) -> usize {
173        self.tensors.len()
174    }
175    /// Get number of edges
176    pub fn num_edges(&self) -> usize {
177        self.edges.len()
178    }
179}
180/// cuQuantum simulation result
181#[derive(Debug, Clone)]
182pub struct CuQuantumResult {
183    /// State vector (if computed)
184    pub state_vector: Option<Array1<Complex64>>,
185    /// Measurement counts
186    pub counts: HashMap<String, usize>,
187    /// Individual measurement outcomes
188    pub measurement_outcomes: Vec<u64>,
189    /// Additional metadata
190    pub metadata: HashMap<String, String>,
191    /// Number of qubits
192    pub num_qubits: usize,
193}
194impl CuQuantumResult {
195    /// Create a new result with state vector
196    pub fn from_state_vector(state: Array1<Complex64>, num_qubits: usize) -> Self {
197        Self {
198            state_vector: Some(state),
199            counts: HashMap::new(),
200            measurement_outcomes: Vec::new(),
201            metadata: HashMap::new(),
202            num_qubits,
203        }
204    }
205    /// Create a new result with measurement counts
206    pub fn from_counts(counts: HashMap<String, usize>, num_qubits: usize) -> Self {
207        Self {
208            state_vector: None,
209            counts,
210            measurement_outcomes: Vec::new(),
211            metadata: HashMap::new(),
212            num_qubits,
213        }
214    }
215    /// Get probabilities from state vector
216    pub fn probabilities(&self) -> Option<Vec<f64>> {
217        self.state_vector
218            .as_ref()
219            .map(|sv| sv.iter().map(|c| c.norm_sqr()).collect())
220    }
221    /// Get expectation value of computational basis measurement
222    pub fn expectation_z(&self, qubit: usize) -> Option<f64> {
223        self.probabilities().map(|probs| {
224            let mut exp = 0.0;
225            for (i, &p) in probs.iter().enumerate() {
226                let bit = (i >> qubit) & 1;
227                exp += if bit == 0 { p } else { -p };
228            }
229            exp
230        })
231    }
232}
233/// Single tensor in the network
234#[derive(Debug, Clone)]
235pub struct Tensor {
236    /// Tensor ID
237    id: usize,
238    /// Shape of the tensor
239    shape: Vec<usize>,
240    /// Data (only stored for leaf tensors)
241    data: Option<Array2<Complex64>>,
242}
243impl Tensor {
244    /// Create initial state tensor |0⟩
245    fn initial_state(qubit: usize) -> Self {
246        let mut data = Array2::zeros((2, 1));
247        data[[0, 0]] = Complex64::new(1.0, 0.0);
248        Self {
249            id: qubit,
250            shape: vec![2],
251            data: Some(data),
252        }
253    }
254    /// Create tensor from gate
255    fn from_gate(gate_idx: usize, _qubits: &[usize]) -> Self {
256        Self {
257            id: gate_idx,
258            shape: vec![2; _qubits.len() * 2],
259            data: None,
260        }
261    }
262}
263/// Edge connecting two tensors
264#[derive(Debug, Clone)]
265pub struct TensorEdge {
266    /// First tensor index
267    tensor_a: usize,
268    /// Second tensor index
269    tensor_b: usize,
270    /// Index being contracted
271    index: usize,
272}
273/// cuStateVec-based state vector simulator
274///
275/// This simulator uses NVIDIA's cuStateVec library for GPU-accelerated
276/// state vector simulation of quantum circuits.
277pub struct CuStateVecSimulator {
278    /// Configuration
279    pub config: CuQuantumConfig,
280    /// Device information
281    pub device_info: Option<CudaDeviceInfo>,
282    /// Simulation statistics
283    pub stats: SimulationStats,
284    /// Whether the simulator is initialized
285    pub initialized: bool,
286    #[cfg(feature = "cuquantum")]
287    pub handle: Option<CuStateVecHandle>,
288    #[cfg(feature = "cuquantum")]
289    pub state_buffer: Option<GpuBuffer>,
290}
291impl CuStateVecSimulator {
292    /// Create a new cuStateVec simulator
293    pub fn new(config: CuQuantumConfig) -> Result<Self> {
294        let device_info = Self::get_device_info(config.device_id)?;
295        Ok(Self {
296            config,
297            device_info: Some(device_info),
298            stats: SimulationStats::default(),
299            initialized: false,
300            #[cfg(feature = "cuquantum")]
301            handle: None,
302            #[cfg(feature = "cuquantum")]
303            state_buffer: None,
304        })
305    }
306    /// Create with default configuration
307    pub fn default_config() -> Result<Self> {
308        Self::new(CuQuantumConfig::default())
309    }
310    /// Check if cuQuantum is available
311    pub fn is_available() -> bool {
312        #[cfg(feature = "cuquantum")]
313        {
314            Self::check_cuquantum_available()
315        }
316        #[cfg(not(feature = "cuquantum"))]
317        {
318            false
319        }
320    }
321    /// Get device information
322    pub fn get_device_info(device_id: i32) -> Result<CudaDeviceInfo> {
323        #[cfg(feature = "cuquantum")]
324        {
325            Self::get_cuda_device_info(device_id)
326        }
327        #[cfg(not(feature = "cuquantum"))]
328        {
329            Ok(CudaDeviceInfo {
330                device_id: if device_id < 0 { 0 } else { device_id },
331                name: "Mock CUDA Device (cuQuantum not available)".to_string(),
332                total_memory: 16 * 1024 * 1024 * 1024,
333                free_memory: 12 * 1024 * 1024 * 1024,
334                compute_capability: (8, 6),
335                sm_count: 84,
336                max_threads_per_block: 1024,
337                warp_size: 32,
338                has_tensor_cores: true,
339            })
340        }
341    }
342    /// Initialize the simulator for a specific number of qubits
343    pub fn initialize(&mut self, num_qubits: usize) -> Result<()> {
344        if num_qubits > self.config.max_statevec_qubits {
345            return Err(SimulatorError::InvalidParameter(format!(
346                "Number of qubits ({}) exceeds maximum ({})",
347                num_qubits, self.config.max_statevec_qubits
348            )));
349        }
350        #[cfg(feature = "cuquantum")]
351        {
352            self.initialize_custatevec(num_qubits)?;
353        }
354        self.initialized = true;
355        Ok(())
356    }
357    /// Simulate a quantum circuit
358    pub fn simulate<const N: usize>(&mut self, circuit: &Circuit<N>) -> Result<CuQuantumResult> {
359        if !self.initialized {
360            self.initialize(N)?;
361        }
362        let start_time = std::time::Instant::now();
363
364        // Enabling the `cuquantum` feature expresses intent to use cuStateVec; it
365        // does not conjure the runtime. Probe for a genuine cuStateVec context and
366        // only hand the circuit to the GPU path when one exists -- otherwise run the
367        // real CPU state-vector path below, which returns the true result of the
368        // circuit rather than an error or a placeholder.
369        #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
370        if Self::check_cuquantum_available() {
371            return self.simulate_with_custatevec(circuit);
372        }
373
374        self.simulate_mock(circuit, start_time)
375    }
376    /// CPU fallback simulation, used whenever no real cuStateVec context is
377    /// available -- on macOS, when the `cuquantum` feature is disabled, and when
378    /// the feature is enabled but the runtime probe finds no cuQuantum library.
379    ///
380    /// This actually runs the circuit: every gate's real matrix
381    /// (`GateOp::matrix()`) is applied to the state vector via a genuine
382    /// tensor-contraction gate application (see [`Self::apply_gate_matrix`]).
383    /// It is *not* GPU-accelerated -- there is no real cuStateVec handle in
384    /// this build -- but the returned state is the true result of running
385    /// the circuit, not a placeholder `|0...0>`.
386    fn simulate_mock<const N: usize>(
387        &mut self,
388        circuit: &Circuit<N>,
389        start_time: std::time::Instant,
390    ) -> Result<CuQuantumResult> {
391        let state_size = 1usize << N;
392        let mut state: Array1<Complex64> = Array1::zeros(state_size);
393        state[0] = Complex64::new(1.0, 0.0);
394
395        for gate in circuit.gates() {
396            let qubits: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
397            let matrix = gate.matrix().map_err(|e| {
398                SimulatorError::InvalidGate(format!(
399                    "failed to get matrix for gate '{}': {e}",
400                    gate.name()
401                ))
402            })?;
403            Self::apply_gate_matrix(&mut state, N, &qubits, &matrix)?;
404        }
405
406        self.stats.total_simulations += 1;
407        self.stats.total_gates += circuit.gates().len();
408        self.stats.total_time_ms += start_time.elapsed().as_millis() as f64;
409        Ok(CuQuantumResult::from_state_vector(state, N))
410    }
411
412    /// Apply an arbitrary `k`-qubit gate matrix (row-major, `2^k x 2^k`,
413    /// with `qubits[0]` as the most-significant index bit) to a full
414    /// `num_qubits`-qubit state vector, addressing the gate's target
415    /// qubits via `qubits`. Used by the CPU fallback path so that
416    /// [`Self::simulate_mock`] runs a real simulation instead of returning
417    /// an unmodified `|0...0>` state.
418    fn apply_gate_matrix(
419        state: &mut Array1<Complex64>,
420        num_qubits: usize,
421        qubits: &[usize],
422        matrix: &[Complex64],
423    ) -> Result<()> {
424        let k = qubits.len();
425        let dim = 1usize << k;
426        if matrix.len() != dim * dim {
427            return Err(SimulatorError::DimensionMismatch(format!(
428                "gate matrix has {} entries, expected {dim}x{dim} for a {k}-qubit gate",
429                matrix.len()
430            )));
431        }
432        for &q in qubits {
433            if q >= num_qubits {
434                return Err(SimulatorError::InvalidQubitIndex {
435                    index: q,
436                    num_qubits,
437                });
438            }
439        }
440
441        let other_qubits: Vec<usize> = (0..num_qubits).filter(|q| !qubits.contains(q)).collect();
442        let num_other = other_qubits.len();
443
444        let mut new_state = state.clone();
445        let mut amps = vec![Complex64::new(0.0, 0.0); dim];
446        let mut indices = vec![0usize; dim];
447
448        for other_bits in 0..(1usize << num_other) {
449            let mut base = 0usize;
450            for (bit_pos, &q) in other_qubits.iter().enumerate() {
451                if (other_bits >> bit_pos) & 1 == 1 {
452                    base |= 1 << q;
453                }
454            }
455
456            for combo in 0..dim {
457                let mut idx = base;
458                for (i, &q) in qubits.iter().enumerate() {
459                    if (combo >> (k - 1 - i)) & 1 == 1 {
460                        idx |= 1 << q;
461                    }
462                }
463                indices[combo] = idx;
464                amps[combo] = state[idx];
465            }
466
467            for (row, &target_idx) in indices.iter().enumerate() {
468                let mut acc = Complex64::new(0.0, 0.0);
469                for (col, amp) in amps.iter().enumerate() {
470                    acc += matrix[row * dim + col] * amp;
471                }
472                new_state[target_idx] = acc;
473            }
474        }
475
476        *state = new_state;
477        Ok(())
478    }
479    /// Get simulation statistics
480    pub fn stats(&self) -> &SimulationStats {
481        &self.stats
482    }
483    /// Reset simulation statistics
484    pub fn reset_stats(&mut self) {
485        self.stats = SimulationStats::default();
486    }
487    /// Get device information
488    pub fn device_info(&self) -> Option<&CudaDeviceInfo> {
489        self.device_info.as_ref()
490    }
491    #[cfg(feature = "cuquantum")]
492    fn check_cuquantum_available() -> bool {
493        false
494    }
495    #[cfg(feature = "cuquantum")]
496    fn get_cuda_device_info(device_id: i32) -> Result<CudaDeviceInfo> {
497        #[cfg(target_os = "macos")]
498        {
499            Ok(CudaDeviceInfo {
500                device_id: if device_id < 0 { 0 } else { device_id },
501                name: "Mock CUDA Device (macOS - no CUDA)".to_string(),
502                total_memory: 24 * 1024 * 1024 * 1024,
503                free_memory: 20 * 1024 * 1024 * 1024,
504                compute_capability: (8, 9),
505                sm_count: 128,
506                max_threads_per_block: 1024,
507                warp_size: 32,
508                has_tensor_cores: true,
509            })
510        }
511        #[cfg(not(target_os = "macos"))]
512        {
513            Ok(CudaDeviceInfo {
514                device_id: if device_id < 0 { 0 } else { device_id },
515                name: "Mock CUDA Device (cuQuantum stub)".to_string(),
516                total_memory: 24 * 1024 * 1024 * 1024,
517                free_memory: 20 * 1024 * 1024 * 1024,
518                compute_capability: (8, 9),
519                sm_count: 128,
520                max_threads_per_block: 1024,
521                warp_size: 32,
522                has_tensor_cores: true,
523            })
524        }
525    }
526    #[cfg(feature = "cuquantum")]
527    fn initialize_custatevec(&mut self, num_qubits: usize) -> Result<()> {
528        Ok(())
529    }
530    /// Run the circuit through a real cuStateVec context.
531    ///
532    /// Only reachable when [`Self::check_cuquantum_available`] reports a usable
533    /// runtime. This build carries no cuStateVec bindings, so the probe always
534    /// reports `false` and callers take the CPU state-vector path instead; the
535    /// error below states plainly that the bindings are missing rather than
536    /// fabricating a result that pretends the GPU ran.
537    #[cfg(feature = "cuquantum")]
538    fn simulate_with_custatevec<const N: usize>(
539        &mut self,
540        _circuit: &Circuit<N>,
541    ) -> Result<CuQuantumResult> {
542        Err(SimulatorError::GpuError(
543            "cuStateVec bindings are not linked into this build; \
544             rebuild against the cuQuantum SDK to use the GPU path"
545                .to_string(),
546        ))
547    }
548}
549/// Simulation statistics
550#[derive(Debug, Clone, Default)]
551pub struct SimulationStats {
552    /// Total number of simulations run
553    pub total_simulations: usize,
554    /// Total gates applied
555    pub total_gates: usize,
556    /// Total simulation time in milliseconds
557    pub total_time_ms: f64,
558    /// Peak GPU memory usage in bytes
559    pub peak_memory_bytes: usize,
560    /// Number of tensor contractions (for cuTensorNet)
561    pub tensor_contractions: usize,
562    /// Total FLOP count
563    pub total_flops: f64,
564}
565impl SimulationStats {
566    /// Get average gates per simulation
567    pub fn avg_gates_per_sim(&self) -> f64 {
568        if self.total_simulations > 0 {
569            self.total_gates as f64 / self.total_simulations as f64
570        } else {
571            0.0
572        }
573    }
574    /// Get average time per simulation in milliseconds
575    pub fn avg_time_per_sim(&self) -> f64 {
576        if self.total_simulations > 0 {
577            self.total_time_ms / self.total_simulations as f64
578        } else {
579            0.0
580        }
581    }
582    /// Get throughput in GFLOP/s
583    pub fn throughput_gflops(&self) -> f64 {
584        if self.total_time_ms > 0.0 {
585            (self.total_flops / 1e9) / (self.total_time_ms / 1000.0)
586        } else {
587            0.0
588        }
589    }
590}
591/// Computation precision
592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
593pub enum ComputePrecision {
594    /// Half precision (float16) - reduced memory, faster on tensor cores
595    /// Suitable for approximate calculations where high precision isn't critical
596    Half,
597    /// Single precision (float32) - balanced precision and performance
598    /// Recommended for most quantum simulations
599    Single,
600    /// Double precision (float64) - highest precision
601    /// Required for high-fidelity simulations and error-sensitive algorithms
602    Double,
603    /// Mixed precision (automatic FP16/FP32 switching)
604    /// Uses FP16 for matrix operations (tensor cores) and FP32 for accumulation
605    /// Provides near-FP32 accuracy with FP16 speed
606    Mixed,
607}
608
609impl ComputePrecision {
610    /// Get bytes per complex amplitude for this precision
611    pub fn bytes_per_amplitude(self) -> usize {
612        match self {
613            ComputePrecision::Half => 4,    // FP16: 2 bytes × 2 (complex)
614            ComputePrecision::Single => 8,  // FP32: 4 bytes × 2 (complex)
615            ComputePrecision::Double => 16, // FP64: 8 bytes × 2 (complex)
616            ComputePrecision::Mixed => 8,   // Mixed: FP32 for state vector storage
617        }
618    }
619
620    /// Get relative speed multiplier (approximate)
621    /// Higher values = faster computation
622    pub fn speed_factor(self) -> f64 {
623        match self {
624            ComputePrecision::Half => 2.0, // ~2x faster than FP32 on tensor cores
625            ComputePrecision::Single => 1.0, // Baseline
626            ComputePrecision::Double => 0.5, // ~2x slower than FP32
627            ComputePrecision::Mixed => 1.7, // ~1.7x faster than FP32 (with tensor cores)
628        }
629    }
630
631    /// Get relative accuracy (approximate)
632    /// Higher values = more accurate
633    pub fn accuracy_factor(self) -> f64 {
634        match self {
635            ComputePrecision::Half => 0.3,   // ~3 decimal digits precision
636            ComputePrecision::Single => 1.0, // ~7 decimal digits precision (baseline)
637            ComputePrecision::Double => 2.2, // ~15 decimal digits precision
638            ComputePrecision::Mixed => 0.95, // Near-FP32 accuracy
639        }
640    }
641
642    /// Check if precision uses tensor cores (if available)
643    pub fn uses_tensor_cores(self) -> bool {
644        matches!(self, ComputePrecision::Half | ComputePrecision::Mixed)
645    }
646
647    /// Get human-readable description
648    pub fn description(self) -> &'static str {
649        match self {
650            ComputePrecision::Half => {
651                "Half precision (FP16): Fastest, lowest memory, reduced accuracy"
652            }
653            ComputePrecision::Single => {
654                "Single precision (FP32): Balanced speed and accuracy, recommended"
655            }
656            ComputePrecision::Double => {
657                "Double precision (FP64): Highest accuracy, slower, more memory"
658            }
659            ComputePrecision::Mixed => {
660                "Mixed precision (FP16/FP32): Near-FP32 accuracy with FP16 speed on tensor cores"
661            }
662        }
663    }
664}
665/// cuQuantum-specific errors
666#[derive(Debug, Error)]
667pub enum CuQuantumError {
668    #[error("cuQuantum not available: {0}")]
669    NotAvailable(String),
670    #[error("CUDA error: {0}")]
671    CudaError(String),
672    #[error("cuStateVec error: {0}")]
673    CuStateVecError(String),
674    #[error("cuTensorNet error: {0}")]
675    CuTensorNetError(String),
676    #[error("Memory allocation error: {0}")]
677    MemoryError(String),
678    #[error("Invalid configuration: {0}")]
679    ConfigError(String),
680    #[error("Device error: {0}")]
681    DeviceError(String),
682    #[error("Simulation error: {0}")]
683    SimulationError(String),
684}
685/// cuTensorNet-based tensor network simulator
686///
687/// This simulator uses NVIDIA's cuTensorNet library for GPU-accelerated
688/// tensor network contraction, enabling simulation of circuits beyond
689/// the state vector memory limit.
690pub struct CuTensorNetSimulator {
691    /// Configuration
692    pub config: CuQuantumConfig,
693    /// Device information
694    pub device_info: Option<CudaDeviceInfo>,
695    /// Simulation statistics
696    pub stats: SimulationStats,
697    /// Tensor network representation of the circuit
698    pub tensor_network: Option<TensorNetworkState>,
699}
700impl CuTensorNetSimulator {
701    /// Create a new cuTensorNet simulator
702    pub fn new(config: CuQuantumConfig) -> Result<Self> {
703        let device_info = CuStateVecSimulator::get_device_info(config.device_id)?;
704        Ok(Self {
705            config,
706            device_info: Some(device_info),
707            stats: SimulationStats::default(),
708            tensor_network: None,
709        })
710    }
711    /// Create with default configuration
712    pub fn default_config() -> Result<Self> {
713        Self::new(CuQuantumConfig::default())
714    }
715    /// Check if cuTensorNet is available
716    pub fn is_available() -> bool {
717        #[cfg(feature = "cuquantum")]
718        {
719            Self::check_cutensornet_available()
720        }
721        #[cfg(not(feature = "cuquantum"))]
722        {
723            false
724        }
725    }
726    /// Build tensor network from circuit
727    pub fn build_network<const N: usize>(&mut self, circuit: &Circuit<N>) -> Result<()> {
728        self.tensor_network = Some(TensorNetworkState::from_circuit(circuit)?);
729        Ok(())
730    }
731    /// Contract the tensor network to compute amplitudes
732    pub fn contract(&mut self, output_indices: &[usize]) -> Result<Array1<Complex64>> {
733        let network = self
734            .tensor_network
735            .as_ref()
736            .ok_or_else(|| SimulatorError::InvalidParameter("Network not built".to_string()))?;
737        #[cfg(target_os = "macos")]
738        {
739            self.contract_mock(network, output_indices)
740        }
741        #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
742        {
743            self.contract_with_cutensornet(network, output_indices)
744        }
745        #[cfg(all(not(feature = "cuquantum"), not(target_os = "macos")))]
746        {
747            self.contract_mock(network, output_indices)
748        }
749    }
750    /// Compute expectation value of an observable
751    pub fn expectation_value(&mut self, observable: &Observable) -> Result<f64> {
752        let _network = self
753            .tensor_network
754            .as_ref()
755            .ok_or_else(|| SimulatorError::InvalidParameter("Network not built".to_string()))?;
756        #[cfg(target_os = "macos")]
757        {
758            let _ = observable;
759            Ok(0.5)
760        }
761        #[cfg(all(feature = "cuquantum", not(target_os = "macos")))]
762        {
763            self.expectation_with_cutensornet(_network, observable)
764        }
765        #[cfg(all(not(feature = "cuquantum"), not(target_os = "macos")))]
766        {
767            let _ = observable;
768            Ok(0.5)
769        }
770    }
771    /// Get optimal contraction order
772    pub fn find_contraction_order(&self) -> Result<ContractionPath> {
773        let network = self
774            .tensor_network
775            .as_ref()
776            .ok_or_else(|| SimulatorError::InvalidParameter("Network not built".to_string()))?;
777        match self.config.tensor_contraction {
778            TensorContractionAlgorithm::Auto => self.auto_contraction_order(network),
779            TensorContractionAlgorithm::Greedy => self.greedy_contraction_order(network),
780            TensorContractionAlgorithm::Optimal => self.optimal_contraction_order(network),
781            TensorContractionAlgorithm::OptimalWithSlicing => {
782                self.optimal_sliced_contraction_order(network)
783            }
784            TensorContractionAlgorithm::RandomGreedy => {
785                self.random_greedy_contraction_order(network)
786            }
787        }
788    }
789    /// Mock contraction for non-CUDA platforms
790    /// Available on macOS (always) and when cuquantum feature is disabled
791    #[cfg(any(target_os = "macos", not(feature = "cuquantum")))]
792    fn contract_mock(
793        &self,
794        _network: &TensorNetworkState,
795        output_indices: &[usize],
796    ) -> Result<Array1<Complex64>> {
797        let size = 1 << output_indices.len();
798        let mut result = Array1::zeros(size);
799        result[0] = Complex64::new(1.0, 0.0);
800        Ok(result)
801    }
802    fn auto_contraction_order(&self, network: &TensorNetworkState) -> Result<ContractionPath> {
803        if network.num_tensors() < 20 {
804            self.optimal_contraction_order(network)
805        } else {
806            self.greedy_contraction_order(network)
807        }
808    }
809    fn greedy_contraction_order(&self, network: &TensorNetworkState) -> Result<ContractionPath> {
810        let mut path = ContractionPath::new();
811        let mut remaining: Vec<usize> = (0..network.num_tensors()).collect();
812        while remaining.len() > 1 {
813            let mut best_cost = f64::MAX;
814            let mut best_pair = (0, 1);
815            for i in 0..remaining.len() {
816                for j in (i + 1)..remaining.len() {
817                    let cost = self.estimate_contraction_cost(remaining[i], remaining[j]);
818                    if cost < best_cost {
819                        best_cost = cost;
820                        best_pair = (i, j);
821                    }
822                }
823            }
824            path.add_contraction(remaining[best_pair.0], remaining[best_pair.1]);
825            remaining.remove(best_pair.1);
826        }
827        Ok(path)
828    }
829    fn optimal_contraction_order(&self, network: &TensorNetworkState) -> Result<ContractionPath> {
830        if network.num_tensors() > 15 {
831            return self.greedy_contraction_order(network);
832        }
833        self.greedy_contraction_order(network)
834    }
835    fn optimal_sliced_contraction_order(
836        &self,
837        network: &TensorNetworkState,
838    ) -> Result<ContractionPath> {
839        let mut path = self.optimal_contraction_order(network)?;
840        path.enable_slicing(self.config.memory_pool_size);
841        Ok(path)
842    }
843    fn random_greedy_contraction_order(
844        &self,
845        network: &TensorNetworkState,
846    ) -> Result<ContractionPath> {
847        use scirs2_core::random::{thread_rng, Rng};
848        let mut rng = thread_rng();
849        let mut best_path = self.greedy_contraction_order(network)?;
850        let mut best_cost = best_path.total_cost();
851        for _ in 0..10 {
852            let path = self.randomized_greedy_order(network, &mut rng)?;
853            let cost = path.total_cost();
854            if cost < best_cost {
855                best_cost = cost;
856                best_path = path;
857            }
858        }
859        Ok(best_path)
860    }
861    fn randomized_greedy_order<R: scirs2_core::random::Rng>(
862        &self,
863        network: &TensorNetworkState,
864        rng: &mut R,
865    ) -> Result<ContractionPath> {
866        let mut path = ContractionPath::new();
867        let mut remaining: Vec<usize> = (0..network.num_tensors()).collect();
868        while remaining.len() > 1 {
869            let mut candidates: Vec<((usize, usize), f64)> = Vec::new();
870            for i in 0..remaining.len() {
871                for j in (i + 1)..remaining.len() {
872                    let cost = self.estimate_contraction_cost(remaining[i], remaining[j]);
873                    candidates.push(((i, j), cost));
874                }
875            }
876            candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
877            let pick_range = (candidates.len() / 3).max(1);
878            let pick_idx = rng.random_range(0..pick_range);
879            let (best_pair, _) = candidates[pick_idx];
880            path.add_contraction(remaining[best_pair.0], remaining[best_pair.1]);
881            remaining.remove(best_pair.1);
882        }
883        Ok(path)
884    }
885    fn estimate_contraction_cost(&self, _tensor_a: usize, _tensor_b: usize) -> f64 {
886        1.0
887    }
888    #[cfg(feature = "cuquantum")]
889    fn check_cutensornet_available() -> bool {
890        false
891    }
892    #[cfg(feature = "cuquantum")]
893    fn contract_with_cutensornet(
894        &self,
895        _network: &TensorNetworkState,
896        _output_indices: &[usize],
897    ) -> Result<Array1<Complex64>> {
898        Err(SimulatorError::GpuError(
899            "cuTensorNet contraction not yet implemented".to_string(),
900        ))
901    }
902    #[cfg(feature = "cuquantum")]
903    fn expectation_with_cutensornet(
904        &self,
905        _network: &TensorNetworkState,
906        _observable: &Observable,
907    ) -> Result<f64> {
908        Err(SimulatorError::GpuError(
909            "cuTensorNet expectation not yet implemented".to_string(),
910        ))
911    }
912}
913/// Observable for expectation value computation
914#[derive(Debug, Clone)]
915pub enum Observable {
916    /// Pauli Z on specified qubits
917    PauliZ(Vec<usize>),
918    /// Pauli X on specified qubits
919    PauliX(Vec<usize>),
920    /// Pauli Y on specified qubits
921    PauliY(Vec<usize>),
922    /// General Hermitian matrix
923    Hermitian(Array2<Complex64>),
924    /// Sum of observables
925    Sum(Vec<Observable>),
926    /// Product of observables
927    Product(Vec<Observable>),
928}
929#[cfg(feature = "cuquantum")]
930pub struct GpuBuffer {
931    _ptr: *mut std::ffi::c_void,
932    _size: usize,
933}
934#[cfg(feature = "cuquantum")]
935pub struct CuStateVecHandle {
936    _handle: *mut std::ffi::c_void,
937}
938/// Performance estimation results for a quantum circuit
939#[derive(Debug, Clone)]
940pub struct PerformanceEstimate {
941    /// Estimated simulation time in milliseconds
942    pub estimated_time_ms: f64,
943    /// Estimated peak memory usage in bytes
944    pub estimated_memory_bytes: usize,
945    /// Estimated FLOPS required
946    pub estimated_flops: f64,
947    /// Recommended backend (state vector or tensor network)
948    pub recommended_backend: RecommendedBackend,
949    /// Whether the simulation will fit in GPU memory
950    pub fits_in_memory: bool,
951    /// Estimated GPU utilization (0.0 to 1.0)
952    pub estimated_gpu_utilization: f64,
953    /// Warnings or suggestions
954    pub suggestions: Vec<String>,
955}
956/// Contraction path for tensor network
957#[derive(Debug, Clone)]
958pub struct ContractionPath {
959    /// Sequence of contractions (pairs of tensor indices)
960    pub contractions: Vec<(usize, usize)>,
961    /// Estimated cost of each contraction
962    pub costs: Vec<f64>,
963    /// Slicing configuration
964    pub slicing: Option<SlicingConfig>,
965}
966impl ContractionPath {
967    /// Create empty path
968    pub fn new() -> Self {
969        Self {
970            contractions: Vec::new(),
971            costs: Vec::new(),
972            slicing: None,
973        }
974    }
975    /// Add a contraction step
976    pub fn add_contraction(&mut self, tensor_a: usize, tensor_b: usize) {
977        self.contractions.push((tensor_a, tensor_b));
978        self.costs.push(1.0);
979    }
980    /// Get total cost
981    pub fn total_cost(&self) -> f64 {
982        self.costs.iter().sum()
983    }
984    /// Enable slicing for memory reduction
985    pub fn enable_slicing(&mut self, memory_limit: usize) {
986        self.slicing = Some(SlicingConfig {
987            memory_limit,
988            slice_indices: Vec::new(),
989        });
990    }
991}
992/// GPU performance estimator for quantum circuit simulation
993#[derive(Debug)]
994pub struct PerformanceEstimator {
995    /// Device information
996    device_info: CudaDeviceInfo,
997    /// Configuration
998    config: CuQuantumConfig,
999}
1000impl PerformanceEstimator {
1001    /// Create a new performance estimator
1002    pub fn new(device_info: CudaDeviceInfo, config: CuQuantumConfig) -> Self {
1003        Self {
1004            device_info,
1005            config,
1006        }
1007    }
1008    /// Create with default device (mock on macOS)
1009    pub fn with_default_device(config: CuQuantumConfig) -> Result<Self> {
1010        let device_info = CuStateVecSimulator::get_device_info(config.device_id)?;
1011        Ok(Self::new(device_info, config))
1012    }
1013    /// Estimate performance for a quantum circuit
1014    pub fn estimate<const N: usize>(&self, circuit: &Circuit<N>) -> PerformanceEstimate {
1015        let num_qubits = N;
1016        let num_gates = circuit.gates().len();
1017        let state_vector_bytes = self.calculate_state_vector_memory(num_qubits);
1018        let estimated_flops = self.calculate_flops(num_qubits, num_gates);
1019        let fits_in_memory =
1020            state_vector_bytes <= (self.device_info.free_memory as f64 * 0.8) as usize;
1021        let recommended_backend = self.recommend_backend(num_qubits, num_gates, fits_in_memory);
1022        let estimated_time_ms = self.estimate_time(num_qubits, num_gates, &recommended_backend);
1023        let estimated_gpu_utilization =
1024            self.estimate_gpu_utilization(num_qubits, num_gates, &recommended_backend);
1025        let suggestions = self.generate_suggestions(num_qubits, num_gates, fits_in_memory);
1026        PerformanceEstimate {
1027            estimated_time_ms,
1028            estimated_memory_bytes: state_vector_bytes,
1029            estimated_flops,
1030            recommended_backend,
1031            fits_in_memory,
1032            estimated_gpu_utilization,
1033            suggestions,
1034        }
1035    }
1036    /// Calculate state vector memory requirements
1037    fn calculate_state_vector_memory(&self, num_qubits: usize) -> usize {
1038        let num_amplitudes: usize = 1 << num_qubits;
1039        num_amplitudes * self.config.precision.bytes_per_amplitude()
1040    }
1041    /// Calculate estimated FLOPS for simulation
1042    fn calculate_flops(&self, num_qubits: usize, num_gates: usize) -> f64 {
1043        let state_size = 1u64 << num_qubits;
1044        let flops_per_gate = state_size as f64 * 8.0;
1045        num_gates as f64 * flops_per_gate
1046    }
1047    /// Recommend the best backend for simulation
1048    fn recommend_backend(
1049        &self,
1050        num_qubits: usize,
1051        num_gates: usize,
1052        fits_in_memory: bool,
1053    ) -> RecommendedBackend {
1054        if !fits_in_memory {
1055            if num_qubits > 50 {
1056                RecommendedBackend::NotFeasible
1057            } else {
1058                RecommendedBackend::TensorNetwork
1059            }
1060        } else if num_qubits <= self.config.max_statevec_qubits {
1061            let circuit_depth = (num_gates as f64 / num_qubits as f64).ceil() as usize;
1062            if circuit_depth > num_qubits * 10 {
1063                RecommendedBackend::Hybrid
1064            } else {
1065                RecommendedBackend::StateVector
1066            }
1067        } else {
1068            RecommendedBackend::TensorNetwork
1069        }
1070    }
1071    /// Estimate simulation time
1072    fn estimate_time(
1073        &self,
1074        num_qubits: usize,
1075        num_gates: usize,
1076        backend: &RecommendedBackend,
1077    ) -> f64 {
1078        let base_flops = self.calculate_flops(num_qubits, num_gates);
1079        let gpu_throughput_gflops = match self.device_info.compute_capability {
1080            (9, _) => 150.0,
1081            (8, 9) => 83.0,
1082            (8, 6) => 35.0,
1083            (8, 0) => 19.5,
1084            (7, _) => 16.0,
1085            _ => 10.0,
1086        } * 1000.0;
1087        let raw_time_ms = base_flops / (gpu_throughput_gflops * 1e6);
1088        let overhead = match backend {
1089            RecommendedBackend::StateVector => 1.2,
1090            RecommendedBackend::TensorNetwork => 2.5,
1091            RecommendedBackend::Hybrid => 1.8,
1092            RecommendedBackend::NotFeasible => f64::MAX,
1093        };
1094        raw_time_ms * overhead
1095    }
1096    /// Estimate GPU utilization
1097    fn estimate_gpu_utilization(
1098        &self,
1099        num_qubits: usize,
1100        num_gates: usize,
1101        backend: &RecommendedBackend,
1102    ) -> f64 {
1103        match backend {
1104            RecommendedBackend::NotFeasible => 0.0,
1105            _ => {
1106                let size_factor = (num_qubits as f64 / 30.0).min(1.0);
1107                let gate_factor = (num_gates as f64 / 1000.0).min(1.0);
1108                (size_factor * 0.6 + gate_factor * 0.4).clamp(0.1, 0.95)
1109            }
1110        }
1111    }
1112    /// Generate performance suggestions
1113    fn generate_suggestions(
1114        &self,
1115        num_qubits: usize,
1116        num_gates: usize,
1117        fits_in_memory: bool,
1118    ) -> Vec<String> {
1119        let mut suggestions = Vec::new();
1120        if !fits_in_memory {
1121            suggestions
1122                .push(
1123                    format!(
1124                        "Circuit requires {} qubits, which exceeds available GPU memory. Consider using tensor network simulation.",
1125                        num_qubits
1126                    ),
1127                );
1128        }
1129        if num_qubits > 25 && self.config.gate_fusion_level != GateFusionLevel::Aggressive {
1130            suggestions.push(
1131                "Enable aggressive gate fusion for better performance on large circuits."
1132                    .to_string(),
1133            );
1134        }
1135        if num_gates > 10000 && !self.config.async_execution {
1136            suggestions.push("Enable async execution for circuits with many gates.".to_string());
1137        }
1138        if num_qubits > 28 && self.config.precision == ComputePrecision::Double {
1139            suggestions.push(
1140                "Consider using single precision for very large circuits to reduce memory usage."
1141                    .to_string(),
1142            );
1143        }
1144        if self.config.multi_gpu && num_qubits < 26 {
1145            suggestions
1146                .push(
1147                    "Multi-GPU mode is overkill for small circuits. Consider single GPU for better efficiency."
1148                        .to_string(),
1149                );
1150        }
1151        suggestions
1152    }
1153    /// Get device information
1154    pub fn device_info(&self) -> &CudaDeviceInfo {
1155        &self.device_info
1156    }
1157}
1158/// Slicing configuration for memory-efficient contraction
1159#[derive(Debug, Clone)]
1160pub struct SlicingConfig {
1161    /// Memory limit in bytes
1162    memory_limit: usize,
1163    /// Indices to slice over
1164    slice_indices: Vec<usize>,
1165}
1166/// Unified cuQuantum simulator that automatically selects the best backend
1167pub struct CuQuantumSimulator {
1168    /// cuStateVec simulator for state vector simulation
1169    pub statevec: Option<CuStateVecSimulator>,
1170    /// cuTensorNet simulator for tensor network simulation
1171    pub tensornet: Option<CuTensorNetSimulator>,
1172    /// Configuration
1173    pub config: CuQuantumConfig,
1174    /// Threshold for switching to tensor network (number of qubits)
1175    pub tensornet_threshold: usize,
1176}
1177impl CuQuantumSimulator {
1178    /// Create a new unified cuQuantum simulator
1179    pub fn new(config: CuQuantumConfig) -> Result<Self> {
1180        let tensornet_threshold = config.max_statevec_qubits;
1181        let statevec = CuStateVecSimulator::new(config.clone()).ok();
1182        let tensornet = CuTensorNetSimulator::new(config.clone()).ok();
1183        Ok(Self {
1184            statevec,
1185            tensornet,
1186            config,
1187            tensornet_threshold,
1188        })
1189    }
1190    /// Check if any cuQuantum backend is available
1191    pub fn is_available() -> bool {
1192        CuStateVecSimulator::is_available() || CuTensorNetSimulator::is_available()
1193    }
1194    /// Simulate a circuit, automatically selecting the best backend
1195    pub fn simulate<const N: usize>(&mut self, circuit: &Circuit<N>) -> Result<CuQuantumResult> {
1196        if N <= self.tensornet_threshold {
1197            if let Some(ref mut sv) = self.statevec {
1198                return sv.simulate(circuit);
1199            }
1200        }
1201        if let Some(ref mut tn) = self.tensornet {
1202            tn.build_network(circuit)?;
1203            let amplitudes = tn.contract(&(0..N).collect::<Vec<_>>())?;
1204            return Ok(CuQuantumResult::from_state_vector(amplitudes, N));
1205        }
1206        Err(SimulatorError::GpuError(
1207            "No cuQuantum backend available".to_string(),
1208        ))
1209    }
1210    /// Get combined statistics
1211    pub fn stats(&self) -> SimulationStats {
1212        let mut stats = SimulationStats::default();
1213        if let Some(ref sv) = self.statevec {
1214            let sv_stats = sv.stats();
1215            stats.total_simulations += sv_stats.total_simulations;
1216            stats.total_gates += sv_stats.total_gates;
1217            stats.total_time_ms += sv_stats.total_time_ms;
1218            stats.peak_memory_bytes = stats.peak_memory_bytes.max(sv_stats.peak_memory_bytes);
1219        }
1220        if let Some(ref tn) = self.tensornet {
1221            stats.tensor_contractions += tn.stats.tensor_contractions;
1222        }
1223        stats
1224    }
1225}
1226/// Gate fusion optimization level
1227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1228pub enum GateFusionLevel {
1229    /// No fusion
1230    None,
1231    /// Conservative fusion (adjacent single-qubit gates)
1232    Conservative,
1233    /// Moderate fusion (single-qubit + some two-qubit)
1234    Moderate,
1235    /// Aggressive fusion (maximize fusion opportunities)
1236    Aggressive,
1237}
1238/// GPU resource planner for multi-circuit simulation
1239#[derive(Debug)]
1240pub struct GpuResourcePlanner {
1241    /// Available devices
1242    devices: Vec<CudaDeviceInfo>,
1243    /// Configuration
1244    config: CuQuantumConfig,
1245}
1246impl GpuResourcePlanner {
1247    /// Create a new resource planner
1248    pub fn new(devices: Vec<CudaDeviceInfo>, config: CuQuantumConfig) -> Self {
1249        Self { devices, config }
1250    }
1251    /// Plan resource allocation for batch simulation
1252    pub fn plan_batch<const N: usize>(&self, circuits: &[Circuit<N>]) -> Vec<(usize, usize)> {
1253        if self.devices.is_empty() || circuits.is_empty() {
1254            return Vec::new();
1255        }
1256        let mut assignments = Vec::new();
1257        for (idx, _circuit) in circuits.iter().enumerate() {
1258            let device_idx = idx % self.devices.len();
1259            assignments.push((self.devices[device_idx].device_id as usize, idx));
1260        }
1261        assignments
1262    }
1263    /// Estimate total memory required for batch simulation
1264    pub fn estimate_batch_memory<const N: usize>(&self, circuits: &[Circuit<N>]) -> usize {
1265        let state_size: usize = 1 << N;
1266        state_size * self.config.precision.bytes_per_amplitude() * circuits.len()
1267    }
1268}
1269/// Circuit complexity analyzer
1270#[derive(Debug, Clone)]
1271pub struct CircuitComplexity {
1272    /// Number of qubits
1273    pub num_qubits: usize,
1274    /// Total number of gates
1275    pub num_gates: usize,
1276    /// Number of single-qubit gates
1277    pub single_qubit_gates: usize,
1278    /// Number of two-qubit gates
1279    pub two_qubit_gates: usize,
1280    /// Number of multi-qubit gates (3+)
1281    pub multi_qubit_gates: usize,
1282    /// Circuit depth
1283    pub depth: usize,
1284    /// Estimated entanglement degree (0.0 to 1.0)
1285    pub entanglement_degree: f64,
1286    /// Gate types used
1287    pub gate_types: Vec<String>,
1288}
1289impl CircuitComplexity {
1290    /// Analyze a quantum circuit
1291    pub fn analyze<const N: usize>(circuit: &Circuit<N>) -> Self {
1292        let mut single_qubit_gates = 0;
1293        let mut two_qubit_gates = 0;
1294        let mut multi_qubit_gates = 0;
1295        let mut gate_types = std::collections::HashSet::new();
1296        for gate in circuit.gates() {
1297            let num_qubits_affected = gate.qubits().len();
1298            match num_qubits_affected {
1299                1 => single_qubit_gates += 1,
1300                2 => two_qubit_gates += 1,
1301                _ => multi_qubit_gates += 1,
1302            }
1303            gate_types.insert(gate.name().to_string());
1304        }
1305        let depth = if N > 0 {
1306            (circuit.gates().len() as f64 / N as f64).ceil() as usize
1307        } else {
1308            0
1309        };
1310        let total_gates = circuit.gates().len();
1311        let entanglement_degree = if total_gates > 0 {
1312            (two_qubit_gates + multi_qubit_gates * 2) as f64 / total_gates as f64
1313        } else {
1314            0.0
1315        };
1316        Self {
1317            num_qubits: N,
1318            num_gates: total_gates,
1319            single_qubit_gates,
1320            two_qubit_gates,
1321            multi_qubit_gates,
1322            depth,
1323            entanglement_degree,
1324            gate_types: gate_types.into_iter().collect(),
1325        }
1326    }
1327}
1328/// Tensor network contraction algorithm
1329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1330pub enum TensorContractionAlgorithm {
1331    /// Automatic selection based on circuit structure
1332    Auto,
1333    /// Greedy contraction order
1334    Greedy,
1335    /// Optimal contraction order (may be expensive for large circuits)
1336    Optimal,
1337    /// Optimal with index slicing for memory reduction
1338    OptimalWithSlicing,
1339    /// Random greedy trials
1340    RandomGreedy,
1341}
1342#[cfg(test)]
1343mod cuquantum_mock_simulation_tests {
1344    use super::*;
1345    use quantrs2_circuit::prelude::Circuit;
1346    use quantrs2_core::qubit::QubitId;
1347
1348    /// Regression test for the P0 finding: `simulate_mock` used to ignore
1349    /// the circuit entirely and always return `|0...0>`. A single X gate
1350    /// on qubit 0 of a 1-qubit circuit must now actually flip the state to
1351    /// `|1>`.
1352    #[test]
1353    fn test_simulate_mock_applies_x_gate() {
1354        let mut simulator =
1355            CuStateVecSimulator::default_config().expect("failed to build simulator");
1356        let mut circuit = Circuit::<1>::new();
1357        circuit.x(QubitId::new(0)).expect("failed to add X gate");
1358
1359        let result = simulator.simulate(&circuit).expect("simulation failed");
1360        let state = result.state_vector.expect("expected a state vector");
1361
1362        assert!(
1363            (state[0].norm()).abs() < 1e-10,
1364            "amplitude of |0> should vanish"
1365        );
1366        assert!(
1367            (state[1].norm() - 1.0).abs() < 1e-10,
1368            "amplitude of |1> should be 1.0 after X, got {:?}",
1369            state[1]
1370        );
1371    }
1372
1373    /// Regression test: a real two-qubit CNOT must actually entangle the
1374    /// state (not silently be dropped as identity).
1375    #[test]
1376    fn test_simulate_mock_applies_cnot_gate() {
1377        let mut simulator =
1378            CuStateVecSimulator::default_config().expect("failed to build simulator");
1379        let mut circuit = Circuit::<2>::new();
1380        circuit.x(QubitId::new(0)).expect("failed to add X gate");
1381        circuit
1382            .cnot(QubitId::new(0), QubitId::new(1))
1383            .expect("failed to add CNOT gate");
1384
1385        let result = simulator.simulate(&circuit).expect("simulation failed");
1386        let state = result.state_vector.expect("expected a state vector");
1387
1388        // Starting from |00>, X(q0) -> |q1=0,q0=1> = index 1,
1389        // then CNOT(control=q0,target=q1) flips q1 -> |q1=1,q0=1> = index 3.
1390        for (i, amp) in state.iter().enumerate() {
1391            if i == 3 {
1392                assert!(
1393                    (amp.norm() - 1.0).abs() < 1e-10,
1394                    "expected amplitude 1.0 at index 3, got {amp:?}"
1395                );
1396            } else {
1397                assert!(
1398                    amp.norm() < 1e-10,
1399                    "expected zero amplitude at index {i}, got {amp:?}"
1400                );
1401            }
1402        }
1403        assert_eq!(simulator.stats().total_gates, 2);
1404    }
1405}