1use ppvm_traits::FoldHashMap as HashMap;
5
6use bitvec::view::BitView;
7use num::PrimInt;
8use num::{
9 Complex, One, Zero,
10 complex::{Complex64, ComplexFloat},
11};
12
13use crate::prelude::*;
14
15const PAULIS: [Pauli; 4] = [Pauli::I, Pauli::X, Pauli::Z, Pauli::Y];
16
17impl<T: Config, I, C: SparseVector<Complex<T::Coeff>, I>> RotationTwo<T>
18 for GeneralizedTableau<T, I, C>
19where
20 <<T as Config>::Storage as BitView>::Store: PrimInt,
21 T::Coeff: Zero + One + Send + Sync + num::Num + PartialOrd,
22 I: TableauIndex + Send + Sync,
23 Complex<T::Coeff>: std::ops::Mul<Output = Complex<T::Coeff>>
24 + std::ops::AddAssign
25 + From<Complex64>
26 + ComplexFloat
27 + Copy,
28{
29 fn rotate_2(
30 &mut self,
31 axis_a: [u8; 2],
32 axis_b: [u8; 2],
33 a: usize,
34 b: usize,
35 theta: <T as Config>::Coeff,
36 ) {
37 let [axis_a_x, axis_a_z] = axis_a;
38 let [axis_b_x, axis_b_z] = axis_b;
39 let pauli_a = PAULIS[(axis_a_z << 1 | axis_a_x) as usize];
40 let pauli_b = PAULIS[(axis_b_z << 1 | axis_b_x) as usize];
41 if self.is_inactive(a) {
43 return self.rotate_1(pauli_b, b, theta);
44 } else if self.is_inactive(b) {
45 return self.rotate_1(pauli_a, a, theta);
46 }
47
48 let (sin, cos) = (theta * 0.5.into()).sin_cos();
49
50 let complex_cos: Complex<T::Coeff> = Complex {
51 re: cos,
52 im: T::Coeff::zero(),
53 };
54
55 let i_complex_sin: Complex<T::Coeff> = Complex {
56 re: T::Coeff::zero(),
57 im: -sin,
58 };
59
60 let mut branch_coefficients = self.coefficients.clone();
61 self.compute_coefficients_after_pauli_apply(&mut branch_coefficients, b, pauli_b);
62 self.compute_coefficients_after_pauli_apply(&mut branch_coefficients, a, pauli_a);
63
64 let old_coefficients = std::mem::replace(&mut self.coefficients, C::new());
65 let mut new_coefficients: HashMap<I, Complex<T::Coeff>> = HashMap::default();
66
67 for (coeff, idx) in old_coefficients {
68 *new_coefficients.entry(idx).or_insert(Complex::zero()) += complex_cos * coeff;
69 }
70
71 for (branch_coeff, idx) in branch_coefficients {
72 *new_coefficients.entry(idx).or_insert(Complex::zero()) += i_complex_sin * branch_coeff;
73 }
74
75 let cutoff = Complex {
76 re: self.coefficient_threshold.clone(),
77 im: T::Coeff::zero(),
78 };
79
80 for (idx, coeff) in new_coefficients {
81 if coeff.abs() > cutoff.abs() {
82 self.coefficients.unsafe_insert(idx, coeff);
83 }
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use ppvm_pauli_sum::config::fxhash::ByteF64;
92 use std::f64::consts::{FRAC_PI_2, PI};
93
94 type TestConfig = ByteF64<1>;
95 type TestTableau = GeneralizedTableau<TestConfig>;
96
97 #[test]
101 fn test_rxx_pi_flips_both_to_one() {
102 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
103 tab.rxx(0, 1, PI);
104 assert_eq!(tab.coefficients.len(), 1, "rxx(π) should not branch");
105 assert!(tab.measure(0).unwrap());
106 assert!(tab.measure(1).unwrap());
107 }
108
109 #[test]
111 fn test_rxx_half_pi_branches() {
112 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
113 tab.rxx(0, 1, FRAC_PI_2);
114 assert_eq!(
115 tab.coefficients.len(),
116 2,
117 "rxx(π/2) should create 2 branches"
118 );
119 }
120
121 #[test]
123 fn test_rxx_half_pi_twice_flips_both() {
124 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
125 tab.rxx(0, 1, FRAC_PI_2);
126 tab.rxx(0, 1, FRAC_PI_2);
127 assert!(tab.measure(0).unwrap());
128 assert!(tab.measure(1).unwrap());
129 }
130
131 #[test]
136 fn test_ryy_pi_flips_both_to_one() {
137 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
138 tab.ryy(0, 1, PI);
139 assert_eq!(tab.coefficients.len(), 1, "ryy(π) should not branch");
140 assert!(tab.measure(0).unwrap());
141 assert!(tab.measure(1).unwrap());
142 }
143
144 #[test]
146 fn test_ryy_half_pi_branches() {
147 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
148 tab.ryy(0, 1, FRAC_PI_2);
149 assert_eq!(
150 tab.coefficients.len(),
151 2,
152 "ryy(π/2) should create 2 branches"
153 );
154 }
155
156 #[test]
158 fn test_ryy_half_pi_twice_flips_both() {
159 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
160 tab.ryy(0, 1, FRAC_PI_2);
161 tab.ryy(0, 1, FRAC_PI_2);
162 assert!(tab.measure(0).unwrap());
163 assert!(tab.measure(1).unwrap());
164 }
165
166 #[test]
171 fn test_rzz_pi_leaves_00() {
172 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
173 tab.rzz(0, 1, PI);
174 assert_eq!(
175 tab.coefficients.len(),
176 1,
177 "rzz(π) on |00⟩ should not branch"
178 );
179 assert!(!tab.measure(0).unwrap());
180 assert!(!tab.measure(1).unwrap());
181 }
182
183 #[test]
185 fn test_rzz_does_not_branch_on_comp_basis() {
186 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
187 tab.rzz(0, 1, FRAC_PI_2);
188 assert_eq!(
189 tab.coefficients.len(),
190 1,
191 "rzz(π/2) on |00⟩ should not branch: ZZ is a stabilizer"
192 );
193 }
194
195 #[test]
197 fn test_rzz_pi_on_10() {
198 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
199 tab.x(0);
200 tab.rzz(0, 1, PI);
201 assert_eq!(
202 tab.coefficients.len(),
203 1,
204 "rzz(π) on |10⟩ should not branch"
205 );
206 assert!(tab.measure(0).unwrap());
207 assert!(!tab.measure(1).unwrap());
208 }
209
210 #[test]
215 fn test_rxx_half_pi_correlated_measurements() {
216 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
217 tab.rxx(0, 1, FRAC_PI_2);
218 assert_eq!(
219 tab.coefficients.len(),
220 2,
221 "rxx(π/2) should create 2 branches"
222 );
223 let r0 = tab.measure(0);
224 let r1 = tab.measure(1);
225 assert_eq!(
226 r0, r1,
227 "rxx(π/2)|00⟩ = (|00⟩-i|11⟩)/√2: measurements must agree"
228 );
229 }
230
231 #[test]
234 fn test_rxx_half_pi_on_10_anticorrelated_measurements() {
235 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
236 tab.x(0);
237 tab.rxx(0, 1, FRAC_PI_2);
238 assert_eq!(
239 tab.coefficients.len(),
240 2,
241 "rxx(π/2) on |10⟩ should create 2 branches"
242 );
243 let r0 = tab.measure(0);
244 let r1 = tab.measure(1);
245 assert_ne!(
246 r0, r1,
247 "rxx(π/2)|10⟩ = (|10⟩-i|01⟩)/√2: measurements must differ"
248 );
249 }
250
251 #[test]
254 fn test_ryy_half_pi_correlated_measurements() {
255 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
256 tab.ryy(0, 1, FRAC_PI_2);
257 assert_eq!(
258 tab.coefficients.len(),
259 2,
260 "ryy(π/2) should create 2 branches"
261 );
262 let r0 = tab.measure(0);
263 let r1 = tab.measure(1);
264 assert_eq!(
265 r0, r1,
266 "ryy(π/2)|00⟩ = (|00⟩+i|11⟩)/√2: measurements must agree"
267 );
268 }
269
270 #[test]
274 fn test_rzz_half_pi_on_plus_plus_branches_and_correlates() {
275 let mut tab: TestTableau = GeneralizedTableau::new(2, 1e-12);
276 tab.h(0);
277 tab.h(1);
278 tab.rzz(0, 1, FRAC_PI_2);
279 assert_eq!(
280 tab.coefficients.len(),
281 2,
282 "rzz(π/2) on |++⟩ should create 2 branches"
283 );
284 tab.h(0);
285 tab.h(1);
286 let r0 = tab.measure(0);
288 let r1 = tab.measure(1);
289 assert_eq!(r0, r1, "H·rzz(π/2)·H = rxx(π/2): measurements must agree");
290 }
291}