Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6d85e81
extend litinski transformation for rx and ry
ShellyGarion Apr 9, 2026
4495a29
merge main branch
ShellyGarion Apr 9, 2026
48c341f
replace pauli str by pauli_z, pauli_x
ShellyGarion Apr 9, 2026
7c77ed5
add comments on the pauli
ShellyGarion Apr 9, 2026
1b7629b
add a test for two-qubit cliffords
ShellyGarion Apr 9, 2026
10baa04
replace get_inverse_pauli by evolve_single_qubit_pauli_by_clifford
ShellyGarion Apr 13, 2026
025622f
minor fix
ShellyGarion Apr 13, 2026
27c53d8
the test_two_qubit_evolve_clifford_gate now checks circuit equality
ShellyGarion Apr 13, 2026
6e4148d
simplify function name to evolve_single_qubit_pauli
ShellyGarion Apr 13, 2026
fd9ff5e
update comments
ShellyGarion Apr 13, 2026
e368971
fix misprint
ShellyGarion Apr 19, 2026
b3d5602
fix confict with main
ShellyGarion Apr 19, 2026
778d552
add a basic Pauli class. add pauli_compose function
ShellyGarion Apr 19, 2026
699cd4f
fix year
ShellyGarion Apr 19, 2026
284ee64
add evolve_pauli_by_clifford
ShellyGarion Apr 20, 2026
d744ad3
update dense_pauli.rs code following review
ShellyGarion Apr 20, 2026
0bb2ec5
add some more tests for pauli I
ShellyGarion Apr 20, 2026
2170be0
handle PPR gates
ShellyGarion Apr 26, 2026
ab3599f
formatting
ShellyGarion Apr 26, 2026
d1a354a
add a helper function to pad paulis
ShellyGarion Apr 27, 2026
e6b121d
use tests module in rust
ShellyGarion Apr 27, 2026
a0cb601
handle PPM gates
ShellyGarion Apr 28, 2026
450c0b8
update tests for PPR/PPM to check circuit equality
ShellyGarion Apr 28, 2026
778bf3b
unify PPR/PPM tests
ShellyGarion Apr 28, 2026
99a7827
add a function unpad_pauli
ShellyGarion Apr 28, 2026
0506fe1
update tests for unpad pauli, add test based on Litinski's paper
ShellyGarion Apr 28, 2026
57c4cdd
formatting
ShellyGarion Apr 28, 2026
24f60a9
update gates in Litinski's example test
ShellyGarion Apr 29, 2026
8dcb860
combine Litnski's paper tests
ShellyGarion Apr 29, 2026
4d4b886
add append_rx, append_ry, append_rz to the clifford rust class
ShellyGarion May 10, 2026
ec9795a
make function public
ShellyGarion May 10, 2026
0a38e2c
litinski transformation now handles RX/RY/RZ with pi/2 angles as clif…
ShellyGarion May 10, 2026
de36c3d
add tests with RX/RY/RZ gates and pi/2 angles
ShellyGarion May 10, 2026
ced111f
remove print
ShellyGarion May 10, 2026
3036ed9
formatting
ShellyGarion May 10, 2026
d33977c
no need to make the module public
ShellyGarion May 11, 2026
479194d
add append_ppr method to clifford class
ShellyGarion May 11, 2026
3569230
litinski now handles PPR with pi/2 angles as cliffords
ShellyGarion May 11, 2026
c62bb18
add tests for PPR with pi/2 angles
ShellyGarion May 11, 2026
ba4bdc0
enhance the docstring
ShellyGarion May 11, 2026
5dfdeea
add release notes
ShellyGarion May 11, 2026
519cfba
add a test for PPRs with and w/o pi/2 angles
ShellyGarion May 12, 2026
1b5c7bf
move modulo 4 to clifford class from litinski
ShellyGarion May 12, 2026
12423db
update comments
ShellyGarion May 12, 2026
80f6640
update docstring
ShellyGarion May 12, 2026
457dcc2
update test_qft_circuits
ShellyGarion May 12, 2026
872098b
change set to list in test
ShellyGarion May 12, 2026
f6068d1
enhance clifford.append_ppr to handle pauli I terms
ShellyGarion May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 130 additions & 4 deletions crates/quantum_info/src/clifford.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
use itertools::Itertools;
use std::fmt;

use fixedbitset::FixedBitSet;
Expand Down Expand Up @@ -287,19 +288,144 @@ impl Clifford {
&mut self.scratch,
);
}
/// Modifies the tableau in-place by appending RZ-gate,
/// with an angle that is an integer multiple of pi/2
pub fn append_rz(&mut self, qubit: usize, multiple: usize) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick about the naming. Seeing the function append_rz defined for a Clifford class is confusing, as appending an RZ gate to a Clifford generally no longer results in a Clifford. Moreover, RZ gates correspond to rotation angles, not "multiples". Maybe you can call this append_multiple_of_s or append_clifford_rz. The same comment applies to other new functions in this file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a similar function in the python clifford class:

def _append_rz(clifford, qubit, multiple):

let multiple = multiple.rem_euclid(4);
match multiple {
0 => {}
1 => self.append_s(qubit),
2 => self.append_z(qubit),
3 => self.append_sdg(qubit),
_ => unreachable!("RZ is only applicable for multiples of pi/2 rotations."),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have this function as a part of public API, we should be able to call it with any integer multiple of pi/2, not necessarily reduced modulo 4. The change is to match multiple.rem_euclid(4) instead. Again, this comment applies to other new functions in this file.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 1b5c7bf

}
}
/// Modifies the tableau in-place by appending RX-gate,
/// with an angle that is an integer multiple of pi/2
pub fn append_rx(&mut self, qubit: usize, multiple: usize) {
let multiple = multiple.rem_euclid(4);
match multiple {
0 => {}
1 => self.append_sx(qubit),
2 => self.append_x(qubit),
3 => self.append_sxdg(qubit),
_ => unreachable!("RX is only applicable for multiples of pi/2 rotations."),
}
}
/// Modifies the tableau in-place by appending RY-gate,
/// with an angle that is an integer multiple of pi/2
pub fn append_ry(&mut self, qubit: usize, multiple: usize) {
let multiple = multiple.rem_euclid(4);
match multiple {
0 => {}
1 => {
self.append_z(qubit);
self.append_h(qubit)
}
2 => self.append_y(qubit),
3 => {
self.append_h(qubit);
self.append_z(qubit)
}
_ => unreachable!("RY is only applicable for multiples of pi/2 rotations."),
}
}
/// Modifies the tableau in-place by appending PPR gate,
/// with an angle that is an integer multiple of pi/2
pub fn append_ppr(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some rust tests for append_ppr? Yes, I know that we are not doing this at the moment, but maybe it's a good time to start.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test here: 519cfba
(it still doesn't work for PPR with "I" terms, which I'm not sure what is the best way to handle)

Copy link
Copy Markdown
Member Author

@ShellyGarion ShellyGarion May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"I" terms are handled here (including tests): f6068d1

&mut self,
pauli_z: &[bool],
pauli_x: &[bool],
indices: &[u32],
multiple: usize,
) {
let multiple = multiple.rem_euclid(4);

// remove pauli I terms
let (new_z, new_x, new_indices): (Vec<bool>, Vec<bool>, Vec<u32>) = pauli_z
.iter()
.zip(pauli_x)
.zip(indices)
.filter(|&((&z, &x), _)| z || x)
.map(|((&z, &x), &i)| (z, x, i))
.multiunzip();

// initial H or SX gates (in case of pauli X or pauli Y respectively)
for qubit in 0..new_indices.len() {
match (new_z[qubit], new_x[qubit]) {
(true, false) => {} // pauli Z on qubit
(true, true) => self.append_sx(new_indices[qubit] as usize), // pauli Y on qubit
(false, true) => self.append_h(new_indices[qubit] as usize), // pauli X on qubit
(false, false) => unreachable!("Pauli I terms were removed from PPR."), // pauli I on qubit (shouldn't get it since pauli is sparse)
}
}

/// Evolving the single-qubit Pauli-Z with Z on qubit qbit.
// CX ladder
if new_indices.len() > 1 {
for ind in (0..new_indices.len() - 1).rev() {
self.append_cx(new_indices[ind + 1] as usize, new_indices[ind] as usize);
}
}
// internal RZ gate
match multiple {
0 => {}
1 => self.append_s(new_indices[0] as usize),
2 => self.append_z(new_indices[0] as usize),
3 => self.append_sdg(new_indices[0] as usize),
_ => unreachable!("PPR is only applicable for multiples of pi/2 rotations."),
}
// CX ladder
if new_indices.len() > 1 {
for ind in 0..new_indices.len() - 1 {
self.append_cx(new_indices[ind + 1] as usize, new_indices[ind] as usize);
}
}
// final H or SXdg gates (in case of pauli X or pauli Y respectively)
for qubit in 0..new_indices.len() {
match (new_z[qubit], new_x[qubit]) {
(true, false) => {} // pauli Z on qubit
(true, true) => self.append_sxdg(new_indices[qubit] as usize), // pauli Y on qubit
(false, true) => self.append_h(new_indices[qubit] as usize), // pauli X on qubit
(false, false) => unreachable!("Pauli I terms were removed from PPR."), // pauli I on qubit (shouldn't get it since pauli is sparse)
}
}
}

/// Evolving a single qubit pauli on qubit qbit by the Clifford.
/// The pauli (X, Y or Z) is given as (pauli_z, pauli_x)
/// Returns the evolved Pauli in the a sparse ZX format: (sign, z, x, indices).
pub fn get_inverse_z(&self, qbit: usize) -> (bool, Vec<bool>, Vec<bool>, Vec<u32>) {
pub fn evolve_single_qubit_pauli(
&self,
pauli_z: bool,
pauli_x: bool,
qbit: usize,
) -> (bool, Vec<bool>, Vec<bool>, Vec<u32>) {
let mut z = Vec::with_capacity(self.num_qubits);
let mut x = Vec::with_capacity(self.num_qubits);
let mut indices = Vec::with_capacity(self.num_qubits);
let mut pauli_indices = Vec::<usize>::with_capacity(2 * self.num_qubits);
// Compute the y-count to avoid recomputing it later
let mut pauli_y_count: u32 = 0;
for i in 0..self.num_qubits {
let z_bit = self.tableau[qbit][i];
let x_bit = self.tableau[qbit][i + self.num_qubits];
let (z_bit, x_bit) = match (pauli_z, pauli_x) {
(true, false) => (
// pauli Z
self.tableau[qbit][i],
self.tableau[qbit][i + self.num_qubits],
),
(false, true) => (
// pauli X
self.tableau[qbit + self.num_qubits][i],
self.tableau[qbit + self.num_qubits][i + self.num_qubits],
),
(true, true) => (
// pauli Y
self.tableau[qbit + self.num_qubits][i] ^ self.tableau[qbit][i],
self.tableau[qbit + self.num_qubits][i + self.num_qubits]
^ self.tableau[qbit][i + self.num_qubits],
),
_ => unreachable!("This is only called for RX/RZ/RY gates."),
};
if z_bit || x_bit {
z.push(z_bit);
x.push(x_bit);
Expand Down
Loading
Loading