1use crate::{
20 error::{QuantRS2Error, QuantRS2Result},
21 gate::GateOp,
22 qubit::QubitId,
23};
24use scirs2_core::ndarray::{Array1, Array2, Array3, Array4};
25use scirs2_core::random::prelude::*;
26use scirs2_core::Complex64;
27use std::collections::HashMap;
28
29pub struct QuantumVolume {
34 pub max_qubits: usize,
36 pub num_circuits: usize,
38 pub num_shots: usize,
40 pub success_threshold: f64,
42 rng: ThreadRng,
44}
45
46impl QuantumVolume {
47 pub fn new(max_qubits: usize, num_circuits: usize, num_shots: usize) -> Self {
49 Self {
50 max_qubits,
51 num_circuits,
52 num_shots,
53 success_threshold: 2.0 / 3.0, rng: thread_rng(),
55 }
56 }
57
58 pub fn run<F>(&mut self, mut circuit_executor: F) -> QuantRS2Result<QuantumVolumeResult>
62 where
63 F: FnMut(&[Box<dyn GateOp>], usize) -> Vec<usize>, {
65 let mut results = HashMap::new();
66 let mut quantum_volume = 1;
67
68 for n_qubits in 1..=self.max_qubits {
69 let success_rate = self.test_quantum_volume(n_qubits, &mut circuit_executor)?;
70
71 results.insert(n_qubits, success_rate);
72
73 if success_rate >= self.success_threshold {
75 quantum_volume = 1 << n_qubits; } else {
77 break; }
79 }
80
81 Ok(QuantumVolumeResult {
82 quantum_volume,
83 success_rates: results,
84 max_qubits_tested: self.max_qubits,
85 })
86 }
87
88 fn test_quantum_volume<F>(
90 &self,
91 n_qubits: usize,
92 circuit_executor: &mut F,
93 ) -> QuantRS2Result<f64>
94 where
95 F: FnMut(&[Box<dyn GateOp>], usize) -> Vec<usize>,
96 {
97 let mut successful_circuits = 0;
98
99 for _ in 0..self.num_circuits {
100 let (circuit, heavy_outputs) = self.generate_random_circuit(n_qubits)?;
102
103 let measurements = circuit_executor(&circuit, self.num_shots);
105
106 let hop = self.calculate_heavy_output_probability(&measurements, &heavy_outputs);
108
109 if hop > 2.0 / 3.0 {
111 successful_circuits += 1;
112 }
113 }
114
115 let success_rate = successful_circuits as f64 / self.num_circuits as f64;
116 Ok(success_rate)
117 }
118
119 fn generate_random_circuit(
123 &self,
124 n_qubits: usize,
125 ) -> QuantRS2Result<(Vec<Box<dyn GateOp>>, Vec<usize>)> {
126 let depth = n_qubits;
128
129 let circuit = vec![];
132
133 let heavy_outputs = self.find_heavy_outputs(n_qubits, &circuit)?;
135
136 Ok((circuit, heavy_outputs))
137 }
138
139 fn find_heavy_outputs(
141 &self,
142 n_qubits: usize,
143 _circuit: &[Box<dyn GateOp>],
144 ) -> QuantRS2Result<Vec<usize>> {
145 let num_states = 1 << n_qubits;
149 let median_prob = 1.0 / (num_states as f64);
150
151 Ok((0..num_states / 2).collect())
158 }
159
160 fn calculate_heavy_output_probability(
162 &self,
163 measurements: &[usize],
164 heavy_outputs: &[usize],
165 ) -> f64 {
166 let heavy_count = measurements
167 .iter()
168 .filter(|&&bitstring| heavy_outputs.contains(&bitstring))
169 .count();
170
171 heavy_count as f64 / measurements.len() as f64
172 }
173}
174
175#[derive(Debug, Clone)]
177pub struct QuantumVolumeResult {
178 pub quantum_volume: usize,
180 pub success_rates: HashMap<usize, f64>,
182 pub max_qubits_tested: usize,
184}
185
186impl QuantumVolumeResult {
187 pub fn num_qubits_achieved(&self) -> usize {
189 (self.quantum_volume as f64).log2() as usize
190 }
191
192 pub fn is_qv_achieved(&self, n_qubits: usize) -> bool {
194 self.success_rates
195 .get(&n_qubits)
196 .map(|&rate| rate >= 2.0 / 3.0)
197 .unwrap_or(false)
198 }
199}
200
201pub struct QuantumProcessTomography {
206 pub num_qubits: usize,
208 pub preparation_basis: Vec<String>,
210 pub measurement_basis: Vec<String>,
212}
213
214impl QuantumProcessTomography {
215 pub fn new(num_qubits: usize) -> Self {
217 let basis = Self::generate_pauli_basis(num_qubits);
219
220 Self {
221 num_qubits,
222 preparation_basis: basis.clone(),
223 measurement_basis: basis,
224 }
225 }
226
227 fn generate_pauli_basis(n_qubits: usize) -> Vec<String> {
229 let paulis = ['I', 'X', 'Y', 'Z'];
230 let basis_size = 4_usize.pow(n_qubits as u32);
231
232 let mut basis = Vec::with_capacity(basis_size);
233
234 for i in 0..basis_size {
235 let mut pauli_string = String::with_capacity(n_qubits);
236 let mut idx = i;
237
238 for _ in 0..n_qubits {
239 pauli_string.push(paulis[idx % 4]);
240 idx /= 4;
241 }
242
243 basis.push(pauli_string);
244 }
245
246 basis
247 }
248
249 pub fn run<F>(&self, mut apply_process: F) -> QuantRS2Result<ProcessMatrix>
253 where
254 F: FnMut(&str, &str) -> Complex64, {
256 let dim = 1 << self.num_qubits;
257 let basis_size = self.preparation_basis.len();
258
259 let mut chi_matrix = Array2::zeros((basis_size, basis_size));
261
262 for (i, prep) in self.preparation_basis.iter().enumerate() {
264 for (j, meas) in self.measurement_basis.iter().enumerate() {
265 let expectation = apply_process(prep, meas);
266 chi_matrix[[i, j]] = expectation;
267 }
268 }
269
270 let chi_matrix = self.enforce_physicality(chi_matrix)?;
272
273 Ok(ProcessMatrix {
274 chi_matrix,
275 num_qubits: self.num_qubits,
276 basis_labels: self.preparation_basis.clone(),
277 })
278 }
279
280 fn enforce_physicality(&self, chi: Array2<Complex64>) -> QuantRS2Result<Array2<Complex64>> {
282 let trace: Complex64 = chi.diag().iter().sum();
290 let normalized = if trace.norm() > 1e-10 {
291 &chi / trace
292 } else {
293 chi.clone()
294 };
295
296 Ok(normalized)
297 }
298
299 pub fn process_fidelity(chi1: &Array2<Complex64>, chi2: &Array2<Complex64>) -> f64 {
301 let product = chi1.t().mapv(|x| x.conj()).dot(chi2);
303 let trace: Complex64 = product.diag().iter().sum();
304 trace.norm()
305 }
306
307 pub fn average_gate_fidelity(
309 &self,
310 chi: &Array2<Complex64>,
311 ideal_chi: &Array2<Complex64>,
312 ) -> f64 {
313 let dim = 1 << self.num_qubits;
314 let d = dim as f64;
315
316 let f_proc = Self::process_fidelity(chi, ideal_chi);
318 (d * f_proc + 1.0) / (d + 1.0)
319 }
320}
321
322#[derive(Debug, Clone)]
324pub struct ProcessMatrix {
325 pub chi_matrix: Array2<Complex64>,
327 pub num_qubits: usize,
329 pub basis_labels: Vec<String>,
331}
332
333impl ProcessMatrix {
334 pub fn get_element(&self, prep_pauli: &str, meas_pauli: &str) -> Option<Complex64> {
336 let i = self.basis_labels.iter().position(|s| s == prep_pauli)?;
337 let j = self.basis_labels.iter().position(|s| s == meas_pauli)?;
338 Some(self.chi_matrix[[i, j]])
339 }
340
341 pub fn is_trace_preserving(&self, tolerance: f64) -> bool {
343 let trace: Complex64 = self.chi_matrix.diag().iter().sum();
344 (trace - Complex64::new(1.0, 0.0)).norm() < tolerance
345 }
346
347 pub fn is_completely_positive(&self, tolerance: f64) -> bool {
349 self.chi_matrix.diag().iter().all(|&x| x.re >= -tolerance)
354 }
355
356 pub fn diamond_distance(&self, other: &ProcessMatrix) -> QuantRS2Result<f64> {
358 if self.num_qubits != other.num_qubits {
359 return Err(QuantRS2Error::InvalidInput(
360 "Process matrices must have same dimension".to_string(),
361 ));
362 }
363
364 let diff = &self.chi_matrix - &other.chi_matrix;
369 let frobenius_norm = diff.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
370
371 Ok(frobenius_norm)
372 }
373}
374
375pub struct GateSetTomography {
380 pub num_qubits: usize,
382 pub gate_set: Vec<String>,
384 pub max_length: usize,
386}
387
388impl GateSetTomography {
389 pub fn new(num_qubits: usize, gate_set: Vec<String>, max_length: usize) -> Self {
391 Self {
392 num_qubits,
393 gate_set,
394 max_length,
395 }
396 }
397
398 pub fn run<F>(&self, mut execute_sequence: F) -> QuantRS2Result<GateSetModel>
402 where
403 F: FnMut(&[&str]) -> f64, {
405 let germs = self.generate_germs();
411 let fiducials = self.generate_fiducials();
412
413 let mut data = HashMap::new();
415
416 for prep_fiducial in &fiducials {
417 for germ in &germs {
418 for meas_fiducial in &fiducials {
419 for power in 1..=self.max_length {
421 let mut sequence = Vec::new();
422
423 sequence.extend_from_slice(prep_fiducial);
425
426 for _ in 0..power {
428 sequence.extend_from_slice(germ);
429 }
430
431 sequence.extend_from_slice(meas_fiducial);
433
434 let probability = execute_sequence(&sequence);
436 data.insert(sequence.clone(), probability);
437 }
438 }
439 }
440 }
441
442 let model = self.fit_model(&data)?;
444
445 Ok(model)
446 }
447
448 fn generate_germs(&self) -> Vec<Vec<&str>> {
450 vec![vec!["I"], vec!["X"], vec!["Y"], vec!["X", "Y"]]
453 }
454
455 fn generate_fiducials(&self) -> Vec<Vec<&str>> {
457 vec![
459 vec!["I"],
460 vec!["X"],
461 vec!["Y"],
462 vec!["X", "X"], ]
464 }
465
466 fn fit_model(&self, _data: &HashMap<Vec<&str>, f64>) -> QuantRS2Result<GateSetModel> {
468 Ok(GateSetModel {
472 num_qubits: self.num_qubits,
473 gate_errors: HashMap::new(),
474 spam_errors: vec![],
475 })
476 }
477}
478
479#[derive(Debug, Clone)]
481pub struct GateSetModel {
482 pub num_qubits: usize,
484 pub gate_errors: HashMap<String, Array2<Complex64>>,
486 pub spam_errors: Vec<f64>,
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn test_quantum_volume_result() {
496 let mut result = QuantumVolumeResult {
497 quantum_volume: 16,
498 success_rates: HashMap::new(),
499 max_qubits_tested: 5,
500 };
501
502 result.success_rates.insert(1, 0.95);
503 result.success_rates.insert(2, 0.85);
504 result.success_rates.insert(3, 0.75);
505 result.success_rates.insert(4, 0.70);
506
507 assert_eq!(result.num_qubits_achieved(), 4);
508 assert!(result.is_qv_achieved(1));
509 assert!(result.is_qv_achieved(2));
510 assert!(result.is_qv_achieved(3));
511 assert!(result.is_qv_achieved(4));
512
513 println!("Quantum Volume: {}", result.quantum_volume);
514 }
515
516 #[test]
517 fn test_pauli_basis_generation() {
518 let basis = QuantumProcessTomography::generate_pauli_basis(1);
519 assert_eq!(basis.len(), 4);
520 assert!(basis.contains(&"I".to_string()));
521 assert!(basis.contains(&"X".to_string()));
522 assert!(basis.contains(&"Y".to_string()));
523 assert!(basis.contains(&"Z".to_string()));
524
525 let basis_2q = QuantumProcessTomography::generate_pauli_basis(2);
526 assert_eq!(basis_2q.len(), 16);
527 }
528
529 #[test]
530 fn test_process_matrix() {
531 let qpt = QuantumProcessTomography::new(1);
532
533 let mock_process = |_prep: &str, meas: &str| {
535 if meas == "I" {
536 Complex64::new(1.0, 0.0)
537 } else {
538 Complex64::new(0.0, 0.0)
539 }
540 };
541
542 let result = qpt.run(mock_process).unwrap();
543
544 assert_eq!(result.num_qubits, 1);
545 assert!(result.is_trace_preserving(1e-6));
546 println!("Process matrix shape: {:?}", result.chi_matrix.dim());
547 }
548
549 #[test]
550 fn test_process_fidelity() {
551 let dim = 4;
552 let identity = Array2::eye(dim);
553 let noisy = &identity * Complex64::new(0.95, 0.0);
554
555 let fidelity = QuantumProcessTomography::process_fidelity(&identity, &noisy);
556
557 println!("Process fidelity: {}", fidelity);
560
561 assert!(fidelity > 0.0 && fidelity <= dim as f64);
563 }
564
565 #[test]
566 fn test_gst_initialization() {
567 let gate_set = vec!["I".to_string(), "X".to_string(), "H".to_string()];
568 let gst = GateSetTomography::new(1, gate_set, 10);
569
570 assert_eq!(gst.num_qubits, 1);
571 assert_eq!(gst.max_length, 10);
572
573 let germs = gst.generate_germs();
574 assert!(!germs.is_empty());
575
576 let fiducials = gst.generate_fiducials();
577 assert!(!fiducials.is_empty());
578 }
579}