Skip to main content

quantrs2_core/characterization/
quantum_volume.rs

1//! Quantum Volume measurement types and engine
2
3use crate::error::{QuantRS2Error, QuantRS2Result};
4use scirs2_core::ndarray::Array2;
5use scirs2_core::random::prelude::*;
6use scirs2_core::random::Distribution;
7use scirs2_core::Complex64 as Complex;
8use std::collections::HashMap;
9
10/// Quantum Volume measurement result
11///
12/// Quantum volume is a metric that quantifies the overall computational power
13/// of a quantum computer, taking into account gate fidelity, connectivity, and
14/// circuit depth capabilities.
15#[derive(Debug, Clone)]
16pub struct QuantumVolumeResult {
17    /// Number of qubits used in the measurement
18    pub num_qubits: usize,
19    /// Measured quantum volume (log2 scale)
20    pub quantum_volume_log2: f64,
21    /// Actual quantum volume (2^quantum_volume_log2)
22    pub quantum_volume: f64,
23    /// Success probability (heavy output generation)
24    pub success_probability: f64,
25    /// Threshold for heavy output (typically 2/3)
26    pub threshold: f64,
27    /// Number of circuits evaluated
28    pub num_circuits: usize,
29    /// Number of shots per circuit
30    pub shots_per_circuit: usize,
31    /// Individual circuit heavy output probabilities
32    pub circuit_probabilities: Vec<f64>,
33    /// Confidence interval (95%)
34    pub confidence_interval: (f64, f64),
35}
36
37impl QuantumVolumeResult {
38    /// Check if quantum volume test passed
39    pub fn passed(&self) -> bool {
40        self.success_probability > self.threshold
41    }
42
43    /// Get quantum volume as integer
44    pub const fn quantum_volume_int(&self) -> u64 {
45        self.quantum_volume as u64
46    }
47}
48
49/// Quantum Volume measurement configuration
50#[derive(Debug, Clone)]
51pub struct QuantumVolumeConfig {
52    /// Number of qubits to test
53    pub num_qubits: usize,
54    /// Number of random circuits to generate
55    pub num_circuits: usize,
56    /// Number of measurement shots per circuit
57    pub shots_per_circuit: usize,
58    /// Circuit depth (typically equal to num_qubits)
59    pub circuit_depth: usize,
60    /// Threshold for heavy output determination (default: 2/3)
61    pub heavy_output_threshold: f64,
62    /// Confidence level for statistical significance (default: 0.95)
63    pub confidence_level: f64,
64    /// Random seed for reproducibility
65    pub seed: Option<u64>,
66}
67
68impl Default for QuantumVolumeConfig {
69    fn default() -> Self {
70        Self {
71            num_qubits: 4,
72            num_circuits: 100,
73            shots_per_circuit: 1000,
74            circuit_depth: 4,
75            heavy_output_threshold: 2.0 / 3.0,
76            confidence_level: 0.95,
77            seed: None,
78        }
79    }
80}
81
82/// Random quantum circuit representation
83#[derive(Debug, Clone)]
84pub struct RandomQuantumCircuit {
85    /// Number of qubits
86    pub num_qubits: usize,
87    /// Circuit layers (each layer contains gates applied in parallel)
88    pub layers: Vec<Vec<RandomGate>>,
89}
90
91/// Random quantum gate
92#[derive(Debug, Clone)]
93pub struct RandomGate {
94    /// Qubits the gate acts on
95    pub qubits: Vec<usize>,
96    /// Unitary matrix of the gate
97    pub unitary: Array2<Complex>,
98}
99
100/// Quantum Volume measurement engine
101pub struct QuantumVolumeMeasurement {
102    config: QuantumVolumeConfig,
103    rng: Box<dyn RngCore>,
104}
105
106impl QuantumVolumeMeasurement {
107    /// Create a new quantum volume measurement
108    pub fn new(config: QuantumVolumeConfig) -> Self {
109        let rng: Box<dyn RngCore> = if let Some(seed) = config.seed {
110            Box::new(seeded_rng(seed))
111        } else {
112            Box::new(thread_rng())
113        };
114
115        Self { config, rng }
116    }
117
118    /// Measure quantum volume using random circuit sampling
119    ///
120    /// This implements the quantum volume protocol:
121    /// 1. Generate random unitary circuits
122    /// 2. Execute circuits and measure outcomes
123    /// 3. Compute heavy output probabilities
124    /// 4. Determine if quantum volume threshold is achieved
125    pub fn measure<F>(&mut self, circuit_executor: F) -> QuantRS2Result<QuantumVolumeResult>
126    where
127        F: Fn(&RandomQuantumCircuit, usize) -> QuantRS2Result<HashMap<String, usize>>,
128    {
129        let mut circuit_probabilities = Vec::new();
130
131        for _ in 0..self.config.num_circuits {
132            let circuit = self.generate_random_circuit()?;
133            let ideal_distribution = self.compute_ideal_distribution(&circuit)?;
134            let heavy_outputs = Self::identify_heavy_outputs(&ideal_distribution)?;
135
136            let measurement_counts = circuit_executor(&circuit, self.config.shots_per_circuit)?;
137
138            let heavy_prob = Self::compute_heavy_output_probability(
139                &measurement_counts,
140                &heavy_outputs,
141                self.config.shots_per_circuit,
142            );
143
144            circuit_probabilities.push(heavy_prob);
145        }
146
147        let success_count = circuit_probabilities
148            .iter()
149            .filter(|&&p| p > self.config.heavy_output_threshold)
150            .count();
151        let success_probability = success_count as f64 / self.config.num_circuits as f64;
152
153        let confidence_interval =
154            Self::compute_confidence_interval(success_count, self.config.num_circuits);
155
156        let quantum_volume_log2 = if success_probability > self.config.heavy_output_threshold {
157            self.config.num_qubits as f64
158        } else {
159            0.0
160        };
161        let quantum_volume = quantum_volume_log2.exp2();
162
163        Ok(QuantumVolumeResult {
164            num_qubits: self.config.num_qubits,
165            quantum_volume_log2,
166            quantum_volume,
167            success_probability,
168            threshold: self.config.heavy_output_threshold,
169            num_circuits: self.config.num_circuits,
170            shots_per_circuit: self.config.shots_per_circuit,
171            circuit_probabilities,
172            confidence_interval,
173        })
174    }
175
176    /// Generate a random quantum circuit for quantum volume measurement
177    fn generate_random_circuit(&mut self) -> QuantRS2Result<RandomQuantumCircuit> {
178        let mut layers = Vec::new();
179
180        for _ in 0..self.config.circuit_depth {
181            let layer = self.generate_random_layer()?;
182            layers.push(layer);
183        }
184
185        Ok(RandomQuantumCircuit {
186            num_qubits: self.config.num_qubits,
187            layers,
188        })
189    }
190
191    /// Generate a random gate layer
192    fn generate_random_layer(&mut self) -> QuantRS2Result<Vec<RandomGate>> {
193        let mut gates = Vec::new();
194        let num_pairs = self.config.num_qubits / 2;
195
196        let mut qubits: Vec<usize> = (0..self.config.num_qubits).collect();
197        self.shuffle_slice(&mut qubits);
198
199        for i in 0..num_pairs {
200            let qubit1 = qubits[2 * i];
201            let qubit2 = qubits[2 * i + 1];
202
203            let unitary = self.generate_random_unitary(4)?;
204            gates.push(RandomGate {
205                qubits: vec![qubit1, qubit2],
206                unitary,
207            });
208        }
209
210        Ok(gates)
211    }
212
213    /// Generate a random unitary matrix using QR decomposition
214    fn generate_random_unitary(&mut self, dim: usize) -> QuantRS2Result<Array2<Complex>> {
215        use scirs2_core::random::distributions_unified::UnifiedNormal;
216
217        let normal = UnifiedNormal::new(0.0, 1.0).map_err(|e| {
218            QuantRS2Error::ComputationError(format!("Normal distribution error: {e}"))
219        })?;
220
221        let mut matrix = Array2::zeros((dim, dim));
222        for i in 0..dim {
223            for j in 0..dim {
224                let real = normal.sample(&mut self.rng);
225                let imag = normal.sample(&mut self.rng);
226                matrix[(i, j)] = Complex::new(real, imag);
227            }
228        }
229
230        Self::gram_schmidt(&matrix)
231    }
232
233    /// Gram-Schmidt orthogonalization
234    fn gram_schmidt(matrix: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
235        let dim = matrix.nrows();
236        let mut result = Array2::<Complex>::zeros((dim, dim));
237
238        for j in 0..dim {
239            let mut col = matrix.column(j).to_owned();
240
241            for k in 0..j {
242                let prev_col = result.column(k);
243                let proj = col
244                    .iter()
245                    .zip(prev_col.iter())
246                    .map(|(a, b)| a * b.conj())
247                    .sum::<Complex>();
248                for i in 0..dim {
249                    col[i] -= proj * prev_col[i];
250                }
251            }
252
253            let norm = col.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
254            if norm < 1e-10 {
255                return Err(QuantRS2Error::ComputationError(
256                    "Gram-Schmidt failed: zero vector".to_string(),
257                ));
258            }
259
260            for i in 0..dim {
261                result[(i, j)] = col[i] / norm;
262            }
263        }
264
265        Ok(result)
266    }
267
268    /// Shuffle a slice using Fisher-Yates algorithm
269    fn shuffle_slice(&mut self, slice: &mut [usize]) {
270        let n = slice.len();
271        for i in 0..n - 1 {
272            let j = i + (self.rng.next_u64() as usize) % (n - i);
273            slice.swap(i, j);
274        }
275    }
276
277    /// Compute ideal probability distribution for a circuit
278    fn compute_ideal_distribution(
279        &self,
280        _circuit: &RandomQuantumCircuit,
281    ) -> QuantRS2Result<HashMap<String, f64>> {
282        let num_outcomes = 2_usize.pow(self.config.num_qubits as u32);
283        let mut distribution = HashMap::new();
284
285        for i in 0..num_outcomes {
286            let bitstring = format!("{:0width$b}", i, width = self.config.num_qubits);
287            distribution.insert(bitstring, 1.0 / num_outcomes as f64);
288        }
289
290        Ok(distribution)
291    }
292
293    /// Identify heavy outputs (above median probability)
294    fn identify_heavy_outputs(distribution: &HashMap<String, f64>) -> QuantRS2Result<Vec<String>> {
295        let mut probs: Vec<(String, f64)> =
296            distribution.iter().map(|(k, v)| (k.clone(), *v)).collect();
297        probs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
298
299        let median_idx = probs.len() / 2;
300        let median_prob = probs[median_idx].1;
301
302        let heavy_outputs: Vec<String> = probs
303            .iter()
304            .filter(|(_, p)| *p > median_prob)
305            .map(|(s, _)| s.clone())
306            .collect();
307
308        Ok(heavy_outputs)
309    }
310
311    /// Compute heavy output probability from measurement counts
312    fn compute_heavy_output_probability(
313        counts: &HashMap<String, usize>,
314        heavy_outputs: &[String],
315        total_shots: usize,
316    ) -> f64 {
317        let heavy_count: usize = counts
318            .iter()
319            .filter(|(outcome, _)| heavy_outputs.contains(outcome))
320            .map(|(_, count)| count)
321            .sum();
322
323        heavy_count as f64 / total_shots as f64
324    }
325
326    /// Compute Wilson score confidence interval
327    fn compute_confidence_interval(successes: usize, trials: usize) -> (f64, f64) {
328        let p = successes as f64 / trials as f64;
329        let n = trials as f64;
330
331        let z = 1.96;
332        let z2 = z * z;
333
334        let denominator = 1.0 + z2 / n;
335        let center = (p + z2 / (2.0 * n)) / denominator;
336        let margin = z * (p * (1.0 - p) / n + z2 / (4.0 * n * n)).sqrt() / denominator;
337
338        (center - margin, center + margin)
339    }
340}