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 .is_some_and(|&rate| rate >= 2.0 / 3.0)
197 }
198}
199
200pub struct QuantumProcessTomography {
205 pub num_qubits: usize,
207 pub preparation_basis: Vec<String>,
209 pub measurement_basis: Vec<String>,
211}
212
213impl QuantumProcessTomography {
214 pub fn new(num_qubits: usize) -> Self {
216 let basis = Self::generate_pauli_basis(num_qubits);
218
219 Self {
220 num_qubits,
221 preparation_basis: basis.clone(),
222 measurement_basis: basis,
223 }
224 }
225
226 fn generate_pauli_basis(n_qubits: usize) -> Vec<String> {
228 let paulis = ['I', 'X', 'Y', 'Z'];
229 let basis_size = 4_usize.pow(n_qubits as u32);
230
231 let mut basis = Vec::with_capacity(basis_size);
232
233 for i in 0..basis_size {
234 let mut pauli_string = String::with_capacity(n_qubits);
235 let mut idx = i;
236
237 for _ in 0..n_qubits {
238 pauli_string.push(paulis[idx % 4]);
239 idx /= 4;
240 }
241
242 basis.push(pauli_string);
243 }
244
245 basis
246 }
247
248 pub fn run<F>(&self, mut apply_process: F) -> QuantRS2Result<ProcessMatrix>
252 where
253 F: FnMut(&str, &str) -> Complex64, {
255 let dim = 1 << self.num_qubits;
256 let basis_size = self.preparation_basis.len();
257
258 let mut chi_matrix = Array2::zeros((basis_size, basis_size));
260
261 for (i, prep) in self.preparation_basis.iter().enumerate() {
263 for (j, meas) in self.measurement_basis.iter().enumerate() {
264 let expectation = apply_process(prep, meas);
265 chi_matrix[[i, j]] = expectation;
266 }
267 }
268
269 let chi_matrix = self.enforce_physicality(chi_matrix)?;
271
272 Ok(ProcessMatrix {
273 chi_matrix,
274 num_qubits: self.num_qubits,
275 basis_labels: self.preparation_basis.clone(),
276 })
277 }
278
279 fn enforce_physicality(&self, chi: Array2<Complex64>) -> QuantRS2Result<Array2<Complex64>> {
281 let trace: Complex64 = chi.diag().iter().sum();
289 let normalized = if trace.norm() > 1e-10 {
290 &chi / trace
291 } else {
292 chi
293 };
294
295 Ok(normalized)
296 }
297
298 pub fn process_fidelity(chi1: &Array2<Complex64>, chi2: &Array2<Complex64>) -> f64 {
300 let product = chi1.t().mapv(|x| x.conj()).dot(chi2);
302 let trace: Complex64 = product.diag().iter().sum();
303 trace.norm()
304 }
305
306 pub fn average_gate_fidelity(
308 &self,
309 chi: &Array2<Complex64>,
310 ideal_chi: &Array2<Complex64>,
311 ) -> f64 {
312 let dim = 1 << self.num_qubits;
313 let d = dim as f64;
314
315 let f_proc = Self::process_fidelity(chi, ideal_chi);
317 (d * f_proc + 1.0) / (d + 1.0)
318 }
319}
320
321#[derive(Debug, Clone)]
323pub struct ProcessMatrix {
324 pub chi_matrix: Array2<Complex64>,
326 pub num_qubits: usize,
328 pub basis_labels: Vec<String>,
330}
331
332impl ProcessMatrix {
333 pub fn get_element(&self, prep_pauli: &str, meas_pauli: &str) -> Option<Complex64> {
335 let i = self.basis_labels.iter().position(|s| s == prep_pauli)?;
336 let j = self.basis_labels.iter().position(|s| s == meas_pauli)?;
337 Some(self.chi_matrix[[i, j]])
338 }
339
340 pub fn is_trace_preserving(&self, tolerance: f64) -> bool {
342 let trace: Complex64 = self.chi_matrix.diag().iter().sum();
343 (trace - Complex64::new(1.0, 0.0)).norm() < tolerance
344 }
345
346 pub fn is_completely_positive(&self, tolerance: f64) -> bool {
348 self.chi_matrix.diag().iter().all(|&x| x.re >= -tolerance)
353 }
354
355 pub fn diamond_distance(&self, other: &Self) -> QuantRS2Result<f64> {
357 if self.num_qubits != other.num_qubits {
358 return Err(QuantRS2Error::InvalidInput(
359 "Process matrices must have same dimension".to_string(),
360 ));
361 }
362
363 let diff = &self.chi_matrix - &other.chi_matrix;
368 let frobenius_norm = diff.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
369
370 Ok(frobenius_norm)
371 }
372}
373
374pub struct GateSetTomography {
379 pub num_qubits: usize,
381 pub gate_set: Vec<String>,
383 pub max_length: usize,
385}
386
387impl GateSetTomography {
388 pub const fn new(num_qubits: usize, gate_set: Vec<String>, max_length: usize) -> Self {
390 Self {
391 num_qubits,
392 gate_set,
393 max_length,
394 }
395 }
396
397 pub fn run<F>(&self, mut execute_sequence: F) -> QuantRS2Result<GateSetModel>
401 where
402 F: FnMut(&[&str]) -> f64, {
404 let germs = self.generate_germs();
410 let fiducials = self.generate_fiducials();
411
412 let mut data = HashMap::new();
414
415 for prep_fiducial in &fiducials {
416 for germ in &germs {
417 for meas_fiducial in &fiducials {
418 for power in 1..=self.max_length {
420 let mut sequence = Vec::new();
421
422 sequence.extend_from_slice(prep_fiducial);
424
425 for _ in 0..power {
427 sequence.extend_from_slice(germ);
428 }
429
430 sequence.extend_from_slice(meas_fiducial);
432
433 let probability = execute_sequence(&sequence);
435 data.insert(sequence.clone(), probability);
436 }
437 }
438 }
439 }
440
441 let model = self.fit_model(&data)?;
443
444 Ok(model)
445 }
446
447 fn generate_germs(&self) -> Vec<Vec<&str>> {
449 vec![vec!["I"], vec!["X"], vec!["Y"], vec!["X", "Y"]]
452 }
453
454 fn generate_fiducials(&self) -> Vec<Vec<&str>> {
456 vec![
458 vec!["I"],
459 vec!["X"],
460 vec!["Y"],
461 vec!["X", "X"], ]
463 }
464
465 fn fit_model(&self, _data: &HashMap<Vec<&str>, f64>) -> QuantRS2Result<GateSetModel> {
467 Ok(GateSetModel {
471 num_qubits: self.num_qubits,
472 gate_errors: HashMap::new(),
473 spam_errors: vec![],
474 })
475 }
476}
477
478#[derive(Debug, Clone)]
480pub struct GateSetModel {
481 pub num_qubits: usize,
483 pub gate_errors: HashMap<String, Array2<Complex64>>,
485 pub spam_errors: Vec<f64>,
487}
488
489#[cfg(test)]
490mod tests {
491 use super::*;
492
493 #[test]
494 fn test_quantum_volume_result() {
495 let mut result = QuantumVolumeResult {
496 quantum_volume: 16,
497 success_rates: HashMap::new(),
498 max_qubits_tested: 5,
499 };
500
501 result.success_rates.insert(1, 0.95);
502 result.success_rates.insert(2, 0.85);
503 result.success_rates.insert(3, 0.75);
504 result.success_rates.insert(4, 0.70);
505
506 assert_eq!(result.num_qubits_achieved(), 4);
507 assert!(result.is_qv_achieved(1));
508 assert!(result.is_qv_achieved(2));
509 assert!(result.is_qv_achieved(3));
510 assert!(result.is_qv_achieved(4));
511
512 println!("Quantum Volume: {}", result.quantum_volume);
513 }
514
515 #[test]
516 fn test_pauli_basis_generation() {
517 let basis = QuantumProcessTomography::generate_pauli_basis(1);
518 assert_eq!(basis.len(), 4);
519 assert!(basis.contains(&"I".to_string()));
520 assert!(basis.contains(&"X".to_string()));
521 assert!(basis.contains(&"Y".to_string()));
522 assert!(basis.contains(&"Z".to_string()));
523
524 let basis_2q = QuantumProcessTomography::generate_pauli_basis(2);
525 assert_eq!(basis_2q.len(), 16);
526 }
527
528 #[test]
529 fn test_process_matrix() {
530 let qpt = QuantumProcessTomography::new(1);
531
532 let mock_process = |_prep: &str, meas: &str| {
534 if meas == "I" {
535 Complex64::new(1.0, 0.0)
536 } else {
537 Complex64::new(0.0, 0.0)
538 }
539 };
540
541 let result = qpt
542 .run(mock_process)
543 .expect("QPT run should succeed with mock process");
544
545 assert_eq!(result.num_qubits, 1);
546 assert!(result.is_trace_preserving(1e-6));
547 println!("Process matrix shape: {:?}", result.chi_matrix.dim());
548 }
549
550 #[test]
551 fn test_process_fidelity() {
552 let dim = 4;
553 let identity = Array2::eye(dim);
554 let noisy = &identity * Complex64::new(0.95, 0.0);
555
556 let fidelity = QuantumProcessTomography::process_fidelity(&identity, &noisy);
557
558 println!("Process fidelity: {}", fidelity);
561
562 assert!(fidelity > 0.0 && fidelity <= dim as f64);
564 }
565
566 #[test]
567 fn test_gst_initialization() {
568 let gate_set = vec!["I".to_string(), "X".to_string(), "H".to_string()];
569 let gst = GateSetTomography::new(1, gate_set, 10);
570
571 assert_eq!(gst.num_qubits, 1);
572 assert_eq!(gst.max_length, 10);
573
574 let germs = gst.generate_germs();
575 assert!(!germs.is_empty());
576
577 let fiducials = gst.generate_fiducials();
578 assert!(!fiducials.is_empty());
579 }
580}