quantrs2_core/characterization/
quantum_volume.rs1use 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#[derive(Debug, Clone)]
16pub struct QuantumVolumeResult {
17 pub num_qubits: usize,
19 pub quantum_volume_log2: f64,
21 pub quantum_volume: f64,
23 pub success_probability: f64,
25 pub threshold: f64,
27 pub num_circuits: usize,
29 pub shots_per_circuit: usize,
31 pub circuit_probabilities: Vec<f64>,
33 pub confidence_interval: (f64, f64),
35}
36
37impl QuantumVolumeResult {
38 pub fn passed(&self) -> bool {
40 self.success_probability > self.threshold
41 }
42
43 pub const fn quantum_volume_int(&self) -> u64 {
45 self.quantum_volume as u64
46 }
47}
48
49#[derive(Debug, Clone)]
51pub struct QuantumVolumeConfig {
52 pub num_qubits: usize,
54 pub num_circuits: usize,
56 pub shots_per_circuit: usize,
58 pub circuit_depth: usize,
60 pub heavy_output_threshold: f64,
62 pub confidence_level: f64,
64 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#[derive(Debug, Clone)]
84pub struct RandomQuantumCircuit {
85 pub num_qubits: usize,
87 pub layers: Vec<Vec<RandomGate>>,
89}
90
91#[derive(Debug, Clone)]
93pub struct RandomGate {
94 pub qubits: Vec<usize>,
96 pub unitary: Array2<Complex>,
98}
99
100pub struct QuantumVolumeMeasurement {
102 config: QuantumVolumeConfig,
103 rng: Box<dyn RngCore>,
104}
105
106impl QuantumVolumeMeasurement {
107 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 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 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 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 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 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 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 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 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 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 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}