1use crate::data::GeneralizedTableau;
21use crate::prelude::*;
22use bitvec::view::BitView;
23use num::PrimInt;
24use num::complex::{Complex, Complex64, ComplexFloat};
25use num::traits::{One, ToPrimitive, Zero};
26use ppvm_pauli_word::pattern::PauliPattern;
27use ppvm_traits::FoldHashMap as HashMap;
28use std::fmt::Debug;
29
30impl<T, I, C> GeneralizedTableau<T, I, C>
31where
32 T: Config,
33 <<T as Config>::Storage as BitView>::Store: PrimInt,
34 C: SparseVector<Complex<T::Coeff>, I> + Debug,
35 T::Coeff:
36 One + Zero + Clone + num::Num + ToPrimitive + Debug + std::ops::Mul<f64> + PartialOrd<f64>,
37 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
38 + From<Complex64>
39 + std::ops::MulAssign
40 + std::ops::AddAssign
41 + One
42 + ComplexFloat
43 + Copy,
44 I: TableauIndex + Debug,
45{
46 pub fn expectation<W: PauliWordTrait>(&self, word: &W) -> f64 {
53 let (phase, stab_anticomm, destab_anticomm) = self.compute_decomposition_word(word);
54 if stab_anticomm == I::zero() {
55 let entries: Vec<(Complex<T::Coeff>, I)> = self.coefficients.iter().copied().collect();
56 Self::compute_overlap_case_b(&entries, phase, destab_anticomm)
57 } else {
58 let coeff_map: HashMap<I, Complex<T::Coeff>> =
59 self.coefficients.iter().map(|&(c, i)| (i, c)).collect();
60 let odd_phase_mask = self.odd_phase_destabilizer_mask();
61 Self::compute_overlap_case_a(
62 &coeff_map,
63 phase,
64 destab_anticomm,
65 stab_anticomm,
66 odd_phase_mask,
67 )
68 }
69 }
70
71 pub fn trace(&self, pattern: &PauliPattern) -> f64 {
78 let mut sum = 0.0f64;
79 for word in pattern.enumerate_matches::<T::Storage>(self.n_qubits()) {
80 sum += self.expectation(&word);
81 }
82 sum
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use ppvm_pauli_sum::config::fxhash::ByteF64;
90
91 type TestTableau = GeneralizedTableau<ByteF64<1>>;
92
93 fn word(s: &str) -> PauliWord<u64> {
94 s.into()
95 }
96
97 fn assert_close(actual: f64, expected: f64, tol: f64) {
98 assert!(
99 (actual - expected).abs() < tol,
100 "expected {expected}, got {actual} (|Δ| = {})",
101 (actual - expected).abs()
102 );
103 }
104
105 #[test]
108 fn expectation_z_on_zero_state_is_one() {
109 let tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
110 assert_close(tab.expectation(&word("Z")), 1.0, 1e-12);
111 }
112
113 #[test]
114 fn expectation_x_on_zero_state_is_zero() {
115 let tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
116 assert_close(tab.expectation(&word("X")), 0.0, 1e-12);
117 }
118
119 #[test]
120 fn expectation_identity_on_zero_state_is_one() {
121 let tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
122 assert_close(tab.expectation(&word("I")), 1.0, 1e-12);
123 }
124
125 #[test]
126 fn expectation_x_on_plus_state_is_one() {
127 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
128 tab.h(0);
129 assert_close(tab.expectation(&word("X")), 1.0, 1e-12);
130 assert_close(tab.expectation(&word("Z")), 0.0, 1e-12);
131 }
132
133 fn bell() -> TestTableau {
136 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
137 tab.h(0);
138 tab.cnot(0, 1);
139 tab
140 }
141
142 #[test]
143 fn bell_state_pauli_expectations() {
144 let tab = bell();
145 assert_close(tab.expectation(&word("II")), 1.0, 1e-12);
146 assert_close(tab.expectation(&word("ZZ")), 1.0, 1e-12);
147 assert_close(tab.expectation(&word("XX")), 1.0, 1e-12);
148 assert_close(tab.expectation(&word("YY")), -1.0, 1e-12);
149 assert_close(tab.expectation(&word("IZ")), 0.0, 1e-12);
151 assert_close(tab.expectation(&word("ZI")), 0.0, 1e-12);
152 assert_close(tab.expectation(&word("XZ")), 0.0, 1e-12);
153 assert_close(tab.expectation(&word("YX")), 0.0, 1e-12);
154 }
155
156 #[test]
159 fn ghz_state_expectations() {
160 let mut tab: TestTableau = GeneralizedTableau::new(3, 1e-12);
161 tab.h(0);
162 tab.cnot(0, 1);
163 tab.cnot(1, 2);
164 assert_close(tab.expectation(&word("III")), 1.0, 1e-12); assert_close(tab.expectation(&word("ZZZ")), 0.0, 1e-12); assert_close(tab.expectation(&word("ZIZ")), 1.0, 1e-12); assert_close(tab.expectation(&word("ZZI")), 1.0, 1e-12); assert_close(tab.expectation(&word("IZI")), 0.0, 1e-12); assert_close(tab.expectation(&word("XXX")), 1.0, 1e-12);
174 assert_close(tab.expectation(&word("YYY")), 0.0, 1e-12);
176 }
177
178 #[test]
181 fn ry_rotation_z_expectation_is_cos_theta() {
182 for theta in [0.0, 0.3, 1.0, std::f64::consts::PI / 2.0] {
184 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
185 tab.ry(0, theta);
186 assert_close(tab.expectation(&word("Z")), theta.cos(), 1e-12);
187 assert_close(tab.expectation(&word("X")), theta.sin(), 1e-12);
188 }
189 }
190
191 #[test]
203 fn t_plus_x_expectation_is_cos_pi_over_4() {
204 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
205 tab.h(0);
206 tab.t(0);
207 assert_close(
208 tab.expectation(&word("X")),
209 std::f64::consts::FRAC_1_SQRT_2,
210 1e-12,
211 );
212 }
213
214 #[test]
215 fn t_plus_y_expectation_is_sin_pi_over_4() {
216 let mut tab: TestTableau = GeneralizedTableau::new(1, 1e-12);
217 tab.h(0);
218 tab.t(0);
219 assert_close(
220 tab.expectation(&word("Y")),
221 std::f64::consts::FRAC_1_SQRT_2,
222 1e-12,
223 );
224 }
225
226 #[test]
229 fn trace_of_z_or_identity_pattern_on_bell_is_two() {
230 let tab = bell();
235 let pat = PauliPattern::parse("Z?{2}").expect("parse Z?{2}");
236 assert_close(tab.trace(&pat), 2.0, 1e-12);
237 }
238
239 #[test]
240 fn trace_of_y_or_identity_pattern_on_bell_is_zero() {
241 let tab = bell();
248 let pat = PauliPattern::parse("Y?{2}").expect("parse Y?{2}");
249 assert_close(tab.trace(&pat), 0.0, 1e-12);
250 }
251
252 #[test]
253 fn trace_of_positional_pattern_on_bell_matches_single_pauli() {
254 let tab = bell();
256 let pat = PauliPattern::parse("Z0Z1").expect("parse Z0Z1");
257 assert_close(tab.trace(&pat), 1.0, 1e-12);
258 }
259
260 #[test]
271 fn forward_shots_match_backward_pauli_sum_under_depolarizing_noise() {
272 use ppvm_pauli_sum::config::indexmap::ByteFxHashF64;
273
274 let p = 0.05_f64;
275 let n_shots: u64 = 4000;
276 let n_qubits = 2;
277
278 let mut sum = 0.0_f64;
279 for shot in 0..n_shots {
280 let mut tab: TestTableau = GeneralizedTableau::new_with_seed(n_qubits, 1e-12, shot);
281 tab.h(0);
282 tab.depolarize1(0, p);
283 tab.cnot(0, 1);
284 tab.depolarize1(0, p);
285 tab.depolarize1(1, p);
286 sum += tab.expectation(&word("ZZ"));
287 }
288 let avg = sum / (n_shots as f64);
289
290 let mut ps: PauliSum<ByteFxHashF64<1>> = PauliSum::builder().n_qubits(n_qubits).build();
291 ps += ("ZZ", 1.0);
292 ps.depolarize1(1, p);
293 ps.depolarize1(0, p);
294 ps.cnot(0, 1);
295 ps.depolarize1(0, p);
296 ps.h(0);
297 let z_or_i = PauliPattern::parse("Z?{2}").expect("parse Z?{2}");
298 let exact = ps.trace(&z_or_i);
299
300 let tol = 5.0 / (n_shots as f64).sqrt();
302 assert!(
303 (avg - exact).abs() < tol,
304 "tableau avg {avg} vs PauliSum exact {exact}, |Δ|={} (tol {tol})",
305 (avg - exact).abs()
306 );
307 }
308}