1use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
8use scirs2_core::parallel_ops::{IndexedParallelIterator, ParallelIterator};
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 #[must_use]
137 pub fn new(params: QVParams) -> Self {
138 let rng = if let Some(seed) = params.seed {
139 ChaCha8Rng::seed_from_u64(seed)
140 } else {
141 ChaCha8Rng::from_rng(&mut thread_rng())
142 };
143
144 Self {
145 rng,
146 backend: None,
147 params,
148 }
149 }
150
151 pub fn with_scirs2_backend(mut self) -> Result<Self> {
153 self.backend = Some(SciRS2Backend::new());
154 Ok(self)
155 }
156
157 pub fn calculate_quantum_volume(&mut self, width: usize) -> Result<QuantumVolumeResult> {
159 let start_time = std::time::Instant::now();
160 let depth = width; let mut stats = QVStats::default();
163 let mut heavy_output_probs = Vec::with_capacity(self.params.circuits_per_width);
164
165 for circuit_idx in 0..self.params.circuits_per_width {
167 let circuit_start = std::time::Instant::now();
168
169 let circuit = self.generate_qv_circuit(width, depth)?;
171 stats.circuit_gen_time_s += circuit_start.elapsed().as_secs_f64();
172
173 let heavy_prob_start = std::time::Instant::now();
175 let heavy_prob = self.calculate_heavy_output_probability(&circuit)?;
176 stats.heavy_output_time_s += heavy_prob_start.elapsed().as_secs_f64();
177
178 heavy_output_probs.push(heavy_prob);
179 stats.total_gates += circuit.gates.len();
180
181 if circuit_idx % 10 == 0 && circuit_idx > 0 {
183 println!(
184 "Processed {}/{} circuits for width {}",
185 circuit_idx, self.params.circuits_per_width, width
186 );
187 }
188 }
189
190 let success_count = heavy_output_probs
192 .iter()
193 .filter(|&&prob| prob >= self.params.heavy_output_threshold)
194 .count();
195 let success_probability = success_count as f64 / heavy_output_probs.len() as f64;
196
197 let required_success_prob = self.get_required_success_probability(width)?;
199 let passed = success_probability >= required_success_prob;
200
201 let quantum_volume = if passed { 1 << width } else { 0 };
203
204 let confidence = self.calculate_confidence(&heavy_output_probs, required_success_prob)?;
206
207 stats.total_time_s = start_time.elapsed().as_secs_f64();
208 stats.memory_usage_bytes = self.estimate_memory_usage(width);
209 stats.avg_fidelity =
210 heavy_output_probs.iter().sum::<f64>() / heavy_output_probs.len() as f64;
211
212 Ok(QuantumVolumeResult {
213 quantum_volume,
214 width,
215 depth,
216 num_circuits: self.params.circuits_per_width,
217 threshold: required_success_prob,
218 success_probability,
219 heavy_output_probs,
220 confidence,
221 passed,
222 stats,
223 })
224 }
225
226 fn generate_qv_circuit(&mut self, width: usize, depth: usize) -> Result<QVCircuit> {
228 if width < 2 {
229 return Err(SimulatorError::InvalidInput(
230 "Quantum volume requires at least 2 qubits".to_string(),
231 ));
232 }
233
234 let mut gates = Vec::new();
235 let mut available_qubits: Vec<usize> = (0..width).collect();
236
237 for layer in 0..depth {
239 available_qubits.shuffle(&mut self.rng);
241
242 for pair_idx in 0..(width / 2) {
244 let qubit1 = available_qubits[2 * pair_idx];
245 let qubit2 = available_qubits[2 * pair_idx + 1];
246
247 let gate = QVGate {
248 qubits: [qubit1, qubit2],
249 matrix: self.generate_random_su4()?,
250 layer,
251 };
252
253 gates.push(gate);
254 }
255 }
256
257 let mut permutation: Vec<usize> = (0..width).collect();
259 permutation.shuffle(&mut self.rng);
260
261 let ideal_amplitudes = self.simulate_ideal_circuit(width, &gates, &permutation)?;
263
264 let heavy_threshold = self.calculate_heavy_threshold(&ideal_amplitudes);
266 let heavy_outputs = self.find_heavy_outputs(&ideal_amplitudes, heavy_threshold);
267
268 Ok(QVCircuit {
269 width,
270 depth,
271 gates,
272 permutation,
273 ideal_amplitudes,
274 heavy_threshold,
275 heavy_outputs,
276 })
277 }
278
279 fn generate_random_su4(&mut self) -> Result<Array2<Complex64>> {
281 let mut params = Vec::with_capacity(15);
286 for _ in 0..15 {
287 params.push(self.rng.gen::<f64>() * 2.0 * std::f64::consts::PI);
288 }
289
290 let mut matrix = Array2::zeros((4, 4));
294
295 for i in 0..4 {
297 for j in 0..4 {
298 let real = self.rng.gen::<f64>() - 0.5;
299 let imag = self.rng.gen::<f64>() - 0.5;
300 matrix[[i, j]] = Complex64::new(real, imag);
301 }
302 }
303
304 self.gram_schmidt_orthogonalize(&mut matrix)?;
306
307 Ok(matrix)
308 }
309
310 fn gram_schmidt_orthogonalize(&self, matrix: &mut Array2<Complex64>) -> Result<()> {
312 let n = matrix.nrows();
313
314 for j in 0..n {
316 let mut norm = 0.0;
318 for i in 0..n {
319 norm += matrix[[i, j]].norm_sqr();
320 }
321 norm = norm.sqrt();
322
323 if norm > 1e-12 {
324 for i in 0..n {
325 matrix[[i, j]] /= norm;
326 }
327 }
328
329 for k in (j + 1)..n {
331 let mut dot_product = Complex64::new(0.0, 0.0);
332 for i in 0..n {
333 dot_product += matrix[[i, j]].conj() * matrix[[i, k]];
334 }
335
336 let column_j_values: Vec<Complex64> = (0..n).map(|i| matrix[[i, j]]).collect();
337 for i in 0..n {
338 matrix[[i, k]] -= dot_product * column_j_values[i];
339 }
340 }
341 }
342
343 Ok(())
344 }
345
346 fn simulate_ideal_circuit(
348 &self,
349 width: usize,
350 gates: &[QVGate],
351 permutation: &[usize],
352 ) -> Result<Array1<Complex64>> {
353 let dim = 1 << width;
354 let mut state = Array1::zeros(dim);
355 state[0] = Complex64::new(1.0, 0.0); for layer in 0..=gates.iter().map(|g| g.layer).max().unwrap_or(0) {
359 let layer_gates: Vec<&QVGate> = gates.iter().filter(|g| g.layer == layer).collect();
360
361 for gate in layer_gates {
363 self.apply_two_qubit_gate(
364 &mut state,
365 width,
366 &gate.matrix,
367 gate.qubits[0],
368 gate.qubits[1],
369 )?;
370 }
371 }
372
373 state = self.apply_permutation(&state, width, permutation)?;
375
376 Ok(state)
377 }
378
379 fn apply_two_qubit_gate(
381 &self,
382 state: &mut Array1<Complex64>,
383 width: usize,
384 gate_matrix: &Array2<Complex64>,
385 qubit1: usize,
386 qubit2: usize,
387 ) -> Result<()> {
388 let dim = 1 << width;
389 let mut new_state = Array1::zeros(dim);
390
391 for basis_state in 0..dim {
392 let bit1 = (basis_state >> (width - 1 - qubit1)) & 1;
393 let bit2 = (basis_state >> (width - 1 - qubit2)) & 1;
394 let two_qubit_state = (bit1 << 1) | bit2;
395
396 for target_two_qubit in 0..4 {
397 let amplitude = gate_matrix[[target_two_qubit, two_qubit_state]];
398
399 if amplitude.norm() > 1e-12 {
400 let target_bit1 = (target_two_qubit >> 1) & 1;
401 let target_bit2 = target_two_qubit & 1;
402
403 let mut target_basis = basis_state;
404
405 if target_bit1 == 1 {
407 target_basis |= 1 << (width - 1 - qubit1);
408 } else {
409 target_basis &= !(1 << (width - 1 - qubit1));
410 }
411
412 if target_bit2 == 1 {
414 target_basis |= 1 << (width - 1 - qubit2);
415 } else {
416 target_basis &= !(1 << (width - 1 - qubit2));
417 }
418
419 new_state[target_basis] += amplitude * state[basis_state];
420 }
421 }
422 }
423
424 *state = new_state;
425 Ok(())
426 }
427
428 fn apply_permutation(
430 &self,
431 state: &Array1<Complex64>,
432 width: usize,
433 permutation: &[usize],
434 ) -> Result<Array1<Complex64>> {
435 let dim = 1 << width;
436 let mut new_state = Array1::zeros(dim);
437
438 for basis_state in 0..dim {
439 let mut permuted_state = 0;
440
441 for (new_pos, &old_pos) in permutation.iter().enumerate() {
442 let bit = (basis_state >> (width - 1 - old_pos)) & 1;
443 permuted_state |= bit << (width - 1 - new_pos);
444 }
445
446 new_state[permuted_state] = state[basis_state];
447 }
448
449 Ok(new_state)
450 }
451
452 fn calculate_heavy_threshold(&self, amplitudes: &Array1<Complex64>) -> f64 {
454 let mut probabilities: Vec<f64> = amplitudes
455 .iter()
456 .map(scirs2_core::Complex::norm_sqr)
457 .collect();
458 probabilities.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
459
460 let median_idx = probabilities.len() / 2;
461 probabilities[median_idx]
462 }
463
464 fn find_heavy_outputs(&self, amplitudes: &Array1<Complex64>, threshold: f64) -> Vec<usize> {
466 amplitudes
467 .iter()
468 .enumerate()
469 .filter_map(|(idx, amp)| {
470 if amp.norm_sqr() > threshold {
471 Some(idx)
472 } else {
473 None
474 }
475 })
476 .collect()
477 }
478
479 fn calculate_heavy_output_probability(&self, circuit: &QVCircuit) -> Result<f64> {
481 let heavy_prob: f64 = circuit
483 .heavy_outputs
484 .iter()
485 .map(|&idx| circuit.ideal_amplitudes[idx].norm_sqr())
486 .sum();
487
488 Ok(heavy_prob)
489 }
490
491 fn get_required_success_probability(&self, width: usize) -> Result<f64> {
493 let baseline_threshold = 2.0 / 3.0;
497
498 let n = self.params.circuits_per_width as f64;
500 let confidence = self.params.confidence_level;
501
502 let z_score = match confidence {
504 x if x >= 0.99 => 2.576,
505 x if x >= 0.95 => 1.96,
506 x if x >= 0.90 => 1.645,
507 _ => 1.96,
508 };
509
510 let standard_error = (baseline_threshold * (1.0 - baseline_threshold) / n).sqrt();
511 let adjusted_threshold = baseline_threshold - z_score * standard_error;
512
513 Ok(adjusted_threshold.max(0.5)) }
515
516 fn calculate_confidence(&self, heavy_probs: &[f64], threshold: f64) -> Result<f64> {
518 let n = heavy_probs.len() as f64;
519 let success_count = heavy_probs.iter().filter(|&&p| p >= threshold).count() as f64;
520 let success_rate = success_count / n;
521
522 let p = success_rate;
524 let standard_error = (p * (1.0 - p) / n).sqrt();
525
526 if success_rate > threshold {
528 let z_score = (success_rate - threshold) / standard_error;
529 Ok(self.normal_cdf(z_score))
530 } else {
531 Ok(0.0)
532 }
533 }
534
535 fn normal_cdf(&self, x: f64) -> f64 {
537 0.5 * (1.0 + self.erf(x / 2.0_f64.sqrt()))
538 }
539
540 fn erf(&self, x: f64) -> f64 {
542 let a1 = 0.254_829_592;
544 let a2 = -0.284_496_736;
545 let a3 = 1.421_413_741;
546 let a4 = -1.453_152_027;
547 let a5 = 1.061_405_429;
548 let p = 0.327_591_1;
549
550 let sign = if x < 0.0 { -1.0 } else { 1.0 };
551 let x = x.abs();
552
553 let t = 1.0 / (1.0 + p * x);
554 let y = ((a5 * t + a4).mul_add(t, a3).mul_add(t, a2).mul_add(t, a1) * t)
555 .mul_add(-(-x * x).exp(), 1.0);
556
557 sign * y
558 }
559
560 const fn estimate_memory_usage(&self, width: usize) -> usize {
562 let state_vector_size = (1 << width) * std::mem::size_of::<Complex64>();
563 let circuit_storage = 1000 * std::mem::size_of::<QVGate>(); state_vector_size + circuit_storage
565 }
566
567 pub fn find_maximum_quantum_volume(&mut self) -> Result<Vec<QuantumVolumeResult>> {
569 let mut results = Vec::new();
570
571 for width in 2..=self.params.max_width {
572 println!("Testing quantum volume for width {width}");
573
574 let result = self.calculate_quantum_volume(width)?;
575
576 println!(
577 "Width {}: QV = {}, Success Prob = {:.3}, Passed = {}",
578 width, result.quantum_volume, result.success_probability, result.passed
579 );
580
581 results.push(result.clone());
582
583 if !result.passed {
585 break;
586 }
587 }
588
589 Ok(results)
590 }
591}
592
593pub fn benchmark_quantum_volume(max_width: usize) -> Result<Vec<QuantumVolumeResult>> {
595 let params = QVParams {
596 circuits_per_width: 20, max_width,
598 ..Default::default()
599 };
600
601 let mut calculator = QuantumVolumeCalculator::new(params);
602 calculator.find_maximum_quantum_volume()
603}
604
605pub fn calculate_quantum_volume_with_params(
607 width: usize,
608 circuits: usize,
609 seed: Option<u64>,
610) -> Result<QuantumVolumeResult> {
611 let params = QVParams {
612 circuits_per_width: circuits,
613 max_width: width,
614 seed,
615 ..Default::default()
616 };
617
618 let mut calculator = QuantumVolumeCalculator::new(params);
619 calculator.calculate_quantum_volume(width)
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625
626 #[test]
627 fn test_qv_calculator_creation() {
628 let params = QVParams::default();
629 let calculator = QuantumVolumeCalculator::new(params);
630 assert!(calculator.params.circuits_per_width > 0);
631 }
632
633 #[test]
634 fn test_random_su4_generation() {
635 let params = QVParams::default();
636 let mut calculator = QuantumVolumeCalculator::new(params);
637
638 let matrix = calculator
639 .generate_random_su4()
640 .expect("Failed to generate random SU(4) matrix");
641 assert_eq!(matrix.shape(), [4, 4]);
642
643 let matrix_dagger = matrix.t().mapv(|x| x.conj());
645 let product = matrix_dagger.dot(&matrix);
646
647 for i in 0..4 {
649 assert!((product[[i, i]].re - 1.0).abs() < 1e-1);
650 assert!(product[[i, i]].im.abs() < 1e-1);
651 }
652 }
653
654 #[test]
655 fn test_qv_circuit_generation() {
656 let params = QVParams::default();
657 let mut calculator = QuantumVolumeCalculator::new(params);
658
659 let circuit = calculator
660 .generate_qv_circuit(4, 4)
661 .expect("Failed to generate QV circuit");
662 assert_eq!(circuit.width, 4);
663 assert_eq!(circuit.depth, 4);
664 assert!(!circuit.gates.is_empty());
665 assert_eq!(circuit.permutation.len(), 4);
666 }
667
668 #[test]
669 fn test_heavy_output_calculation() {
670 let amplitudes = Array1::from_vec(vec![
671 Complex64::new(0.6, 0.0), Complex64::new(0.3, 0.0), Complex64::new(0.1, 0.0), Complex64::new(0.05, 0.0), ]);
676
677 let params = QVParams::default();
678 let calculator = QuantumVolumeCalculator::new(params);
679
680 let threshold = calculator.calculate_heavy_threshold(&litudes);
681 let heavy_outputs = calculator.find_heavy_outputs(&litudes, threshold);
682
683 assert!(threshold > 0.0);
684 assert!(!heavy_outputs.is_empty());
685 }
686
687 #[test]
688 fn test_small_quantum_volume() {
689 let result = calculate_quantum_volume_with_params(3, 10, Some(42))
690 .expect("Failed to calculate quantum volume");
691
692 assert_eq!(result.width, 3);
693 assert_eq!(result.depth, 3);
694 assert_eq!(result.num_circuits, 10);
695 assert!(!result.heavy_output_probs.is_empty());
696 }
697
698 #[test]
699 fn test_permutation_application() {
700 let params = QVParams::default();
701 let calculator = QuantumVolumeCalculator::new(params);
702
703 let state = Array1::from_vec(vec![
704 Complex64::new(1.0, 0.0),
705 Complex64::new(0.0, 0.0),
706 Complex64::new(0.0, 0.0),
707 Complex64::new(0.0, 0.0),
708 ]);
709
710 let permutation = vec![1, 0]; let permuted = calculator
712 .apply_permutation(&state, 2, &permutation)
713 .expect("Failed to apply permutation");
714
715 assert!((permuted[0].re - 1.0).abs() < 1e-10);
717 }
718}