1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct CrossEntropyResult {
22 pub linear_xeb_fidelity: f64,
24 pub cross_entropy_difference: f64,
26 pub num_samples: usize,
28 pub confidence: f64,
30 pub porter_thomas: PorterThomasResult,
32 pub hog_score: f64,
34 pub cost_comparison: CostComparison,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PorterThomasResult {
41 pub chi_squared: f64,
43 pub degrees_freedom: usize,
45 pub p_value: f64,
47 pub is_porter_thomas: bool,
49 pub ks_statistic: f64,
51 pub mean: f64,
53 pub variance: f64,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct HOGAnalysis {
60 pub heavy_fraction: f64,
62 pub expected_heavy_fraction: f64,
64 pub threshold: f64,
66 pub total_outputs: usize,
68 pub heavy_count: usize,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct CostComparison {
75 pub classical_time: f64,
77 pub quantum_time: f64,
79 pub classical_memory: usize,
81 pub speedup_factor: f64,
83 pub operation_count: usize,
85}
86
87pub struct QuantumSupremacyVerifier {
89 num_qubits: usize,
91 rng: ChaCha8Rng,
93 backend: Option<SciRS2Backend>,
95 params: VerificationParams,
97}
98
99#[derive(Debug, Clone)]
101pub struct VerificationParams {
102 pub num_circuits: usize,
104 pub samples_per_circuit: usize,
106 pub circuit_depth: usize,
108 pub gate_set: GateSet,
110 pub significance_level: f64,
112 pub pt_bins: usize,
114 pub heavy_threshold_percentile: f64,
116}
117
118#[derive(Debug, Clone)]
120pub enum GateSet {
121 Sycamore,
123 GoogleSupremacy,
125 Universal,
127 IBM,
129 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 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 pub fn with_scirs2_backend(mut self) -> Result<Self> {
160 self.backend = Some(SciRS2Backend::new());
161 Ok(self)
162 }
163
164 #[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 pub fn verify_quantum_supremacy(&mut self) -> Result<CrossEntropyResult> {
173 let start_time = std::time::Instant::now();
174
175 let circuits = self.generate_random_circuits()?;
177
178 let ideal_amplitudes = self.compute_ideal_amplitudes(&circuits)?;
180
181 let quantum_samples = self.simulate_quantum_sampling(&ideal_amplitudes)?;
183
184 let linear_xeb = self.compute_linear_xeb(&ideal_amplitudes, &quantum_samples)?;
186
187 let porter_thomas = self.analyze_porter_thomas(&ideal_amplitudes)?;
189
190 let hog_score = self.compute_hog_score(&ideal_amplitudes, &quantum_samples)?;
192
193 let cross_entropy_diff =
195 self.compute_cross_entropy_difference(&ideal_amplitudes, &quantum_samples)?;
196
197 let confidence =
199 self.compute_statistical_confidence(&ideal_amplitudes, &quantum_samples)?;
200
201 let classical_time = start_time.elapsed().as_secs_f64();
202
203 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 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 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 fn generate_sycamore_layer(&mut self, layer_idx: usize) -> Result<CircuitLayer> {
262 let mut gates = Vec::new();
263
264 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 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 fn generate_google_supremacy_layer(&mut self, layer_idx: usize) -> Result<CircuitLayer> {
301 self.generate_sycamore_layer(layer_idx)
303 }
304
305 fn generate_universal_layer(&mut self, _layer_idx: usize) -> Result<CircuitLayer> {
307 let mut gates = Vec::new();
308
309 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 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 fn generate_ibm_layer(&mut self, _layer_idx: usize) -> Result<CircuitLayer> {
344 let mut gates = Vec::new();
345
346 for qubit in 0..self.num_qubits {
348 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 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 const fn generate_custom_layer(
371 &self,
372 _layer_idx: usize,
373 _gates: &[String],
374 ) -> Result<CircuitLayer> {
375 Ok(CircuitLayer { gates: Vec::new() })
377 }
378
379 fn generate_measurement_pattern(&self) -> Vec<usize> {
381 (0..self.num_qubits).collect()
383 }
384
385 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 let mut state = vec![Complex64::new(0.0, 0.0); dim];
396 state[0] = Complex64::new(1.0, 0.0);
397
398 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 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 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 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 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 "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 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 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 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 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 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 "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 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 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 _ => {}
722 }
723
724 Ok(())
725 }
726
727 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 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 let mut probs: Vec<f64> = amplitudes.iter().map(|a| a.norm_sqr()).collect();
755
756 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 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 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 let outcome = cdf.partition_point(|&c| c < u).min(dim - 1);
781
782 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 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 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 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 fn analyze_porter_thomas(
847 &self,
848 ideal_amplitudes: &[Array1<Complex64>],
849 ) -> Result<PorterThomasResult> {
850 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 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 let expected_mean = 1.0 / f64::from(1 << self.num_qubits);
866 let expected_variance = expected_mean.powi(2);
867
868 let bins = self.params.pt_bins;
870 let mut observed = vec![0; bins];
871 let mut expected = vec![0.0; bins];
872
873 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 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 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 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 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 let p_value = self.chi_squared_p_value(chi_squared, degrees_freedom);
905
906 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 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 fn compute_circuit_hog_score(
938 &self,
939 amplitudes: &Array1<Complex64>,
940 samples: &[Vec<u8>],
941 ) -> Result<f64> {
942 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 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 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 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 for sample in samples {
996 *sample_counts.entry(sample.clone()).or_insert(0) += 1;
997 }
998
999 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 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 return Ok(0.0);
1061 }
1062
1063 Ok(self.chi_squared_p_value(total_chi_squared, total_degrees_freedom))
1064 }
1065
1066 fn estimate_quantum_time(&self) -> Result<f64> {
1068 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; let readout_time = 1e-6; Ok((total_gates as f64).mul_add(gate_time, self.params.num_circuits as f64 * readout_time))
1075 }
1076
1077 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 fn chi_squared_p_value(&self, chi_squared: f64, degrees_freedom: usize) -> f64 {
1085 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 } else if chi_squared < 2.0 * expected {
1094 0.5 } else {
1096 0.01 }
1098 }
1099
1100 fn kolmogorov_smirnov_test(&self, data: &[f64], expected_mean: f64) -> f64 {
1102 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(); let diff = (empirical_cdf - theoretical_cdf).abs();
1113 max_diff = max_diff.max(diff);
1114 }
1115
1116 max_diff
1117 }
1118}
1119
1120#[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#[derive(Debug, Clone)]
1130pub struct CircuitLayer {
1131 pub gates: Vec<QuantumGate>,
1132}
1133
1134#[derive(Debug, Clone)]
1136pub struct QuantumGate {
1137 pub gate_type: String,
1138 pub qubits: Vec<usize>,
1139 pub parameters: Vec<f64>,
1140}
1141
1142pub 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
1155pub 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 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(&litudes, &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 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(&litudes)
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}