quantrs2_core/error_correction/
pauli.rs

1//! Pauli operators and strings for quantum error correction
2
3use crate::error::{QuantRS2Error, QuantRS2Result};
4use scirs2_core::ndarray::Array2;
5use scirs2_core::Complex64;
6use std::fmt;
7
8/// Pauli operator representation
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Pauli {
11    I,
12    X,
13    Y,
14    Z,
15}
16
17impl Pauli {
18    /// Get matrix representation
19    pub fn matrix(&self) -> Array2<Complex64> {
20        match self {
21            Self::I => Array2::from_shape_vec(
22                (2, 2),
23                vec![
24                    Complex64::new(1.0, 0.0),
25                    Complex64::new(0.0, 0.0),
26                    Complex64::new(0.0, 0.0),
27                    Complex64::new(1.0, 0.0),
28                ],
29            )
30            .expect("Pauli I matrix: 2x2 shape with 4 elements is always valid"),
31            Self::X => Array2::from_shape_vec(
32                (2, 2),
33                vec![
34                    Complex64::new(0.0, 0.0),
35                    Complex64::new(1.0, 0.0),
36                    Complex64::new(1.0, 0.0),
37                    Complex64::new(0.0, 0.0),
38                ],
39            )
40            .expect("Pauli X matrix: 2x2 shape with 4 elements is always valid"),
41            Self::Y => Array2::from_shape_vec(
42                (2, 2),
43                vec![
44                    Complex64::new(0.0, 0.0),
45                    Complex64::new(0.0, -1.0),
46                    Complex64::new(0.0, 1.0),
47                    Complex64::new(0.0, 0.0),
48                ],
49            )
50            .expect("Pauli Y matrix: 2x2 shape with 4 elements is always valid"),
51            Self::Z => Array2::from_shape_vec(
52                (2, 2),
53                vec![
54                    Complex64::new(1.0, 0.0),
55                    Complex64::new(0.0, 0.0),
56                    Complex64::new(0.0, 0.0),
57                    Complex64::new(-1.0, 0.0),
58                ],
59            )
60            .expect("Pauli Z matrix: 2x2 shape with 4 elements is always valid"),
61        }
62    }
63
64    /// Multiply two Pauli operators
65    pub const fn multiply(&self, other: &Self) -> (Complex64, Self) {
66        use Pauli::{I, X, Y, Z};
67        match (self, other) {
68            (I, p) | (p, I) => (Complex64::new(1.0, 0.0), *p),
69            (X, X) | (Y, Y) | (Z, Z) => (Complex64::new(1.0, 0.0), I),
70            (X, Y) => (Complex64::new(0.0, 1.0), Z),
71            (Y, X) => (Complex64::new(0.0, -1.0), Z),
72            (Y, Z) => (Complex64::new(0.0, 1.0), X),
73            (Z, Y) => (Complex64::new(0.0, -1.0), X),
74            (Z, X) => (Complex64::new(0.0, 1.0), Y),
75            (X, Z) => (Complex64::new(0.0, -1.0), Y),
76        }
77    }
78}
79
80/// Multi-qubit Pauli operator
81#[derive(Debug, Clone, PartialEq)]
82pub struct PauliString {
83    /// Phase factor (±1, ±i)
84    pub phase: Complex64,
85    /// Pauli operators for each qubit
86    pub paulis: Vec<Pauli>,
87}
88
89impl PauliString {
90    /// Create a new Pauli string
91    pub const fn new(paulis: Vec<Pauli>) -> Self {
92        Self {
93            phase: Complex64::new(1.0, 0.0),
94            paulis,
95        }
96    }
97
98    /// Create identity on n qubits
99    pub fn identity(n: usize) -> Self {
100        Self::new(vec![Pauli::I; n])
101    }
102
103    /// Get the weight (number of non-identity operators)
104    pub fn weight(&self) -> usize {
105        self.paulis.iter().filter(|&&p| p != Pauli::I).count()
106    }
107
108    /// Multiply two Pauli strings
109    pub fn multiply(&self, other: &Self) -> QuantRS2Result<Self> {
110        if self.paulis.len() != other.paulis.len() {
111            return Err(QuantRS2Error::InvalidInput(
112                "Pauli strings must have same length".to_string(),
113            ));
114        }
115
116        let mut phase = self.phase * other.phase;
117        let mut paulis = Vec::with_capacity(self.paulis.len());
118
119        for (p1, p2) in self.paulis.iter().zip(&other.paulis) {
120            let (factor, result) = p1.multiply(p2);
121            phase *= factor;
122            paulis.push(result);
123        }
124
125        Ok(Self { phase, paulis })
126    }
127
128    /// Check if two Pauli strings commute
129    pub fn commutes_with(&self, other: &Self) -> QuantRS2Result<bool> {
130        if self.paulis.len() != other.paulis.len() {
131            return Err(QuantRS2Error::InvalidInput(
132                "Pauli strings must have same length".to_string(),
133            ));
134        }
135
136        let mut commutation_count = 0;
137        for (p1, p2) in self.paulis.iter().zip(&other.paulis) {
138            if *p1 != Pauli::I && *p2 != Pauli::I && p1 != p2 {
139                commutation_count += 1;
140            }
141        }
142
143        Ok(commutation_count % 2 == 0)
144    }
145}
146
147impl fmt::Display for PauliString {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        let phase_str = if self.phase == Complex64::new(1.0, 0.0) {
150            "+".to_string()
151        } else if self.phase == Complex64::new(-1.0, 0.0) {
152            "-".to_string()
153        } else if self.phase == Complex64::new(0.0, 1.0) {
154            "+i".to_string()
155        } else {
156            "-i".to_string()
157        };
158
159        write!(f, "{phase_str}")?;
160        for p in &self.paulis {
161            write!(f, "{p:?}")?;
162        }
163        Ok(())
164    }
165}