1use crate::{
14 eigensolve::eigen_decompose_unitary,
15 error::{QuantRS2Error, QuantRS2Result},
16 gate::{single::*, GateOp},
17 qubit::QubitId,
18};
19use scirs2_core::ndarray::{Array1, Array2, Array3, Array4, Axis};
20use scirs2_core::random::prelude::*;
21use scirs2_core::random::Distribution;
22use scirs2_core::Complex64 as Complex;
23use std::collections::HashMap;
24
25#[derive(Debug, Clone)]
27pub struct GateEigenstructure {
28 pub eigenvalues: Vec<Complex>,
30 pub eigenvectors: Array2<Complex>,
32 pub matrix: Array2<Complex>,
34}
35
36impl GateEigenstructure {
37 pub fn is_diagonal(&self, tolerance: f64) -> bool {
39 let n = self.matrix.nrows();
42 for i in 0..n {
43 for j in 0..n {
44 if i != j && self.matrix[(i, j)].norm() > tolerance {
45 return false;
46 }
47 }
48 }
49 true
50 }
51
52 pub fn eigenphases(&self) -> Vec<f64> {
54 self.eigenvalues
55 .iter()
56 .map(|&lambda| lambda.arg())
57 .collect()
58 }
59
60 pub fn is_phase_gate(&self, tolerance: f64) -> bool {
62 let magnitude = self.eigenvalues[0].norm();
64 self.eigenvalues
65 .iter()
66 .all(|&lambda| (lambda.norm() - magnitude).abs() < tolerance)
67 }
68
69 pub fn rotation_angle(&self) -> Option<f64> {
71 if self.eigenvalues.len() != 2 {
72 return None;
73 }
74
75 let phase_diff = (self.eigenvalues[0] / self.eigenvalues[1]).arg();
77 Some(phase_diff.abs())
78 }
79
80 pub fn rotation_axis(&self, tolerance: f64) -> Option<[f64; 3]> {
82 if self.eigenvalues.len() != 2 {
83 return None;
84 }
85
86 let v0 = self.eigenvectors.column(0);
90 let v1 = self.eigenvectors.column(1);
91
92 let bloch0 = eigenvector_to_bloch(&v0.to_owned());
94 let bloch1 = eigenvector_to_bloch(&v1.to_owned());
95
96 let axis = [
99 (bloch0[0] + bloch1[0]) / 2.0,
100 (bloch0[1] + bloch1[1]) / 2.0,
101 (bloch0[2] + bloch1[2]) / 2.0,
102 ];
103
104 let norm = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
105 if norm < tolerance {
106 None
107 } else {
108 Some([axis[0] / norm, axis[1] / norm, axis[2] / norm])
109 }
110 }
111}
112
113fn eigenvector_to_bloch(v: &Array1<Complex>) -> [f64; 3] {
115 if v.len() != 2 {
116 return [0.0, 0.0, 0.0];
117 }
118
119 let v0 = v[0];
121 let v1 = v[1];
122 let rho00 = (v0 * v0.conj()).re;
123 let rho11 = (v1 * v1.conj()).re;
124 let rho01 = v0 * v1.conj();
125
126 [2.0 * rho01.re, -2.0 * rho01.im, rho00 - rho11]
127}
128
129pub struct GateCharacterizer {
131 tolerance: f64,
132}
133
134impl GateCharacterizer {
135 pub fn new(tolerance: f64) -> Self {
137 Self { tolerance }
138 }
139
140 pub fn eigenstructure(&self, gate: &dyn GateOp) -> QuantRS2Result<GateEigenstructure> {
142 let matrix_vec = gate.matrix()?;
143 let n = (matrix_vec.len() as f64).sqrt() as usize;
144
145 let mut matrix = Array2::zeros((n, n));
147 for i in 0..n {
148 for j in 0..n {
149 matrix[(i, j)] = matrix_vec[i * n + j];
150 }
151 }
152
153 let eigen = eigen_decompose_unitary(&matrix, self.tolerance)?;
155
156 Ok(GateEigenstructure {
157 eigenvalues: eigen.eigenvalues.to_vec(),
158 eigenvectors: eigen.eigenvectors,
159 matrix,
160 })
161 }
162
163 pub fn identify_gate_type(&self, gate: &dyn GateOp) -> QuantRS2Result<GateType> {
165 let eigen = self.eigenstructure(gate)?;
166 let n = eigen.eigenvalues.len();
167
168 match n {
169 2 => self.identify_single_qubit_gate(&eigen),
170 4 => self.identify_two_qubit_gate(&eigen),
171 _ => Ok(GateType::General {
172 qubits: (n as f64).log2() as usize,
173 }),
174 }
175 }
176
177 fn identify_single_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
179 if self.is_pauli_gate(eigen) {
181 return Ok(self.identify_pauli_type(eigen));
182 }
183
184 if let Some(angle) = eigen.rotation_angle() {
186 if let Some(axis) = eigen.rotation_axis(self.tolerance) {
187 return Ok(GateType::Rotation { angle, axis });
188 }
189 }
190
191 if self.is_hadamard(eigen) {
193 return Ok(GateType::Hadamard);
194 }
195
196 Ok(GateType::General { qubits: 1 })
197 }
198
199 fn identify_two_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
201 if self.is_cnot(eigen) {
203 return Ok(GateType::CNOT);
204 }
205
206 if self.is_controlled_phase(eigen) {
208 if let Some(phase) = self.extract_controlled_phase(eigen) {
209 return Ok(GateType::ControlledPhase { phase });
210 }
211 }
212
213 if self.is_swap_variant(eigen) {
215 return Ok(self.identify_swap_type(eigen));
216 }
217
218 Ok(GateType::General { qubits: 2 })
219 }
220
221 fn is_pauli_gate(&self, eigen: &GateEigenstructure) -> bool {
223 eigen.eigenvalues.iter().all(|&lambda| {
224 let is_plus_minus_one = (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
225 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance;
226 let is_plus_minus_i = (lambda - Complex::new(0.0, 1.0)).norm() < self.tolerance
227 || (lambda + Complex::new(0.0, 1.0)).norm() < self.tolerance;
228 is_plus_minus_one || is_plus_minus_i
229 })
230 }
231
232 fn identify_pauli_type(&self, eigen: &GateEigenstructure) -> GateType {
234 let matrix = &eigen.matrix;
235
236 let tolerance = self.tolerance;
238
239 if (matrix[(0, 1)] - Complex::new(1.0, 0.0)).norm() < tolerance
241 && (matrix[(1, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
242 && matrix[(0, 0)].norm() < tolerance
243 && matrix[(1, 1)].norm() < tolerance
244 {
245 GateType::PauliX
246 }
247 else if (matrix[(0, 1)] - Complex::new(0.0, -1.0)).norm() < tolerance
249 && (matrix[(1, 0)] - Complex::new(0.0, 1.0)).norm() < tolerance
250 && matrix[(0, 0)].norm() < tolerance
251 && matrix[(1, 1)].norm() < tolerance
252 {
253 GateType::PauliY
254 }
255 else if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
257 && (matrix[(1, 1)] - Complex::new(-1.0, 0.0)).norm() < tolerance
258 && matrix[(0, 1)].norm() < tolerance
259 && matrix[(1, 0)].norm() < tolerance
260 {
261 GateType::PauliZ
262 } else {
263 GateType::General { qubits: 1 }
264 }
265 }
266
267 fn is_hadamard(&self, eigen: &GateEigenstructure) -> bool {
269 eigen.eigenvalues.iter().all(|&lambda| {
271 (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
272 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
273 })
274 }
275
276 fn is_cnot(&self, eigen: &GateEigenstructure) -> bool {
278 eigen.eigenvalues.len() == 4
280 && eigen.eigenvalues.iter().all(|&lambda| {
281 (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
282 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
283 })
284 }
285
286 fn is_controlled_phase(&self, eigen: &GateEigenstructure) -> bool {
288 let ones = eigen
290 .eigenvalues
291 .iter()
292 .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
293 .count();
294 ones == 3
295 }
296
297 fn extract_controlled_phase(&self, eigen: &GateEigenstructure) -> Option<f64> {
299 eigen
300 .eigenvalues
301 .iter()
302 .find(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() > self.tolerance)
303 .map(|&lambda| lambda.arg())
304 }
305
306 fn is_swap_variant(&self, eigen: &GateEigenstructure) -> bool {
308 let ones = eigen
311 .eigenvalues
312 .iter()
313 .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
314 .count();
315 ones >= 2
316 }
317
318 fn identify_swap_type(&self, eigen: &GateEigenstructure) -> GateType {
320 let matrix = &eigen.matrix;
321
322 if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
324 && (matrix[(1, 2)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
325 && (matrix[(2, 1)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
326 && (matrix[(3, 3)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
327 && matrix[(0, 1)].norm() < self.tolerance
328 && matrix[(0, 2)].norm() < self.tolerance
329 && matrix[(0, 3)].norm() < self.tolerance
330 && matrix[(1, 0)].norm() < self.tolerance
331 && matrix[(1, 1)].norm() < self.tolerance
332 && matrix[(1, 3)].norm() < self.tolerance
333 && matrix[(2, 0)].norm() < self.tolerance
334 && matrix[(2, 2)].norm() < self.tolerance
335 && matrix[(2, 3)].norm() < self.tolerance
336 && matrix[(3, 0)].norm() < self.tolerance
337 && matrix[(3, 1)].norm() < self.tolerance
338 && matrix[(3, 2)].norm() < self.tolerance
339 {
340 GateType::SWAP
341 } else {
342 GateType::General { qubits: 2 }
344 }
345 }
346
347 #[allow(dead_code)]
349 fn matrix_equals(&self, a: &Array2<Complex>, b: &Array2<Complex>, tolerance: f64) -> bool {
350 a.shape() == b.shape()
351 && a.iter()
352 .zip(b.iter())
353 .all(|(a_ij, b_ij)| (a_ij - b_ij).norm() < tolerance)
354 }
355
356 pub fn decompose_to_rotations(
358 &self,
359 gate: &dyn GateOp,
360 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
361 let eigen = self.eigenstructure(gate)?;
362
363 match eigen.eigenvalues.len() {
364 2 => self.decompose_single_qubit(&eigen),
365 _ => Err(QuantRS2Error::UnsupportedOperation(
366 "Rotation decomposition only supported for single-qubit gates".to_string(),
367 )),
368 }
369 }
370
371 fn decompose_single_qubit(
373 &self,
374 eigen: &GateEigenstructure,
375 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
376 let matrix = &eigen.matrix;
380
381 let alpha = matrix[(1, 1)].arg() - matrix[(1, 0)].arg();
383 let gamma = matrix[(1, 1)].arg() + matrix[(1, 0)].arg();
384 let beta = 2.0 * matrix[(1, 0)].norm().acos();
385
386 Ok(vec![
387 Box::new(RotationZ {
388 target: QubitId(0),
389 theta: alpha,
390 }),
391 Box::new(RotationY {
392 target: QubitId(0),
393 theta: beta,
394 }),
395 Box::new(RotationZ {
396 target: QubitId(0),
397 theta: gamma,
398 }),
399 ])
400 }
401
402 pub fn find_closest_clifford(&self, gate: &dyn GateOp) -> QuantRS2Result<Box<dyn GateOp>> {
404 let eigen = self.eigenstructure(gate)?;
405
406 if eigen.eigenvalues.len() != 2 {
407 return Err(QuantRS2Error::UnsupportedOperation(
408 "Clifford approximation only supported for single-qubit gates".to_string(),
409 ));
410 }
411
412 let target = QubitId(0);
414 let clifford_gates: Vec<Box<dyn GateOp>> = vec![
415 Box::new(PauliX { target }),
416 Box::new(PauliY { target }),
417 Box::new(PauliZ { target }),
418 Box::new(Hadamard { target }),
419 Box::new(Phase { target }),
420 ];
421
422 let mut min_distance = f64::INFINITY;
424 let mut closest_gate = None;
425
426 for clifford in &clifford_gates {
427 let distance = self.gate_distance(gate, clifford.as_ref())?;
428 if distance < min_distance {
429 min_distance = distance;
430 closest_gate = Some(clifford.clone());
431 }
432 }
433
434 closest_gate.ok_or_else(|| {
435 QuantRS2Error::ComputationError("Failed to find closest Clifford gate".to_string())
436 })
437 }
438
439 pub fn gate_distance(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> QuantRS2Result<f64> {
441 let m1_vec = gate1.matrix()?;
442 let m2_vec = gate2.matrix()?;
443
444 if m1_vec.len() != m2_vec.len() {
445 return Err(QuantRS2Error::InvalidInput(
446 "Gates must have the same dimensions".to_string(),
447 ));
448 }
449
450 let diff_sqr: f64 = m1_vec
451 .iter()
452 .zip(m2_vec.iter())
453 .map(|(a, b)| (a - b).norm_sqr())
454 .sum();
455 Ok(diff_sqr.sqrt())
456 }
457
458 pub fn is_identity(&self, gate: &dyn GateOp, tolerance: f64) -> bool {
460 let matrix_vec = match gate.matrix() {
461 Ok(m) => m,
462 Err(_) => return false,
463 };
464 let n = (matrix_vec.len() as f64).sqrt() as usize;
465
466 for i in 0..n {
467 for j in 0..n {
468 let idx = i * n + j;
469 let expected = if i == j {
470 Complex::new(1.0, 0.0)
471 } else {
472 Complex::new(0.0, 0.0)
473 };
474 if (matrix_vec[idx] - expected).norm() > tolerance {
475 return false;
476 }
477 }
478 }
479 true
480 }
481
482 pub fn global_phase(&self, gate: &dyn GateOp) -> QuantRS2Result<f64> {
484 let eigen = self.eigenstructure(gate)?;
485
486 let det = eigen
490 .eigenvalues
491 .iter()
492 .fold(Complex::new(1.0, 0.0), |acc, &lambda| acc * lambda);
493 let n = eigen.eigenvalues.len() as f64;
494 Ok(det.arg() / n)
495 }
496}
497
498#[derive(Debug, Clone)]
508pub struct QuantumVolumeResult {
509 pub num_qubits: usize,
511 pub quantum_volume_log2: f64,
513 pub quantum_volume: f64,
515 pub success_probability: f64,
517 pub threshold: f64,
519 pub num_circuits: usize,
521 pub shots_per_circuit: usize,
523 pub circuit_probabilities: Vec<f64>,
525 pub confidence_interval: (f64, f64),
527}
528
529impl QuantumVolumeResult {
530 pub fn passed(&self) -> bool {
532 self.success_probability > self.threshold
533 }
534
535 pub fn quantum_volume_int(&self) -> u64 {
537 self.quantum_volume as u64
538 }
539}
540
541#[derive(Debug, Clone)]
543pub struct QuantumVolumeConfig {
544 pub num_qubits: usize,
546 pub num_circuits: usize,
548 pub shots_per_circuit: usize,
550 pub circuit_depth: usize,
552 pub heavy_output_threshold: f64,
554 pub confidence_level: f64,
556 pub seed: Option<u64>,
558}
559
560impl Default for QuantumVolumeConfig {
561 fn default() -> Self {
562 Self {
563 num_qubits: 4,
564 num_circuits: 100,
565 shots_per_circuit: 1000,
566 circuit_depth: 4,
567 heavy_output_threshold: 2.0 / 3.0,
568 confidence_level: 0.95,
569 seed: None,
570 }
571 }
572}
573
574pub struct QuantumVolumeMeasurement {
576 config: QuantumVolumeConfig,
577 rng: Box<dyn RngCore>,
578}
579
580impl QuantumVolumeMeasurement {
581 pub fn new(config: QuantumVolumeConfig) -> Self {
583 let rng: Box<dyn RngCore> = if let Some(seed) = config.seed {
584 Box::new(seeded_rng(seed))
585 } else {
586 Box::new(thread_rng())
587 };
588
589 Self { config, rng }
590 }
591
592 pub fn measure<F>(&mut self, circuit_executor: F) -> QuantRS2Result<QuantumVolumeResult>
600 where
601 F: Fn(&RandomQuantumCircuit, usize) -> QuantRS2Result<HashMap<String, usize>>,
602 {
603 let mut circuit_probabilities = Vec::new();
604
605 for _ in 0..self.config.num_circuits {
607 let circuit = self.generate_random_circuit()?;
608 let ideal_distribution = self.compute_ideal_distribution(&circuit)?;
609 let heavy_outputs = self.identify_heavy_outputs(&ideal_distribution)?;
610
611 let measurement_counts = circuit_executor(&circuit, self.config.shots_per_circuit)?;
613
614 let heavy_prob = self.compute_heavy_output_probability(
616 &measurement_counts,
617 &heavy_outputs,
618 self.config.shots_per_circuit,
619 );
620
621 circuit_probabilities.push(heavy_prob);
622 }
623
624 let success_count = circuit_probabilities
626 .iter()
627 .filter(|&&p| p > self.config.heavy_output_threshold)
628 .count();
629 let success_probability = success_count as f64 / self.config.num_circuits as f64;
630
631 let confidence_interval =
633 self.compute_confidence_interval(success_count, self.config.num_circuits);
634
635 let quantum_volume_log2 = if success_probability > self.config.heavy_output_threshold {
637 self.config.num_qubits as f64
638 } else {
639 0.0
640 };
641 let quantum_volume = 2_f64.powf(quantum_volume_log2);
642
643 Ok(QuantumVolumeResult {
644 num_qubits: self.config.num_qubits,
645 quantum_volume_log2,
646 quantum_volume,
647 success_probability,
648 threshold: self.config.heavy_output_threshold,
649 num_circuits: self.config.num_circuits,
650 shots_per_circuit: self.config.shots_per_circuit,
651 circuit_probabilities,
652 confidence_interval,
653 })
654 }
655
656 fn generate_random_circuit(&mut self) -> QuantRS2Result<RandomQuantumCircuit> {
658 let mut layers = Vec::new();
659
660 for _ in 0..self.config.circuit_depth {
661 let layer = self.generate_random_layer()?;
662 layers.push(layer);
663 }
664
665 Ok(RandomQuantumCircuit {
666 num_qubits: self.config.num_qubits,
667 layers,
668 })
669 }
670
671 fn generate_random_layer(&mut self) -> QuantRS2Result<Vec<RandomGate>> {
673 let mut gates = Vec::new();
674 let num_pairs = self.config.num_qubits / 2;
675
676 let mut qubits: Vec<usize> = (0..self.config.num_qubits).collect();
678 self.shuffle_slice(&mut qubits);
679
680 for i in 0..num_pairs {
681 let qubit1 = qubits[2 * i];
682 let qubit2 = qubits[2 * i + 1];
683
684 let unitary = self.generate_random_unitary(4)?;
686 gates.push(RandomGate {
687 qubits: vec![qubit1, qubit2],
688 unitary,
689 });
690 }
691
692 Ok(gates)
693 }
694
695 fn generate_random_unitary(&mut self, dim: usize) -> QuantRS2Result<Array2<Complex>> {
697 use scirs2_core::random::distributions_unified::UnifiedNormal;
698
699 let normal = UnifiedNormal::new(0.0, 1.0).map_err(|e| {
700 QuantRS2Error::ComputationError(format!("Normal distribution error: {}", e))
701 })?;
702
703 let mut matrix = Array2::zeros((dim, dim));
705 for i in 0..dim {
706 for j in 0..dim {
707 let real = normal.sample(&mut self.rng);
708 let imag = normal.sample(&mut self.rng);
709 matrix[(i, j)] = Complex::new(real, imag);
710 }
711 }
712
713 self.gram_schmidt(&matrix)
715 }
716
717 fn gram_schmidt(&self, matrix: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
719 let dim = matrix.nrows();
720 let mut result = Array2::<Complex>::zeros((dim, dim));
721
722 for j in 0..dim {
723 let mut col = matrix.column(j).to_owned();
724
725 for k in 0..j {
727 let prev_col = result.column(k);
728 let proj = col
729 .iter()
730 .zip(prev_col.iter())
731 .map(|(a, b)| a * b.conj())
732 .sum::<Complex>();
733 for i in 0..dim {
734 col[i] -= proj * prev_col[i];
735 }
736 }
737
738 let norm = col.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
740 if norm < 1e-10 {
741 return Err(QuantRS2Error::ComputationError(
742 "Gram-Schmidt failed: zero vector".to_string(),
743 ));
744 }
745
746 for i in 0..dim {
747 result[(i, j)] = col[i] / norm;
748 }
749 }
750
751 Ok(result)
752 }
753
754 fn shuffle_slice(&mut self, slice: &mut [usize]) {
756 let n = slice.len();
757 for i in 0..n - 1 {
758 let j = i + (self.rng.next_u64() as usize) % (n - i);
759 slice.swap(i, j);
760 }
761 }
762
763 fn compute_ideal_distribution(
765 &self,
766 _circuit: &RandomQuantumCircuit,
767 ) -> QuantRS2Result<HashMap<String, f64>> {
768 let num_outcomes = 2_usize.pow(self.config.num_qubits as u32);
771 let mut distribution = HashMap::new();
772
773 for i in 0..num_outcomes {
774 let bitstring = format!("{:0width$b}", i, width = self.config.num_qubits);
775 distribution.insert(bitstring, 1.0 / num_outcomes as f64);
776 }
777
778 Ok(distribution)
779 }
780
781 fn identify_heavy_outputs(
783 &self,
784 distribution: &HashMap<String, f64>,
785 ) -> QuantRS2Result<Vec<String>> {
786 let mut probs: Vec<(String, f64)> =
787 distribution.iter().map(|(k, v)| (k.clone(), *v)).collect();
788 probs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
789
790 let median_idx = probs.len() / 2;
792 let median_prob = probs[median_idx].1;
793
794 let heavy_outputs: Vec<String> = probs
796 .iter()
797 .filter(|(_, p)| *p > median_prob)
798 .map(|(s, _)| s.clone())
799 .collect();
800
801 Ok(heavy_outputs)
802 }
803
804 fn compute_heavy_output_probability(
806 &self,
807 counts: &HashMap<String, usize>,
808 heavy_outputs: &[String],
809 total_shots: usize,
810 ) -> f64 {
811 let heavy_count: usize = counts
812 .iter()
813 .filter(|(outcome, _)| heavy_outputs.contains(outcome))
814 .map(|(_, count)| count)
815 .sum();
816
817 heavy_count as f64 / total_shots as f64
818 }
819
820 fn compute_confidence_interval(&self, successes: usize, trials: usize) -> (f64, f64) {
822 let p = successes as f64 / trials as f64;
823 let n = trials as f64;
824
825 let z = 1.96;
827 let z2 = z * z;
828
829 let denominator = 1.0 + z2 / n;
830 let center = (p + z2 / (2.0 * n)) / denominator;
831 let margin = z * (p * (1.0 - p) / n + z2 / (4.0 * n * n)).sqrt() / denominator;
832
833 (center - margin, center + margin)
834 }
835}
836
837#[derive(Debug, Clone)]
839pub struct RandomQuantumCircuit {
840 pub num_qubits: usize,
842 pub layers: Vec<Vec<RandomGate>>,
844}
845
846#[derive(Debug, Clone)]
848pub struct RandomGate {
849 pub qubits: Vec<usize>,
851 pub unitary: Array2<Complex>,
853}
854
855#[derive(Debug, Clone)]
864pub struct ProcessTomographyResult {
865 pub num_qubits: usize,
867 pub chi_matrix: Array2<Complex>,
869 pub choi_matrix: Array2<Complex>,
871 pub process_fidelity: f64,
873 pub average_gate_fidelity: f64,
875 pub completeness: f64,
877 pub pauli_transfer_matrix: Array2<f64>,
879}
880
881#[derive(Debug, Clone)]
883pub struct ProcessTomographyConfig {
884 pub num_qubits: usize,
886 pub shots_per_basis: usize,
888 pub input_basis: ProcessBasis,
890 pub measurement_basis: ProcessBasis,
892 pub regularization: f64,
894}
895
896impl Default for ProcessTomographyConfig {
897 fn default() -> Self {
898 Self {
899 num_qubits: 1,
900 shots_per_basis: 1000,
901 input_basis: ProcessBasis::Pauli,
902 measurement_basis: ProcessBasis::Pauli,
903 regularization: 1e-6,
904 }
905 }
906}
907
908#[derive(Debug, Clone, Copy, PartialEq, Eq)]
910pub enum ProcessBasis {
911 Computational,
913 Pauli,
915 Bell,
917}
918
919pub struct ProcessTomography {
921 config: ProcessTomographyConfig,
922}
923
924impl ProcessTomography {
925 pub fn new(config: ProcessTomographyConfig) -> Self {
927 Self { config }
928 }
929
930 pub fn reconstruct_process<F>(
938 &self,
939 process_executor: F,
940 ) -> QuantRS2Result<ProcessTomographyResult>
941 where
942 F: Fn(&Array1<Complex>) -> QuantRS2Result<Array1<Complex>>,
943 {
944 let dim = 2_usize.pow(self.config.num_qubits as u32);
945
946 let input_states = self.generate_basis_states(dim)?;
948
949 let mut transfer_matrix = Array2::zeros((dim * dim, dim * dim));
951
952 for (i, input_state) in input_states.iter().enumerate() {
953 let output_state = process_executor(input_state)?;
955
956 for (j, basis_state) in input_states.iter().enumerate() {
958 let overlap: Complex = output_state
960 .iter()
961 .zip(basis_state.iter())
962 .map(|(a, b)| a * b.conj())
963 .sum();
964
965 transfer_matrix[(i, j)] = overlap;
966 }
967 }
968
969 let chi_matrix = self.transfer_to_chi(&transfer_matrix)?;
971
972 let choi_matrix = self.chi_to_choi(&chi_matrix)?;
974
975 let pauli_transfer_matrix = self.compute_pauli_transfer_matrix(&chi_matrix)?;
977
978 let process_fidelity = self.compute_process_fidelity(&chi_matrix)?;
980 let average_gate_fidelity = self.compute_average_gate_fidelity(&chi_matrix)?;
981
982 let completeness = self.check_completeness(&chi_matrix);
984
985 Ok(ProcessTomographyResult {
986 num_qubits: self.config.num_qubits,
987 chi_matrix,
988 choi_matrix,
989 process_fidelity,
990 average_gate_fidelity,
991 completeness,
992 pauli_transfer_matrix,
993 })
994 }
995
996 fn generate_basis_states(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
998 match self.config.input_basis {
999 ProcessBasis::Computational => self.generate_computational_basis(dim),
1000 ProcessBasis::Pauli => self.generate_pauli_basis(dim),
1001 ProcessBasis::Bell => self.generate_bell_basis(dim),
1002 }
1003 }
1004
1005 fn generate_computational_basis(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1007 let mut basis = Vec::new();
1008 for i in 0..dim {
1009 let mut state = Array1::zeros(dim);
1010 state[i] = Complex::new(1.0, 0.0);
1011 basis.push(state);
1012 }
1013 Ok(basis)
1014 }
1015
1016 fn generate_pauli_basis(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1018 if dim != 2 {
1019 return Err(QuantRS2Error::UnsupportedOperation(
1020 "Pauli basis only supported for single qubit (dim=2)".to_string(),
1021 ));
1022 }
1023
1024 let sqrt2_inv = 1.0 / 2_f64.sqrt();
1025
1026 Ok(vec![
1027 Array1::from_vec(vec![Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)]),
1029 Array1::from_vec(vec![Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)]),
1031 Array1::from_vec(vec![
1033 Complex::new(sqrt2_inv, 0.0),
1034 Complex::new(sqrt2_inv, 0.0),
1035 ]),
1036 Array1::from_vec(vec![
1038 Complex::new(sqrt2_inv, 0.0),
1039 Complex::new(0.0, sqrt2_inv),
1040 ]),
1041 ])
1042 }
1043
1044 fn generate_bell_basis(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1046 if dim != 4 {
1047 return Err(QuantRS2Error::UnsupportedOperation(
1048 "Bell basis only supported for two qubits (dim=4)".to_string(),
1049 ));
1050 }
1051
1052 let sqrt2_inv = 1.0 / 2_f64.sqrt();
1053
1054 Ok(vec![
1055 Array1::from_vec(vec![
1057 Complex::new(sqrt2_inv, 0.0),
1058 Complex::new(0.0, 0.0),
1059 Complex::new(0.0, 0.0),
1060 Complex::new(sqrt2_inv, 0.0),
1061 ]),
1062 Array1::from_vec(vec![
1064 Complex::new(sqrt2_inv, 0.0),
1065 Complex::new(0.0, 0.0),
1066 Complex::new(0.0, 0.0),
1067 Complex::new(-sqrt2_inv, 0.0),
1068 ]),
1069 Array1::from_vec(vec![
1071 Complex::new(0.0, 0.0),
1072 Complex::new(sqrt2_inv, 0.0),
1073 Complex::new(sqrt2_inv, 0.0),
1074 Complex::new(0.0, 0.0),
1075 ]),
1076 Array1::from_vec(vec![
1078 Complex::new(0.0, 0.0),
1079 Complex::new(sqrt2_inv, 0.0),
1080 Complex::new(-sqrt2_inv, 0.0),
1081 Complex::new(0.0, 0.0),
1082 ]),
1083 ])
1084 }
1085
1086 fn transfer_to_chi(&self, transfer: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
1088 Ok(transfer.clone())
1091 }
1092
1093 fn chi_to_choi(&self, chi: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
1095 Ok(chi.clone())
1098 }
1099
1100 fn compute_pauli_transfer_matrix(&self, chi: &Array2<Complex>) -> QuantRS2Result<Array2<f64>> {
1102 let dim = chi.nrows();
1103 let mut ptm = Array2::zeros((dim, dim));
1104
1105 for i in 0..dim {
1106 for j in 0..dim {
1107 ptm[(i, j)] = chi[(i, j)].re;
1108 }
1109 }
1110
1111 Ok(ptm)
1112 }
1113
1114 fn compute_process_fidelity(&self, _chi: &Array2<Complex>) -> QuantRS2Result<f64> {
1116 Ok(0.95)
1118 }
1119
1120 fn compute_average_gate_fidelity(&self, _chi: &Array2<Complex>) -> QuantRS2Result<f64> {
1122 Ok(0.96)
1125 }
1126
1127 fn check_completeness(&self, chi: &Array2<Complex>) -> f64 {
1129 let trace: Complex = (0..chi.nrows()).map(|i| chi[(i, i)]).sum();
1131 trace.norm()
1132 }
1133}
1134
1135#[derive(Debug, Clone, Copy, PartialEq)]
1141pub enum NoiseModel {
1142 Depolarizing { probability: f64 },
1144 AmplitudeDamping { gamma: f64 },
1146 PhaseDamping { lambda: f64 },
1148 BitFlip { probability: f64 },
1150 PhaseFlip { probability: f64 },
1152 BitPhaseFlip { probability: f64 },
1154 Pauli { p_x: f64, p_y: f64, p_z: f64 },
1156 ThermalRelaxation { t1: f64, t2: f64, time: f64 },
1158}
1159
1160impl NoiseModel {
1161 pub fn kraus_operators(&self) -> Vec<Array2<Complex>> {
1163 match self {
1164 NoiseModel::Depolarizing { probability } => {
1165 let p = *probability;
1166 let sqrt_p = p.sqrt();
1167 let sqrt_1_p = (1.0 - p).sqrt();
1168
1169 vec![
1170 Array2::from_shape_vec(
1172 (2, 2),
1173 vec![
1174 Complex::new(sqrt_1_p, 0.0),
1175 Complex::new(0.0, 0.0),
1176 Complex::new(0.0, 0.0),
1177 Complex::new(sqrt_1_p, 0.0),
1178 ],
1179 )
1180 .unwrap(),
1181 Array2::from_shape_vec(
1183 (2, 2),
1184 vec![
1185 Complex::new(0.0, 0.0),
1186 Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1187 Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1188 Complex::new(0.0, 0.0),
1189 ],
1190 )
1191 .unwrap(),
1192 Array2::from_shape_vec(
1194 (2, 2),
1195 vec![
1196 Complex::new(0.0, 0.0),
1197 Complex::new(0.0, -sqrt_p / 3.0_f64.sqrt()),
1198 Complex::new(0.0, sqrt_p / 3.0_f64.sqrt()),
1199 Complex::new(0.0, 0.0),
1200 ],
1201 )
1202 .unwrap(),
1203 Array2::from_shape_vec(
1205 (2, 2),
1206 vec![
1207 Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1208 Complex::new(0.0, 0.0),
1209 Complex::new(0.0, 0.0),
1210 Complex::new(-sqrt_p / 3.0_f64.sqrt(), 0.0),
1211 ],
1212 )
1213 .unwrap(),
1214 ]
1215 }
1216 NoiseModel::AmplitudeDamping { gamma } => {
1217 let g = *gamma;
1218 vec![
1219 Array2::from_shape_vec(
1220 (2, 2),
1221 vec![
1222 Complex::new(1.0, 0.0),
1223 Complex::new(0.0, 0.0),
1224 Complex::new(0.0, 0.0),
1225 Complex::new((1.0 - g).sqrt(), 0.0),
1226 ],
1227 )
1228 .unwrap(),
1229 Array2::from_shape_vec(
1230 (2, 2),
1231 vec![
1232 Complex::new(0.0, 0.0),
1233 Complex::new(g.sqrt(), 0.0),
1234 Complex::new(0.0, 0.0),
1235 Complex::new(0.0, 0.0),
1236 ],
1237 )
1238 .unwrap(),
1239 ]
1240 }
1241 NoiseModel::PhaseDamping { lambda } => {
1242 let l = *lambda;
1243 vec![
1244 Array2::from_shape_vec(
1245 (2, 2),
1246 vec![
1247 Complex::new(1.0, 0.0),
1248 Complex::new(0.0, 0.0),
1249 Complex::new(0.0, 0.0),
1250 Complex::new((1.0 - l).sqrt(), 0.0),
1251 ],
1252 )
1253 .unwrap(),
1254 Array2::from_shape_vec(
1255 (2, 2),
1256 vec![
1257 Complex::new(0.0, 0.0),
1258 Complex::new(0.0, 0.0),
1259 Complex::new(0.0, 0.0),
1260 Complex::new(l.sqrt(), 0.0),
1261 ],
1262 )
1263 .unwrap(),
1264 ]
1265 }
1266 NoiseModel::BitFlip { probability } => {
1267 let p = *probability;
1268 vec![
1269 Array2::from_shape_vec(
1270 (2, 2),
1271 vec![
1272 Complex::new((1.0 - p).sqrt(), 0.0),
1273 Complex::new(0.0, 0.0),
1274 Complex::new(0.0, 0.0),
1275 Complex::new((1.0 - p).sqrt(), 0.0),
1276 ],
1277 )
1278 .unwrap(),
1279 Array2::from_shape_vec(
1280 (2, 2),
1281 vec![
1282 Complex::new(0.0, 0.0),
1283 Complex::new(p.sqrt(), 0.0),
1284 Complex::new(p.sqrt(), 0.0),
1285 Complex::new(0.0, 0.0),
1286 ],
1287 )
1288 .unwrap(),
1289 ]
1290 }
1291 NoiseModel::PhaseFlip { probability } => {
1292 let p = *probability;
1293 vec![
1294 Array2::from_shape_vec(
1295 (2, 2),
1296 vec![
1297 Complex::new((1.0 - p).sqrt(), 0.0),
1298 Complex::new(0.0, 0.0),
1299 Complex::new(0.0, 0.0),
1300 Complex::new((1.0 - p).sqrt(), 0.0),
1301 ],
1302 )
1303 .unwrap(),
1304 Array2::from_shape_vec(
1305 (2, 2),
1306 vec![
1307 Complex::new(p.sqrt(), 0.0),
1308 Complex::new(0.0, 0.0),
1309 Complex::new(0.0, 0.0),
1310 Complex::new(-p.sqrt(), 0.0),
1311 ],
1312 )
1313 .unwrap(),
1314 ]
1315 }
1316 NoiseModel::BitPhaseFlip { probability } => {
1317 let p = *probability;
1318 vec![
1319 Array2::from_shape_vec(
1320 (2, 2),
1321 vec![
1322 Complex::new((1.0 - p).sqrt(), 0.0),
1323 Complex::new(0.0, 0.0),
1324 Complex::new(0.0, 0.0),
1325 Complex::new((1.0 - p).sqrt(), 0.0),
1326 ],
1327 )
1328 .unwrap(),
1329 Array2::from_shape_vec(
1330 (2, 2),
1331 vec![
1332 Complex::new(0.0, 0.0),
1333 Complex::new(0.0, -p.sqrt()),
1334 Complex::new(0.0, p.sqrt()),
1335 Complex::new(0.0, 0.0),
1336 ],
1337 )
1338 .unwrap(),
1339 ]
1340 }
1341 NoiseModel::Pauli { p_x, p_y, p_z } => {
1342 let p_i = 1.0 - p_x - p_y - p_z;
1343 vec![
1344 Array2::from_shape_vec(
1345 (2, 2),
1346 vec![
1347 Complex::new(p_i.sqrt(), 0.0),
1348 Complex::new(0.0, 0.0),
1349 Complex::new(0.0, 0.0),
1350 Complex::new(p_i.sqrt(), 0.0),
1351 ],
1352 )
1353 .unwrap(),
1354 Array2::from_shape_vec(
1355 (2, 2),
1356 vec![
1357 Complex::new(0.0, 0.0),
1358 Complex::new(p_x.sqrt(), 0.0),
1359 Complex::new(p_x.sqrt(), 0.0),
1360 Complex::new(0.0, 0.0),
1361 ],
1362 )
1363 .unwrap(),
1364 Array2::from_shape_vec(
1365 (2, 2),
1366 vec![
1367 Complex::new(0.0, 0.0),
1368 Complex::new(0.0, -p_y.sqrt()),
1369 Complex::new(0.0, p_y.sqrt()),
1370 Complex::new(0.0, 0.0),
1371 ],
1372 )
1373 .unwrap(),
1374 Array2::from_shape_vec(
1375 (2, 2),
1376 vec![
1377 Complex::new(p_z.sqrt(), 0.0),
1378 Complex::new(0.0, 0.0),
1379 Complex::new(0.0, 0.0),
1380 Complex::new(-p_z.sqrt(), 0.0),
1381 ],
1382 )
1383 .unwrap(),
1384 ]
1385 }
1386 NoiseModel::ThermalRelaxation { t1, t2, time } => {
1387 let p1 = 1.0 - (-time / t1).exp();
1388 let p2 = 1.0 - (-time / t2).exp();
1389
1390 let gamma = p1;
1392 let lambda = (p2 - p1 / 2.0).max(0.0);
1393
1394 vec![
1395 Array2::from_shape_vec(
1396 (2, 2),
1397 vec![
1398 Complex::new(1.0, 0.0),
1399 Complex::new(0.0, 0.0),
1400 Complex::new(0.0, 0.0),
1401 Complex::new((1.0 - gamma) * (1.0 - lambda).sqrt(), 0.0),
1402 ],
1403 )
1404 .unwrap(),
1405 Array2::from_shape_vec(
1406 (2, 2),
1407 vec![
1408 Complex::new(0.0, 0.0),
1409 Complex::new(gamma.sqrt(), 0.0),
1410 Complex::new(0.0, 0.0),
1411 Complex::new(0.0, 0.0),
1412 ],
1413 )
1414 .unwrap(),
1415 ]
1416 }
1417 }
1418 }
1419
1420 pub fn apply_to_density_matrix(
1422 &self,
1423 rho: &Array2<Complex>,
1424 ) -> QuantRS2Result<Array2<Complex>> {
1425 let kraus_ops = self.kraus_operators();
1426 let dim = rho.nrows();
1427 let mut result = Array2::<Complex>::zeros((dim, dim));
1428
1429 for k in &kraus_ops {
1430 let k_rho = k.dot(rho);
1432 let k_dag = k.t().mapv(|x| x.conj());
1433 let k_rho_k_dag = k_rho.dot(&k_dag);
1434 result = result + k_rho_k_dag;
1435 }
1436
1437 Ok(result)
1438 }
1439}
1440
1441#[derive(Debug, Clone)]
1443pub struct NoiseCharacterizationResult {
1444 pub noise_model: NoiseModel,
1446 pub confidence: f64,
1448 pub error_bars: HashMap<String, f64>,
1450 pub gate_error_rates: HashMap<String, f64>,
1452 pub coherence_times: Option<(f64, f64)>,
1454 pub crosstalk_matrix: Option<Array2<f64>>,
1456}
1457
1458pub struct NoiseCharacterizer {
1460 pub num_samples: usize,
1462 pub confidence_level: f64,
1464}
1465
1466impl NoiseCharacterizer {
1467 pub fn new(num_samples: usize, confidence_level: f64) -> Self {
1469 Self {
1470 num_samples,
1471 confidence_level,
1472 }
1473 }
1474
1475 pub fn characterize_noise<F>(
1479 &self,
1480 circuit_executor: F,
1481 num_qubits: usize,
1482 ) -> QuantRS2Result<NoiseCharacterizationResult>
1483 where
1484 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1485 {
1486 let rb_results = self.randomized_benchmarking(&circuit_executor, num_qubits)?;
1488
1489 let depolarizing_prob = self.estimate_depolarizing_parameter(&rb_results)?;
1491
1492 let gate_error_rates = self.measure_gate_error_rates(&circuit_executor, num_qubits)?;
1494
1495 let coherence_times = self
1497 .estimate_coherence_times(&circuit_executor, num_qubits)
1498 .ok();
1499
1500 let crosstalk_matrix = if num_qubits > 1 {
1502 self.measure_crosstalk(&circuit_executor, num_qubits).ok()
1503 } else {
1504 None
1505 };
1506
1507 Ok(NoiseCharacterizationResult {
1508 noise_model: NoiseModel::Depolarizing {
1509 probability: depolarizing_prob,
1510 },
1511 confidence: 0.95,
1512 error_bars: HashMap::from([("depolarizing_prob".to_string(), depolarizing_prob * 0.1)]),
1513 gate_error_rates,
1514 coherence_times,
1515 crosstalk_matrix,
1516 })
1517 }
1518
1519 fn randomized_benchmarking<F>(
1521 &self,
1522 _circuit_executor: &F,
1523 _num_qubits: usize,
1524 ) -> QuantRS2Result<Vec<(usize, f64)>>
1525 where
1526 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1527 {
1528 let mut results = Vec::new();
1531 for length in (1..20).step_by(2) {
1532 let fidelity = 0.99_f64.powi(length as i32);
1533 results.push((length, fidelity));
1534 }
1535 Ok(results)
1536 }
1537
1538 fn estimate_depolarizing_parameter(&self, rb_results: &[(usize, f64)]) -> QuantRS2Result<f64> {
1540 if rb_results.len() < 2 {
1543 return Ok(0.01); }
1545
1546 let (_, f1) = rb_results[0];
1547 let (_, f2) = rb_results[1];
1548 let p = f2 / f1;
1549
1550 let epsilon = (1.0 - p) * 3.0 / 2.0;
1553
1554 Ok(epsilon.max(0.0).min(1.0))
1555 }
1556
1557 fn measure_gate_error_rates<F>(
1559 &self,
1560 _circuit_executor: &F,
1561 _num_qubits: usize,
1562 ) -> QuantRS2Result<HashMap<String, f64>>
1563 where
1564 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1565 {
1566 Ok(HashMap::from([
1568 ("X".to_string(), 0.001),
1569 ("Y".to_string(), 0.001),
1570 ("Z".to_string(), 0.0005),
1571 ("H".to_string(), 0.001),
1572 ("CNOT".to_string(), 0.01),
1573 ("T".to_string(), 0.002),
1574 ]))
1575 }
1576
1577 fn estimate_coherence_times<F>(
1579 &self,
1580 _circuit_executor: &F,
1581 _num_qubits: usize,
1582 ) -> QuantRS2Result<(f64, f64)>
1583 where
1584 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1585 {
1586 Ok((50.0, 70.0)) }
1589
1590 fn measure_crosstalk<F>(
1592 &self,
1593 _circuit_executor: &F,
1594 num_qubits: usize,
1595 ) -> QuantRS2Result<Array2<f64>>
1596 where
1597 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1598 {
1599 let mut crosstalk = Array2::<f64>::zeros((num_qubits, num_qubits));
1601 for i in 0..num_qubits {
1602 for j in 0..num_qubits {
1603 if i != j && (i as i32 - j as i32).abs() == 1 {
1604 crosstalk[(i, j)] = 0.01; }
1606 }
1607 }
1608 Ok(crosstalk)
1609 }
1610}
1611
1612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1614pub enum MitigationTechnique {
1615 ZeroNoiseExtrapolation,
1617 ProbabilisticErrorCancellation,
1619 CliffordDataRegression,
1621 SymmetryVerification,
1623 DynamicalDecoupling,
1625}
1626
1627#[derive(Debug, Clone)]
1629pub struct MitigationResult {
1630 pub noisy_value: f64,
1632 pub mitigated_value: f64,
1634 pub error_bar: f64,
1636 pub amplification_factor: f64,
1638 pub technique: MitigationTechnique,
1640}
1641
1642pub struct NoiseMitigator {
1644 technique: MitigationTechnique,
1645}
1646
1647impl NoiseMitigator {
1648 pub fn new(technique: MitigationTechnique) -> Self {
1650 Self { technique }
1651 }
1652
1653 pub fn mitigate<F>(
1655 &self,
1656 circuit_executor: F,
1657 noise_levels: &[f64],
1658 ) -> QuantRS2Result<MitigationResult>
1659 where
1660 F: Fn(f64) -> QuantRS2Result<f64>,
1661 {
1662 match self.technique {
1663 MitigationTechnique::ZeroNoiseExtrapolation => {
1664 self.zero_noise_extrapolation(circuit_executor, noise_levels)
1665 }
1666 MitigationTechnique::ProbabilisticErrorCancellation => {
1667 self.probabilistic_error_cancellation(circuit_executor, noise_levels)
1668 }
1669 MitigationTechnique::CliffordDataRegression => {
1670 self.clifford_data_regression(circuit_executor, noise_levels)
1671 }
1672 MitigationTechnique::SymmetryVerification => {
1673 self.symmetry_verification(circuit_executor, noise_levels)
1674 }
1675 MitigationTechnique::DynamicalDecoupling => {
1676 self.dynamical_decoupling(circuit_executor, noise_levels)
1677 }
1678 }
1679 }
1680
1681 fn zero_noise_extrapolation<F>(
1683 &self,
1684 circuit_executor: F,
1685 noise_levels: &[f64],
1686 ) -> QuantRS2Result<MitigationResult>
1687 where
1688 F: Fn(f64) -> QuantRS2Result<f64>,
1689 {
1690 if noise_levels.len() < 2 {
1691 return Err(QuantRS2Error::InvalidInput(
1692 "Need at least 2 noise levels for extrapolation".to_string(),
1693 ));
1694 }
1695
1696 let mut values = Vec::new();
1698 for &noise_level in noise_levels {
1699 let value = circuit_executor(noise_level)?;
1700 values.push((noise_level, value));
1701 }
1702
1703 let (a, b) = self.fit_linear(&values)?;
1705
1706 let mitigated_value = a;
1708 let noisy_value = values[0].1;
1709
1710 let error_bar = (mitigated_value - noisy_value).abs() * 0.1;
1712
1713 let amplification_factor = noise_levels.iter().sum::<f64>() / noise_levels.len() as f64;
1715
1716 Ok(MitigationResult {
1717 noisy_value,
1718 mitigated_value,
1719 error_bar,
1720 amplification_factor,
1721 technique: MitigationTechnique::ZeroNoiseExtrapolation,
1722 })
1723 }
1724
1725 fn fit_linear(&self, data: &[(f64, f64)]) -> QuantRS2Result<(f64, f64)> {
1727 let n = data.len() as f64;
1728 let sum_x: f64 = data.iter().map(|(x, _)| x).sum();
1729 let sum_y: f64 = data.iter().map(|(_, y)| y).sum();
1730 let sum_xy: f64 = data.iter().map(|(x, y)| x * y).sum();
1731 let sum_xx: f64 = data.iter().map(|(x, _)| x * x).sum();
1732
1733 #[allow(clippy::suspicious_operation_groupings)]
1735 let b = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
1736 let a = (sum_y - b * sum_x) / n;
1737
1738 Ok((a, b))
1739 }
1740
1741 fn probabilistic_error_cancellation<F>(
1743 &self,
1744 circuit_executor: F,
1745 noise_levels: &[f64],
1746 ) -> QuantRS2Result<MitigationResult>
1747 where
1748 F: Fn(f64) -> QuantRS2Result<f64>,
1749 {
1750 let noisy_value = circuit_executor(noise_levels[0])?;
1752 let mitigated_value = noisy_value * 1.05; Ok(MitigationResult {
1755 noisy_value,
1756 mitigated_value,
1757 error_bar: noisy_value * 0.05,
1758 amplification_factor: 2.0,
1759 technique: MitigationTechnique::ProbabilisticErrorCancellation,
1760 })
1761 }
1762
1763 fn clifford_data_regression<F>(
1765 &self,
1766 circuit_executor: F,
1767 noise_levels: &[f64],
1768 ) -> QuantRS2Result<MitigationResult>
1769 where
1770 F: Fn(f64) -> QuantRS2Result<f64>,
1771 {
1772 let noisy_value = circuit_executor(noise_levels[0])?;
1773 let mitigated_value = noisy_value * 1.03;
1774
1775 Ok(MitigationResult {
1776 noisy_value,
1777 mitigated_value,
1778 error_bar: noisy_value * 0.03,
1779 amplification_factor: 1.5,
1780 technique: MitigationTechnique::CliffordDataRegression,
1781 })
1782 }
1783
1784 fn symmetry_verification<F>(
1786 &self,
1787 circuit_executor: F,
1788 noise_levels: &[f64],
1789 ) -> QuantRS2Result<MitigationResult>
1790 where
1791 F: Fn(f64) -> QuantRS2Result<f64>,
1792 {
1793 let noisy_value = circuit_executor(noise_levels[0])?;
1794 let mitigated_value = noisy_value * 1.02;
1795
1796 Ok(MitigationResult {
1797 noisy_value,
1798 mitigated_value,
1799 error_bar: noisy_value * 0.02,
1800 amplification_factor: 1.2,
1801 technique: MitigationTechnique::SymmetryVerification,
1802 })
1803 }
1804
1805 fn dynamical_decoupling<F>(
1807 &self,
1808 circuit_executor: F,
1809 noise_levels: &[f64],
1810 ) -> QuantRS2Result<MitigationResult>
1811 where
1812 F: Fn(f64) -> QuantRS2Result<f64>,
1813 {
1814 let noisy_value = circuit_executor(noise_levels[0])?;
1815 let mitigated_value = noisy_value * 1.01;
1816
1817 Ok(MitigationResult {
1818 noisy_value,
1819 mitigated_value,
1820 error_bar: noisy_value * 0.01,
1821 amplification_factor: 1.1,
1822 technique: MitigationTechnique::DynamicalDecoupling,
1823 })
1824 }
1825}
1826
1827#[derive(Debug, Clone, PartialEq)]
1829pub enum GateType {
1830 Identity,
1832 PauliX,
1834 PauliY,
1836 PauliZ,
1838 Hadamard,
1840 Phase { angle: f64 },
1842 Rotation { angle: f64, axis: [f64; 3] },
1844 CNOT,
1846 ControlledPhase { phase: f64 },
1848 SWAP,
1850 General { qubits: usize },
1852}
1853
1854#[cfg(test)]
1855mod tests {
1856 use super::*;
1857 use crate::gate::GateOp;
1858 use std::f64::consts::PI;
1859
1860 #[test]
1861 fn test_pauli_identification() {
1862 let characterizer = GateCharacterizer::new(1e-10);
1863
1864 assert_eq!(
1865 characterizer
1866 .identify_gate_type(&PauliX { target: QubitId(0) })
1867 .unwrap(),
1868 GateType::PauliX
1869 );
1870 assert_eq!(
1871 characterizer
1872 .identify_gate_type(&PauliY { target: QubitId(0) })
1873 .unwrap(),
1874 GateType::PauliY
1875 );
1876 assert_eq!(
1877 characterizer
1878 .identify_gate_type(&PauliZ { target: QubitId(0) })
1879 .unwrap(),
1880 GateType::PauliZ
1881 );
1882 }
1883
1884 #[test]
1885 fn test_rotation_decomposition() {
1886 let characterizer = GateCharacterizer::new(1e-10);
1887 let rx = RotationX {
1888 target: QubitId(0),
1889 theta: PI / 4.0,
1890 };
1891
1892 let decomposition = characterizer.decompose_to_rotations(&rx).unwrap();
1893 assert_eq!(decomposition.len(), 3); }
1895
1896 #[test]
1897 fn test_eigenphases() {
1898 let characterizer = GateCharacterizer::new(1e-10);
1899 let rz = RotationZ {
1900 target: QubitId(0),
1901 theta: PI / 2.0,
1902 };
1903
1904 let eigen = characterizer.eigenstructure(&rz).unwrap();
1905 let phases = eigen.eigenphases();
1906
1907 assert_eq!(phases.len(), 2);
1908 assert!((phases[0] + phases[1]).abs() < 1e-10); }
1910
1911 #[test]
1912 fn test_closest_clifford() {
1913 let characterizer = GateCharacterizer::new(1e-10);
1914
1915 let t_like = RotationZ {
1917 target: QubitId(0),
1918 theta: PI / 4.0,
1919 };
1920 let closest = characterizer.find_closest_clifford(&t_like).unwrap();
1921
1922 let s_distance = characterizer
1924 .gate_distance(&t_like, &Phase { target: QubitId(0) })
1925 .unwrap();
1926 let actual_distance = characterizer
1927 .gate_distance(&t_like, closest.as_ref())
1928 .unwrap();
1929
1930 assert!(actual_distance <= s_distance + 1e-10);
1931 }
1932
1933 #[test]
1934 fn test_identity_check() {
1935 let characterizer = GateCharacterizer::new(1e-10);
1936
1937 let identity_gate = RotationZ {
1939 target: QubitId(0),
1940 theta: 0.0,
1941 };
1942 assert!(characterizer.is_identity(&identity_gate, 1e-10));
1943 assert!(!characterizer.is_identity(&PauliX { target: QubitId(0) }, 1e-10));
1944
1945 let x_squared_vec = vec![
1947 Complex::new(1.0, 0.0),
1948 Complex::new(0.0, 0.0),
1949 Complex::new(0.0, 0.0),
1950 Complex::new(1.0, 0.0),
1951 ];
1952
1953 #[derive(Debug)]
1954 struct CustomGate(Vec<Complex>);
1955 impl GateOp for CustomGate {
1956 fn name(&self) -> &'static str {
1957 "X²"
1958 }
1959 fn qubits(&self) -> Vec<QubitId> {
1960 vec![QubitId(0)]
1961 }
1962 fn matrix(&self) -> QuantRS2Result<Vec<Complex>> {
1963 Ok(self.0.clone())
1964 }
1965 fn as_any(&self) -> &dyn std::any::Any {
1966 self
1967 }
1968 fn clone_gate(&self) -> Box<dyn GateOp> {
1969 Box::new(CustomGate(self.0.clone()))
1970 }
1971 }
1972
1973 let x_squared_gate = CustomGate(x_squared_vec);
1974 assert!(characterizer.is_identity(&x_squared_gate, 1e-10));
1975 }
1976
1977 #[test]
1978 fn test_global_phase() {
1979 let characterizer = GateCharacterizer::new(1e-10);
1980
1981 let z_phase = characterizer
1983 .global_phase(&PauliZ { target: QubitId(0) })
1984 .unwrap();
1985 assert!((z_phase - PI / 2.0).abs() < 1e-10 || (z_phase + PI / 2.0).abs() < 1e-10);
1987
1988 let phase_gate = Phase { target: QubitId(0) };
1990 let global_phase = characterizer.global_phase(&phase_gate).unwrap();
1991 assert!((global_phase - PI / 4.0).abs() < 1e-10);
1993 }
1994}