Skip to main content

quantrs2_sim/
quantum_supremacy.rs

1//! Quantum supremacy verification algorithms and benchmarks.
2//!
3//! This module implements various algorithms and statistical tests for verifying
4//! quantum supremacy claims, including cross-entropy benchmarking, Porter-Thomas
5//! distribution analysis, and linear cross-entropy benchmarking (Linear XEB).
6
7use scirs2_core::ndarray::Array1;
8use scirs2_core::random::prelude::*;
9use scirs2_core::random::ChaCha8Rng;
10use scirs2_core::random::{RngExt, SeedableRng};
11use scirs2_core::Complex64;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::f64::consts::PI;
15
16use crate::error::{Result, SimulatorError};
17use crate::scirs2_integration::SciRS2Backend;
18
19/// Cross-entropy benchmarking result
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct CrossEntropyResult {
22    /// Linear cross-entropy benchmarking (XEB) fidelity
23    pub linear_xeb_fidelity: f64,
24    /// Cross-entropy difference
25    pub cross_entropy_difference: f64,
26    /// Number of samples used
27    pub num_samples: usize,
28    /// Statistical confidence (p-value)
29    pub confidence: f64,
30    /// Porter-Thomas test results
31    pub porter_thomas: PorterThomasResult,
32    /// Heavy output generation (HOG) score
33    pub hog_score: f64,
34    /// Computational cost comparison
35    pub cost_comparison: CostComparison,
36}
37
38/// Porter-Thomas distribution analysis
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PorterThomasResult {
41    /// Chi-squared statistic
42    pub chi_squared: f64,
43    /// Degrees of freedom
44    pub degrees_freedom: usize,
45    /// P-value of the test
46    pub p_value: f64,
47    /// Whether distribution matches Porter-Thomas
48    pub is_porter_thomas: bool,
49    /// Kolmogorov-Smirnov test statistic
50    pub ks_statistic: f64,
51    /// Mean of the distribution
52    pub mean: f64,
53    /// Variance of the distribution
54    pub variance: f64,
55}
56
57/// Heavy Output Generation analysis
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct HOGAnalysis {
60    /// Fraction of heavy outputs
61    pub heavy_fraction: f64,
62    /// Expected heavy fraction for random circuit
63    pub expected_heavy_fraction: f64,
64    /// Threshold for heavy outputs
65    pub threshold: f64,
66    /// Total outputs analyzed
67    pub total_outputs: usize,
68    /// Heavy outputs count
69    pub heavy_count: usize,
70}
71
72/// Computational cost comparison
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct CostComparison {
75    /// Classical simulation time (seconds)
76    pub classical_time: f64,
77    /// Quantum execution time estimate (seconds)
78    pub quantum_time: f64,
79    /// Memory usage for classical simulation (bytes)
80    pub classical_memory: usize,
81    /// Speedup factor
82    pub speedup_factor: f64,
83    /// Number of operations
84    pub operation_count: usize,
85}
86
87/// Quantum supremacy verifier
88pub struct QuantumSupremacyVerifier {
89    /// Number of qubits
90    num_qubits: usize,
91    /// Random number generator
92    rng: ChaCha8Rng,
93    /// `SciRS2` backend for optimization
94    backend: Option<SciRS2Backend>,
95    /// Verification parameters
96    params: VerificationParams,
97}
98
99/// Parameters for quantum supremacy verification
100#[derive(Debug, Clone)]
101pub struct VerificationParams {
102    /// Number of random circuits to test
103    pub num_circuits: usize,
104    /// Number of samples per circuit
105    pub samples_per_circuit: usize,
106    /// Circuit depth
107    pub circuit_depth: usize,
108    /// Gate set to use
109    pub gate_set: GateSet,
110    /// Significance level for statistical tests
111    pub significance_level: f64,
112    /// Porter-Thomas test bins
113    pub pt_bins: usize,
114    /// Heavy output threshold percentile
115    pub heavy_threshold_percentile: f64,
116}
117
118/// Gate set for random circuit generation
119#[derive(Debug, Clone)]
120pub enum GateSet {
121    /// Sycamore-like gate set (√X, √Y, √W, CZ)
122    Sycamore,
123    /// Google's quantum supremacy gate set
124    GoogleSupremacy,
125    /// Universal gate set (H, T, CNOT)
126    Universal,
127    /// IBM-like gate set (RZ, SX, CNOT)
128    IBM,
129    /// Custom gate set
130    Custom(Vec<String>),
131}
132
133impl Default for VerificationParams {
134    fn default() -> Self {
135        Self {
136            num_circuits: 100,
137            samples_per_circuit: 1000,
138            circuit_depth: 20,
139            gate_set: GateSet::Sycamore,
140            significance_level: 0.05,
141            pt_bins: 100,
142            heavy_threshold_percentile: 50.0,
143        }
144    }
145}
146
147impl QuantumSupremacyVerifier {
148    /// Create new quantum supremacy verifier
149    pub fn new(num_qubits: usize, params: VerificationParams) -> Result<Self> {
150        Ok(Self {
151            num_qubits,
152            rng: ChaCha8Rng::from_rng(&mut thread_rng()),
153            backend: None,
154            params,
155        })
156    }
157
158    /// Initialize with `SciRS2` backend
159    pub fn with_scirs2_backend(mut self) -> Result<Self> {
160        self.backend = Some(SciRS2Backend::new());
161        Ok(self)
162    }
163
164    /// Set random seed for reproducibility
165    #[must_use]
166    pub fn with_seed(mut self, seed: u64) -> Self {
167        self.rng = ChaCha8Rng::seed_from_u64(seed);
168        self
169    }
170
171    /// Perform comprehensive quantum supremacy verification
172    pub fn verify_quantum_supremacy(&mut self) -> Result<CrossEntropyResult> {
173        let start_time = std::time::Instant::now();
174
175        // Generate random quantum circuits
176        let circuits = self.generate_random_circuits()?;
177
178        // Compute ideal amplitudes (classical simulation)
179        let ideal_amplitudes = self.compute_ideal_amplitudes(&circuits)?;
180
181        // Simulate quantum sampling using ideal state amplitudes as the probability distribution
182        let quantum_samples = self.simulate_quantum_sampling(&ideal_amplitudes)?;
183
184        // Perform cross-entropy benchmarking
185        let linear_xeb = self.compute_linear_xeb(&ideal_amplitudes, &quantum_samples)?;
186
187        // Analyze Porter-Thomas distribution
188        let porter_thomas = self.analyze_porter_thomas(&ideal_amplitudes)?;
189
190        // Compute Heavy Output Generation score
191        let hog_score = self.compute_hog_score(&ideal_amplitudes, &quantum_samples)?;
192
193        // Cross-entropy difference
194        let cross_entropy_diff =
195            self.compute_cross_entropy_difference(&ideal_amplitudes, &quantum_samples)?;
196
197        // Statistical confidence
198        let confidence =
199            self.compute_statistical_confidence(&ideal_amplitudes, &quantum_samples)?;
200
201        let classical_time = start_time.elapsed().as_secs_f64();
202
203        // Cost comparison
204        let cost_comparison = CostComparison {
205            classical_time,
206            quantum_time: self.estimate_quantum_time()?,
207            classical_memory: self.estimate_classical_memory(),
208            speedup_factor: classical_time / self.estimate_quantum_time()?,
209            operation_count: circuits.len() * self.params.circuit_depth,
210        };
211
212        Ok(CrossEntropyResult {
213            linear_xeb_fidelity: linear_xeb,
214            cross_entropy_difference: cross_entropy_diff,
215            num_samples: self.params.samples_per_circuit,
216            confidence,
217            porter_thomas,
218            hog_score,
219            cost_comparison,
220        })
221    }
222
223    /// Generate random quantum circuits
224    fn generate_random_circuits(&mut self) -> Result<Vec<RandomCircuit>> {
225        let mut circuits = Vec::with_capacity(self.params.num_circuits);
226
227        for _ in 0..self.params.num_circuits {
228            let circuit = self.generate_single_random_circuit()?;
229            circuits.push(circuit);
230        }
231
232        Ok(circuits)
233    }
234
235    /// Generate a single random circuit
236    fn generate_single_random_circuit(&mut self) -> Result<RandomCircuit> {
237        let mut layers = Vec::with_capacity(self.params.circuit_depth);
238
239        for layer_idx in 0..self.params.circuit_depth {
240            let layer = match &self.params.gate_set {
241                GateSet::Sycamore => self.generate_sycamore_layer(layer_idx)?,
242                GateSet::GoogleSupremacy => self.generate_google_supremacy_layer(layer_idx)?,
243                GateSet::Universal => self.generate_universal_layer(layer_idx)?,
244                GateSet::IBM => self.generate_ibm_layer(layer_idx)?,
245                GateSet::Custom(gates) => {
246                    let gates_clone = gates.clone();
247                    self.generate_custom_layer(layer_idx, &gates_clone)?
248                }
249            };
250            layers.push(layer);
251        }
252
253        Ok(RandomCircuit {
254            num_qubits: self.num_qubits,
255            layers,
256            measurement_pattern: self.generate_measurement_pattern(),
257        })
258    }
259
260    /// Generate Sycamore-style layer
261    fn generate_sycamore_layer(&mut self, layer_idx: usize) -> Result<CircuitLayer> {
262        let mut gates = Vec::new();
263
264        // Single-qubit gates
265        for qubit in 0..self.num_qubits {
266            let gate_type = match self.rng.random_range(0..3) {
267                0 => "SqrtX",
268                1 => "SqrtY",
269                _ => "SqrtW",
270            };
271
272            gates.push(QuantumGate {
273                gate_type: gate_type.to_string(),
274                qubits: vec![qubit],
275                parameters: vec![],
276            });
277        }
278
279        // Two-qubit gates (CZ gates with connectivity pattern)
280        let offset = layer_idx % 2;
281        for row in 0..((self.num_qubits as f64).sqrt() as usize) {
282            for col in (offset..((self.num_qubits as f64).sqrt() as usize)).step_by(2) {
283                let qubit1 = row * ((self.num_qubits as f64).sqrt() as usize) + col;
284                let qubit2 = qubit1 + 1;
285
286                if qubit2 < self.num_qubits {
287                    gates.push(QuantumGate {
288                        gate_type: "CZ".to_string(),
289                        qubits: vec![qubit1, qubit2],
290                        parameters: vec![],
291                    });
292                }
293            }
294        }
295
296        Ok(CircuitLayer { gates })
297    }
298
299    /// Generate Google quantum supremacy layer
300    fn generate_google_supremacy_layer(&mut self, layer_idx: usize) -> Result<CircuitLayer> {
301        // Similar to Sycamore but with specific gate rotations
302        self.generate_sycamore_layer(layer_idx)
303    }
304
305    /// Generate universal gate set layer
306    fn generate_universal_layer(&mut self, _layer_idx: usize) -> Result<CircuitLayer> {
307        let mut gates = Vec::new();
308
309        // Random single-qubit gates
310        for qubit in 0..self.num_qubits {
311            let gate_type = match self.rng.random_range(0..3) {
312                0 => "H",
313                1 => "T",
314                _ => "S",
315            };
316
317            gates.push(QuantumGate {
318                gate_type: gate_type.to_string(),
319                qubits: vec![qubit],
320                parameters: vec![],
321            });
322        }
323
324        // Random CNOT gates
325        let num_cnots = self.rng.random_range(1..=self.num_qubits / 2);
326        for _ in 0..num_cnots {
327            let control = self.rng.random_range(0..self.num_qubits);
328            let target = self.rng.random_range(0..self.num_qubits);
329
330            if control != target {
331                gates.push(QuantumGate {
332                    gate_type: "CNOT".to_string(),
333                    qubits: vec![control, target],
334                    parameters: vec![],
335                });
336            }
337        }
338
339        Ok(CircuitLayer { gates })
340    }
341
342    /// Generate IBM gate set layer
343    fn generate_ibm_layer(&mut self, _layer_idx: usize) -> Result<CircuitLayer> {
344        let mut gates = Vec::new();
345
346        // RZ and SX gates
347        for qubit in 0..self.num_qubits {
348            // Random RZ rotation
349            let angle = self.rng.random::<f64>() * 2.0 * PI;
350            gates.push(QuantumGate {
351                gate_type: "RZ".to_string(),
352                qubits: vec![qubit],
353                parameters: vec![angle],
354            });
355
356            // SX gate with probability 0.5
357            if self.rng.random::<bool>() {
358                gates.push(QuantumGate {
359                    gate_type: "SX".to_string(),
360                    qubits: vec![qubit],
361                    parameters: vec![],
362                });
363            }
364        }
365
366        Ok(CircuitLayer { gates })
367    }
368
369    /// Generate custom gate set layer
370    const fn generate_custom_layer(
371        &self,
372        _layer_idx: usize,
373        _gates: &[String],
374    ) -> Result<CircuitLayer> {
375        // Implement custom gate generation
376        Ok(CircuitLayer { gates: Vec::new() })
377    }
378
379    /// Generate measurement pattern
380    fn generate_measurement_pattern(&self) -> Vec<usize> {
381        // For now, measure all qubits in computational basis
382        (0..self.num_qubits).collect()
383    }
384
385    /// Compute ideal amplitudes using classical state-vector simulation
386    fn compute_ideal_amplitudes(
387        &self,
388        circuits: &[RandomCircuit],
389    ) -> Result<Vec<Array1<Complex64>>> {
390        let mut amplitudes = Vec::with_capacity(circuits.len());
391
392        for circuit in circuits {
393            let dim = 1usize << self.num_qubits;
394            // Initialise to |0...0⟩
395            let mut state = vec![Complex64::new(0.0, 0.0); dim];
396            state[0] = Complex64::new(1.0, 0.0);
397
398            // Apply circuit layers
399            for layer in &circuit.layers {
400                for gate in &layer.gates {
401                    self.apply_gate_to_state(&mut state, gate)?;
402                }
403            }
404
405            let final_state = Array1::from_vec(state);
406            amplitudes.push(final_state);
407        }
408
409        Ok(amplitudes)
410    }
411
412    /// Apply a single gate to a raw state-vector slice.
413    ///
414    /// All gate matrices are given in the standard computational-basis order.
415    fn apply_gate_to_state(&self, state: &mut Vec<Complex64>, gate: &QuantumGate) -> Result<()> {
416        let n = self.num_qubits;
417        let dim = state.len();
418
419        /// Apply a 2×2 single-qubit gate matrix `m` (row-major, len 4) to `state`
420        /// at `target_bit` (0 = least-significant qubit).
421        fn apply_1q(state: &mut [Complex64], m: &[Complex64; 4], target_bit: usize, dim: usize) {
422            for i in 0..dim {
423                if (i >> target_bit) & 1 == 0 {
424                    let j = i | (1 << target_bit);
425                    let a = state[i];
426                    let b = state[j];
427                    state[i] = m[0] * a + m[1] * b;
428                    state[j] = m[2] * a + m[3] * b;
429                }
430            }
431        }
432
433        /// Apply a 4×4 two-qubit gate matrix `m` (row-major, len 16) to `state`
434        /// for (`ctrl_bit`, `tgt_bit`).  The matrix is ordered |00⟩,|01⟩,|10⟩,|11⟩
435        /// where the first index is `ctrl_bit` and the second is `tgt_bit`.
436        fn apply_2q(
437            state: &mut [Complex64],
438            m: &[Complex64; 16],
439            ctrl_bit: usize,
440            tgt_bit: usize,
441            dim: usize,
442        ) {
443            for i in 0..dim {
444                // Only process the "00" representative of each 4-element group
445                if (i >> ctrl_bit) & 1 == 0 && (i >> tgt_bit) & 1 == 0 {
446                    let i00 = i;
447                    let i01 = i | (1 << tgt_bit);
448                    let i10 = i | (1 << ctrl_bit);
449                    let i11 = i | (1 << ctrl_bit) | (1 << tgt_bit);
450                    let s00 = state[i00];
451                    let s01 = state[i01];
452                    let s10 = state[i10];
453                    let s11 = state[i11];
454                    state[i00] = m[0] * s00 + m[1] * s01 + m[2] * s10 + m[3] * s11;
455                    state[i01] = m[4] * s00 + m[5] * s01 + m[6] * s10 + m[7] * s11;
456                    state[i10] = m[8] * s00 + m[9] * s01 + m[10] * s10 + m[11] * s11;
457                    state[i11] = m[12] * s00 + m[13] * s01 + m[14] * s10 + m[15] * s11;
458                }
459            }
460        }
461
462        match gate.gate_type.as_str() {
463            // ─── single-qubit gates ───────────────────────────────────────
464            "H" => {
465                let q = *gate.qubits.first().ok_or_else(|| {
466                    SimulatorError::InvalidInput("H gate requires a qubit argument".to_string())
467                })?;
468                if q >= n {
469                    return Err(SimulatorError::InvalidInput(format!(
470                        "Qubit index {q} out of range ({n} qubits)"
471                    )));
472                }
473                let f = std::f64::consts::FRAC_1_SQRT_2;
474                let c = Complex64::new(f, 0.0);
475                let m = [c, c, c, -c];
476                apply_1q(state, &m, q, dim);
477            }
478            "X" | "PauliX" => {
479                let q = *gate.qubits.first().ok_or_else(|| {
480                    SimulatorError::InvalidInput("X gate requires a qubit argument".to_string())
481                })?;
482                if q >= n {
483                    return Err(SimulatorError::InvalidInput(format!(
484                        "Qubit index {q} out of range ({n} qubits)"
485                    )));
486                }
487                let m = [
488                    Complex64::new(0.0, 0.0),
489                    Complex64::new(1.0, 0.0),
490                    Complex64::new(1.0, 0.0),
491                    Complex64::new(0.0, 0.0),
492                ];
493                apply_1q(state, &m, q, dim);
494            }
495            "Y" | "PauliY" => {
496                let q = *gate.qubits.first().ok_or_else(|| {
497                    SimulatorError::InvalidInput("Y gate requires a qubit argument".to_string())
498                })?;
499                if q >= n {
500                    return Err(SimulatorError::InvalidInput(format!(
501                        "Qubit index {q} out of range ({n} qubits)"
502                    )));
503                }
504                let m = [
505                    Complex64::new(0.0, 0.0),
506                    Complex64::new(0.0, -1.0),
507                    Complex64::new(0.0, 1.0),
508                    Complex64::new(0.0, 0.0),
509                ];
510                apply_1q(state, &m, q, dim);
511            }
512            "Z" | "PauliZ" => {
513                let q = *gate.qubits.first().ok_or_else(|| {
514                    SimulatorError::InvalidInput("Z gate requires a qubit argument".to_string())
515                })?;
516                if q >= n {
517                    return Err(SimulatorError::InvalidInput(format!(
518                        "Qubit index {q} out of range ({n} qubits)"
519                    )));
520                }
521                let m = [
522                    Complex64::new(1.0, 0.0),
523                    Complex64::new(0.0, 0.0),
524                    Complex64::new(0.0, 0.0),
525                    Complex64::new(-1.0, 0.0),
526                ];
527                apply_1q(state, &m, q, dim);
528            }
529            "S" => {
530                let q = *gate.qubits.first().ok_or_else(|| {
531                    SimulatorError::InvalidInput("S gate requires a qubit argument".to_string())
532                })?;
533                if q >= n {
534                    return Err(SimulatorError::InvalidInput(format!(
535                        "Qubit index {q} out of range ({n} qubits)"
536                    )));
537                }
538                let m = [
539                    Complex64::new(1.0, 0.0),
540                    Complex64::new(0.0, 0.0),
541                    Complex64::new(0.0, 0.0),
542                    Complex64::new(0.0, 1.0),
543                ];
544                apply_1q(state, &m, q, dim);
545            }
546            "T" => {
547                let q = *gate.qubits.first().ok_or_else(|| {
548                    SimulatorError::InvalidInput("T gate requires a qubit argument".to_string())
549                })?;
550                if q >= n {
551                    return Err(SimulatorError::InvalidInput(format!(
552                        "Qubit index {q} out of range ({n} qubits)"
553                    )));
554                }
555                let f = std::f64::consts::FRAC_1_SQRT_2;
556                let m = [
557                    Complex64::new(1.0, 0.0),
558                    Complex64::new(0.0, 0.0),
559                    Complex64::new(0.0, 0.0),
560                    Complex64::new(f, f),
561                ];
562                apply_1q(state, &m, q, dim);
563            }
564            "SX" | "SqrtX" => {
565                // √X = ½ [[1+i, 1-i], [1-i, 1+i]]
566                let q = *gate.qubits.first().ok_or_else(|| {
567                    SimulatorError::InvalidInput("SX gate requires a qubit argument".to_string())
568                })?;
569                if q >= n {
570                    return Err(SimulatorError::InvalidInput(format!(
571                        "Qubit index {q} out of range ({n} qubits)"
572                    )));
573                }
574                let pp = Complex64::new(0.5, 0.5);
575                let pm = Complex64::new(0.5, -0.5);
576                let m = [pp, pm, pm, pp];
577                apply_1q(state, &m, q, dim);
578            }
579            "SqrtY" => {
580                // √Y = ½ [[1+i, -1-i], [1+i,  1+i]]
581                let q = *gate.qubits.first().ok_or_else(|| {
582                    SimulatorError::InvalidInput("SqrtY gate requires a qubit argument".to_string())
583                })?;
584                if q >= n {
585                    return Err(SimulatorError::InvalidInput(format!(
586                        "Qubit index {q} out of range ({n} qubits)"
587                    )));
588                }
589                let pp = Complex64::new(0.5, 0.5);
590                let nm = Complex64::new(-0.5, -0.5);
591                let m = [pp, nm, pp, pp];
592                apply_1q(state, &m, q, dim);
593            }
594            "SqrtW" => {
595                // √W where W = (X+Y)/√2; matrix = [[cos(π/4), -i·sin(π/4)·e^(-iπ/4)], [i·sin(π/4)·e^(iπ/4), cos(π/4)]]
596                // A common definition for √W used in Sycamore benchmarking:
597                // √W = [[1, -√(i)/√2], [√(-i)/√2, 1]] / √2, simplified to a unitary below.
598                let q = *gate.qubits.first().ok_or_else(|| {
599                    SimulatorError::InvalidInput("SqrtW gate requires a qubit argument".to_string())
600                })?;
601                if q >= n {
602                    return Err(SimulatorError::InvalidInput(format!(
603                        "Qubit index {q} out of range ({n} qubits)"
604                    )));
605                }
606                // Use: W = (X+Y)/√2, so W matrix = 1/√2 * [[0,1-i],[1+i,0]]
607                // √W unitary (one principal square root):
608                let f = 0.5_f64;
609                let m = [
610                    Complex64::new(f, f),
611                    Complex64::new(f, -f),
612                    Complex64::new(f, f),
613                    Complex64::new(-f, f),
614                ];
615                apply_1q(state, &m, q, dim);
616            }
617            "RZ" => {
618                let q = *gate.qubits.first().ok_or_else(|| {
619                    SimulatorError::InvalidInput("RZ gate requires a qubit argument".to_string())
620                })?;
621                if q >= n {
622                    return Err(SimulatorError::InvalidInput(format!(
623                        "Qubit index {q} out of range ({n} qubits)"
624                    )));
625                }
626                let angle = gate.parameters.first().copied().unwrap_or(0.0);
627                // RZ(θ) = [[e^(-iθ/2), 0], [0, e^(iθ/2)]]
628                let half = angle / 2.0;
629                let m = [
630                    Complex64::new((-half).cos(), (-half).sin()),
631                    Complex64::new(0.0, 0.0),
632                    Complex64::new(0.0, 0.0),
633                    Complex64::new(half.cos(), half.sin()),
634                ];
635                apply_1q(state, &m, q, dim);
636            }
637            "RX" => {
638                let q = *gate.qubits.first().ok_or_else(|| {
639                    SimulatorError::InvalidInput("RX gate requires a qubit argument".to_string())
640                })?;
641                if q >= n {
642                    return Err(SimulatorError::InvalidInput(format!(
643                        "Qubit index {q} out of range ({n} qubits)"
644                    )));
645                }
646                let angle = gate.parameters.first().copied().unwrap_or(0.0);
647                let half = angle / 2.0;
648                let c = half.cos();
649                let s = half.sin();
650                let m = [
651                    Complex64::new(c, 0.0),
652                    Complex64::new(0.0, -s),
653                    Complex64::new(0.0, -s),
654                    Complex64::new(c, 0.0),
655                ];
656                apply_1q(state, &m, q, dim);
657            }
658            "RY" => {
659                let q = *gate.qubits.first().ok_or_else(|| {
660                    SimulatorError::InvalidInput("RY gate requires a qubit argument".to_string())
661                })?;
662                if q >= n {
663                    return Err(SimulatorError::InvalidInput(format!(
664                        "Qubit index {q} out of range ({n} qubits)"
665                    )));
666                }
667                let angle = gate.parameters.first().copied().unwrap_or(0.0);
668                let half = angle / 2.0;
669                let c = half.cos();
670                let s = half.sin();
671                let m = [
672                    Complex64::new(c, 0.0),
673                    Complex64::new(-s, 0.0),
674                    Complex64::new(s, 0.0),
675                    Complex64::new(c, 0.0),
676                ];
677                apply_1q(state, &m, q, dim);
678            }
679            // ─── two-qubit gates ──────────────────────────────────────────
680            "CNOT" | "CX" => {
681                if gate.qubits.len() < 2 {
682                    return Err(SimulatorError::InvalidInput(
683                        "CNOT gate requires 2 qubit arguments".to_string(),
684                    ));
685                }
686                let ctrl = gate.qubits[0];
687                let tgt = gate.qubits[1];
688                if ctrl >= n || tgt >= n {
689                    return Err(SimulatorError::InvalidInput(format!(
690                        "Qubit index out of range ({n} qubits)"
691                    )));
692                }
693                // CNOT matrix |ctrl,tgt⟩: [[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]]
694                let o = Complex64::new(0.0, 0.0);
695                let l = Complex64::new(1.0, 0.0);
696                let m: [Complex64; 16] = [l, o, o, o, o, l, o, o, o, o, o, l, o, o, l, o];
697                apply_2q(state, &m, ctrl, tgt, dim);
698            }
699            "CZ" => {
700                if gate.qubits.len() < 2 {
701                    return Err(SimulatorError::InvalidInput(
702                        "CZ gate requires 2 qubit arguments".to_string(),
703                    ));
704                }
705                let ctrl = gate.qubits[0];
706                let tgt = gate.qubits[1];
707                if ctrl >= n || tgt >= n {
708                    return Err(SimulatorError::InvalidInput(format!(
709                        "Qubit index out of range ({n} qubits)"
710                    )));
711                }
712                // CZ: flip phase of |11⟩ component
713                let mask = (1 << ctrl) | (1 << tgt);
714                for i in 0..dim {
715                    if (i & mask) == mask {
716                        state[i] = -state[i];
717                    }
718                }
719            }
720            // Unknown / custom gates: skip silently (no-op) to allow custom gate sets
721            _ => {}
722        }
723
724        Ok(())
725    }
726
727    /// Simulate quantum sampling using precomputed ideal state amplitudes.
728    ///
729    /// For each circuit's amplitude vector we build a cumulative-probability
730    /// table and draw `samples_per_circuit` bitstrings from it, faithfully
731    /// mirroring the quantum probability distribution obtained by the classical
732    /// state-vector simulation.
733    fn simulate_quantum_sampling(
734        &mut self,
735        ideal_amplitudes: &[Array1<Complex64>],
736    ) -> Result<Vec<Vec<Vec<u8>>>> {
737        let mut all_samples = Vec::with_capacity(ideal_amplitudes.len());
738
739        for amplitudes in ideal_amplitudes {
740            let samples = self.sample_from_amplitudes(amplitudes)?;
741            all_samples.push(samples);
742        }
743
744        Ok(all_samples)
745    }
746
747    /// Draw `samples_per_circuit` bitstrings from a state-amplitude vector
748    /// using inverse-CDF (alias-free) sampling.
749    fn sample_from_amplitudes(&mut self, amplitudes: &Array1<Complex64>) -> Result<Vec<Vec<u8>>> {
750        let dim = amplitudes.len();
751        let n = self.num_qubits;
752
753        // Build probability distribution from |amplitude|²
754        let mut probs: Vec<f64> = amplitudes.iter().map(|a| a.norm_sqr()).collect();
755
756        // Normalise to guard against accumulated floating-point drift
757        let total: f64 = probs.iter().sum();
758        if total <= 0.0 {
759            return Err(SimulatorError::InvalidInput(
760                "State vector has zero norm — cannot sample".to_string(),
761            ));
762        }
763        for p in &mut probs {
764            *p /= total;
765        }
766
767        // Build cumulative distribution (CDF)
768        let mut cdf = Vec::with_capacity(dim);
769        let mut running = 0.0_f64;
770        for p in &probs {
771            running += p;
772            cdf.push(running);
773        }
774
775        // Sample `samples_per_circuit` bitstrings via binary search on CDF
776        let mut samples = Vec::with_capacity(self.params.samples_per_circuit);
777        for _ in 0..self.params.samples_per_circuit {
778            let u: f64 = self.rng.random();
779            // Binary-search for the first CDF entry >= u
780            let outcome = cdf.partition_point(|&c| c < u).min(dim - 1);
781
782            // Convert outcome index to a per-qubit bitstring (MSB first)
783            let mut bitstring = Vec::with_capacity(n);
784            for q in (0..n).rev() {
785                bitstring.push(((outcome >> q) & 1) as u8);
786            }
787            samples.push(bitstring);
788        }
789
790        Ok(samples)
791    }
792
793    /// Compute Linear Cross-Entropy Benchmarking (XEB) fidelity
794    fn compute_linear_xeb(
795        &self,
796        ideal_amplitudes: &[Array1<Complex64>],
797        quantum_samples: &[Vec<Vec<u8>>],
798    ) -> Result<f64> {
799        let mut total_fidelity = 0.0;
800
801        for (amplitudes, samples) in ideal_amplitudes.iter().zip(quantum_samples.iter()) {
802            let circuit_fidelity = self.compute_circuit_linear_xeb(amplitudes, samples)?;
803            total_fidelity += circuit_fidelity;
804        }
805
806        Ok(total_fidelity / ideal_amplitudes.len() as f64)
807    }
808
809    /// Compute linear XEB for a single circuit
810    fn compute_circuit_linear_xeb(
811        &self,
812        amplitudes: &Array1<Complex64>,
813        samples: &[Vec<u8>],
814    ) -> Result<f64> {
815        let dim = amplitudes.len();
816        let uniform_prob = 1.0 / dim as f64;
817
818        let mut sum_probs = 0.0;
819
820        for sample in samples {
821            let bitstring_index = self.bitstring_to_index(sample);
822            if bitstring_index < dim {
823                let prob = amplitudes[bitstring_index].norm_sqr();
824                sum_probs += prob;
825            }
826        }
827
828        let mean_prob = sum_probs / samples.len() as f64;
829        let linear_xeb = (mean_prob - uniform_prob) / uniform_prob;
830
831        Ok(linear_xeb)
832    }
833
834    /// Convert bit string to state index
835    fn bitstring_to_index(&self, bitstring: &[u8]) -> usize {
836        let mut index = 0;
837        for (i, &bit) in bitstring.iter().enumerate() {
838            if bit == 1 {
839                index |= 1 << (self.num_qubits - 1 - i);
840            }
841        }
842        index
843    }
844
845    /// Analyze Porter-Thomas distribution
846    fn analyze_porter_thomas(
847        &self,
848        ideal_amplitudes: &[Array1<Complex64>],
849    ) -> Result<PorterThomasResult> {
850        // Collect all probability amplitudes
851        let mut all_probs = Vec::new();
852
853        for amplitudes in ideal_amplitudes {
854            for amplitude in amplitudes {
855                all_probs.push(amplitude.norm_sqr());
856            }
857        }
858
859        // Compute statistics
860        let mean = all_probs.iter().sum::<f64>() / all_probs.len() as f64;
861        let variance =
862            all_probs.iter().map(|p| (p - mean).powi(2)).sum::<f64>() / all_probs.len() as f64;
863
864        // Expected values for Porter-Thomas distribution
865        let expected_mean = 1.0 / f64::from(1 << self.num_qubits);
866        let expected_variance = expected_mean.powi(2);
867
868        // Chi-squared test
869        let bins = self.params.pt_bins;
870        let mut observed = vec![0; bins];
871        let mut expected = vec![0.0; bins];
872
873        // Create histogram
874        let max_prob = all_probs.iter().copied().fold(0.0f64, f64::max);
875        for &prob in &all_probs {
876            let bin = ((prob / max_prob) * (bins - 1) as f64) as usize;
877            observed[bin.min(bins - 1)] += 1;
878        }
879
880        // Expected counts for Porter-Thomas
881        let total_samples = all_probs.len() as f64;
882        for i in 0..bins {
883            let x = (i as f64 + 0.5) / bins as f64 * max_prob;
884            // Porter-Thomas: p(P) = N * exp(-N*P) where N = 2^n
885            let n = 1 << self.num_qubits;
886            expected[i] = total_samples * f64::from(n) * (f64::from(-n) * x).exp() / bins as f64;
887        }
888
889        // Chi-squared statistic
890        let mut chi_squared = 0.0;
891        let mut degrees_freedom: usize = 0;
892
893        for i in 0..bins {
894            if expected[i] > 5.0 {
895                // Only use bins with sufficient expected count
896                chi_squared += (f64::from(observed[i]) - expected[i]).powi(2) / expected[i];
897                degrees_freedom += 1;
898            }
899        }
900
901        degrees_freedom = degrees_freedom.saturating_sub(1);
902
903        // Approximate p-value (simplified)
904        let p_value = self.chi_squared_p_value(chi_squared, degrees_freedom);
905
906        // Kolmogorov-Smirnov test (simplified)
907        let ks_statistic = self.kolmogorov_smirnov_test(&all_probs, expected_mean);
908
909        Ok(PorterThomasResult {
910            chi_squared,
911            degrees_freedom,
912            p_value,
913            is_porter_thomas: p_value > self.params.significance_level,
914            ks_statistic,
915            mean,
916            variance,
917        })
918    }
919
920    /// Compute HOG (Heavy Output Generation) score
921    fn compute_hog_score(
922        &self,
923        ideal_amplitudes: &[Array1<Complex64>],
924        quantum_samples: &[Vec<Vec<u8>>],
925    ) -> Result<f64> {
926        let mut total_heavy_fraction = 0.0;
927
928        for (amplitudes, samples) in ideal_amplitudes.iter().zip(quantum_samples.iter()) {
929            let heavy_fraction = self.compute_circuit_hog_score(amplitudes, samples)?;
930            total_heavy_fraction += heavy_fraction;
931        }
932
933        Ok(total_heavy_fraction / ideal_amplitudes.len() as f64)
934    }
935
936    /// Compute HOG score for single circuit
937    fn compute_circuit_hog_score(
938        &self,
939        amplitudes: &Array1<Complex64>,
940        samples: &[Vec<u8>],
941    ) -> Result<f64> {
942        // Find median probability
943        let mut probs: Vec<f64> = amplitudes
944            .iter()
945            .map(scirs2_core::Complex::norm_sqr)
946            .collect();
947        probs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
948        let median_prob = probs[probs.len() / 2];
949
950        // Count heavy outputs (above median)
951        let mut heavy_count = 0;
952
953        for sample in samples {
954            let index = self.bitstring_to_index(sample);
955            if index < amplitudes.len() {
956                let prob = amplitudes[index].norm_sqr();
957                if prob > median_prob {
958                    heavy_count += 1;
959                }
960            }
961        }
962
963        Ok(f64::from(heavy_count) / samples.len() as f64)
964    }
965
966    /// Compute cross-entropy difference
967    fn compute_cross_entropy_difference(
968        &self,
969        ideal_amplitudes: &[Array1<Complex64>],
970        quantum_samples: &[Vec<Vec<u8>>],
971    ) -> Result<f64> {
972        let mut total_difference = 0.0;
973
974        for (amplitudes, samples) in ideal_amplitudes.iter().zip(quantum_samples.iter()) {
975            let difference = self.compute_circuit_cross_entropy(amplitudes, samples)?;
976            total_difference += difference;
977        }
978
979        Ok(total_difference / ideal_amplitudes.len() as f64)
980    }
981
982    /// Compute cross-entropy for single circuit
983    fn compute_circuit_cross_entropy(
984        &self,
985        amplitudes: &Array1<Complex64>,
986        samples: &[Vec<u8>],
987    ) -> Result<f64> {
988        let dim = amplitudes.len();
989        let uniform_entropy = (dim as f64).ln();
990
991        let mut quantum_entropy = 0.0;
992        let mut sample_counts = HashMap::new();
993
994        // Count sample frequencies
995        for sample in samples {
996            *sample_counts.entry(sample.clone()).or_insert(0) += 1;
997        }
998
999        // Compute empirical entropy
1000        for count in sample_counts.values() {
1001            let prob = f64::from(*count) / samples.len() as f64;
1002            if prob > 0.0 {
1003                quantum_entropy -= prob * prob.ln();
1004            }
1005        }
1006
1007        Ok(uniform_entropy - quantum_entropy)
1008    }
1009
1010    /// Compute statistical confidence
1011    const fn compute_statistical_confidence(
1012        &self,
1013        _ideal_amplitudes: &[Array1<Complex64>],
1014        _quantum_samples: &[Vec<Vec<u8>>],
1015    ) -> Result<f64> {
1016        // Placeholder implementation
1017        Ok(0.95)
1018    }
1019
1020    /// Estimate quantum execution time
1021    fn estimate_quantum_time(&self) -> Result<f64> {
1022        // Estimate based on gate count and typical gate times
1023        let gates_per_circuit = self.params.circuit_depth * self.num_qubits;
1024        let total_gates = gates_per_circuit * self.params.num_circuits;
1025        let gate_time = 100e-9; // 100 ns per gate
1026        let readout_time = 1e-6; // 1 μs readout
1027
1028        Ok((total_gates as f64).mul_add(gate_time, self.params.num_circuits as f64 * readout_time))
1029    }
1030
1031    /// Estimate classical memory usage
1032    const fn estimate_classical_memory(&self) -> usize {
1033        let state_size = (1 << self.num_qubits) * std::mem::size_of::<Complex64>();
1034        state_size * self.params.num_circuits
1035    }
1036
1037    /// Simplified chi-squared p-value computation
1038    fn chi_squared_p_value(&self, chi_squared: f64, degrees_freedom: usize) -> f64 {
1039        // Very simplified approximation
1040        if degrees_freedom == 0 {
1041            return 1.0;
1042        }
1043
1044        let expected = degrees_freedom as f64;
1045        if chi_squared < expected {
1046            0.95 // High p-value
1047        } else if chi_squared < 2.0 * expected {
1048            0.5 // Medium p-value
1049        } else {
1050            0.01 // Low p-value
1051        }
1052    }
1053
1054    /// Kolmogorov-Smirnov test statistic
1055    fn kolmogorov_smirnov_test(&self, data: &[f64], expected_mean: f64) -> f64 {
1056        // Simplified implementation
1057        let n = data.len() as f64;
1058        let mut sorted_data = data.to_vec();
1059        sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1060
1061        let mut max_diff: f64 = 0.0;
1062
1063        for (i, &value) in sorted_data.iter().enumerate() {
1064            let empirical_cdf = (i + 1) as f64 / n;
1065            let theoretical_cdf = 1.0 - (-value / expected_mean).exp(); // Exponential CDF
1066            let diff = (empirical_cdf - theoretical_cdf).abs();
1067            max_diff = max_diff.max(diff);
1068        }
1069
1070        max_diff
1071    }
1072}
1073
1074/// Random quantum circuit representation
1075#[derive(Debug, Clone)]
1076pub struct RandomCircuit {
1077    pub num_qubits: usize,
1078    pub layers: Vec<CircuitLayer>,
1079    pub measurement_pattern: Vec<usize>,
1080}
1081
1082/// Layer of gates in a circuit
1083#[derive(Debug, Clone)]
1084pub struct CircuitLayer {
1085    pub gates: Vec<QuantumGate>,
1086}
1087
1088/// Quantum gate representation
1089#[derive(Debug, Clone)]
1090pub struct QuantumGate {
1091    pub gate_type: String,
1092    pub qubits: Vec<usize>,
1093    pub parameters: Vec<f64>,
1094}
1095
1096/// Benchmark quantum supremacy verification
1097pub fn benchmark_quantum_supremacy(num_qubits: usize) -> Result<CrossEntropyResult> {
1098    let params = VerificationParams {
1099        num_circuits: 10,
1100        samples_per_circuit: 100,
1101        circuit_depth: 10,
1102        ..Default::default()
1103    };
1104
1105    let mut verifier = QuantumSupremacyVerifier::new(num_qubits, params)?;
1106    verifier.verify_quantum_supremacy()
1107}
1108
1109/// Verify specific quantum supremacy claim
1110pub fn verify_supremacy_claim(
1111    num_qubits: usize,
1112    circuit_depth: usize,
1113    experimental_data: &[Vec<u8>],
1114) -> Result<CrossEntropyResult> {
1115    let params = VerificationParams {
1116        num_circuits: 1,
1117        samples_per_circuit: experimental_data.len(),
1118        circuit_depth,
1119        ..Default::default()
1120    };
1121
1122    let mut verifier = QuantumSupremacyVerifier::new(num_qubits, params)?;
1123
1124    // This would require the actual circuit that produced the experimental data
1125    // For now, return a placeholder result
1126    Ok(CrossEntropyResult {
1127        linear_xeb_fidelity: 0.0,
1128        cross_entropy_difference: 0.0,
1129        num_samples: experimental_data.len(),
1130        confidence: 0.0,
1131        porter_thomas: PorterThomasResult {
1132            chi_squared: 0.0,
1133            degrees_freedom: 0,
1134            p_value: 0.0,
1135            is_porter_thomas: false,
1136            ks_statistic: 0.0,
1137            mean: 0.0,
1138            variance: 0.0,
1139        },
1140        hog_score: 0.0,
1141        cost_comparison: CostComparison {
1142            classical_time: 0.0,
1143            quantum_time: 0.0,
1144            classical_memory: 0,
1145            speedup_factor: 0.0,
1146            operation_count: 0,
1147        },
1148    })
1149}
1150
1151#[cfg(test)]
1152mod tests {
1153    use super::*;
1154
1155    #[test]
1156    fn test_verifier_creation() {
1157        let params = VerificationParams::default();
1158        let verifier = QuantumSupremacyVerifier::new(10, params);
1159        assert!(verifier.is_ok());
1160    }
1161
1162    #[test]
1163    fn test_linear_xeb_calculation() {
1164        let mut verifier = QuantumSupremacyVerifier::new(3, VerificationParams::default())
1165            .expect("Failed to create verifier");
1166
1167        // Create dummy data
1168        let amplitudes = Array1::from_vec(vec![
1169            Complex64::new(0.5, 0.0),
1170            Complex64::new(0.5, 0.0),
1171            Complex64::new(0.5, 0.0),
1172            Complex64::new(0.5, 0.0),
1173            Complex64::new(0.0, 0.0),
1174            Complex64::new(0.0, 0.0),
1175            Complex64::new(0.0, 0.0),
1176            Complex64::new(0.0, 0.0),
1177        ]);
1178
1179        let samples = vec![vec![0, 0, 0], vec![0, 0, 1], vec![0, 1, 0], vec![0, 1, 1]];
1180
1181        let xeb = verifier
1182            .compute_circuit_linear_xeb(&amplitudes, &samples)
1183            .expect("Failed to compute linear XEB");
1184        assert!(xeb >= 0.0);
1185    }
1186
1187    #[test]
1188    fn test_bitstring_conversion() {
1189        let verifier = QuantumSupremacyVerifier::new(3, VerificationParams::default())
1190            .expect("Failed to create verifier");
1191
1192        assert_eq!(verifier.bitstring_to_index(&[0, 0, 0]), 0);
1193        assert_eq!(verifier.bitstring_to_index(&[0, 0, 1]), 1);
1194        assert_eq!(verifier.bitstring_to_index(&[1, 1, 1]), 7);
1195    }
1196
1197    #[test]
1198    fn test_porter_thomas_analysis() {
1199        let verifier = QuantumSupremacyVerifier::new(2, VerificationParams::default())
1200            .expect("Failed to create verifier");
1201
1202        // Create uniform random amplitudes
1203        let amplitudes = vec![Array1::from_vec(vec![
1204            Complex64::new(0.5, 0.0),
1205            Complex64::new(0.5, 0.0),
1206            Complex64::new(0.5, 0.0),
1207            Complex64::new(0.5, 0.0),
1208        ])];
1209
1210        let result = verifier
1211            .analyze_porter_thomas(&amplitudes)
1212            .expect("Failed to analyze Porter-Thomas distribution");
1213        assert!(result.chi_squared >= 0.0);
1214        assert!(result.p_value >= 0.0 && result.p_value <= 1.0);
1215    }
1216}