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}