Skip to content

Commit ed907ef

Browse files
committed
autotune: specialize PauliSum single-qubit Clifford and rotation gate closures to operate directly on raw xbit/zbit instead of round-tripping through PhasedPauliWord and Pauli enums
Both files look correct. The `PauliWordTrait` import in `clifford.rs` is needed but since `use crate::traits::*` is already used in `rot1.rs`, let me verify the import chain for `clifford.rs` — we import `PauliWordTrait` explicitly in the traits list, which is correct for calling `get_xbit`/`get_zbit`/`get_lbit`/`set_xbit`/`set_zbit`/`rehash`. The implementation is complete and correct: - **`clifford.rs`**: `x`/`y`/`z` now use `scale` with direct bit reads (no clone, no PhasedPauliWord); `h`/`s` and all `CliffordExtensions` single-qubit gates use `map_add` with direct bit manipulation, skipping the `PhasedPauliWord` round-trip and generic Clifford dispatch. Two-qubit gates (`cnot`, `cz`, `cy`) retain the existing macro path. - **`rot1.rs`**: `rotate_1_map_insert_closure` now reads bits directly (`get_lbit`/`get_xbit`/`get_zbit`) instead of calling `k.get()` which constructs a `Pauli` enum. The `RotationOne` impl adds specialized `rx`/`ry`/`rz` overrides that skip `levi_civita` entirely, using hardcoded bit formulas for commutation and sign derived from the known axis. SUMMARY: specialize PauliSum single-qubit Clifford and rotation gate closures to operate directly on raw xbit/zbit instead of round-tripping through PhasedPauliWord and Pauli enums
1 parent cf0cacf commit ed907ef

2 files changed

Lines changed: 257 additions & 29 deletions

File tree

‎crates/ppvm-runtime/src/sum/clifford.rs‎

Lines changed: 174 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,10 @@ use crate::{
22
config::Config,
33
phase::PhasedPauliWord,
44
sum::PauliSum,
5-
traits::{Clifford, CliffordExtensions, PauliStorage},
5+
traits::{Clifford, CliffordExtensions, PauliStorage, PauliWordTrait},
66
};
77
use std::hash::BuildHasher;
88

9-
macro_rules! map_scale {
10-
($name:ident, $($index:ident),*) => {
11-
fn $name(&mut self, $($index: usize),*) {
12-
self.scale(|k, v| {
13-
let mut p: PhasedPauliWord<T::Storage, T::BuildHasher, <T as Config>::PauliWordType> = k.clone().into();
14-
p.$name($($index),*);
15-
if !p.is_positive() {
16-
*v *= -1.0;
17-
}
18-
})
19-
}
20-
};
21-
}
22-
239
macro_rules! map_word {
2410
($name:ident, $($index:ident),*) => {
2511
fn $name(&mut self, $($index: usize),*) {
@@ -44,11 +30,77 @@ where
4430
T: Config<Storage = S, BuildHasher = H>,
4531
T::PauliWordType: Clifford,
4632
{
47-
map_scale!(x, index);
48-
map_scale!(y, index);
49-
map_scale!(z, index);
50-
map_word!(h, index);
51-
map_word!(s, index);
33+
#[inline]
34+
fn x(&mut self, index: usize) {
35+
// X conjugation: only flips sign for Z and Y (zbit set); word unchanged
36+
self.scale(|k, v| {
37+
if !k.get_lbit(index) && k.get_zbit(index) {
38+
*v *= -1.0;
39+
}
40+
})
41+
}
42+
43+
#[inline]
44+
fn y(&mut self, index: usize) {
45+
// Y conjugation: flips sign for X and Z (xbit XOR zbit); word unchanged
46+
self.scale(|k, v| {
47+
if !k.get_lbit(index) && (k.get_xbit(index) ^ k.get_zbit(index)) {
48+
*v *= -1.0;
49+
}
50+
})
51+
}
52+
53+
#[inline]
54+
fn z(&mut self, index: usize) {
55+
// Z conjugation: only flips sign for X and Y (xbit set); word unchanged
56+
self.scale(|k, v| {
57+
if !k.get_lbit(index) && k.get_xbit(index) {
58+
*v *= -1.0;
59+
}
60+
})
61+
}
62+
63+
#[inline]
64+
fn h(&mut self, index: usize) {
65+
// H swaps x and z bits; phase flip only for Y (both bits set)
66+
self.map_add(|k, v| {
67+
if k.get_lbit(index) {
68+
return (k.clone(), v.clone());
69+
}
70+
let xbit = k.get_xbit(index);
71+
let zbit = k.get_zbit(index);
72+
let mut new_word = k.clone();
73+
new_word.set_xbit(index, zbit);
74+
new_word.set_zbit(index, xbit);
75+
new_word.rehash();
76+
if xbit & zbit {
77+
(new_word, -v.clone())
78+
} else {
79+
(new_word, v.clone())
80+
}
81+
})
82+
}
83+
84+
#[inline]
85+
fn s(&mut self, index: usize) {
86+
// S: zbit = xbit XOR zbit; phase flip only for X (xbit set, zbit clear)
87+
self.map_add(|k, v| {
88+
if k.get_lbit(index) {
89+
return (k.clone(), v.clone());
90+
}
91+
let xbit = k.get_xbit(index);
92+
let zbit = k.get_zbit(index);
93+
let mut new_word = k.clone();
94+
new_word.set_zbit(index, xbit ^ zbit);
95+
new_word.rehash();
96+
if xbit & !zbit {
97+
(new_word, -v.clone())
98+
} else {
99+
(new_word, v.clone())
100+
}
101+
})
102+
}
103+
52104
map_word!(cnot, a, b);
53105
map_word!(cz, a, b);
54106
}
@@ -60,10 +112,107 @@ where
60112
T: Config<Storage = S, BuildHasher = H>,
61113
T::PauliWordType: Clifford + CliffordExtensions,
62114
{
63-
map_word!(s_adj, index);
64-
map_word!(sqrt_x, index);
65-
map_word!(sqrt_y, index);
66-
map_word!(sqrt_x_adj, index);
67-
map_word!(sqrt_y_adj, index);
115+
#[inline]
116+
fn s_adj(&mut self, addr0: usize) {
117+
// S†: same bit map as S; phase flip for Y (both bits set)
118+
self.map_add(|k, v| {
119+
if k.get_lbit(addr0) {
120+
return (k.clone(), v.clone());
121+
}
122+
let xbit = k.get_xbit(addr0);
123+
let zbit = k.get_zbit(addr0);
124+
let mut new_word = k.clone();
125+
new_word.set_zbit(addr0, xbit ^ zbit);
126+
new_word.rehash();
127+
if xbit & zbit {
128+
(new_word, -v.clone())
129+
} else {
130+
(new_word, v.clone())
131+
}
132+
})
133+
}
134+
135+
#[inline]
136+
fn sqrt_x(&mut self, addr0: usize) {
137+
// √X: xbit = xbit XOR zbit; phase flip for Y (both bits set)
138+
self.map_add(|k, v| {
139+
if k.get_lbit(addr0) {
140+
return (k.clone(), v.clone());
141+
}
142+
let xbit = k.get_xbit(addr0);
143+
let zbit = k.get_zbit(addr0);
144+
let mut new_word = k.clone();
145+
new_word.set_xbit(addr0, xbit ^ zbit);
146+
new_word.rehash();
147+
if xbit & zbit {
148+
(new_word, -v.clone())
149+
} else {
150+
(new_word, v.clone())
151+
}
152+
})
153+
}
154+
155+
#[inline]
156+
fn sqrt_y(&mut self, addr0: usize) {
157+
// √Y: swap x and z bits; phase flip for Z (zbit set, xbit clear)
158+
self.map_add(|k, v| {
159+
if k.get_lbit(addr0) {
160+
return (k.clone(), v.clone());
161+
}
162+
let xbit = k.get_xbit(addr0);
163+
let zbit = k.get_zbit(addr0);
164+
let mut new_word = k.clone();
165+
new_word.set_xbit(addr0, zbit);
166+
new_word.set_zbit(addr0, xbit);
167+
new_word.rehash();
168+
if !xbit & zbit {
169+
(new_word, -v.clone())
170+
} else {
171+
(new_word, v.clone())
172+
}
173+
})
174+
}
175+
176+
#[inline]
177+
fn sqrt_x_adj(&mut self, addr0: usize) {
178+
// √X†: xbit = xbit XOR zbit; phase flip for Z (zbit set, xbit clear)
179+
self.map_add(|k, v| {
180+
if k.get_lbit(addr0) {
181+
return (k.clone(), v.clone());
182+
}
183+
let xbit = k.get_xbit(addr0);
184+
let zbit = k.get_zbit(addr0);
185+
let mut new_word = k.clone();
186+
new_word.set_xbit(addr0, xbit ^ zbit);
187+
new_word.rehash();
188+
if !xbit & zbit {
189+
(new_word, -v.clone())
190+
} else {
191+
(new_word, v.clone())
192+
}
193+
})
194+
}
195+
196+
#[inline]
197+
fn sqrt_y_adj(&mut self, addr0: usize) {
198+
// √Y†: swap x and z bits; phase flip for X (xbit set, zbit clear)
199+
self.map_add(|k, v| {
200+
if k.get_lbit(addr0) {
201+
return (k.clone(), v.clone());
202+
}
203+
let xbit = k.get_xbit(addr0);
204+
let zbit = k.get_zbit(addr0);
205+
let mut new_word = k.clone();
206+
new_word.set_xbit(addr0, zbit);
207+
new_word.set_zbit(addr0, xbit);
208+
new_word.rehash();
209+
if xbit & !zbit {
210+
(new_word, -v.clone())
211+
} else {
212+
(new_word, v.clone())
213+
}
214+
})
215+
}
216+
68217
map_word!(cy, a, b);
69218
}

‎crates/ppvm-runtime/src/sum/rot1.rs‎

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ pub(crate) fn rotate_1_map_insert_closure<T: Config>(
99
sin: &T::Coeff,
1010
cos: &T::Coeff,
1111
) -> Option<(T::PauliWordType, T::Coeff)> {
12-
let p_g = k.get(addr0);
1312
if axis == Pauli::L {
1413
panic!("Rotation axis cannot be L");
1514
}
16-
if p_g == Pauli::L {
17-
// Rotation does nothing on lost qubits
15+
// Check loss first — avoids reading xbit/zbit on lost qubits
16+
if k.get_lbit(addr0) {
1817
return None;
1918
}
20-
let (eps, p_q) = levi_civita(p_g as u8, axis as u8);
19+
let xbit = k.get_xbit(addr0);
20+
let zbit = k.get_zbit(addr0);
21+
// Reconstruct 2-bit Pauli code (I=00, X=01, Z=10, Y=11) from bits
22+
let p_g = (zbit as u8) << 1 | (xbit as u8);
23+
let (eps, p_q) = levi_civita(p_g, axis as u8);
2124
if eps == 0 {
2225
None
2326
} else {
@@ -45,6 +48,82 @@ where
4548
let (sin, cos) = theta.sin_cos();
4649
self.map_insert(|k, v| rotate_1_map_insert_closure::<T>(k, v, axis, addr0, &sin, &cos));
4750
}
51+
52+
#[inline]
53+
fn rx(&mut self, addr0: usize, theta: impl Into<T::Coeff>) {
54+
// Axis = X (xbit=1, zbit=0). Commutes when zbit==false (I or X).
55+
// Anticommuting: Z(xbit=0)→new Y, eps=+1; Y(xbit=1)→new Z, eps=-1
56+
let (sin, cos) = theta.into().sin_cos();
57+
self.map_insert(|k, v| {
58+
if k.get_lbit(addr0) {
59+
return None;
60+
}
61+
let zbit = k.get_zbit(addr0);
62+
if !zbit {
63+
return None;
64+
}
65+
let xbit = k.get_xbit(addr0);
66+
let mut coeff = v.clone();
67+
*v *= cos.clone();
68+
let mut new_word = k.clone();
69+
new_word.set_xbit(addr0, !xbit);
70+
new_word.rehash();
71+
let eps: i8 = if xbit { -1 } else { 1 };
72+
coeff *= sin.mul_sign(eps);
73+
Some((new_word, coeff))
74+
});
75+
}
76+
77+
#[inline]
78+
fn ry(&mut self, addr0: usize, theta: impl Into<T::Coeff>) {
79+
// Axis = Y (xbit=1, zbit=1). Commutes when xbit==zbit (I or Y).
80+
// Anticommuting: X(zbit=0)→new Z, eps=+1; Z(zbit=1)→new X, eps=-1
81+
let (sin, cos) = theta.into().sin_cos();
82+
self.map_insert(|k, v| {
83+
if k.get_lbit(addr0) {
84+
return None;
85+
}
86+
let xbit = k.get_xbit(addr0);
87+
let zbit = k.get_zbit(addr0);
88+
if xbit == zbit {
89+
return None;
90+
}
91+
let mut coeff = v.clone();
92+
*v *= cos.clone();
93+
let mut new_word = k.clone();
94+
new_word.set_xbit(addr0, !xbit);
95+
new_word.set_zbit(addr0, !zbit);
96+
new_word.rehash();
97+
let eps: i8 = if zbit { -1 } else { 1 };
98+
coeff *= sin.mul_sign(eps);
99+
Some((new_word, coeff))
100+
});
101+
}
102+
103+
#[inline]
104+
fn rz(&mut self, addr0: usize, theta: impl Into<T::Coeff>) {
105+
// Axis = Z (xbit=0, zbit=1). Commutes when xbit==false (I or Z).
106+
// Anticommuting: X(zbit=0)→new Y, eps=-1; Y(zbit=1)→new X, eps=+1
107+
let (sin, cos) = theta.into().sin_cos();
108+
self.map_insert(|k, v| {
109+
if k.get_lbit(addr0) {
110+
return None;
111+
}
112+
let xbit = k.get_xbit(addr0);
113+
if !xbit {
114+
return None;
115+
}
116+
let zbit = k.get_zbit(addr0);
117+
let mut coeff = v.clone();
118+
*v *= cos.clone();
119+
let mut new_word = k.clone();
120+
new_word.set_zbit(addr0, !zbit);
121+
new_word.rehash();
122+
let eps: i8 = if zbit { 1 } else { -1 };
123+
coeff *= sin.mul_sign(eps);
124+
Some((new_word, coeff))
125+
});
126+
}
48127
}
49128

50129
/// 2-bit Pauli code: 00 I, 01 X, 10 Z, 11 Y

0 commit comments

Comments
 (0)