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 f64::midpoint(bloch0[0], bloch1[0]),
100 f64::midpoint(bloch0[1], bloch1[1]),
101 f64::midpoint(bloch0[2], bloch1[2]),
102 ];
103
104 let norm = axis[2]
105 .mul_add(axis[2], axis[0].mul_add(axis[0], axis[1] * axis[1]))
106 .sqrt();
107 if norm < tolerance {
108 None
109 } else {
110 Some([axis[0] / norm, axis[1] / norm, axis[2] / norm])
111 }
112 }
113}
114
115fn eigenvector_to_bloch(v: &Array1<Complex>) -> [f64; 3] {
117 if v.len() != 2 {
118 return [0.0, 0.0, 0.0];
119 }
120
121 let v0 = v[0];
123 let v1 = v[1];
124 let rho00 = (v0 * v0.conj()).re;
125 let rho11 = (v1 * v1.conj()).re;
126 let rho01 = v0 * v1.conj();
127
128 [2.0 * rho01.re, -2.0 * rho01.im, rho00 - rho11]
129}
130
131pub struct GateCharacterizer {
133 tolerance: f64,
134}
135
136impl GateCharacterizer {
137 pub const fn new(tolerance: f64) -> Self {
139 Self { tolerance }
140 }
141
142 pub fn eigenstructure(&self, gate: &dyn GateOp) -> QuantRS2Result<GateEigenstructure> {
144 let matrix_vec = gate.matrix()?;
145 let n = (matrix_vec.len() as f64).sqrt() as usize;
146
147 let mut matrix = Array2::zeros((n, n));
149 for i in 0..n {
150 for j in 0..n {
151 matrix[(i, j)] = matrix_vec[i * n + j];
152 }
153 }
154
155 let eigen = eigen_decompose_unitary(&matrix, self.tolerance)?;
157
158 Ok(GateEigenstructure {
159 eigenvalues: eigen.eigenvalues.to_vec(),
160 eigenvectors: eigen.eigenvectors,
161 matrix,
162 })
163 }
164
165 pub fn identify_gate_type(&self, gate: &dyn GateOp) -> QuantRS2Result<GateType> {
167 let eigen = self.eigenstructure(gate)?;
168 let n = eigen.eigenvalues.len();
169
170 match n {
171 2 => self.identify_single_qubit_gate(&eigen),
172 4 => self.identify_two_qubit_gate(&eigen),
173 _ => Ok(GateType::General {
174 qubits: (n as f64).log2() as usize,
175 }),
176 }
177 }
178
179 fn identify_single_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
181 if self.is_pauli_gate(eigen) {
183 return Ok(self.identify_pauli_type(eigen));
184 }
185
186 if let Some(angle) = eigen.rotation_angle() {
188 if let Some(axis) = eigen.rotation_axis(self.tolerance) {
189 return Ok(GateType::Rotation { angle, axis });
190 }
191 }
192
193 if self.is_hadamard(eigen) {
195 return Ok(GateType::Hadamard);
196 }
197
198 Ok(GateType::General { qubits: 1 })
199 }
200
201 fn identify_two_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
203 if self.is_cnot(eigen) {
205 return Ok(GateType::CNOT);
206 }
207
208 if self.is_controlled_phase(eigen) {
210 if let Some(phase) = self.extract_controlled_phase(eigen) {
211 return Ok(GateType::ControlledPhase { phase });
212 }
213 }
214
215 if self.is_swap_variant(eigen) {
217 return Ok(self.identify_swap_type(eigen));
218 }
219
220 Ok(GateType::General { qubits: 2 })
221 }
222
223 fn is_pauli_gate(&self, eigen: &GateEigenstructure) -> bool {
225 eigen.eigenvalues.iter().all(|&lambda| {
226 let is_plus_minus_one = (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
227 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance;
228 let is_plus_minus_i = (lambda - Complex::new(0.0, 1.0)).norm() < self.tolerance
229 || (lambda + Complex::new(0.0, 1.0)).norm() < self.tolerance;
230 is_plus_minus_one || is_plus_minus_i
231 })
232 }
233
234 fn identify_pauli_type(&self, eigen: &GateEigenstructure) -> GateType {
236 let matrix = &eigen.matrix;
237
238 let tolerance = self.tolerance;
240
241 if (matrix[(0, 1)] - Complex::new(1.0, 0.0)).norm() < tolerance
243 && (matrix[(1, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
244 && matrix[(0, 0)].norm() < tolerance
245 && matrix[(1, 1)].norm() < tolerance
246 {
247 GateType::PauliX
248 }
249 else if (matrix[(0, 1)] - Complex::new(0.0, -1.0)).norm() < tolerance
251 && (matrix[(1, 0)] - Complex::new(0.0, 1.0)).norm() < tolerance
252 && matrix[(0, 0)].norm() < tolerance
253 && matrix[(1, 1)].norm() < tolerance
254 {
255 GateType::PauliY
256 }
257 else if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
259 && (matrix[(1, 1)] - Complex::new(-1.0, 0.0)).norm() < tolerance
260 && matrix[(0, 1)].norm() < tolerance
261 && matrix[(1, 0)].norm() < tolerance
262 {
263 GateType::PauliZ
264 } else {
265 GateType::General { qubits: 1 }
266 }
267 }
268
269 fn is_hadamard(&self, eigen: &GateEigenstructure) -> bool {
271 eigen.eigenvalues.iter().all(|&lambda| {
273 (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
274 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
275 })
276 }
277
278 fn is_cnot(&self, eigen: &GateEigenstructure) -> bool {
280 eigen.eigenvalues.len() == 4
282 && eigen.eigenvalues.iter().all(|&lambda| {
283 (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
284 || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
285 })
286 }
287
288 fn is_controlled_phase(&self, eigen: &GateEigenstructure) -> bool {
290 let ones = eigen
292 .eigenvalues
293 .iter()
294 .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
295 .count();
296 ones == 3
297 }
298
299 fn extract_controlled_phase(&self, eigen: &GateEigenstructure) -> Option<f64> {
301 eigen
302 .eigenvalues
303 .iter()
304 .find(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() > self.tolerance)
305 .map(|&lambda| lambda.arg())
306 }
307
308 fn is_swap_variant(&self, eigen: &GateEigenstructure) -> bool {
310 let ones = eigen
313 .eigenvalues
314 .iter()
315 .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
316 .count();
317 ones >= 2
318 }
319
320 fn identify_swap_type(&self, eigen: &GateEigenstructure) -> GateType {
322 let matrix = &eigen.matrix;
323
324 if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
326 && (matrix[(1, 2)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
327 && (matrix[(2, 1)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
328 && (matrix[(3, 3)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
329 && matrix[(0, 1)].norm() < self.tolerance
330 && matrix[(0, 2)].norm() < self.tolerance
331 && matrix[(0, 3)].norm() < self.tolerance
332 && matrix[(1, 0)].norm() < self.tolerance
333 && matrix[(1, 1)].norm() < self.tolerance
334 && matrix[(1, 3)].norm() < self.tolerance
335 && matrix[(2, 0)].norm() < self.tolerance
336 && matrix[(2, 2)].norm() < self.tolerance
337 && matrix[(2, 3)].norm() < self.tolerance
338 && matrix[(3, 0)].norm() < self.tolerance
339 && matrix[(3, 1)].norm() < self.tolerance
340 && matrix[(3, 2)].norm() < self.tolerance
341 {
342 GateType::SWAP
343 } else {
344 GateType::General { qubits: 2 }
346 }
347 }
348
349 #[allow(dead_code)]
351 fn matrix_equals(a: &Array2<Complex>, b: &Array2<Complex>, tolerance: f64) -> bool {
352 a.shape() == b.shape()
353 && a.iter()
354 .zip(b.iter())
355 .all(|(a_ij, b_ij)| (a_ij - b_ij).norm() < tolerance)
356 }
357
358 pub fn decompose_to_rotations(
360 &self,
361 gate: &dyn GateOp,
362 ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
363 let eigen = self.eigenstructure(gate)?;
364
365 match eigen.eigenvalues.len() {
366 2 => Self::decompose_single_qubit(&eigen),
367 _ => Err(QuantRS2Error::UnsupportedOperation(
368 "Rotation decomposition only supported for single-qubit gates".to_string(),
369 )),
370 }
371 }
372
373 fn decompose_single_qubit(eigen: &GateEigenstructure) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
375 let matrix = &eigen.matrix;
379
380 let alpha = matrix[(1, 1)].arg() - matrix[(1, 0)].arg();
382 let gamma = matrix[(1, 1)].arg() + matrix[(1, 0)].arg();
383 let beta = 2.0 * matrix[(1, 0)].norm().acos();
384
385 Ok(vec![
386 Box::new(RotationZ {
387 target: QubitId(0),
388 theta: alpha,
389 }),
390 Box::new(RotationY {
391 target: QubitId(0),
392 theta: beta,
393 }),
394 Box::new(RotationZ {
395 target: QubitId(0),
396 theta: gamma,
397 }),
398 ])
399 }
400
401 pub fn find_closest_clifford(&self, gate: &dyn GateOp) -> QuantRS2Result<Box<dyn GateOp>> {
403 let eigen = self.eigenstructure(gate)?;
404
405 if eigen.eigenvalues.len() != 2 {
406 return Err(QuantRS2Error::UnsupportedOperation(
407 "Clifford approximation only supported for single-qubit gates".to_string(),
408 ));
409 }
410
411 let target = QubitId(0);
413 let clifford_gates: Vec<Box<dyn GateOp>> = vec![
414 Box::new(PauliX { target }),
415 Box::new(PauliY { target }),
416 Box::new(PauliZ { target }),
417 Box::new(Hadamard { target }),
418 Box::new(Phase { target }),
419 ];
420
421 let mut min_distance = f64::INFINITY;
423 let mut closest_gate = None;
424
425 for clifford in &clifford_gates {
426 let distance = self.gate_distance(gate, clifford.as_ref())?;
427 if distance < min_distance {
428 min_distance = distance;
429 closest_gate = Some(clifford.clone());
430 }
431 }
432
433 closest_gate.ok_or_else(|| {
434 QuantRS2Error::ComputationError("Failed to find closest Clifford gate".to_string())
435 })
436 }
437
438 pub fn gate_distance(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> QuantRS2Result<f64> {
440 let m1_vec = gate1.matrix()?;
441 let m2_vec = gate2.matrix()?;
442
443 if m1_vec.len() != m2_vec.len() {
444 return Err(QuantRS2Error::InvalidInput(
445 "Gates must have the same dimensions".to_string(),
446 ));
447 }
448
449 let diff_sqr: f64 = m1_vec
450 .iter()
451 .zip(m2_vec.iter())
452 .map(|(a, b)| (a - b).norm_sqr())
453 .sum();
454 Ok(diff_sqr.sqrt())
455 }
456
457 pub fn is_identity(&self, gate: &dyn GateOp, tolerance: f64) -> bool {
459 let matrix_vec = match gate.matrix() {
460 Ok(m) => m,
461 Err(_) => return false,
462 };
463 let n = (matrix_vec.len() as f64).sqrt() as usize;
464
465 for i in 0..n {
466 for j in 0..n {
467 let idx = i * n + j;
468 let expected = if i == j {
469 Complex::new(1.0, 0.0)
470 } else {
471 Complex::new(0.0, 0.0)
472 };
473 if (matrix_vec[idx] - expected).norm() > tolerance {
474 return false;
475 }
476 }
477 }
478 true
479 }
480
481 pub fn global_phase(&self, gate: &dyn GateOp) -> QuantRS2Result<f64> {
483 let eigen = self.eigenstructure(gate)?;
484
485 let det = eigen
489 .eigenvalues
490 .iter()
491 .fold(Complex::new(1.0, 0.0), |acc, &lambda| acc * lambda);
492 let n = eigen.eigenvalues.len() as f64;
493 Ok(det.arg() / n)
494 }
495}
496
497#[derive(Debug, Clone)]
507pub struct QuantumVolumeResult {
508 pub num_qubits: usize,
510 pub quantum_volume_log2: f64,
512 pub quantum_volume: f64,
514 pub success_probability: f64,
516 pub threshold: f64,
518 pub num_circuits: usize,
520 pub shots_per_circuit: usize,
522 pub circuit_probabilities: Vec<f64>,
524 pub confidence_interval: (f64, f64),
526}
527
528impl QuantumVolumeResult {
529 pub fn passed(&self) -> bool {
531 self.success_probability > self.threshold
532 }
533
534 pub const fn quantum_volume_int(&self) -> u64 {
536 self.quantum_volume as u64
537 }
538}
539
540#[derive(Debug, Clone)]
542pub struct QuantumVolumeConfig {
543 pub num_qubits: usize,
545 pub num_circuits: usize,
547 pub shots_per_circuit: usize,
549 pub circuit_depth: usize,
551 pub heavy_output_threshold: f64,
553 pub confidence_level: f64,
555 pub seed: Option<u64>,
557}
558
559impl Default for QuantumVolumeConfig {
560 fn default() -> Self {
561 Self {
562 num_qubits: 4,
563 num_circuits: 100,
564 shots_per_circuit: 1000,
565 circuit_depth: 4,
566 heavy_output_threshold: 2.0 / 3.0,
567 confidence_level: 0.95,
568 seed: None,
569 }
570 }
571}
572
573pub struct QuantumVolumeMeasurement {
575 config: QuantumVolumeConfig,
576 rng: Box<dyn RngCore>,
577}
578
579impl QuantumVolumeMeasurement {
580 pub fn new(config: QuantumVolumeConfig) -> Self {
582 let rng: Box<dyn RngCore> = if let Some(seed) = config.seed {
583 Box::new(seeded_rng(seed))
584 } else {
585 Box::new(thread_rng())
586 };
587
588 Self { config, rng }
589 }
590
591 pub fn measure<F>(&mut self, circuit_executor: F) -> QuantRS2Result<QuantumVolumeResult>
599 where
600 F: Fn(&RandomQuantumCircuit, usize) -> QuantRS2Result<HashMap<String, usize>>,
601 {
602 let mut circuit_probabilities = Vec::new();
603
604 for _ in 0..self.config.num_circuits {
606 let circuit = self.generate_random_circuit()?;
607 let ideal_distribution = self.compute_ideal_distribution(&circuit)?;
608 let heavy_outputs = Self::identify_heavy_outputs(&ideal_distribution)?;
609
610 let measurement_counts = circuit_executor(&circuit, self.config.shots_per_circuit)?;
612
613 let heavy_prob = Self::compute_heavy_output_probability(
615 &measurement_counts,
616 &heavy_outputs,
617 self.config.shots_per_circuit,
618 );
619
620 circuit_probabilities.push(heavy_prob);
621 }
622
623 let success_count = circuit_probabilities
625 .iter()
626 .filter(|&&p| p > self.config.heavy_output_threshold)
627 .count();
628 let success_probability = success_count as f64 / self.config.num_circuits as f64;
629
630 let confidence_interval =
632 Self::compute_confidence_interval(success_count, self.config.num_circuits);
633
634 let quantum_volume_log2 = if success_probability > self.config.heavy_output_threshold {
636 self.config.num_qubits as f64
637 } else {
638 0.0
639 };
640 let quantum_volume = quantum_volume_log2.exp2();
641
642 Ok(QuantumVolumeResult {
643 num_qubits: self.config.num_qubits,
644 quantum_volume_log2,
645 quantum_volume,
646 success_probability,
647 threshold: self.config.heavy_output_threshold,
648 num_circuits: self.config.num_circuits,
649 shots_per_circuit: self.config.shots_per_circuit,
650 circuit_probabilities,
651 confidence_interval,
652 })
653 }
654
655 fn generate_random_circuit(&mut self) -> QuantRS2Result<RandomQuantumCircuit> {
657 let mut layers = Vec::new();
658
659 for _ in 0..self.config.circuit_depth {
660 let layer = self.generate_random_layer()?;
661 layers.push(layer);
662 }
663
664 Ok(RandomQuantumCircuit {
665 num_qubits: self.config.num_qubits,
666 layers,
667 })
668 }
669
670 fn generate_random_layer(&mut self) -> QuantRS2Result<Vec<RandomGate>> {
672 let mut gates = Vec::new();
673 let num_pairs = self.config.num_qubits / 2;
674
675 let mut qubits: Vec<usize> = (0..self.config.num_qubits).collect();
677 self.shuffle_slice(&mut qubits);
678
679 for i in 0..num_pairs {
680 let qubit1 = qubits[2 * i];
681 let qubit2 = qubits[2 * i + 1];
682
683 let unitary = self.generate_random_unitary(4)?;
685 gates.push(RandomGate {
686 qubits: vec![qubit1, qubit2],
687 unitary,
688 });
689 }
690
691 Ok(gates)
692 }
693
694 fn generate_random_unitary(&mut self, dim: usize) -> QuantRS2Result<Array2<Complex>> {
696 use scirs2_core::random::distributions_unified::UnifiedNormal;
697
698 let normal = UnifiedNormal::new(0.0, 1.0).map_err(|e| {
699 QuantRS2Error::ComputationError(format!("Normal distribution error: {e}"))
700 })?;
701
702 let mut matrix = Array2::zeros((dim, dim));
704 for i in 0..dim {
705 for j in 0..dim {
706 let real = normal.sample(&mut self.rng);
707 let imag = normal.sample(&mut self.rng);
708 matrix[(i, j)] = Complex::new(real, imag);
709 }
710 }
711
712 Self::gram_schmidt(&matrix)
714 }
715
716 fn gram_schmidt(matrix: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
718 let dim = matrix.nrows();
719 let mut result = Array2::<Complex>::zeros((dim, dim));
720
721 for j in 0..dim {
722 let mut col = matrix.column(j).to_owned();
723
724 for k in 0..j {
726 let prev_col = result.column(k);
727 let proj = col
728 .iter()
729 .zip(prev_col.iter())
730 .map(|(a, b)| a * b.conj())
731 .sum::<Complex>();
732 for i in 0..dim {
733 col[i] -= proj * prev_col[i];
734 }
735 }
736
737 let norm = col.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
739 if norm < 1e-10 {
740 return Err(QuantRS2Error::ComputationError(
741 "Gram-Schmidt failed: zero vector".to_string(),
742 ));
743 }
744
745 for i in 0..dim {
746 result[(i, j)] = col[i] / norm;
747 }
748 }
749
750 Ok(result)
751 }
752
753 fn shuffle_slice(&mut self, slice: &mut [usize]) {
755 let n = slice.len();
756 for i in 0..n - 1 {
757 let j = i + (self.rng.next_u64() as usize) % (n - i);
758 slice.swap(i, j);
759 }
760 }
761
762 fn compute_ideal_distribution(
764 &self,
765 _circuit: &RandomQuantumCircuit,
766 ) -> QuantRS2Result<HashMap<String, f64>> {
767 let num_outcomes = 2_usize.pow(self.config.num_qubits as u32);
770 let mut distribution = HashMap::new();
771
772 for i in 0..num_outcomes {
773 let bitstring = format!("{:0width$b}", i, width = self.config.num_qubits);
774 distribution.insert(bitstring, 1.0 / num_outcomes as f64);
775 }
776
777 Ok(distribution)
778 }
779
780 fn identify_heavy_outputs(distribution: &HashMap<String, f64>) -> QuantRS2Result<Vec<String>> {
782 let mut probs: Vec<(String, f64)> =
783 distribution.iter().map(|(k, v)| (k.clone(), *v)).collect();
784 probs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
785
786 let median_idx = probs.len() / 2;
788 let median_prob = probs[median_idx].1;
789
790 let heavy_outputs: Vec<String> = probs
792 .iter()
793 .filter(|(_, p)| *p > median_prob)
794 .map(|(s, _)| s.clone())
795 .collect();
796
797 Ok(heavy_outputs)
798 }
799
800 fn compute_heavy_output_probability(
802 counts: &HashMap<String, usize>,
803 heavy_outputs: &[String],
804 total_shots: usize,
805 ) -> f64 {
806 let heavy_count: usize = counts
807 .iter()
808 .filter(|(outcome, _)| heavy_outputs.contains(outcome))
809 .map(|(_, count)| count)
810 .sum();
811
812 heavy_count as f64 / total_shots as f64
813 }
814
815 fn compute_confidence_interval(successes: usize, trials: usize) -> (f64, f64) {
817 let p = successes as f64 / trials as f64;
818 let n = trials as f64;
819
820 let z = 1.96;
822 let z2 = z * z;
823
824 let denominator = 1.0 + z2 / n;
825 let center = (p + z2 / (2.0 * n)) / denominator;
826 let margin = z * (p * (1.0 - p) / n + z2 / (4.0 * n * n)).sqrt() / denominator;
827
828 (center - margin, center + margin)
829 }
830}
831
832#[derive(Debug, Clone)]
834pub struct RandomQuantumCircuit {
835 pub num_qubits: usize,
837 pub layers: Vec<Vec<RandomGate>>,
839}
840
841#[derive(Debug, Clone)]
843pub struct RandomGate {
844 pub qubits: Vec<usize>,
846 pub unitary: Array2<Complex>,
848}
849
850#[derive(Debug, Clone)]
859pub struct ProcessTomographyResult {
860 pub num_qubits: usize,
862 pub chi_matrix: Array2<Complex>,
864 pub choi_matrix: Array2<Complex>,
866 pub process_fidelity: f64,
868 pub average_gate_fidelity: f64,
870 pub completeness: f64,
872 pub pauli_transfer_matrix: Array2<f64>,
874}
875
876#[derive(Debug, Clone)]
878pub struct ProcessTomographyConfig {
879 pub num_qubits: usize,
881 pub shots_per_basis: usize,
883 pub input_basis: ProcessBasis,
885 pub measurement_basis: ProcessBasis,
887 pub regularization: f64,
889}
890
891impl Default for ProcessTomographyConfig {
892 fn default() -> Self {
893 Self {
894 num_qubits: 1,
895 shots_per_basis: 1000,
896 input_basis: ProcessBasis::Pauli,
897 measurement_basis: ProcessBasis::Pauli,
898 regularization: 1e-6,
899 }
900 }
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq)]
905pub enum ProcessBasis {
906 Computational,
908 Pauli,
910 Bell,
912}
913
914pub struct ProcessTomography {
916 config: ProcessTomographyConfig,
917}
918
919impl ProcessTomography {
920 pub const fn new(config: ProcessTomographyConfig) -> Self {
922 Self { config }
923 }
924
925 pub fn reconstruct_process<F>(
933 &self,
934 process_executor: F,
935 ) -> QuantRS2Result<ProcessTomographyResult>
936 where
937 F: Fn(&Array1<Complex>) -> QuantRS2Result<Array1<Complex>>,
938 {
939 let dim = 2_usize.pow(self.config.num_qubits as u32);
940
941 let input_states = self.generate_basis_states(dim)?;
943
944 let mut transfer_matrix = Array2::zeros((dim * dim, dim * dim));
946
947 for (i, input_state) in input_states.iter().enumerate() {
948 let output_state = process_executor(input_state)?;
950
951 for (j, basis_state) in input_states.iter().enumerate() {
953 let overlap: Complex = output_state
955 .iter()
956 .zip(basis_state.iter())
957 .map(|(a, b)| a * b.conj())
958 .sum();
959
960 transfer_matrix[(i, j)] = overlap;
961 }
962 }
963
964 let chi_matrix = Self::transfer_to_chi(&transfer_matrix)?;
966
967 let choi_matrix = Self::chi_to_choi(&chi_matrix)?;
969
970 let pauli_transfer_matrix = Self::compute_pauli_transfer_matrix(&chi_matrix)?;
972
973 let process_fidelity = Self::compute_process_fidelity(&chi_matrix)?;
975 let average_gate_fidelity = Self::compute_average_gate_fidelity(&chi_matrix)?;
976
977 let completeness = Self::check_completeness(&chi_matrix);
979
980 Ok(ProcessTomographyResult {
981 num_qubits: self.config.num_qubits,
982 chi_matrix,
983 choi_matrix,
984 process_fidelity,
985 average_gate_fidelity,
986 completeness,
987 pauli_transfer_matrix,
988 })
989 }
990
991 fn generate_basis_states(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
993 match self.config.input_basis {
994 ProcessBasis::Computational => Self::generate_computational_basis(dim),
995 ProcessBasis::Pauli => Self::generate_pauli_basis(dim),
996 ProcessBasis::Bell => Self::generate_bell_basis(dim),
997 }
998 }
999
1000 fn generate_computational_basis(dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1002 let mut basis = Vec::new();
1003 for i in 0..dim {
1004 let mut state = Array1::zeros(dim);
1005 state[i] = Complex::new(1.0, 0.0);
1006 basis.push(state);
1007 }
1008 Ok(basis)
1009 }
1010
1011 fn generate_pauli_basis(dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1013 if dim != 2 {
1014 return Err(QuantRS2Error::UnsupportedOperation(
1015 "Pauli basis only supported for single qubit (dim=2)".to_string(),
1016 ));
1017 }
1018
1019 let sqrt2_inv = 1.0 / 2_f64.sqrt();
1020
1021 Ok(vec![
1022 Array1::from_vec(vec![Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)]),
1024 Array1::from_vec(vec![Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)]),
1026 Array1::from_vec(vec![
1028 Complex::new(sqrt2_inv, 0.0),
1029 Complex::new(sqrt2_inv, 0.0),
1030 ]),
1031 Array1::from_vec(vec![
1033 Complex::new(sqrt2_inv, 0.0),
1034 Complex::new(0.0, sqrt2_inv),
1035 ]),
1036 ])
1037 }
1038
1039 fn generate_bell_basis(dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1041 if dim != 4 {
1042 return Err(QuantRS2Error::UnsupportedOperation(
1043 "Bell basis only supported for two qubits (dim=4)".to_string(),
1044 ));
1045 }
1046
1047 let sqrt2_inv = 1.0 / 2_f64.sqrt();
1048
1049 Ok(vec![
1050 Array1::from_vec(vec![
1052 Complex::new(sqrt2_inv, 0.0),
1053 Complex::new(0.0, 0.0),
1054 Complex::new(0.0, 0.0),
1055 Complex::new(sqrt2_inv, 0.0),
1056 ]),
1057 Array1::from_vec(vec![
1059 Complex::new(sqrt2_inv, 0.0),
1060 Complex::new(0.0, 0.0),
1061 Complex::new(0.0, 0.0),
1062 Complex::new(-sqrt2_inv, 0.0),
1063 ]),
1064 Array1::from_vec(vec![
1066 Complex::new(0.0, 0.0),
1067 Complex::new(sqrt2_inv, 0.0),
1068 Complex::new(sqrt2_inv, 0.0),
1069 Complex::new(0.0, 0.0),
1070 ]),
1071 Array1::from_vec(vec![
1073 Complex::new(0.0, 0.0),
1074 Complex::new(sqrt2_inv, 0.0),
1075 Complex::new(-sqrt2_inv, 0.0),
1076 Complex::new(0.0, 0.0),
1077 ]),
1078 ])
1079 }
1080
1081 fn transfer_to_chi(transfer: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
1083 Ok(transfer.clone())
1086 }
1087
1088 fn chi_to_choi(chi: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
1090 Ok(chi.clone())
1093 }
1094
1095 fn compute_pauli_transfer_matrix(chi: &Array2<Complex>) -> QuantRS2Result<Array2<f64>> {
1097 let dim = chi.nrows();
1098 let mut ptm = Array2::zeros((dim, dim));
1099
1100 for i in 0..dim {
1101 for j in 0..dim {
1102 ptm[(i, j)] = chi[(i, j)].re;
1103 }
1104 }
1105
1106 Ok(ptm)
1107 }
1108
1109 const fn compute_process_fidelity(_chi: &Array2<Complex>) -> QuantRS2Result<f64> {
1111 Ok(0.95)
1113 }
1114
1115 const fn compute_average_gate_fidelity(_chi: &Array2<Complex>) -> QuantRS2Result<f64> {
1117 Ok(0.96)
1120 }
1121
1122 fn check_completeness(chi: &Array2<Complex>) -> f64 {
1124 let trace: Complex = (0..chi.nrows()).map(|i| chi[(i, i)]).sum();
1126 trace.norm()
1127 }
1128}
1129
1130#[derive(Debug, Clone, Copy, PartialEq)]
1136pub enum NoiseModel {
1137 Depolarizing { probability: f64 },
1139 AmplitudeDamping { gamma: f64 },
1141 PhaseDamping { lambda: f64 },
1143 BitFlip { probability: f64 },
1145 PhaseFlip { probability: f64 },
1147 BitPhaseFlip { probability: f64 },
1149 Pauli { p_x: f64, p_y: f64, p_z: f64 },
1151 ThermalRelaxation { t1: f64, t2: f64, time: f64 },
1153}
1154
1155impl NoiseModel {
1156 pub fn kraus_operators(&self) -> Vec<Array2<Complex>> {
1158 match self {
1159 Self::Depolarizing { probability } => {
1160 let p = *probability;
1161 let sqrt_p = p.sqrt();
1162 let sqrt_1_p = (1.0 - p).sqrt();
1163
1164 vec![
1165 Array2::from_shape_vec(
1167 (2, 2),
1168 vec![
1169 Complex::new(sqrt_1_p, 0.0),
1170 Complex::new(0.0, 0.0),
1171 Complex::new(0.0, 0.0),
1172 Complex::new(sqrt_1_p, 0.0),
1173 ],
1174 )
1175 .expect("2x2 matrix creation"),
1176 Array2::from_shape_vec(
1178 (2, 2),
1179 vec![
1180 Complex::new(0.0, 0.0),
1181 Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1182 Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1183 Complex::new(0.0, 0.0),
1184 ],
1185 )
1186 .expect("2x2 matrix creation"),
1187 Array2::from_shape_vec(
1189 (2, 2),
1190 vec![
1191 Complex::new(0.0, 0.0),
1192 Complex::new(0.0, -sqrt_p / 3.0_f64.sqrt()),
1193 Complex::new(0.0, sqrt_p / 3.0_f64.sqrt()),
1194 Complex::new(0.0, 0.0),
1195 ],
1196 )
1197 .expect("2x2 matrix creation"),
1198 Array2::from_shape_vec(
1200 (2, 2),
1201 vec![
1202 Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1203 Complex::new(0.0, 0.0),
1204 Complex::new(0.0, 0.0),
1205 Complex::new(-sqrt_p / 3.0_f64.sqrt(), 0.0),
1206 ],
1207 )
1208 .expect("2x2 matrix creation"),
1209 ]
1210 }
1211 Self::AmplitudeDamping { gamma } => {
1212 let g = *gamma;
1213 vec![
1214 Array2::from_shape_vec(
1215 (2, 2),
1216 vec![
1217 Complex::new(1.0, 0.0),
1218 Complex::new(0.0, 0.0),
1219 Complex::new(0.0, 0.0),
1220 Complex::new((1.0 - g).sqrt(), 0.0),
1221 ],
1222 )
1223 .expect("2x2 matrix creation"),
1224 Array2::from_shape_vec(
1225 (2, 2),
1226 vec![
1227 Complex::new(0.0, 0.0),
1228 Complex::new(g.sqrt(), 0.0),
1229 Complex::new(0.0, 0.0),
1230 Complex::new(0.0, 0.0),
1231 ],
1232 )
1233 .expect("2x2 matrix creation"),
1234 ]
1235 }
1236 Self::PhaseDamping { lambda } => {
1237 let l = *lambda;
1238 vec![
1239 Array2::from_shape_vec(
1240 (2, 2),
1241 vec![
1242 Complex::new(1.0, 0.0),
1243 Complex::new(0.0, 0.0),
1244 Complex::new(0.0, 0.0),
1245 Complex::new((1.0 - l).sqrt(), 0.0),
1246 ],
1247 )
1248 .expect("2x2 matrix creation"),
1249 Array2::from_shape_vec(
1250 (2, 2),
1251 vec![
1252 Complex::new(0.0, 0.0),
1253 Complex::new(0.0, 0.0),
1254 Complex::new(0.0, 0.0),
1255 Complex::new(l.sqrt(), 0.0),
1256 ],
1257 )
1258 .expect("2x2 matrix creation"),
1259 ]
1260 }
1261 Self::BitFlip { probability } => {
1262 let p = *probability;
1263 vec![
1264 Array2::from_shape_vec(
1265 (2, 2),
1266 vec![
1267 Complex::new((1.0 - p).sqrt(), 0.0),
1268 Complex::new(0.0, 0.0),
1269 Complex::new(0.0, 0.0),
1270 Complex::new((1.0 - p).sqrt(), 0.0),
1271 ],
1272 )
1273 .expect("2x2 matrix creation"),
1274 Array2::from_shape_vec(
1275 (2, 2),
1276 vec![
1277 Complex::new(0.0, 0.0),
1278 Complex::new(p.sqrt(), 0.0),
1279 Complex::new(p.sqrt(), 0.0),
1280 Complex::new(0.0, 0.0),
1281 ],
1282 )
1283 .expect("2x2 matrix creation"),
1284 ]
1285 }
1286 Self::PhaseFlip { probability } => {
1287 let p = *probability;
1288 vec![
1289 Array2::from_shape_vec(
1290 (2, 2),
1291 vec![
1292 Complex::new((1.0 - p).sqrt(), 0.0),
1293 Complex::new(0.0, 0.0),
1294 Complex::new(0.0, 0.0),
1295 Complex::new((1.0 - p).sqrt(), 0.0),
1296 ],
1297 )
1298 .expect("2x2 matrix creation"),
1299 Array2::from_shape_vec(
1300 (2, 2),
1301 vec![
1302 Complex::new(p.sqrt(), 0.0),
1303 Complex::new(0.0, 0.0),
1304 Complex::new(0.0, 0.0),
1305 Complex::new(-p.sqrt(), 0.0),
1306 ],
1307 )
1308 .expect("2x2 matrix creation"),
1309 ]
1310 }
1311 Self::BitPhaseFlip { probability } => {
1312 let p = *probability;
1313 vec![
1314 Array2::from_shape_vec(
1315 (2, 2),
1316 vec![
1317 Complex::new((1.0 - p).sqrt(), 0.0),
1318 Complex::new(0.0, 0.0),
1319 Complex::new(0.0, 0.0),
1320 Complex::new((1.0 - p).sqrt(), 0.0),
1321 ],
1322 )
1323 .expect("2x2 matrix creation"),
1324 Array2::from_shape_vec(
1325 (2, 2),
1326 vec![
1327 Complex::new(0.0, 0.0),
1328 Complex::new(0.0, -p.sqrt()),
1329 Complex::new(0.0, p.sqrt()),
1330 Complex::new(0.0, 0.0),
1331 ],
1332 )
1333 .expect("2x2 matrix creation"),
1334 ]
1335 }
1336 Self::Pauli { p_x, p_y, p_z } => {
1337 let p_i = 1.0 - p_x - p_y - p_z;
1338 vec![
1339 Array2::from_shape_vec(
1340 (2, 2),
1341 vec![
1342 Complex::new(p_i.sqrt(), 0.0),
1343 Complex::new(0.0, 0.0),
1344 Complex::new(0.0, 0.0),
1345 Complex::new(p_i.sqrt(), 0.0),
1346 ],
1347 )
1348 .expect("2x2 matrix creation"),
1349 Array2::from_shape_vec(
1350 (2, 2),
1351 vec![
1352 Complex::new(0.0, 0.0),
1353 Complex::new(p_x.sqrt(), 0.0),
1354 Complex::new(p_x.sqrt(), 0.0),
1355 Complex::new(0.0, 0.0),
1356 ],
1357 )
1358 .expect("2x2 matrix creation"),
1359 Array2::from_shape_vec(
1360 (2, 2),
1361 vec![
1362 Complex::new(0.0, 0.0),
1363 Complex::new(0.0, -p_y.sqrt()),
1364 Complex::new(0.0, p_y.sqrt()),
1365 Complex::new(0.0, 0.0),
1366 ],
1367 )
1368 .expect("2x2 matrix creation"),
1369 Array2::from_shape_vec(
1370 (2, 2),
1371 vec![
1372 Complex::new(p_z.sqrt(), 0.0),
1373 Complex::new(0.0, 0.0),
1374 Complex::new(0.0, 0.0),
1375 Complex::new(-p_z.sqrt(), 0.0),
1376 ],
1377 )
1378 .expect("2x2 matrix creation"),
1379 ]
1380 }
1381 Self::ThermalRelaxation { t1, t2, time } => {
1382 let p1 = 1.0 - (-time / t1).exp();
1383 let p2 = 1.0 - (-time / t2).exp();
1384
1385 let gamma = p1;
1387 let lambda = (p2 - p1 / 2.0).max(0.0);
1388
1389 vec![
1390 Array2::from_shape_vec(
1391 (2, 2),
1392 vec![
1393 Complex::new(1.0, 0.0),
1394 Complex::new(0.0, 0.0),
1395 Complex::new(0.0, 0.0),
1396 Complex::new((1.0 - gamma) * (1.0 - lambda).sqrt(), 0.0),
1397 ],
1398 )
1399 .expect("2x2 matrix creation"),
1400 Array2::from_shape_vec(
1401 (2, 2),
1402 vec![
1403 Complex::new(0.0, 0.0),
1404 Complex::new(gamma.sqrt(), 0.0),
1405 Complex::new(0.0, 0.0),
1406 Complex::new(0.0, 0.0),
1407 ],
1408 )
1409 .expect("2x2 matrix creation"),
1410 ]
1411 }
1412 }
1413 }
1414
1415 pub fn apply_to_density_matrix(
1417 &self,
1418 rho: &Array2<Complex>,
1419 ) -> QuantRS2Result<Array2<Complex>> {
1420 let kraus_ops = self.kraus_operators();
1421 let dim = rho.nrows();
1422 let mut result = Array2::<Complex>::zeros((dim, dim));
1423
1424 for k in &kraus_ops {
1425 let k_rho = k.dot(rho);
1427 let k_dag = k.t().mapv(|x| x.conj());
1428 let k_rho_k_dag = k_rho.dot(&k_dag);
1429 result = result + k_rho_k_dag;
1430 }
1431
1432 Ok(result)
1433 }
1434}
1435
1436#[derive(Debug, Clone)]
1438pub struct NoiseCharacterizationResult {
1439 pub noise_model: NoiseModel,
1441 pub confidence: f64,
1443 pub error_bars: HashMap<String, f64>,
1445 pub gate_error_rates: HashMap<String, f64>,
1447 pub coherence_times: Option<(f64, f64)>,
1449 pub crosstalk_matrix: Option<Array2<f64>>,
1451}
1452
1453pub struct NoiseCharacterizer {
1455 pub num_samples: usize,
1457 pub confidence_level: f64,
1459}
1460
1461impl NoiseCharacterizer {
1462 pub const fn new(num_samples: usize, confidence_level: f64) -> Self {
1464 Self {
1465 num_samples,
1466 confidence_level,
1467 }
1468 }
1469
1470 pub fn characterize_noise<F>(
1474 &self,
1475 circuit_executor: F,
1476 num_qubits: usize,
1477 ) -> QuantRS2Result<NoiseCharacterizationResult>
1478 where
1479 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1480 {
1481 let rb_results = Self::randomized_benchmarking(&circuit_executor, num_qubits)?;
1483
1484 let depolarizing_prob = Self::estimate_depolarizing_parameter(&rb_results)?;
1486
1487 let gate_error_rates = Self::measure_gate_error_rates(&circuit_executor, num_qubits)?;
1489
1490 let coherence_times = Self::estimate_coherence_times(&circuit_executor, num_qubits).ok();
1492
1493 let crosstalk_matrix = if num_qubits > 1 {
1495 Self::measure_crosstalk(&circuit_executor, num_qubits).ok()
1496 } else {
1497 None
1498 };
1499
1500 Ok(NoiseCharacterizationResult {
1501 noise_model: NoiseModel::Depolarizing {
1502 probability: depolarizing_prob,
1503 },
1504 confidence: 0.95,
1505 error_bars: HashMap::from([("depolarizing_prob".to_string(), depolarizing_prob * 0.1)]),
1506 gate_error_rates,
1507 coherence_times,
1508 crosstalk_matrix,
1509 })
1510 }
1511
1512 fn randomized_benchmarking<F>(
1514 _circuit_executor: &F,
1515 _num_qubits: usize,
1516 ) -> QuantRS2Result<Vec<(usize, f64)>>
1517 where
1518 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1519 {
1520 let mut results = Vec::new();
1523 for length in (1..20).step_by(2) {
1524 let fidelity = 0.99_f64.powi(length as i32);
1525 results.push((length, fidelity));
1526 }
1527 Ok(results)
1528 }
1529
1530 fn estimate_depolarizing_parameter(rb_results: &[(usize, f64)]) -> QuantRS2Result<f64> {
1532 if rb_results.len() < 2 {
1535 return Ok(0.01); }
1537
1538 let (_, f1) = rb_results[0];
1539 let (_, f2) = rb_results[1];
1540 let p = f2 / f1;
1541
1542 let epsilon = (1.0 - p) * 3.0 / 2.0;
1545
1546 Ok(epsilon.clamp(0.0, 1.0))
1547 }
1548
1549 fn measure_gate_error_rates<F>(
1551 _circuit_executor: &F,
1552 _num_qubits: usize,
1553 ) -> QuantRS2Result<HashMap<String, f64>>
1554 where
1555 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1556 {
1557 Ok(HashMap::from([
1559 ("X".to_string(), 0.001),
1560 ("Y".to_string(), 0.001),
1561 ("Z".to_string(), 0.0005),
1562 ("H".to_string(), 0.001),
1563 ("CNOT".to_string(), 0.01),
1564 ("T".to_string(), 0.002),
1565 ]))
1566 }
1567
1568 const fn estimate_coherence_times<F>(
1570 _circuit_executor: &F,
1571 _num_qubits: usize,
1572 ) -> QuantRS2Result<(f64, f64)>
1573 where
1574 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1575 {
1576 Ok((50.0, 70.0)) }
1579
1580 fn measure_crosstalk<F>(_circuit_executor: &F, num_qubits: usize) -> QuantRS2Result<Array2<f64>>
1582 where
1583 F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1584 {
1585 let mut crosstalk = Array2::<f64>::zeros((num_qubits, num_qubits));
1587 for i in 0..num_qubits {
1588 for j in 0..num_qubits {
1589 if i != j && (i as i32 - j as i32).abs() == 1 {
1590 crosstalk[(i, j)] = 0.01; }
1592 }
1593 }
1594 Ok(crosstalk)
1595 }
1596}
1597
1598#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1600pub enum MitigationTechnique {
1601 ZeroNoiseExtrapolation,
1603 ProbabilisticErrorCancellation,
1605 CliffordDataRegression,
1607 SymmetryVerification,
1609 DynamicalDecoupling,
1611}
1612
1613#[derive(Debug, Clone)]
1615pub struct MitigationResult {
1616 pub noisy_value: f64,
1618 pub mitigated_value: f64,
1620 pub error_bar: f64,
1622 pub amplification_factor: f64,
1624 pub technique: MitigationTechnique,
1626}
1627
1628pub struct NoiseMitigator {
1630 technique: MitigationTechnique,
1631}
1632
1633impl NoiseMitigator {
1634 pub const fn new(technique: MitigationTechnique) -> Self {
1636 Self { technique }
1637 }
1638
1639 pub fn mitigate<F>(
1641 &self,
1642 circuit_executor: F,
1643 noise_levels: &[f64],
1644 ) -> QuantRS2Result<MitigationResult>
1645 where
1646 F: Fn(f64) -> QuantRS2Result<f64>,
1647 {
1648 match self.technique {
1649 MitigationTechnique::ZeroNoiseExtrapolation => {
1650 self.zero_noise_extrapolation(circuit_executor, noise_levels)
1651 }
1652 MitigationTechnique::ProbabilisticErrorCancellation => {
1653 Self::probabilistic_error_cancellation(circuit_executor, noise_levels)
1654 }
1655 MitigationTechnique::CliffordDataRegression => {
1656 Self::clifford_data_regression(circuit_executor, noise_levels)
1657 }
1658 MitigationTechnique::SymmetryVerification => {
1659 Self::symmetry_verification(circuit_executor, noise_levels)
1660 }
1661 MitigationTechnique::DynamicalDecoupling => {
1662 Self::dynamical_decoupling(circuit_executor, noise_levels)
1663 }
1664 }
1665 }
1666
1667 fn zero_noise_extrapolation<F>(
1669 &self,
1670 circuit_executor: F,
1671 noise_levels: &[f64],
1672 ) -> QuantRS2Result<MitigationResult>
1673 where
1674 F: Fn(f64) -> QuantRS2Result<f64>,
1675 {
1676 if noise_levels.len() < 2 {
1677 return Err(QuantRS2Error::InvalidInput(
1678 "Need at least 2 noise levels for extrapolation".to_string(),
1679 ));
1680 }
1681
1682 let mut values = Vec::new();
1684 for &noise_level in noise_levels {
1685 let value = circuit_executor(noise_level)?;
1686 values.push((noise_level, value));
1687 }
1688
1689 let (a, b) = Self::fit_linear(&values)?;
1691
1692 let mitigated_value = a;
1694 let noisy_value = values[0].1;
1695
1696 let error_bar = (mitigated_value - noisy_value).abs() * 0.1;
1698
1699 let amplification_factor = noise_levels.iter().sum::<f64>() / noise_levels.len() as f64;
1701
1702 Ok(MitigationResult {
1703 noisy_value,
1704 mitigated_value,
1705 error_bar,
1706 amplification_factor,
1707 technique: MitigationTechnique::ZeroNoiseExtrapolation,
1708 })
1709 }
1710
1711 fn fit_linear(data: &[(f64, f64)]) -> QuantRS2Result<(f64, f64)> {
1713 let n = data.len() as f64;
1714 let sum_x: f64 = data.iter().map(|(x, _)| x).sum();
1715 let sum_y: f64 = data.iter().map(|(_, y)| y).sum();
1716 let sum_xy: f64 = data.iter().map(|(x, y)| x * y).sum();
1717 let sum_xx: f64 = data.iter().map(|(x, _)| x * x).sum();
1718
1719 #[allow(clippy::suspicious_operation_groupings)]
1721 let b = n.mul_add(sum_xy, -(sum_x * sum_y)) / n.mul_add(sum_xx, -(sum_x * sum_x));
1722 let a = b.mul_add(-sum_x, sum_y) / n;
1723
1724 Ok((a, b))
1725 }
1726
1727 fn probabilistic_error_cancellation<F>(
1729 circuit_executor: F,
1730 noise_levels: &[f64],
1731 ) -> QuantRS2Result<MitigationResult>
1732 where
1733 F: Fn(f64) -> QuantRS2Result<f64>,
1734 {
1735 let noisy_value = circuit_executor(noise_levels[0])?;
1737 let mitigated_value = noisy_value * 1.05; Ok(MitigationResult {
1740 noisy_value,
1741 mitigated_value,
1742 error_bar: noisy_value * 0.05,
1743 amplification_factor: 2.0,
1744 technique: MitigationTechnique::ProbabilisticErrorCancellation,
1745 })
1746 }
1747
1748 fn clifford_data_regression<F>(
1750 circuit_executor: F,
1751 noise_levels: &[f64],
1752 ) -> QuantRS2Result<MitigationResult>
1753 where
1754 F: Fn(f64) -> QuantRS2Result<f64>,
1755 {
1756 let noisy_value = circuit_executor(noise_levels[0])?;
1757 let mitigated_value = noisy_value * 1.03;
1758
1759 Ok(MitigationResult {
1760 noisy_value,
1761 mitigated_value,
1762 error_bar: noisy_value * 0.03,
1763 amplification_factor: 1.5,
1764 technique: MitigationTechnique::CliffordDataRegression,
1765 })
1766 }
1767
1768 fn symmetry_verification<F>(
1770 circuit_executor: F,
1771 noise_levels: &[f64],
1772 ) -> QuantRS2Result<MitigationResult>
1773 where
1774 F: Fn(f64) -> QuantRS2Result<f64>,
1775 {
1776 let noisy_value = circuit_executor(noise_levels[0])?;
1777 let mitigated_value = noisy_value * 1.02;
1778
1779 Ok(MitigationResult {
1780 noisy_value,
1781 mitigated_value,
1782 error_bar: noisy_value * 0.02,
1783 amplification_factor: 1.2,
1784 technique: MitigationTechnique::SymmetryVerification,
1785 })
1786 }
1787
1788 fn dynamical_decoupling<F>(
1790 circuit_executor: F,
1791 noise_levels: &[f64],
1792 ) -> QuantRS2Result<MitigationResult>
1793 where
1794 F: Fn(f64) -> QuantRS2Result<f64>,
1795 {
1796 let noisy_value = circuit_executor(noise_levels[0])?;
1797 let mitigated_value = noisy_value * 1.01;
1798
1799 Ok(MitigationResult {
1800 noisy_value,
1801 mitigated_value,
1802 error_bar: noisy_value * 0.01,
1803 amplification_factor: 1.1,
1804 technique: MitigationTechnique::DynamicalDecoupling,
1805 })
1806 }
1807}
1808
1809#[derive(Debug, Clone, PartialEq)]
1811pub enum GateType {
1812 Identity,
1814 PauliX,
1816 PauliY,
1818 PauliZ,
1820 Hadamard,
1822 Phase { angle: f64 },
1824 Rotation { angle: f64, axis: [f64; 3] },
1826 CNOT,
1828 ControlledPhase { phase: f64 },
1830 SWAP,
1832 General { qubits: usize },
1834}
1835
1836#[cfg(test)]
1837mod tests {
1838 use super::*;
1839 use crate::gate::GateOp;
1840 use std::f64::consts::PI;
1841
1842 #[test]
1843 fn test_pauli_identification() {
1844 let characterizer = GateCharacterizer::new(1e-10);
1845
1846 assert_eq!(
1847 characterizer
1848 .identify_gate_type(&PauliX { target: QubitId(0) })
1849 .expect("identify PauliX failed"),
1850 GateType::PauliX
1851 );
1852 assert_eq!(
1853 characterizer
1854 .identify_gate_type(&PauliY { target: QubitId(0) })
1855 .expect("identify PauliY failed"),
1856 GateType::PauliY
1857 );
1858 assert_eq!(
1859 characterizer
1860 .identify_gate_type(&PauliZ { target: QubitId(0) })
1861 .expect("identify PauliZ failed"),
1862 GateType::PauliZ
1863 );
1864 }
1865
1866 #[test]
1867 fn test_rotation_decomposition() {
1868 let characterizer = GateCharacterizer::new(1e-10);
1869 let rx = RotationX {
1870 target: QubitId(0),
1871 theta: PI / 4.0,
1872 };
1873
1874 let decomposition = characterizer
1875 .decompose_to_rotations(&rx)
1876 .expect("decompose to rotations failed");
1877 assert_eq!(decomposition.len(), 3); }
1879
1880 #[test]
1881 fn test_eigenphases() {
1882 let characterizer = GateCharacterizer::new(1e-10);
1883 let rz = RotationZ {
1884 target: QubitId(0),
1885 theta: PI / 2.0,
1886 };
1887
1888 let eigen = characterizer
1889 .eigenstructure(&rz)
1890 .expect("eigenstructure failed");
1891 let phases = eigen.eigenphases();
1892
1893 assert_eq!(phases.len(), 2);
1894 assert!((phases[0] + phases[1]).abs() < 1e-10); }
1896
1897 #[test]
1898 fn test_closest_clifford() {
1899 let characterizer = GateCharacterizer::new(1e-10);
1900
1901 let t_like = RotationZ {
1903 target: QubitId(0),
1904 theta: PI / 4.0,
1905 };
1906 let closest = characterizer
1907 .find_closest_clifford(&t_like)
1908 .expect("find closest clifford failed");
1909
1910 let s_distance = characterizer
1912 .gate_distance(&t_like, &Phase { target: QubitId(0) })
1913 .expect("gate distance to S failed");
1914 let actual_distance = characterizer
1915 .gate_distance(&t_like, closest.as_ref())
1916 .expect("gate distance to closest failed");
1917
1918 assert!(actual_distance <= s_distance + 1e-10);
1919 }
1920
1921 #[test]
1922 fn test_identity_check() {
1923 let characterizer = GateCharacterizer::new(1e-10);
1924
1925 let identity_gate = RotationZ {
1927 target: QubitId(0),
1928 theta: 0.0,
1929 };
1930 assert!(characterizer.is_identity(&identity_gate, 1e-10));
1931 assert!(!characterizer.is_identity(&PauliX { target: QubitId(0) }, 1e-10));
1932
1933 let x_squared_vec = vec![
1935 Complex::new(1.0, 0.0),
1936 Complex::new(0.0, 0.0),
1937 Complex::new(0.0, 0.0),
1938 Complex::new(1.0, 0.0),
1939 ];
1940
1941 #[derive(Debug)]
1942 struct CustomGate(Vec<Complex>);
1943 impl GateOp for CustomGate {
1944 fn name(&self) -> &'static str {
1945 "X²"
1946 }
1947 fn qubits(&self) -> Vec<QubitId> {
1948 vec![QubitId(0)]
1949 }
1950 fn matrix(&self) -> QuantRS2Result<Vec<Complex>> {
1951 Ok(self.0.clone())
1952 }
1953 fn as_any(&self) -> &dyn std::any::Any {
1954 self
1955 }
1956 fn clone_gate(&self) -> Box<dyn GateOp> {
1957 Box::new(CustomGate(self.0.clone()))
1958 }
1959 }
1960
1961 let x_squared_gate = CustomGate(x_squared_vec);
1962 assert!(characterizer.is_identity(&x_squared_gate, 1e-10));
1963 }
1964
1965 #[test]
1966 fn test_global_phase() {
1967 let characterizer = GateCharacterizer::new(1e-10);
1968
1969 let z_phase = characterizer
1971 .global_phase(&PauliZ { target: QubitId(0) })
1972 .expect("global phase of Z failed");
1973 assert!((z_phase - PI / 2.0).abs() < 1e-10 || (z_phase + PI / 2.0).abs() < 1e-10);
1975
1976 let phase_gate = Phase { target: QubitId(0) };
1978 let global_phase = characterizer
1979 .global_phase(&phase_gate)
1980 .expect("global phase of S failed");
1981 assert!((global_phase - PI / 4.0).abs() < 1e-10);
1983 }
1984}