Skip to main content

bloq_circuit/
gate.rs

1use bloq_utils::PauliBasis;
2use strum::{Display, EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
3
4/// A quantum gate, named after its Stim mnemonic.
5///
6/// Variant names follow Stim's gate set. `strum` aliases map the common
7/// synonyms (e.g. `CNOT` and `ZCX` both parse to [`GateType::CX`], and `R`
8/// renders/parses [`GateType::RZ`]).
9#[expect(
10    non_camel_case_types,
11    reason = "variants preserve canonical Stim gate mnemonics"
12)]
13#[derive(
14    Debug,
15    Clone,
16    Copy,
17    PartialEq,
18    Eq,
19    Hash,
20    EnumIter,
21    EnumString,
22    Display,
23    IntoStaticStr,
24    PartialOrd,
25    Ord,
26    serde::Serialize,
27    serde::Deserialize,
28)]
29pub enum GateType {
30    // Reset gates
31    /// X-basis reset.
32    RX,
33    /// Y-basis reset.
34    RY,
35    /// Z-basis reset (`R` in Stim text).
36    #[strum(serialize = "RZ", to_string = "R")]
37    RZ,
38    // Controlled gates
39    /// X-controlled X.
40    XCX,
41    /// X-controlled Y.
42    XCY,
43    /// X-controlled Z.
44    XCZ,
45    /// Y-controlled X.
46    YCX,
47    /// Y-controlled Y.
48    YCY,
49    /// Y-controlled Z.
50    YCZ,
51    /// Z-controlled X.
52    #[strum(to_string = "CX", serialize = "ZCX", serialize = "CNOT")]
53    CX,
54    /// Z-controlled Y.
55    #[strum(to_string = "CY", serialize = "ZCY")]
56    CY,
57    /// Z-controlled Z.
58    #[strum(to_string = "CZ", serialize = "ZCZ")]
59    CZ,
60    // Hadamard-like gates
61    /// Exchanges X and Z axes.
62    #[strum(to_string = "H", serialize = "H_XZ")]
63    H,
64    /// Exchanges X and Y axes.
65    H_XY,
66    /// Exchanges Y and Z axes.
67    H_YZ,
68    /// Negated X/Y-axis exchange.
69    H_NXY,
70    /// Negated X/Z-axis exchange.
71    H_NXZ,
72    /// Negated Y/Z-axis exchange.
73    H_NYZ,
74    // Pauli gates
75    /// Identity.
76    I,
77    /// Pauli X.
78    X,
79    /// Pauli Y.
80    Y,
81    /// Pauli Z.
82    Z,
83    // Period 4 gates
84    /// Positive square root of X.
85    SQRT_X,
86    /// Inverse square root of X.
87    SQRT_X_DAG,
88    /// Positive square root of Y.
89    SQRT_Y,
90    /// Inverse square root of Y.
91    SQRT_Y_DAG,
92    /// Positive square root of Z.
93    #[strum(to_string = "S", serialize = "SQRT_Z")]
94    S,
95    /// Inverse square root of Z.
96    #[strum(to_string = "S_DAG", serialize = "SQRT_Z_DAG")]
97    S_DAG,
98    // Period 3 gates (order-3 single-qubit Cliffords)
99    /// Cycles X to Y to Z.
100    C_XYZ,
101    /// Cycles Z to Y to X.
102    C_ZYX,
103    /// Negated X/Y/Z cycle.
104    C_NXYZ,
105    /// X/negated-Y/Z cycle.
106    C_XNYZ,
107    /// X/Y/negated-Z cycle.
108    C_XYNZ,
109    /// Negated-X/Z/Y cycle.
110    C_NZYX,
111    /// Z/negated-Y/X cycle.
112    C_ZNYX,
113    /// Z/Y/negated-X cycle.
114    C_ZYNX,
115    // Non-Clifford Gate
116    /// Positive pi/4 rotation about X.
117    T_YZ,
118    /// Negative pi/4 rotation about X.
119    T_YZ_DAG,
120    /// Positive pi/4 rotation about Y.
121    T_XZ,
122    /// Negative pi/4 rotation about Y.
123    T_XZ_DAG,
124    /// Positive pi/4 rotation about Z.
125    #[strum(to_string = "T", serialize = "T_XY")]
126    T,
127    /// Negative pi/4 rotation about Z.
128    #[strum(to_string = "T_DAG", serialize = "T_XY_DAG")]
129    T_DAG,
130}
131
132impl GateType {
133    /// Iterates over every gate. Lets a caller sweep the whole gate set —
134    /// filtered by [`Self::is_reset`], [`Self::is_non_clifford`], and
135    /// [`Self::is_two_qubit_gate`] — without depending on `strum` itself.
136    pub fn iter() -> impl Iterator<Item = Self> {
137        <Self as IntoEnumIterator>::iter()
138    }
139
140    /// Returns `true` for the reset gates ([`RX`](GateType::RX),
141    /// [`RY`](GateType::RY), [`RZ`](GateType::RZ)).
142    pub const fn is_reset(&self) -> bool {
143        matches!(self, GateType::RX | GateType::RY | GateType::RZ)
144    }
145
146    /// Returns `true` for the non-Clifford [`T`](GateType::T)-family gates.
147    pub const fn is_non_clifford(&self) -> bool {
148        matches!(
149            self,
150            GateType::T_YZ
151                | GateType::T_YZ_DAG
152                | GateType::T_XZ
153                | GateType::T_XZ_DAG
154                | GateType::T
155                | GateType::T_DAG
156        )
157    }
158
159    /// Returns `true` for the two-qubit unitary (controlled) gates.
160    pub const fn is_two_qubit_gate(&self) -> bool {
161        matches!(
162            self,
163            GateType::XCX
164                | GateType::XCY
165                | GateType::XCZ
166                | GateType::YCX
167                | GateType::YCY
168                | GateType::YCZ
169                | GateType::CX
170                | GateType::CY
171                | GateType::CZ
172        )
173    }
174
175    /// Maps a non-Clifford gate to the Clifford that shares its axis and sign,
176    /// so stabilizer-flow analysis can treat a [`T`](GateType::T) gate as its
177    /// [`S`](GateType::S) proxy.
178    /// Clifford gates map to themselves.
179    pub const fn clifford_proxy(&self) -> GateType {
180        match self {
181            GateType::T => GateType::S,
182            GateType::T_DAG => GateType::S_DAG,
183            GateType::T_YZ => GateType::SQRT_X,
184            GateType::T_YZ_DAG => GateType::SQRT_X_DAG,
185            GateType::T_XZ => GateType::SQRT_Y,
186            GateType::T_XZ_DAG => GateType::SQRT_Y_DAG,
187            _ => *self,
188        }
189    }
190
191    /// For two-qubit gates in the `ACB` naming convention, returns the (control,
192    /// target) Pauli bases — `CX` (= `ZCX`) is Z-control, X-target. `None` for
193    /// non-two-qubit gates.
194    pub const fn two_qubit_bases(&self) -> Option<(PauliBasis, PauliBasis)> {
195        match self {
196            GateType::CX => Some((PauliBasis::Z, PauliBasis::X)),
197            GateType::CY => Some((PauliBasis::Z, PauliBasis::Y)),
198            GateType::CZ => Some((PauliBasis::Z, PauliBasis::Z)),
199            GateType::XCX => Some((PauliBasis::X, PauliBasis::X)),
200            GateType::XCY => Some((PauliBasis::X, PauliBasis::Y)),
201            GateType::XCZ => Some((PauliBasis::X, PauliBasis::Z)),
202            GateType::YCX => Some((PauliBasis::Y, PauliBasis::X)),
203            GateType::YCY => Some((PauliBasis::Y, PauliBasis::Y)),
204            GateType::YCZ => Some((PauliBasis::Y, PauliBasis::Z)),
205            _ => None,
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn canonical_mnemonics_match_display_and_parse() {
216        for gate in GateType::iter() {
217            let mnemonic: &'static str = gate.into();
218            assert_eq!(mnemonic, gate.to_string());
219            assert_eq!(mnemonic.parse::<GateType>().unwrap(), gate);
220        }
221    }
222}