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 that the observed samples are
1011    /// consistent with the ideal quantum distribution.
1012    ///
1013    /// Performs a real chi-squared goodness-of-fit test per circuit,
1014    /// comparing each bitstring's observed sample frequency against its
1015    /// expected probability `|amplitude|^2` under the ideal state, then
1016    /// aggregates the chi-squared statistics (and their degrees of
1017    /// freedom) across all circuits and converts the combined statistic
1018    /// to a p-value via [`Self::chi_squared_p_value`].
1019    fn compute_statistical_confidence(
1020        &self,
1021        ideal_amplitudes: &[Array1<Complex64>],
1022        quantum_samples: &[Vec<Vec<u8>>],
1023    ) -> Result<f64> {
1024        let mut total_chi_squared = 0.0;
1025        let mut total_degrees_freedom = 0usize;
1026
1027        for (amplitudes, samples) in ideal_amplitudes.iter().zip(quantum_samples.iter()) {
1028            if samples.is_empty() {
1029                continue;
1030            }
1031            let dim = amplitudes.len();
1032            let n = samples.len() as f64;
1033
1034            let mut observed_counts: HashMap<usize, usize> = HashMap::new();
1035            for sample in samples {
1036                let index = self.bitstring_to_index(sample);
1037                *observed_counts.entry(index).or_insert(0) += 1;
1038            }
1039
1040            let mut chi_squared = 0.0;
1041            let mut categories_with_support = 0usize;
1042            for index in 0..dim {
1043                let expected_prob = amplitudes[index].norm_sqr();
1044                let expected_count = expected_prob * n;
1045                let observed_count = *observed_counts.get(&index).unwrap_or(&0) as f64;
1046                if expected_count > 1e-12 {
1047                    chi_squared += (observed_count - expected_count).powi(2) / expected_count;
1048                    categories_with_support += 1;
1049                }
1050            }
1051
1052            total_chi_squared += chi_squared;
1053            total_degrees_freedom += categories_with_support.saturating_sub(1);
1054        }
1055
1056        if total_degrees_freedom == 0 {
1057            // No usable statistics (e.g. every circuit had zero samples):
1058            // honestly report zero confidence rather than a fabricated
1059            // constant.
1060            return Ok(0.0);
1061        }
1062
1063        Ok(self.chi_squared_p_value(total_chi_squared, total_degrees_freedom))
1064    }
1065
1066    /// Estimate quantum execution time
1067    fn estimate_quantum_time(&self) -> Result<f64> {
1068        // Estimate based on gate count and typical gate times
1069        let gates_per_circuit = self.params.circuit_depth * self.num_qubits;
1070        let total_gates = gates_per_circuit * self.params.num_circuits;
1071        let gate_time = 100e-9; // 100 ns per gate
1072        let readout_time = 1e-6; // 1 μs readout
1073
1074        Ok((total_gates as f64).mul_add(gate_time, self.params.num_circuits as f64 * readout_time))
1075    }
1076
1077    /// Estimate classical memory usage
1078    const fn estimate_classical_memory(&self) -> usize {
1079        let state_size = (1 << self.num_qubits) * std::mem::size_of::<Complex64>();
1080        state_size * self.params.num_circuits
1081    }
1082
1083    /// Simplified chi-squared p-value computation
1084    fn chi_squared_p_value(&self, chi_squared: f64, degrees_freedom: usize) -> f64 {
1085        // Very simplified approximation
1086        if degrees_freedom == 0 {
1087            return 1.0;
1088        }
1089
1090        let expected = degrees_freedom as f64;
1091        if chi_squared < expected {
1092            0.95 // High p-value
1093        } else if chi_squared < 2.0 * expected {
1094            0.5 // Medium p-value
1095        } else {
1096            0.01 // Low p-value
1097        }
1098    }
1099
1100    /// Kolmogorov-Smirnov test statistic
1101    fn kolmogorov_smirnov_test(&self, data: &[f64], expected_mean: f64) -> f64 {
1102        // Simplified implementation
1103        let n = data.len() as f64;
1104        let mut sorted_data = data.to_vec();
1105        sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1106
1107        let mut max_diff: f64 = 0.0;
1108
1109        for (i, &value) in sorted_data.iter().enumerate() {
1110            let empirical_cdf = (i + 1) as f64 / n;
1111            let theoretical_cdf = 1.0 - (-value / expected_mean).exp(); // Exponential CDF
1112            let diff = (empirical_cdf - theoretical_cdf).abs();
1113            max_diff = max_diff.max(diff);
1114        }
1115
1116        max_diff
1117    }
1118}
1119
1120/// Random quantum circuit representation
1121#[derive(Debug, Clone)]
1122pub struct RandomCircuit {
1123    pub num_qubits: usize,
1124    pub layers: Vec<CircuitLayer>,
1125    pub measurement_pattern: Vec<usize>,
1126}
1127
1128/// Layer of gates in a circuit
1129#[derive(Debug, Clone)]
1130pub struct CircuitLayer {
1131    pub gates: Vec<QuantumGate>,
1132}
1133
1134/// Quantum gate representation
1135#[derive(Debug, Clone)]
1136pub struct QuantumGate {
1137    pub gate_type: String,
1138    pub qubits: Vec<usize>,
1139    pub parameters: Vec<f64>,
1140}
1141
1142/// Benchmark quantum supremacy verification
1143pub fn benchmark_quantum_supremacy(num_qubits: usize) -> Result<CrossEntropyResult> {
1144    let params = VerificationParams {
1145        num_circuits: 10,
1146        samples_per_circuit: 100,
1147        circuit_depth: 10,
1148        ..Default::default()
1149    };
1150
1151    let mut verifier = QuantumSupremacyVerifier::new(num_qubits, params)?;
1152    verifier.verify_quantum_supremacy()
1153}
1154
1155/// Verify a specific quantum supremacy claim against real experimental data.
1156///
1157/// Unlike a benchmark (which generates its own random circuits), verifying
1158/// an actual claim requires the exact circuit that produced
1159/// `experimental_data` -- there is no way to honestly cross-entropy-check
1160/// samples against a circuit we don't know. `circuit` must therefore be
1161/// supplied by the caller (e.g. reconstructed from the published circuit
1162/// description of the claim being verified); this function classically
1163/// simulates it to get the ideal amplitudes and then runs the real
1164/// Linear XEB / Porter-Thomas / HOG / chi-squared analysis against
1165/// `experimental_data`.
1166pub fn verify_supremacy_claim(
1167    num_qubits: usize,
1168    circuit_depth: usize,
1169    circuit: &RandomCircuit,
1170    experimental_data: &[Vec<u8>],
1171) -> Result<CrossEntropyResult> {
1172    if circuit.num_qubits != num_qubits {
1173        return Err(SimulatorError::InvalidConfiguration(format!(
1174            "supplied circuit acts on {} qubits, but verification was requested for {num_qubits} qubits",
1175            circuit.num_qubits
1176        )));
1177    }
1178    if experimental_data.is_empty() {
1179        return Err(SimulatorError::InvalidInput(
1180            "verify_supremacy_claim requires at least one experimental sample".to_string(),
1181        ));
1182    }
1183
1184    let start_time = std::time::Instant::now();
1185
1186    let params = VerificationParams {
1187        num_circuits: 1,
1188        samples_per_circuit: experimental_data.len(),
1189        circuit_depth,
1190        ..Default::default()
1191    };
1192
1193    let verifier = QuantumSupremacyVerifier::new(num_qubits, params)?;
1194
1195    let circuits = vec![circuit.clone()];
1196    let ideal_amplitudes = verifier.compute_ideal_amplitudes(&circuits)?;
1197    let quantum_samples = vec![experimental_data.to_vec()];
1198
1199    let linear_xeb = verifier.compute_linear_xeb(&ideal_amplitudes, &quantum_samples)?;
1200    let porter_thomas = verifier.analyze_porter_thomas(&ideal_amplitudes)?;
1201    let hog_score = verifier.compute_hog_score(&ideal_amplitudes, &quantum_samples)?;
1202    let cross_entropy_diff =
1203        verifier.compute_cross_entropy_difference(&ideal_amplitudes, &quantum_samples)?;
1204    let confidence =
1205        verifier.compute_statistical_confidence(&ideal_amplitudes, &quantum_samples)?;
1206
1207    let classical_time = start_time.elapsed().as_secs_f64();
1208    let quantum_time = verifier.estimate_quantum_time()?;
1209
1210    let cost_comparison = CostComparison {
1211        classical_time,
1212        quantum_time,
1213        classical_memory: verifier.estimate_classical_memory(),
1214        speedup_factor: if quantum_time > 0.0 {
1215            classical_time / quantum_time
1216        } else {
1217            0.0
1218        },
1219        operation_count: circuit_depth * num_qubits,
1220    };
1221
1222    Ok(CrossEntropyResult {
1223        linear_xeb_fidelity: linear_xeb,
1224        cross_entropy_difference: cross_entropy_diff,
1225        num_samples: experimental_data.len(),
1226        confidence,
1227        porter_thomas,
1228        hog_score,
1229        cost_comparison,
1230    })
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235    use super::*;
1236
1237    #[test]
1238    fn test_verifier_creation() {
1239        let params = VerificationParams::default();
1240        let verifier = QuantumSupremacyVerifier::new(10, params);
1241        assert!(verifier.is_ok());
1242    }
1243
1244    #[test]
1245    fn test_linear_xeb_calculation() {
1246        let mut verifier = QuantumSupremacyVerifier::new(3, VerificationParams::default())
1247            .expect("Failed to create verifier");
1248
1249        // Create dummy data
1250        let amplitudes = Array1::from_vec(vec![
1251            Complex64::new(0.5, 0.0),
1252            Complex64::new(0.5, 0.0),
1253            Complex64::new(0.5, 0.0),
1254            Complex64::new(0.5, 0.0),
1255            Complex64::new(0.0, 0.0),
1256            Complex64::new(0.0, 0.0),
1257            Complex64::new(0.0, 0.0),
1258            Complex64::new(0.0, 0.0),
1259        ]);
1260
1261        let samples = vec![vec![0, 0, 0], vec![0, 0, 1], vec![0, 1, 0], vec![0, 1, 1]];
1262
1263        let xeb = verifier
1264            .compute_circuit_linear_xeb(&amplitudes, &samples)
1265            .expect("Failed to compute linear XEB");
1266        assert!(xeb >= 0.0);
1267    }
1268
1269    #[test]
1270    fn test_bitstring_conversion() {
1271        let verifier = QuantumSupremacyVerifier::new(3, VerificationParams::default())
1272            .expect("Failed to create verifier");
1273
1274        assert_eq!(verifier.bitstring_to_index(&[0, 0, 0]), 0);
1275        assert_eq!(verifier.bitstring_to_index(&[0, 0, 1]), 1);
1276        assert_eq!(verifier.bitstring_to_index(&[1, 1, 1]), 7);
1277    }
1278
1279    #[test]
1280    fn test_porter_thomas_analysis() {
1281        let verifier = QuantumSupremacyVerifier::new(2, VerificationParams::default())
1282            .expect("Failed to create verifier");
1283
1284        // Create uniform random amplitudes
1285        let amplitudes = vec![Array1::from_vec(vec![
1286            Complex64::new(0.5, 0.0),
1287            Complex64::new(0.5, 0.0),
1288            Complex64::new(0.5, 0.0),
1289            Complex64::new(0.5, 0.0),
1290        ])];
1291
1292        let result = verifier
1293            .analyze_porter_thomas(&amplitudes)
1294            .expect("Failed to analyze Porter-Thomas distribution");
1295        assert!(result.chi_squared >= 0.0);
1296        assert!(result.p_value >= 0.0 && result.p_value <= 1.0);
1297    }
1298}