1use std::fmt;
4
5use num_complex::Complex64;
6
7use crate::pauli_string::PauliString;
8use crate::pauli_sum::PauliSum;
9use crate::phase::Phase;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum StabilizerError {
14 GeneratorCount {
16 expected: usize,
18 found: usize,
20 },
21 QubitOutOfRange {
23 generator: usize,
25 num_qubits: usize,
27 },
28 NotCommuting {
30 first: usize,
32 second: usize,
34 },
35 Dependent {
37 generator: usize,
39 },
40}
41
42impl fmt::Display for StabilizerError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Self::GeneratorCount { expected, found } => write!(
46 f,
47 "a stabilizer state on {expected} qubits needs exactly {expected} generators, \
48 got {found}",
49 ),
50 Self::QubitOutOfRange { generator, num_qubits } => {
51 write!(f, "generator {generator} acts on a qubit at or beyond index {num_qubits}",)
52 }
53 Self::NotCommuting { first, second } => write!(
54 f,
55 "generators {first} and {second} anticommute; stabilizer generators must \
56 pairwise commute",
57 ),
58 Self::Dependent { generator } => write!(
59 f,
60 "generator {generator} is a product of the others (or the generators imply \
61 -I ∈ S); stabilizer generators must be independent over GF(2)",
62 ),
63 }
64 }
65}
66
67impl std::error::Error for StabilizerError {}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71struct Column {
72 word: usize,
73 mask: u64,
74 is_z: bool,
75}
76
77impl Column {
78 fn is_set<const W: usize>(self, p: &PauliString<W>) -> bool {
79 let half = if self.is_z { &p.z } else { &p.x };
80 half[self.word] & self.mask != 0
81 }
82}
83
84fn columns(num_qubits: usize) -> Vec<Column> {
86 let mut columns = Vec::with_capacity(2 * num_qubits);
87 for is_z in [false, true] {
88 for q in 0..num_qubits {
89 columns.push(Column { word: q / 64, mask: 1u64 << (q % 64), is_z });
90 }
91 }
92 columns
93}
94
95#[derive(Clone, Copy, Debug)]
97struct Row<const W: usize> {
98 key: PauliString<W>,
99 negative: bool,
100 pivot: Column,
101}
102
103#[derive(Clone, Debug)]
107pub struct StabilizerState<const W: usize> {
108 num_qubits: usize,
109 rows: Vec<Row<W>>,
111}
112
113impl<const W: usize> StabilizerState<W> {
114 pub fn from_generators(
122 num_qubits: usize,
123 generators: &[(PauliString<W>, bool)],
124 ) -> Result<Self, StabilizerError> {
125 assert!(
126 num_qubits <= 64 * W,
127 "StabilizerState::from_generators: num_qubits {num_qubits} exceeds the {W}-word width",
128 );
129 if generators.len() != num_qubits {
130 return Err(StabilizerError::GeneratorCount {
131 expected: num_qubits,
132 found: generators.len(),
133 });
134 }
135 for (i, (key, _)) in generators.iter().enumerate() {
136 if !key.is_within(num_qubits) {
137 return Err(StabilizerError::QubitOutOfRange { generator: i, num_qubits });
138 }
139 }
140 for i in 0..generators.len() {
141 for j in (i + 1)..generators.len() {
142 if !generators[i].0.commutes_with(&generators[j].0) {
143 return Err(StabilizerError::NotCommuting { first: i, second: j });
144 }
145 }
146 }
147
148 let mut work: Vec<(PauliString<W>, bool, usize)> = generators
150 .iter()
151 .enumerate()
152 .map(|(i, (key, negative))| (*key, *negative, i))
153 .collect();
154 let mut rows: Vec<Row<W>> = Vec::with_capacity(num_qubits);
155
156 for column in columns(num_qubits) {
157 let rank = rows.len();
158 let Some(p) = (rank..work.len()).find(|&r| column.is_set(&work[r].0)) else {
159 continue;
160 };
161 work.swap(rank, p);
162 let (key, negative, _) = work[rank];
163 for (r, row) in work.iter_mut().enumerate() {
164 if r == rank || !column.is_set(&row.0) {
165 continue;
166 }
167 let phase = row.0.mul_assign(&key);
168 assert_eq!(
170 phase.exponent() & 1,
171 0,
172 "a product of commuting Hermitian Paulis came out imaginary"
173 );
174 row.1 ^= negative ^ (phase == Phase::MINUS_ONE);
175 }
176 rows.push(Row { key, negative, pivot: column });
177 if rows.len() == num_qubits {
178 break;
179 }
180 }
181
182 if rows.len() < num_qubits {
183 return Err(StabilizerError::Dependent { generator: work[rows.len()].2 });
184 }
185 Ok(Self { num_qubits, rows })
186 }
187
188 pub fn num_qubits(&self) -> usize {
190 self.num_qubits
191 }
192
193 pub(crate) fn sign_of(&self, key: &PauliString<W>) -> Option<bool> {
195 let mut reduced = *key;
196 let mut phase = Phase::ONE;
197 let mut negative = false;
198 for row in &self.rows {
199 if row.pivot.is_set(&reduced) {
200 phase += reduced.mul_assign(&row.key);
201 negative ^= row.negative;
202 }
203 }
204 if reduced != PauliString::<W>::identity() {
205 return None;
206 }
207 debug_assert_eq!(
209 phase.exponent() & 1,
210 0,
211 "stabilizer membership produced an imaginary phase i^{}",
212 phase.exponent(),
213 );
214 Some(negative ^ (phase == Phase::MINUS_ONE))
215 }
216}
217
218impl<const W: usize> PauliSum<W> {
219 pub fn expectation_stabilizer(&self, state: &StabilizerState<W>) -> Complex64 {
225 assert_eq!(
226 self.num_qubits(),
227 state.num_qubits(),
228 "PauliSum::expectation_stabilizer: num_qubits mismatch ({} vs {})",
229 self.num_qubits(),
230 state.num_qubits(),
231 );
232 self.sum_over_terms(|x, z, coeff| match state.sign_of(&PauliString::<W> { x: *x, z: *z }) {
233 None => Complex64::new(0.0, 0.0),
234 Some(false) => coeff,
235 Some(true) => -coeff,
236 })
237 }
238}
239
240#[cfg(test)]
241mod tests;