paulistrings/channel/
rotation.rs1use super::prepared::{PrepareToken, Prepared, PreparedRotation, MAX_LOCAL_SUPPORT};
4use super::{Channel, OutputBuffer};
5use crate::pauli_string::PauliString;
6use crate::pauli_sum::hash::Gf2Hash;
7use crate::phase::Phase;
8use num_complex::Complex64;
9
10#[derive(Clone, Debug)]
14pub struct PauliRotation<const W: usize> {
15 generator: PauliString<W>,
16 theta: f64,
18}
19
20impl<const W: usize> PauliRotation<W> {
21 pub fn new(generator: PauliString<W>, theta: f64) -> Self {
23 Self { generator, theta }
24 }
25
26 pub fn generator(&self) -> PauliString<W> {
28 self.generator
29 }
30
31 pub fn theta(&self) -> f64 {
33 self.theta
34 }
35
36 pub fn weight(&self) -> usize {
38 self.generator.weight() as usize
39 }
40
41 fn apply_with_theta(
43 &self,
44 theta: f64,
45 input_x: &[u64; W],
46 input_z: &[u64; W],
47 coeff: Complex64,
48 out: &mut OutputBuffer<W>,
49 ) {
50 let input = PauliString::<W> { x: *input_x, z: *input_z };
51 if input.commutes_with(&self.generator) {
52 out.push(*input_x, *input_z, coeff);
53 return;
54 }
55
56 let (sin, cos) = sin_cos(theta);
57
58 out.push(*input_x, *input_z, coeff * cos);
59
60 let mut product = input;
62 let phase = product.mul_assign(&self.generator);
63 let total_phase = Phase::I + phase;
64 out.push(product.x, product.z, total_phase.apply(coeff) * sin);
65 }
66}
67
68fn sin_cos(theta: f64) -> (f64, f64) {
70 const SNAP: f64 = 1e-15;
71 match theta.sin_cos() {
72 (s, c) if s.abs() < SNAP => (0.0, c.signum()),
73 (s, c) if c.abs() < SNAP => (s.signum(), 0.0),
74 sc => sc,
75 }
76}
77
78impl<const W: usize> Channel<W> for PauliRotation<W> {
79 fn support(&self) -> [u64; W] {
80 core::array::from_fn(|w| self.generator.x[w] | self.generator.z[w])
81 }
82
83 fn apply(
84 &self,
85 input_x: &[u64; W],
86 input_z: &[u64; W],
87 coeff: Complex64,
88 out: &mut OutputBuffer<W>,
89 ) {
90 self.apply_with_theta(self.theta, input_x, input_z, coeff, out);
91 }
92
93 fn apply_adjoint(
94 &self,
95 input_x: &[u64; W],
96 input_z: &[u64; W],
97 coeff: Complex64,
98 out: &mut OutputBuffer<W>,
99 ) {
100 self.apply_with_theta(-self.theta, input_x, input_z, coeff, out);
101 }
102
103 fn prepare_for_layer(
105 &self,
106 hash: &Gf2Hash<W>,
107 adjoint: bool,
108 _: PrepareToken,
109 ) -> Option<Prepared<W>> {
110 if self.weight() <= MAX_LOCAL_SUPPORT {
111 return Prepared::derive_local(self, hash, adjoint);
112 }
113 let (sin, cos) = sin_cos(if adjoint { -self.theta } else { self.theta });
114 let generator = self.generator;
115 Some(Prepared::Rotation(PreparedRotation {
116 generator,
117 cos,
118 sin,
119 bucket_delta_identity: 0,
120 bucket_delta_generator: hash.bucket_of(&generator.x, &generator.z),
121 }))
122 }
123}
124
125#[cfg(test)]
126mod tests;