paulistrings/channel/
rotation.rs

1//! [`PauliRotation`], the rotation `exp(-i·θ·P/2)` about a Pauli generator.
2
3use 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/// A rotation `U = exp(-i · θ · P / 2)` about a Pauli generator `P` of any weight.
11///
12/// The support is derived from the generator: a qubit belongs to it iff `P` is non-identity there.
13#[derive(Clone, Debug)]
14pub struct PauliRotation<const W: usize> {
15    generator: PauliString<W>,
16    /// Radians.
17    theta: f64,
18}
19
20impl<const W: usize> PauliRotation<W> {
21    /// A rotation by `theta` radians about `generator`.
22    pub fn new(generator: PauliString<W>, theta: f64) -> Self {
23        Self { generator, theta }
24    }
25
26    /// The generator `P`.
27    pub fn generator(&self) -> PauliString<W> {
28        self.generator
29    }
30
31    /// The rotation angle in radians.
32    pub fn theta(&self) -> f64 {
33        self.theta
34    }
35
36    /// Number of qubits in the support, i.e. the generator's Pauli weight.
37    pub fn weight(&self) -> usize {
38        self.generator.weight() as usize
39    }
40
41    /// Body of `apply` and `apply_adjoint`, the adjoint being the rotation by `-θ`.
42    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        // `i · sin θ · Q · P`, the leading `i` folded into the product's phase.
61        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
68/// `theta.sin_cos()` with quarter turns snapped to exact `0`/`±1`, so a rotation by `k·π/2` stays fanout 1.
69fn 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    /// The tabulated default up to `MAX_LOCAL_SUPPORT`, `Prepared::Rotation` above it.
104    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;