paulistrings/channel/ptm/
clifford.rs

1//! The Clifford constructors of [`PtmChannel`], stored as signed permutations.
2
3use super::{assert_distinct, PtmChannel, PtmKind, PtmStorage};
4use crate::channel::prepared::LOCAL_DIM;
5use crate::channel::{read_pauli, write_pauli};
6use crate::pauli_string::PauliString;
7use crate::phase::Phase;
8
9impl PtmChannel {
10    /// Hadamard: `X → Z, Z → X, Y → −Y`.
11    pub fn hadamard(qubit: usize) -> Self {
12        Self::clifford_1q(qubit, [0, 2, 1, 3], 0b1000, PtmKind::Hadamard)
13    }
14
15    /// Phase gate `S = diag(1, i)`: `X → Y, Z → Z, Y → −X`.
16    pub fn phase_gate(qubit: usize) -> Self {
17        Self::clifford_1q(qubit, [0, 3, 2, 1], 0b1000, PtmKind::PhaseGate)
18    }
19
20    /// Pauli-X gate: `X → X, Z → −Z, Y → −Y`.
21    pub fn pauli_x(qubit: usize) -> Self {
22        Self::clifford_1q(qubit, [0, 1, 2, 3], 0b1100, PtmKind::PauliX)
23    }
24
25    /// Pauli-Y gate: `X → −X, Z → −Z, Y → Y`.
26    pub fn pauli_y(qubit: usize) -> Self {
27        Self::clifford_1q(qubit, [0, 1, 2, 3], 0b0110, PtmKind::PauliY)
28    }
29
30    /// Pauli-Z gate: `X → −X, Z → Z, Y → −Y`.
31    pub fn pauli_z(qubit: usize) -> Self {
32        Self::clifford_1q(qubit, [0, 1, 2, 3], 0b1010, PtmKind::PauliZ)
33    }
34
35    /// CNOT: `X⊗I → X⊗X, I⊗X → I⊗X, Z⊗I → Z⊗I, I⊗Z → Z⊗Z`, control first.
36    ///
37    /// # Panics
38    ///
39    /// If `control == target`.
40    pub fn cnot(control: usize, target: usize) -> Self {
41        assert_distinct("cnot", control, target);
42        Self::clifford_2q(
43            [control, target],
44            PtmKind::Cnot,
45            [pack(1, 0, 1, 0), pack(0, 1, 0, 0), pack(0, 0, 1, 0), pack(0, 1, 0, 1)],
46        )
47    }
48
49    /// CZ: `X⊗I → X⊗Z, I⊗X → Z⊗X, Z⊗I → Z⊗I, I⊗Z → I⊗Z`.
50    ///
51    /// # Panics
52    ///
53    /// If `q0 == q1`.
54    pub fn cz(q0: usize, q1: usize) -> Self {
55        assert_distinct("cz", q0, q1);
56        Self::clifford_2q(
57            [q0, q1],
58            PtmKind::Cz,
59            [pack(1, 0, 0, 1), pack(0, 1, 0, 0), pack(0, 1, 1, 0), pack(0, 0, 0, 1)],
60        )
61    }
62
63    /// SWAP: `P⊗Q → Q⊗P`.
64    ///
65    /// # Panics
66    ///
67    /// If `q0 == q1`.
68    pub fn swap(q0: usize, q1: usize) -> Self {
69        assert_distinct("swap", q0, q1);
70        Self::clifford_2q(
71            [q0, q1],
72            PtmKind::Swap,
73            [pack(0, 0, 1, 0), pack(0, 0, 0, 1), pack(1, 0, 0, 0), pack(0, 1, 0, 0)],
74        )
75    }
76
77    fn clifford_1q(qubit: usize, image: [u8; 4], negated: u16, kind: PtmKind) -> Self {
78        let mut padded = [0u8; LOCAL_DIM];
79        padded[..4].copy_from_slice(&image);
80        Self::new(&[qubit], PtmStorage::SignedPermutation { image: padded, negated }, kind)
81    }
82
83    /// The signed permutation from the images of `X₀, Z₀, X₁, Z₁`, multiplied out per input.
84    fn clifford_2q(qubits: [usize; 2], kind: PtmKind, generator_images: [u8; 4]) -> Self {
85        let mut image = [0u8; LOCAL_DIM];
86        let mut negated = 0u16;
87        for (index, slot) in image.iter_mut().enumerate() {
88            // The image of `X^{x0} Z^{z0} X^{x1} Z^{z1}`; each input `Y = i · X · Z` adds an `i`.
89            let mut product = PauliString::<1>::identity();
90            let mut phase = Phase::ONE;
91            for (generator, &generator_image) in generator_images.iter().enumerate() {
92                if (index >> generator) & 1 == 1 {
93                    phase += product.mul_assign(&unpack(generator_image));
94                }
95            }
96            let y_count =
97                ((index & 1) & ((index >> 1) & 1)) + (((index >> 2) & 1) & ((index >> 3) & 1));
98            phase += Phase::new(y_count as u8);
99            debug_assert!(
100                phase == Phase::ONE || phase == Phase::MINUS_ONE,
101                "a Clifford maps a Hermitian Pauli to ± a Hermitian Pauli"
102            );
103            *slot = (read_pauli(&product.x, &product.z, 0, 0)
104                | (read_pauli(&product.x, &product.z, 0, 1) << 2)) as u8;
105            if phase == Phase::MINUS_ONE {
106                negated |= 1 << index;
107            }
108        }
109        Self::new(&qubits, PtmStorage::SignedPermutation { image, negated }, kind)
110    }
111}
112
113/// Pack `(x0, z0, x1, z1)` into a two-qubit Pauli index.
114const fn pack(x0: u8, z0: u8, x1: u8, z1: u8) -> u8 {
115    x0 | (z0 << 1) | (x1 << 2) | (z1 << 3)
116}
117
118/// A two-qubit Pauli index as a Pauli string on qubits 0 and 1.
119fn unpack(packed: u8) -> PauliString<1> {
120    let mut string = PauliString::<1>::identity();
121    write_pauli(&mut string.x, &mut string.z, 0, 0, 1, (packed & 3) as usize);
122    write_pauli(&mut string.x, &mut string.z, 0, 1, 2, ((packed >> 2) & 3) as usize);
123    string
124}