ppvm_traits/traits/clifford.rs
1// SPDX-FileCopyrightText: 2026 The PPVM Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::traits::PauliWordTrait;
5
6/// The minimal Clifford gate set: the single-qubit Paulis (`X`, `Y`, `Z`),
7/// Hadamard (`H`), phase gate (`S`), and the two entangling Cliffords
8/// `CNOT` and `CZ`.
9///
10/// Implemented by `PauliSum`, by every tableau type, and — via the
11/// blanket impl in this module — by every [`PauliWordTrait`]
12/// implementor.
13///
14/// # Examples
15///
16/// Build the GHZ-preparation circuit on a `PauliSum` (Heisenberg picture,
17/// so gates are applied in reverse). `PauliSum` lives in the downstream
18/// `ppvm-pauli-sum` crate, so this example is `ignore`d here:
19///
20/// ```ignore
21/// use ppvm_pauli_sum::prelude::*;
22///
23/// let mut state: PauliSum<config::indexmap::ByteFxHashF64<1>> =
24/// PauliSum::builder().n_qubits(2).build();
25/// state += ("ZZ", 1.0);
26/// state.cnot(0, 1);
27/// state.h(0);
28/// assert_eq!(state.len(), 1);
29/// ```
30pub trait Clifford {
31 /// Apply Pauli `X` to one qubit.
32 fn x(&mut self, addr0: usize);
33 /// Apply Pauli `Y` to one qubit.
34 fn y(&mut self, addr0: usize);
35 /// Apply Pauli `Z` to one qubit.
36 fn z(&mut self, addr0: usize);
37 /// Apply Hadamard `H` to one qubit.
38 fn h(&mut self, addr0: usize);
39 /// Apply phase gate `S` to one qubit.
40 fn s(&mut self, addr0: usize);
41 /// Apply `CNOT` to one `(control, target)` pair.
42 fn cnot(&mut self, control: usize, target: usize);
43 /// Apply `CZ` to one qubit pair.
44 fn cz(&mut self, addr0: usize, addr1: usize);
45
46 /// stim alias for [`cnot`](Clifford::cnot).
47 fn cx(&mut self, control: usize, target: usize) {
48 self.cnot(control, target)
49 }
50 /// stim alias for [`cnot`](Clifford::cnot).
51 fn zcx(&mut self, control: usize, target: usize) {
52 self.cnot(control, target)
53 }
54 /// stim alias for [`cz`](Clifford::cz).
55 fn zcz(&mut self, addr0: usize, addr1: usize) {
56 self.cz(addr0, addr1)
57 }
58}
59
60/// Additional Clifford gates beyond the minimal set: `S†`, `√X`, `√X†`,
61/// `√Y`, `√Y†`, and `CY`.
62pub trait CliffordExtensions: Clifford {
63 /// Apply `S†` to one qubit.
64 fn s_dag(&mut self, addr0: usize);
65 /// Apply `√X` to one qubit.
66 fn sqrt_x(&mut self, addr0: usize);
67 /// Apply `(√X)†` to one qubit.
68 fn sqrt_x_dag(&mut self, addr0: usize);
69 /// Apply `√Y` to one qubit.
70 fn sqrt_y(&mut self, addr0: usize);
71 /// Apply `(√Y)†` to one qubit.
72 fn sqrt_y_dag(&mut self, addr0: usize);
73 /// Apply `CY` to one `(control, target)` pair.
74 fn cy(&mut self, control: usize, target: usize);
75 /// stim alias for [`cy`](CliffordExtensions::cy).
76 fn zcy(&mut self, control: usize, target: usize) {
77 self.cy(control, target)
78 }
79}
80
81// === Blanket Clifford impl for PauliWordTrait ===
82//
83// Any type implementing [`PauliWordTrait`] automatically gets word-level
84// Clifford gate behavior. X/Y/Z are no-ops at the word level (they only
85// affect phase, tracked separately in `PhasedPauliWord`). H, S, CNOT, CZ
86// transform the bit representation. All gates honor loss bits via
87// `get_lbit`, which returns `false` for non-lossy types.
88//
89// New PauliWordTrait implementors get this behavior for free.
90//
91// Breaking change for downstream crates: this blanket impl conflicts with
92// any external `impl Clifford for MyWord where MyWord: PauliWordTrait`.
93// Downstreams that need custom Clifford semantics must not implement
94// `PauliWordTrait` on that type — see `PauliWordTrait`'s docs and the
95// `PhasedPauliWord` impl in `crate::phase::clifford` for the pattern.
96
97impl<T: PauliWordTrait> Clifford for T {
98 #[inline]
99 fn x(&mut self, _addr0: usize) {
100 // X * I * X = I 00 -> 00, 0
101 // X * X * X = X 10 -> 10, 0
102 // X * Z * X = -Z 01 -> 01, 1
103 // X * Y * X = -Y 11 -> 11, 1
104 // word-level no-op: phase tracked at PhasedPauliWord level
105 }
106
107 #[inline]
108 fn y(&mut self, _addr0: usize) {
109 // word-level no-op
110 }
111
112 #[inline]
113 fn z(&mut self, _addr0: usize) {
114 // word-level no-op
115 }
116
117 #[inline]
118 fn h(&mut self, index: usize) {
119 // H * I * H = I, H * X * H = Z, H * Z * H = X, H * Y * H = -Y
120 if self.get_lbit(index) {
121 return;
122 }
123 let ix = self.get_xbit(index);
124 let iz = self.get_zbit(index);
125 self.set_xbit(index, iz);
126 self.set_zbit(index, ix);
127 self.rehash();
128 }
129
130 #[inline]
131 fn s(&mut self, index: usize) {
132 // S * I * S = I, S * X * S = Y, S * Z * S = Z, S * Y * S = -X
133 if self.get_lbit(index) {
134 return;
135 }
136 let z = self.get_xbit(index) ^ self.get_zbit(index);
137 self.set_zbit(index, z);
138 self.rehash();
139 }
140
141 #[inline]
142 fn cnot(&mut self, control: usize, target: usize) {
143 if self.get_lbit(control) || self.get_lbit(target) {
144 return;
145 }
146 let control_z = self.get_zbit(target) ^ self.get_zbit(control);
147 let target_x = self.get_xbit(control) ^ self.get_xbit(target);
148 self.set_zbit(control, control_z);
149 self.set_xbit(target, target_x);
150 self.rehash();
151 }
152
153 #[inline]
154 fn cz(&mut self, control: usize, target: usize) {
155 if self.get_lbit(control) || self.get_lbit(target) {
156 return;
157 }
158 // flip the control z if target x is 1
159 let control_z = self.get_zbit(control) ^ self.get_xbit(target);
160 self.set_zbit(control, control_z);
161 // flip the target z if control x is 1
162 let target_z = self.get_zbit(target) ^ self.get_xbit(control);
163 self.set_zbit(target, target_z);
164 self.rehash();
165 }
166}
167
168/// Batched Clifford gates: apply the same gate to many qubits in one call.
169///
170/// Default implementations loop over the corresponding single-qubit
171/// (or single-pair) method on [`Clifford`]. Types like the stabilizer
172/// `Tableau` override these methods with a fused inner-loop or bitmask
173/// implementation. Types that don't need specialization can implement
174/// this trait with an empty `impl` to use the defaults.
175pub trait CliffordBatch: Clifford {
176 /// Apply Pauli `X` to every qubit in `indices`.
177 fn x_many(&mut self, indices: &[usize]) {
178 for &q in indices {
179 self.x(q);
180 }
181 }
182 /// Apply Pauli `Y` to every qubit in `indices`.
183 fn y_many(&mut self, indices: &[usize]) {
184 for &q in indices {
185 self.y(q);
186 }
187 }
188 /// Apply Pauli `Z` to every qubit in `indices`.
189 fn z_many(&mut self, indices: &[usize]) {
190 for &q in indices {
191 self.z(q);
192 }
193 }
194 /// Apply Hadamard `H` to every qubit in `indices`.
195 fn h_many(&mut self, indices: &[usize]) {
196 for &q in indices {
197 self.h(q);
198 }
199 }
200 /// Apply phase gate `S` to every qubit in `indices`.
201 fn s_many(&mut self, indices: &[usize]) {
202 for &q in indices {
203 self.s(q);
204 }
205 }
206 /// Apply `CNOT` to every `(control, target)` pair.
207 fn cnot_many(&mut self, pairs: &[(usize, usize)]) {
208 for &(c, t) in pairs {
209 self.cnot(c, t);
210 }
211 }
212 /// Apply `CZ` to every `(control, target)` pair.
213 fn cz_many(&mut self, pairs: &[(usize, usize)]) {
214 for &(c, t) in pairs {
215 self.cz(c, t);
216 }
217 }
218}
219
220/// Batched form of [`CliffordExtensions`].
221pub trait CliffordExtensionsBatch: CliffordExtensions + CliffordBatch {
222 /// Apply `S†` to every qubit in `indices`.
223 fn s_dag_many(&mut self, indices: &[usize]) {
224 for &q in indices {
225 self.s_dag(q);
226 }
227 }
228 /// Apply `√X` to every qubit in `indices`.
229 fn sqrt_x_many(&mut self, indices: &[usize]) {
230 for &q in indices {
231 self.sqrt_x(q);
232 }
233 }
234 /// Apply `(√X)†` to every qubit in `indices`.
235 fn sqrt_x_dag_many(&mut self, indices: &[usize]) {
236 for &q in indices {
237 self.sqrt_x_dag(q);
238 }
239 }
240 /// Apply `√Y` to every qubit in `indices`.
241 fn sqrt_y_many(&mut self, indices: &[usize]) {
242 for &q in indices {
243 self.sqrt_y(q);
244 }
245 }
246 /// Apply `(√Y)†` to every qubit in `indices`.
247 fn sqrt_y_dag_many(&mut self, indices: &[usize]) {
248 for &q in indices {
249 self.sqrt_y_dag(q);
250 }
251 }
252 /// Apply `CY` to every `(control, target)` pair.
253 fn cy_many(&mut self, pairs: &[(usize, usize)]) {
254 for &(c, t) in pairs {
255 self.cy(c, t);
256 }
257 }
258}
259
260impl<T: PauliWordTrait> CliffordExtensions for T {
261 // | Gate | X | Y | Z |
262 // |:----------:|:---:|:---:|:---:|
263 // | s | -Y | X | Z |
264 // | s_dag | Y | -X | Z |
265 // | sqrt_x | X | -Z | Y |
266 // | sqrt_x_dag | X | Z | -Y |
267 // | sqrt_y | Z | Y | -X |
268 // | sqrt_y_dag | -Z | Y | X |
269
270 #[inline]
271 fn s_dag(&mut self, addr0: usize) {
272 // s_dag has the same bit mapping as s (only phases differ)
273 self.s(addr0);
274 }
275
276 #[inline]
277 fn sqrt_x(&mut self, addr0: usize) {
278 if self.get_lbit(addr0) {
279 return;
280 }
281 let x = self.get_xbit(addr0);
282 let z = self.get_zbit(addr0);
283 self.set_xbit(addr0, x ^ z);
284 self.rehash();
285 }
286
287 #[inline]
288 fn sqrt_x_dag(&mut self, addr0: usize) {
289 if self.get_lbit(addr0) {
290 return;
291 }
292 let x = self.get_xbit(addr0);
293 let z = self.get_zbit(addr0);
294 self.set_xbit(addr0, x ^ z);
295 self.rehash();
296 }
297
298 #[inline]
299 fn sqrt_y(&mut self, addr0: usize) {
300 if self.get_lbit(addr0) {
301 return;
302 }
303 let x = self.get_xbit(addr0);
304 let z = self.get_zbit(addr0);
305 self.set_xbit(addr0, z);
306 self.set_zbit(addr0, x);
307 self.rehash();
308 }
309
310 #[inline]
311 fn sqrt_y_dag(&mut self, addr0: usize) {
312 if self.get_lbit(addr0) {
313 return;
314 }
315 let x = self.get_xbit(addr0);
316 let z = self.get_zbit(addr0);
317 self.set_xbit(addr0, z);
318 self.set_zbit(addr0, x);
319 self.rehash();
320 }
321
322 // | CY | I | X | Y | Z |
323 // |:---:|:---:|:---:|:---:|:---:|
324 // | I | II | ZX | IY | ZZ |
325 // | X | XY | -YZ | XI | YX |
326 // | Y | YY | XZ | YI | -XX |
327 // | Z | ZI | IX | ZY | IZ |
328 #[inline]
329 fn cy(&mut self, addr0: usize, addr1: usize) {
330 if self.get_lbit(addr0) || self.get_lbit(addr1) {
331 return;
332 }
333 let xc = self.get_xbit(addr0);
334 let zc = self.get_zbit(addr0);
335 let xt = self.get_xbit(addr1);
336 let zt = self.get_zbit(addr1);
337 self.set_zbit(addr0, zc ^ xt ^ zt);
338 self.set_xbit(addr1, xt ^ xc);
339 self.set_zbit(addr1, zt ^ xc);
340 self.rehash();
341 }
342}