1use super::rot1::rotate_1_map_insert_closure;
5use crate::sum::PauliSum;
6use ppvm_traits::char::Pauli;
7use ppvm_traits::config::Config;
8use ppvm_traits::traits::*;
9
10const PAULIS: [Pauli; 4] = [Pauli::I, Pauli::X, Pauli::Z, Pauli::Y];
11
12impl<T: Config> RotationTwo<T> for PauliSum<T>
13where
14 T::Coeff: std::ops::MulAssign,
15 T::Map: ACMapInsert<T::Storage, T::Coeff, T::BuildHasher, T::PauliWordType> + ACMapConsume,
16{
17 fn rotate_2(&mut self, axis_a: [u8; 2], axis_b: [u8; 2], a: usize, b: usize, theta: T::Coeff) {
18 let [axis_a_x, axis_a_z] = axis_a;
19 let [axis_b_x, axis_b_z] = axis_b;
20 if axis_a_x > 3 || axis_a_z > 3 || axis_b_x > 3 || axis_b_z > 3 {
21 panic!("Rotation axis cannot be L");
22 }
23 let (sin, cos) = theta.sin_cos();
24 let pauli_a = PAULIS[(axis_a_z << 1 | axis_a_x) as usize];
25 let pauli_b = PAULIS[(axis_b_z << 1 | axis_b_x) as usize];
26 self.map_insert(|k, v| {
27 if k.get_lbit(a) {
29 return rotate_1_map_insert_closure::<T>(k, v, pauli_b, b, &sin, &cos);
31 }
32 if k.get_lbit(b) {
33 return rotate_1_map_insert_closure::<T>(k, v, pauli_a, a, &sin, &cos);
35 }
36 let (eps, x_a, z_a, x_b, z_b) = comm_2(
37 axis_a,
38 axis_b,
39 [k.get_xbit(a) as u8, k.get_zbit(a) as u8],
40 [k.get_xbit(b) as u8, k.get_zbit(b) as u8],
41 );
42
43 if eps == 0 {
44 None
45 } else {
46 let mut coeff = v.clone();
47 *v *= cos.clone();
48
49 let mut new_word = k.clone();
50 new_word.set_xbit(a, x_a == 1);
51 new_word.set_xbit(b, x_b == 1);
52 new_word.set_zbit(a, z_a == 1);
53 new_word.set_zbit(b, z_b == 1);
54 new_word.rehash();
55
56 coeff *= sin.mul_sign(-eps);
57 Some((new_word, coeff))
58 }
59 });
60 }
61
62 #[inline]
63 fn rzz(&mut self, a: usize, b: usize, theta: impl Into<T::Coeff>) {
64 let (sin, cos) = theta.into().sin_cos();
65 self.map_insert(|k, v| {
66 if k.get_lbit(a) {
71 return rotate_1_map_insert_closure::<T>(k, v, Pauli::Z, b, &sin, &cos);
72 }
73 if k.get_lbit(b) {
74 return rotate_1_map_insert_closure::<T>(k, v, Pauli::Z, a, &sin, &cos);
75 }
76 let xa = k.get_xbit(a);
77 let xb = k.get_xbit(b);
78 if xa == xb {
80 return None;
81 }
82 let za = k.get_zbit(a);
83 let zb = k.get_zbit(b);
84 let z_anti = if xa { za } else { zb };
87 let eps: i8 = if z_anti { 1 } else { -1 };
88
89 let mut coeff = v.clone();
90 *v *= cos.clone();
91
92 let mut new_word = k.clone();
93 new_word.set_zbit(a, !za);
94 new_word.set_zbit(b, !zb);
95 new_word.rehash();
96
97 coeff *= sin.mul_sign(eps);
98 Some((new_word, coeff))
99 });
100 }
101
102 #[inline]
103 fn rxx(&mut self, a: usize, b: usize, theta: impl Into<T::Coeff>) {
104 let (sin, cos) = theta.into().sin_cos();
105 self.map_insert(|k, v| {
106 if k.get_lbit(a) {
110 return rotate_1_map_insert_closure::<T>(k, v, Pauli::X, b, &sin, &cos);
111 }
112 if k.get_lbit(b) {
113 return rotate_1_map_insert_closure::<T>(k, v, Pauli::X, a, &sin, &cos);
114 }
115 let za = k.get_zbit(a);
116 let zb = k.get_zbit(b);
117 if za == zb {
120 return None;
121 }
122 let xa = k.get_xbit(a);
123 let xb = k.get_xbit(b);
124 let x_anti = if za { xa } else { xb };
127 let eps: i8 = if x_anti { -1 } else { 1 };
128
129 let mut coeff = v.clone();
130 *v *= cos.clone();
131
132 let mut new_word = k.clone();
133 new_word.set_xbit(a, !xa);
134 new_word.set_xbit(b, !xb);
135 new_word.rehash();
136
137 coeff *= sin.mul_sign(eps);
138 Some((new_word, coeff))
139 });
140 }
141
142 #[inline]
143 fn ryy(&mut self, a: usize, b: usize, theta: impl Into<T::Coeff>) {
144 let (sin, cos) = theta.into().sin_cos();
145 self.map_insert(|k, v| {
146 if k.get_lbit(a) {
150 return rotate_1_map_insert_closure::<T>(k, v, Pauli::Y, b, &sin, &cos);
151 }
152 if k.get_lbit(b) {
153 return rotate_1_map_insert_closure::<T>(k, v, Pauli::Y, a, &sin, &cos);
154 }
155 let xa = k.get_xbit(a);
156 let za = k.get_zbit(a);
157 let xb = k.get_xbit(b);
158 let zb = k.get_zbit(b);
159 let pa = xa ^ za;
162 let pb = xb ^ zb;
163 if pa == pb {
164 return None;
165 }
166 let x_anti = if pa { xa } else { xb };
169 let eps: i8 = if x_anti { 1 } else { -1 };
170
171 let mut coeff = v.clone();
172 *v *= cos.clone();
173
174 let mut new_word = k.clone();
175 new_word.set_xbit(a, !xa);
176 new_word.set_zbit(a, !za);
177 new_word.set_xbit(b, !xb);
178 new_word.set_zbit(b, !zb);
179 new_word.rehash();
180
181 coeff *= sin.mul_sign(eps);
182 Some((new_word, coeff))
183 });
184 }
185}
186
187#[inline(always)]
212pub fn comm_2(q0: [u8; 2], q1: [u8; 2], p0: [u8; 2], p1: [u8; 2]) -> (i8, u8, u8, u8, u8) {
213 let [x_a, z_a] = q0;
214 let [x_b, z_b] = q1;
215 let [x_c, z_c] = p0;
216 let [x_d, z_d] = p1;
217 let a0 = (x_a & z_c) ^ (z_a & x_c); let a1 = (x_b & z_d) ^ (z_b & x_d); let present = a0 ^ a1; const SIGN_NEG: u16 = 0x2840; let idx0 = (z_a << 3) | (x_a << 2) | (z_c << 1) | x_c; let idx1 = (z_b << 3) | (x_b << 2) | (z_d << 1) | x_d; let neg0 = (((SIGN_NEG >> idx0) as u8) & 1) & a0; let neg1 = (((SIGN_NEG >> idx1) as u8) & 1) & a1;
233
234 let coeff = (((1 - ((neg0 as i8) << 1)) * (a0 as i8))
236 + ((1 - ((neg1 as i8) << 1)) * (a1 as i8)))
237 * (present as i8); let x_out0 = (x_a ^ x_c) & present;
241 let z_out0 = (z_a ^ z_c) & present;
242 let x_out1 = (x_b ^ x_d) & present;
243 let z_out1 = (z_b ^ z_d) & present;
244
245 (coeff, x_out0, z_out0, x_out1, z_out1)
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::config::fxhash::ByteF64;
252
253 type C = ByteF64<2>;
254 const PAULI_CHARS: [char; 4] = ['I', 'X', 'Y', 'Z'];
255
256 fn sum_with(word: &str) -> PauliSum<C> {
257 let mut s: PauliSum<C> = PauliSum::builder().n_qubits(2).build();
258 s += (word, 1.0);
259 s
260 }
261
262 fn assert_matches_generic(
267 axis: [u8; 2],
268 special: impl Fn(&mut PauliSum<C>, usize, usize, f64),
269 ) {
270 let theta = 0.7_f64;
271 for &p in &PAULI_CHARS {
272 for &q in &PAULI_CHARS {
273 let word = format!("{p}{q}");
274
275 let mut got = sum_with(&word);
276 special(&mut got, 0, 1, theta);
277
278 let mut want = sum_with(&word);
279 want.rotate_2(axis, axis, 0, 1, theta);
280
281 assert_eq!(got, want, "fast path disagrees with rotate_2 for {word}");
282 }
283 }
284 }
285
286 #[test]
287 fn rxx_matches_generic() {
288 assert_matches_generic([1, 0], |s, a, b, t| s.rxx(a, b, t));
289 }
290
291 #[test]
292 fn ryy_matches_generic() {
293 assert_matches_generic([1, 1], |s, a, b, t| s.ryy(a, b, t));
294 }
295
296 #[test]
297 fn rzz_matches_generic() {
298 assert_matches_generic([0, 1], |s, a, b, t| s.rzz(a, b, t));
299 }
300
301 #[test]
304 fn rzz_explicit_values() {
305 let t = 0.7_f64;
306
307 let mut s = sum_with("XI");
309 s.rzz(0, 1, t);
310 let mut want: PauliSum<C> = PauliSum::builder().n_qubits(2).build();
311 want += ("XI", t.cos());
312 want += ("YZ", -t.sin());
313 assert_eq!(s, want);
314
315 let mut s = sum_with("YI");
317 s.rzz(0, 1, t);
318 let mut want: PauliSum<C> = PauliSum::builder().n_qubits(2).build();
319 want += ("YI", t.cos());
320 want += ("XZ", t.sin());
321 assert_eq!(s, want);
322
323 let mut s = sum_with("ZZ");
325 s.rzz(0, 1, t);
326 assert_eq!(s, sum_with("ZZ"));
327 }
328
329 #[test]
332 fn rzz_non_adjacent_addressing() {
333 let theta = 0.7_f64;
334 for &p in &PAULI_CHARS {
335 for &q in &PAULI_CHARS {
336 let word = format!("{p}I{q}");
338
339 let mut got: PauliSum<C> = PauliSum::builder().n_qubits(3).build();
340 got += (word.as_str(), 1.0);
341 got.rzz(0, 2, theta);
342
343 let mut want: PauliSum<C> = PauliSum::builder().n_qubits(3).build();
344 want += (word.as_str(), 1.0);
345 want.rotate_2([0, 1], [0, 1], 0, 2, theta);
346
347 assert_eq!(got, want, "rzz(0,2) disagrees with rotate_2 for {word}");
348 }
349 }
350 }
351}