1use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
8use scirs2_core::parallel_ops::*;
9use scirs2_core::random::prelude::*;
10use scirs2_core::random::{ChaCha8Rng, Rng as RngTrait, SeedableRng}; use scirs2_core::Complex64;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15use crate::error::{Result, SimulatorError};
16use crate::scirs2_integration::SciRS2Backend;
17use crate::shot_sampling::{QuantumSampler, SamplingConfig};
18use crate::statevector::StateVectorSimulator;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct QuantumVolumeResult {
23 pub quantum_volume: u64,
25 pub width: usize,
27 pub depth: usize,
29 pub num_circuits: usize,
31 pub threshold: f64,
33 pub success_probability: f64,
35 pub heavy_output_probs: Vec<f64>,
37 pub confidence: f64,
39 pub passed: bool,
41 pub stats: QVStats,
43}
44
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47pub struct QVStats {
48 pub total_time_s: f64,
50 pub classical_sim_time_s: f64,
52 pub circuit_gen_time_s: f64,
54 pub heavy_output_time_s: f64,
56 pub memory_usage_bytes: usize,
58 pub total_gates: usize,
60 pub avg_fidelity: f64,
62}
63
64#[derive(Debug, Clone)]
66pub struct QVCircuit {
67 pub width: usize,
69 pub depth: usize,
71 pub gates: Vec<QVGate>,
73 pub permutation: Vec<usize>,
75 pub ideal_amplitudes: Array1<Complex64>,
77 pub heavy_threshold: f64,
79 pub heavy_outputs: Vec<usize>,
81}
82
83#[derive(Debug, Clone)]
85pub struct QVGate {
86 pub qubits: [usize; 2],
88 pub matrix: Array2<Complex64>,
90 pub layer: usize,
92}
93
94pub struct QuantumVolumeCalculator {
96 rng: ChaCha8Rng,
98 backend: Option<SciRS2Backend>,
100 params: QVParams,
102}
103
104#[derive(Debug, Clone)]
106pub struct QVParams {
107 pub circuits_per_width: usize,
109 pub heavy_output_threshold: f64,
111 pub confidence_level: f64,
113 pub max_width: usize,
115 pub seed: Option<u64>,
117 pub parallel: bool,
119}
120
121impl Default for QVParams {
122 fn default() -> Self {
123 Self {
124 circuits_per_width: 100,
125 heavy_output_threshold: 2.0 / 3.0,
126 confidence_level: 0.95,
127 max_width: 8,
128 seed: None,
129 parallel: true,
130 }
131 }
132}
133
134impl QuantumVolumeCalculator {
135 pub fn new(params: QVParams) -> Self {
137 let rng = if let Some(seed) = params.seed {
138 ChaCha8Rng::seed_from_u64(seed)
139 } else {
140 ChaCha8Rng::from_rng(&mut thread_rng())
141 };
142
143 Self {
144 rng,
145 backend: None,
146 params,
147 }
148 }
149
150 pub fn with_scirs2_backend(mut self) -> Result<Self> {
152 self.backend = Some(SciRS2Backend::new());
153 Ok(self)
154 }
155
156 pub fn calculate_quantum_volume(&mut self, width: usize) -> Result<QuantumVolumeResult> {
158 let start_time = std::time::Instant::now();
159 let depth = width; let mut stats = QVStats::default();
162 let mut heavy_output_probs = Vec::with_capacity(self.params.circuits_per_width);
163
164 for circuit_idx in 0..self.params.circuits_per_width {
166 let circuit_start = std::time::Instant::now();
167
168 let circuit = self.generate_qv_circuit(width, depth)?;
170 stats.circuit_gen_time_s += circuit_start.elapsed().as_secs_f64();
171
172 let heavy_prob_start = std::time::Instant::now();
174 let heavy_prob = self.calculate_heavy_output_probability(&circuit)?;
175 stats.heavy_output_time_s += heavy_prob_start.elapsed().as_secs_f64();
176
177 heavy_output_probs.push(heavy_prob);
178 stats.total_gates += circuit.gates.len();
179
180 if circuit_idx % 10 == 0 && circuit_idx > 0 {
182 println!(
183 "Processed {}/{} circuits for width {}",
184 circuit_idx, self.params.circuits_per_width, width
185 );
186 }
187 }
188
189 let success_count = heavy_output_probs
191 .iter()
192 .filter(|&&prob| prob >= self.params.heavy_output_threshold)
193 .count();
194 let success_probability = success_count as f64 / heavy_output_probs.len() as f64;
195
196 let required_success_prob = self.get_required_success_probability(width)?;
198 let passed = success_probability >= required_success_prob;
199
200 let quantum_volume = if passed { 1 << width } else { 0 };
202
203 let confidence = self.calculate_confidence(&heavy_output_probs, required_success_prob)?;
205
206 stats.total_time_s = start_time.elapsed().as_secs_f64();
207 stats.memory_usage_bytes = self.estimate_memory_usage(width);
208 stats.avg_fidelity =
209 heavy_output_probs.iter().sum::<f64>() / heavy_output_probs.len() as f64;
210
211 Ok(QuantumVolumeResult {
212 quantum_volume,
213 width,
214 depth,
215 num_circuits: self.params.circuits_per_width,
216 threshold: required_success_prob,
217 success_probability,
218 heavy_output_probs,
219 confidence,
220 passed,
221 stats,
222 })
223 }
224
225 fn generate_qv_circuit(&mut self, width: usize, depth: usize) -> Result<QVCircuit> {
227 if width < 2 {
228 return Err(SimulatorError::InvalidInput(
229 "Quantum volume requires at least 2 qubits".to_string(),
230 ));
231 }
232
233 let mut gates = Vec::new();
234 let mut available_qubits: Vec<usize> = (0..width).collect();
235
236 for layer in 0..depth {
238 available_qubits.shuffle(&mut self.rng);
240
241 for pair_idx in 0..(width / 2) {
243 let qubit1 = available_qubits[2 * pair_idx];
244 let qubit2 = available_qubits[2 * pair_idx + 1];
245
246 let gate = QVGate {
247 qubits: [qubit1, qubit2],
248 matrix: self.generate_random_su4()?,
249 layer,
250 };
251
252 gates.push(gate);
253 }
254 }
255
256 let mut permutation: Vec<usize> = (0..width).collect();
258 permutation.shuffle(&mut self.rng);
259
260 let ideal_amplitudes = self.simulate_ideal_circuit(width, &gates, &permutation)?;
262
263 let heavy_threshold = self.calculate_heavy_threshold(&ideal_amplitudes);
265 let heavy_outputs = self.find_heavy_outputs(&ideal_amplitudes, heavy_threshold);
266
267 Ok(QVCircuit {
268 width,
269 depth,
270 gates,
271 permutation,
272 ideal_amplitudes,
273 heavy_threshold,
274 heavy_outputs,
275 })
276 }
277
278 fn generate_random_su4(&mut self) -> Result<Array2<Complex64>> {
280 let mut params = Vec::with_capacity(15);
285 for _ in 0..15 {
286 params.push(self.rng.gen::<f64>() * 2.0 * std::f64::consts::PI);
287 }
288
289 let mut matrix = Array2::zeros((4, 4));
293
294 for i in 0..4 {
296 for j in 0..4 {
297 let real = self.rng.gen::<f64>() - 0.5;
298 let imag = self.rng.gen::<f64>() - 0.5;
299 matrix[[i, j]] = Complex64::new(real, imag);
300 }
301 }
302
303 self.gram_schmidt_orthogonalize(&mut matrix)?;
305
306 Ok(matrix)
307 }
308
309 fn gram_schmidt_orthogonalize(&self, matrix: &mut Array2<Complex64>) -> Result<()> {
311 let n = matrix.nrows();
312
313 for j in 0..n {
315 let mut norm = 0.0;
317 for i in 0..n {
318 norm += matrix[[i, j]].norm_sqr();
319 }
320 norm = norm.sqrt();
321
322 if norm > 1e-12 {
323 for i in 0..n {
324 matrix[[i, j]] /= norm;
325 }
326 }
327
328 for k in (j + 1)..n {
330 let mut dot_product = Complex64::new(0.0, 0.0);
331 for i in 0..n {
332 dot_product += matrix[[i, j]].conj() * matrix[[i, k]];
333 }
334
335 let column_j_values: Vec<Complex64> = (0..n).map(|i| matrix[[i, j]]).collect();
336 for i in 0..n {
337 matrix[[i, k]] -= dot_product * column_j_values[i];
338 }
339 }
340 }
341
342 Ok(())
343 }
344
345 fn simulate_ideal_circuit(
347 &self,
348 width: usize,
349 gates: &[QVGate],
350 permutation: &[usize],
351 ) -> Result<Array1<Complex64>> {
352 let dim = 1 << width;
353 let mut state = Array1::zeros(dim);
354 state[0] = Complex64::new(1.0, 0.0); for layer in 0..=gates.iter().map(|g| g.layer).max().unwrap_or(0) {
358 let layer_gates: Vec<&QVGate> = gates.iter().filter(|g| g.layer == layer).collect();
359
360 for gate in layer_gates {
362 self.apply_two_qubit_gate(
363 &mut state,
364 width,
365 &gate.matrix,
366 gate.qubits[0],
367 gate.qubits[1],
368 )?;
369 }
370 }
371
372 state = self.apply_permutation(&state, width, permutation)?;
374
375 Ok(state)
376 }
377
378 fn apply_two_qubit_gate(
380 &self,
381 state: &mut Array1<Complex64>,
382 width: usize,
383 gate_matrix: &Array2<Complex64>,
384 qubit1: usize,
385 qubit2: usize,
386 ) -> Result<()> {
387 let dim = 1 << width;
388 let mut new_state = Array1::zeros(dim);
389
390 for basis_state in 0..dim {
391 let bit1 = (basis_state >> (width - 1 - qubit1)) & 1;
392 let bit2 = (basis_state >> (width - 1 - qubit2)) & 1;
393 let two_qubit_state = (bit1 << 1) | bit2;
394
395 for target_two_qubit in 0..4 {
396 let amplitude = gate_matrix[[target_two_qubit, two_qubit_state]];
397
398 if amplitude.norm() > 1e-12 {
399 let target_bit1 = (target_two_qubit >> 1) & 1;
400 let target_bit2 = target_two_qubit & 1;
401
402 let mut target_basis = basis_state;
403
404 if target_bit1 == 1 {
406 target_basis |= 1 << (width - 1 - qubit1);
407 } else {
408 target_basis &= !(1 << (width - 1 - qubit1));
409 }
410
411 if target_bit2 == 1 {
413 target_basis |= 1 << (width - 1 - qubit2);
414 } else {
415 target_basis &= !(1 << (width - 1 - qubit2));
416 }
417
418 new_state[target_basis] += amplitude * state[basis_state];
419 }
420 }
421 }
422
423 *state = new_state;
424 Ok(())
425 }
426
427 fn apply_permutation(
429 &self,
430 state: &Array1<Complex64>,
431 width: usize,
432 permutation: &[usize],
433 ) -> Result<Array1<Complex64>> {
434 let dim = 1 << width;
435 let mut new_state = Array1::zeros(dim);
436
437 for basis_state in 0..dim {
438 let mut permuted_state = 0;
439
440 for (new_pos, &old_pos) in permutation.iter().enumerate() {
441 let bit = (basis_state >> (width - 1 - old_pos)) & 1;
442 permuted_state |= bit << (width - 1 - new_pos);
443 }
444
445 new_state[permuted_state] = state[basis_state];
446 }
447
448 Ok(new_state)
449 }
450
451 fn calculate_heavy_threshold(&self, amplitudes: &Array1<Complex64>) -> f64 {
453 let mut probabilities: Vec<f64> = amplitudes.iter().map(|amp| amp.norm_sqr()).collect();
454 probabilities.sort_by(|a, b| a.partial_cmp(b).unwrap());
455
456 let median_idx = probabilities.len() / 2;
457 probabilities[median_idx]
458 }
459
460 fn find_heavy_outputs(&self, amplitudes: &Array1<Complex64>, threshold: f64) -> Vec<usize> {
462 amplitudes
463 .iter()
464 .enumerate()
465 .filter_map(|(idx, amp)| {
466 if amp.norm_sqr() > threshold {
467 Some(idx)
468 } else {
469 None
470 }
471 })
472 .collect()
473 }
474
475 fn calculate_heavy_output_probability(&self, circuit: &QVCircuit) -> Result<f64> {
477 let heavy_prob: f64 = circuit
479 .heavy_outputs
480 .iter()
481 .map(|&idx| circuit.ideal_amplitudes[idx].norm_sqr())
482 .sum();
483
484 Ok(heavy_prob)
485 }
486
487 fn get_required_success_probability(&self, width: usize) -> Result<f64> {
489 let baseline_threshold = 2.0 / 3.0;
493
494 let n = self.params.circuits_per_width as f64;
496 let confidence = self.params.confidence_level;
497
498 let z_score = match confidence {
500 x if x >= 0.99 => 2.576,
501 x if x >= 0.95 => 1.96,
502 x if x >= 0.90 => 1.645,
503 _ => 1.96,
504 };
505
506 let standard_error = (baseline_threshold * (1.0 - baseline_threshold) / n).sqrt();
507 let adjusted_threshold = baseline_threshold - z_score * standard_error;
508
509 Ok(adjusted_threshold.max(0.5)) }
511
512 fn calculate_confidence(&self, heavy_probs: &[f64], threshold: f64) -> Result<f64> {
514 let n = heavy_probs.len() as f64;
515 let success_count = heavy_probs.iter().filter(|&&p| p >= threshold).count() as f64;
516 let success_rate = success_count / n;
517
518 let p = success_rate;
520 let standard_error = (p * (1.0 - p) / n).sqrt();
521
522 if success_rate > threshold {
524 let z_score = (success_rate - threshold) / standard_error;
525 Ok(self.normal_cdf(z_score))
526 } else {
527 Ok(0.0)
528 }
529 }
530
531 fn normal_cdf(&self, x: f64) -> f64 {
533 0.5 * (1.0 + self.erf(x / 2.0_f64.sqrt()))
534 }
535
536 fn erf(&self, x: f64) -> f64 {
538 let a1 = 0.254829592;
540 let a2 = -0.284496736;
541 let a3 = 1.421413741;
542 let a4 = -1.453152027;
543 let a5 = 1.061405429;
544 let p = 0.3275911;
545
546 let sign = if x < 0.0 { -1.0 } else { 1.0 };
547 let x = x.abs();
548
549 let t = 1.0 / (1.0 + p * x);
550 let y = ((a5 * t + a4).mul_add(t, a3).mul_add(t, a2).mul_add(t, a1) * t)
551 .mul_add(-(-x * x).exp(), 1.0);
552
553 sign * y
554 }
555
556 const fn estimate_memory_usage(&self, width: usize) -> usize {
558 let state_vector_size = (1 << width) * std::mem::size_of::<Complex64>();
559 let circuit_storage = 1000 * std::mem::size_of::<QVGate>(); state_vector_size + circuit_storage
561 }
562
563 pub fn find_maximum_quantum_volume(&mut self) -> Result<Vec<QuantumVolumeResult>> {
565 let mut results = Vec::new();
566
567 for width in 2..=self.params.max_width {
568 println!("Testing quantum volume for width {width}");
569
570 let result = self.calculate_quantum_volume(width)?;
571
572 println!(
573 "Width {}: QV = {}, Success Prob = {:.3}, Passed = {}",
574 width, result.quantum_volume, result.success_probability, result.passed
575 );
576
577 results.push(result.clone());
578
579 if !result.passed {
581 break;
582 }
583 }
584
585 Ok(results)
586 }
587}
588
589pub fn benchmark_quantum_volume(max_width: usize) -> Result<Vec<QuantumVolumeResult>> {
591 let params = QVParams {
592 circuits_per_width: 20, max_width,
594 ..Default::default()
595 };
596
597 let mut calculator = QuantumVolumeCalculator::new(params);
598 calculator.find_maximum_quantum_volume()
599}
600
601pub fn calculate_quantum_volume_with_params(
603 width: usize,
604 circuits: usize,
605 seed: Option<u64>,
606) -> Result<QuantumVolumeResult> {
607 let params = QVParams {
608 circuits_per_width: circuits,
609 max_width: width,
610 seed,
611 ..Default::default()
612 };
613
614 let mut calculator = QuantumVolumeCalculator::new(params);
615 calculator.calculate_quantum_volume(width)
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[test]
623 fn test_qv_calculator_creation() {
624 let params = QVParams::default();
625 let calculator = QuantumVolumeCalculator::new(params);
626 assert!(calculator.params.circuits_per_width > 0);
627 }
628
629 #[test]
630 fn test_random_su4_generation() {
631 let params = QVParams::default();
632 let mut calculator = QuantumVolumeCalculator::new(params);
633
634 let matrix = calculator.generate_random_su4().unwrap();
635 assert_eq!(matrix.shape(), [4, 4]);
636
637 let matrix_dagger = matrix.t().mapv(|x| x.conj());
639 let product = matrix_dagger.dot(&matrix);
640
641 for i in 0..4 {
643 assert!((product[[i, i]].re - 1.0).abs() < 1e-1);
644 assert!(product[[i, i]].im.abs() < 1e-1);
645 }
646 }
647
648 #[test]
649 fn test_qv_circuit_generation() {
650 let params = QVParams::default();
651 let mut calculator = QuantumVolumeCalculator::new(params);
652
653 let circuit = calculator.generate_qv_circuit(4, 4).unwrap();
654 assert_eq!(circuit.width, 4);
655 assert_eq!(circuit.depth, 4);
656 assert!(!circuit.gates.is_empty());
657 assert_eq!(circuit.permutation.len(), 4);
658 }
659
660 #[test]
661 fn test_heavy_output_calculation() {
662 let amplitudes = Array1::from_vec(vec![
663 Complex64::new(0.6, 0.0), Complex64::new(0.3, 0.0), Complex64::new(0.1, 0.0), Complex64::new(0.05, 0.0), ]);
668
669 let params = QVParams::default();
670 let calculator = QuantumVolumeCalculator::new(params);
671
672 let threshold = calculator.calculate_heavy_threshold(&litudes);
673 let heavy_outputs = calculator.find_heavy_outputs(&litudes, threshold);
674
675 assert!(threshold > 0.0);
676 assert!(!heavy_outputs.is_empty());
677 }
678
679 #[test]
680 fn test_small_quantum_volume() {
681 let result = calculate_quantum_volume_with_params(3, 10, Some(42)).unwrap();
682
683 assert_eq!(result.width, 3);
684 assert_eq!(result.depth, 3);
685 assert_eq!(result.num_circuits, 10);
686 assert!(!result.heavy_output_probs.is_empty());
687 }
688
689 #[test]
690 fn test_permutation_application() {
691 let params = QVParams::default();
692 let calculator = QuantumVolumeCalculator::new(params);
693
694 let state = Array1::from_vec(vec![
695 Complex64::new(1.0, 0.0),
696 Complex64::new(0.0, 0.0),
697 Complex64::new(0.0, 0.0),
698 Complex64::new(0.0, 0.0),
699 ]);
700
701 let permutation = vec![1, 0]; let permuted = calculator
703 .apply_permutation(&state, 2, &permutation)
704 .unwrap();
705
706 assert!((permuted[0].re - 1.0).abs() < 1e-10);
708 }
709}