Skip to content

Extend litinski transformation for all Pauli rotations#15974

Open
ShellyGarion wants to merge 48 commits into
Qiskit:mainfrom
ShellyGarion:extend_litinski
Open

Extend litinski transformation for all Pauli rotations#15974
ShellyGarion wants to merge 48 commits into
Qiskit:mainfrom
ShellyGarion:extend_litinski

Conversation

@ShellyGarion
Copy link
Copy Markdown
Member

@ShellyGarion ShellyGarion commented Apr 9, 2026

Summary

  • Extend Litinski transformation for the Pauli rotations RX and RY.
  • Extend Litinski transformation for PPR and PPM.

Close #15862

Details and comments

In order to support PPR/PPM, this PR should include the following functions and methods:

  • clifford.evolve_single_qubit_pauli - evolve a single qubit pauli (X,Y,Z) by the given Clifford
  • pauli_compose(P1, P2) - a function that composes two n-qubit paulis P1 and P2
  • evolve_pauli_by_clifford(P, C) - a function that uses the above in order to evolve an n-qubit pauli P by a clifford C

The functions above part were moved to #16137, and this PR should be based on #16137 (DensePauli class)

  • update the Litinski transform for PPM and PPR, which shall use the evolved pauli.
  • update the litinski transform to treat RZ/RX/RY with pi/2 angles as Clifford gates
  • update the litinski transform to treat PPR with pi/2 angles as Clifford gates

This PR should therefore replace #14677
This PR should be based on #16137 (DensePauli class)

In a follow up PR, we shall better organize and extend the functionality in rust of the DensePauli and Clifford classes.

@ShellyGarion ShellyGarion added this to the 2.5.0 milestone Apr 9, 2026
@ShellyGarion ShellyGarion added the fault tolerance related to fault tolerance compilation label Apr 9, 2026
@github-project-automation github-project-automation Bot moved this to Ready in Qiskit 2.5 Apr 9, 2026
Copy link
Copy Markdown
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

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

Thanks Shelly for starting this work. Though what we want is a bit more general that what you have right now: we would like to evolve an arbitrary (multi-qubit) Pauli under a Clifford, that is given $P$ finding $Q$ such that $P C = C Q$, and to extend LitinskiTransformation to handle circuits with PPR and PPM gates.

Comment thread crates/quantum_info/src/clifford.rs Outdated
Comment thread crates/transpiler/src/passes/litinski_transformation.rs Outdated
@alexanderivrii
Copy link
Copy Markdown
Member

alexanderivrii commented Apr 19, 2026

Thanks Shelly, I agree that we really-really need a good Rust structure for representing Paulis, and this is a really great start.

It's still not completely clear to me how exactly you are planning to use it for extending Litinski transformation to handle general Pauli product rotations and measurements. For instance, are you thinking of refactoring PauliProductRotation and PauliProductMeasurement to something like

pub struct PauliProductRotation {
    pub pauli: Pauli,
    pub angle: Param,
}
pub struct PauliProductMeasurement {
    pub pauli: Pauli,  // "neg" is incorporated into `pauli_phase`
}

Though this specific suggestion is not perfect as it would require storing and reasoning about an unnecessary internal pauli_phase field for Pauli product rotations, as well as introduce additional values for the pauli_phase field for PauliProductMeasurement.

Copy link
Copy Markdown
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

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

Thanks Shelly, I have left some more comments.

Comment thread crates/quantum_info/src/dense_pauli.rs Outdated
Comment thread crates/quantum_info/src/dense_pauli.rs Outdated
Comment thread crates/quantum_info/src/dense_pauli.rs Outdated
Comment thread crates/quantum_info/src/dense_pauli.rs Outdated
Comment thread crates/quantum_info/src/dense_pauli.rs Outdated
Comment on lines +64 to +90
for qbit in 0..(pauli_num_qubits) {
let out_pauli = Pauli {
pauli_z: out_z,
pauli_x: out_x,
pauli_phase: out_phase,
};

// evolve the singe qubit pauli by cliff
let pz = pauli_z[qbit];
let px = pauli_x[qbit];
let (ev_p_sign, ev_p_z, ev_p_x, ev_p_indices) =
cliff.evolve_single_qubit_pauli(pz, px, qbit);

let evolved_pauli_phase = if ev_p_sign { 2 as u8 } else { 0 as u8 };
// transform the evolved pauli to a dense format
let mut evolved_pauli_z = vec![false; pauli_num_qubits];
let mut evolved_pauli_x = vec![false; pauli_num_qubits];
for (&i, (&zv, &xv)) in ev_p_indices.iter().zip(ev_p_z.iter().zip(ev_p_x.iter())) {
evolved_pauli_z[i as usize] = zv;
evolved_pauli_x[i as usize] = xv;
}
// compose the ouput evolved dense paulies
let evolved_pauli = Pauli {
pauli_z: evolved_pauli_z,
pauli_x: evolved_pauli_x,
pauli_phase: evolved_pauli_phase,
};
Copy link
Copy Markdown
Member

@alexanderivrii alexanderivrii Apr 20, 2026

Choose a reason for hiding this comment

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

Logic-wise, this seems ok. But this does create a new Pauli object (with dense vectors z and x) a large number of times (pauli_num_qubits). I am wondering if you can improve performance of this code: by avoiding to conjugate by Is (as these do not change pauli), and by introducing a variant of evolve_pauli_by_clifford that modifies pauli in-place.

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.

I added a check that the single qubit Pauli isn't I (and some tests) in d744ad3.

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.

is the variant of evolve_pauli_by_clifford that modifies pauli in-place needed for this PR? we'll add PPM/PPR and consider this.

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.

this should be part of #16137

Comment thread crates/quantum_info/src/dense_pauli.rs Outdated
Comment on lines +29 to +31
/// Compose the Paulis p1 and p2.
/// Returns the output Pauli.
pub fn pauli_compose(p1: &Pauli, p2: &Pauli) -> Pauli {
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.

Can you please clarify: do we first apply p1 and then p2, or the other way around?

Copy link
Copy Markdown
Member Author

@ShellyGarion ShellyGarion Apr 27, 2026

Choose a reason for hiding this comment

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

it's like p1.compose(p2) in the default python code:
https://quantum.cloud.ibm.com/docs/en/api/qiskit/qiskit.quantum_info.Pauli#compose
I also added a comment in d1a354a

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.

this should be part of #16137

pub struct Pauli {
pub pauli_z: Vec<bool>,
pub pauli_x: Vec<bool>,
pub pauli_phase: u8,
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.

I think after the offline discussion, we want this to be xz-phase and not the group phase.

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.

this should be part of #16137

qct.global_phase = 0

# The original circuit (written only using Clifford gates, PPR and PPM).
PPRZ = PauliProductRotationGate(Pauli("Z"), np.pi / 4)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we put this into a separate test and use only PPRs (except maybe for the CX)? It would be good to keep the original test in place as reference that the old paths still work as expected.

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.

The pass doesn't identify yet PPR gates (or RZ/RX/RY gates) with pi/2 angles as Cliffords. I'll add this capability and then update this test accordingly.

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.

now there is a seperate test! see c62bb18

@ShellyGarion ShellyGarion added the Changelog: Added Add an "Added" entry in the GitHub Release changelog. label May 11, 2026
@ShellyGarion ShellyGarion changed the title [WIP] Extend litinski transformation for all Pauli rotations Extend litinski transformation for all Pauli rotations May 11, 2026
use qiskit_circuit::{BlocksMode, Qubit, VarsMode};

use super::remove_identity_equiv::average_gate_fidelity_below_tol; // ToDo: move a shared file?
use super::substitute_pi4_rotations::is_angle_close_to_multiple_of_pi_k; // ToDo: move a shared 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.

shoule we move these functions (as well as is_ppr_angle_close_to_multiple_of_pi2 below to a shared place)?
perhaps unifying them?
and if so - where?

"pauli_product_measurement",
];

const MINIMUM_TOL: f64 = 1e-12; // ToDo: add approxination_degree to the pass?
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.

should we add an approxination_degree to the pass?
(to decide when a rotation gate is close enough to a pi/2 rotation, and then clifford)

Copy link
Copy Markdown
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

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

Thanks for the great work, Shelly. Functionality-wise, this is shaping up quite well, I have left a few comments. I have not carefully looked at the dense_pauli code since we are planning to replace it by a more efficient code anyway, and I have not deeply looked at all of the tests yet.

The other question is: what benchmarks should we consider for measuring the performance of the new aspects of the LitinskiTransformation pass: identifying clifford-like rotations (especially PPRs) and commuting PPRs through Cliffords? Note that I am not suggesting to add these benchmarks to ASV as a part of this PR, but having some script to evaluate the pass performance is quite important.

}
/// 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):

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

Comment thread crates/quantum_info/src/clifford.rs Outdated
(true, false) => {} // pauli Z on qubit
(true, true) => self.append_sx(indices[qubit] as usize), // pauli Y on qubit
(false, true) => self.append_h(indices[qubit] as usize), // pauli X on qubit
(false, false) => {} // pauli I on qubit (shouldn't get it since pauli is sparse)
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.

I don't understand this comment that "pauli is sparse". You are defining a public function which accepts slices of z-values, x-values and indices. Nothing prevents a possible user of this function to pass paulis with I-terms, and actually I think this should be an acceptable behavior. If you really want to have a function that does not accept I-terms, I would like to see this reflected in the name and the docstring, and possibly have this function return some kind of an error rather than panicking or silently performing an incorrect computation.

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.

I'm not sure what is the best way to handle the "I" terms in this case, since it also affects the CX ladders.

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.

"I" terms are handled here: f6068d1

}
/// 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

use qiskit_circuit::{BlocksMode, Qubit, VarsMode};

use super::remove_identity_equiv::average_gate_fidelity_below_tol; // ToDo: move a shared file?
use super::substitute_pi4_rotations::is_angle_close_to_multiple_of_pi_k; // ToDo: move a shared file?
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.

I think we should eventually move these, but it does not have to be a part of this PR. In fact, let's leave this cleanup to a followup, since there are likely other common utility functions scattered throughout the code base.

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.

when we merge this, let's open an issue to track the code organization

Comment on lines +42 to +43
As well as the rotation gates above with angles that are integral multiples of :math:`pi/2`
(which are also Clifford).
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.

As per your previous question, we should add the approximation_degree to the pass and slightly improve this comment (explaining the role of approximation degree when detecting Clifford rotations). Also you should mention PPRs here, right?

Comment on lines +6 to +7
In addition, rotation gates and :class:`.PauliProductRotationGates` with angles
that are integral multiples of :math:`pi/2` are treated as Clifford gates.
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.

same comment as before that this should be related to the approximation degree.

Comment on lines +145 to +161
self.assertNotIn("rz", qc_litinski.count_ops())
# self.assertNotIn("rz", qc_litinski.count_ops()) # ToDo: can we replace it by another check?
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.

what is the reason for this?

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.

This is since after Litinski we still have some remaining RZ gates with pi/2 angles (which are Clifford)

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.

Any suggestion about how to update this test?

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.

I see, so now the Clifford-like RZ(pi/2) moves to the Clifford part of the circuit. I guess this is what we want (the alternative could be to synthesize this into explicit Clifford gates, but I don't like this either). Maybe then we can have two subtests: with fix_clifford=True we expect the operators to be equal, and with fix_clifford=False we expect to see no RZ gates remaining.

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.

that's a nice idea, I enhanced the QFT test here: 457dcc2

Comment on lines +214 to +215
def test_rotation_clifford_gates(self):
"""Test circuit with rotation gates with multiple pi/2 angles which are clifford gates."""
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.

Do we also have a test with non-clifford rotations, clifford rotations and genuine cliffords?

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.

see 519cfba
(still without PPRs with "I" terms)

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: f6068d1
and some tests are added

Comment on lines +582 to +591
# The transformed circuit (as shown at the bottom-right of the figure).
expected = QuantumCircuit(4, 4)
expected.append(PauliProductRotationGate(Pauli("Z"), np.pi / 4), [0])
expected.append(PauliProductRotationGate(Pauli("YX"), np.pi / 4), [1, 2])
expected.append(PauliProductRotationGate(Pauli("Y"), -np.pi / 4), [3])
expected.append(PauliProductRotationGate(Pauli("YZZZ"), -np.pi / 4), [0, 1, 2, 3])
expected.append(PauliProductMeasurement(Pauli("YZZY")), [0, 1, 2, 3], [0])
expected.append(PauliProductMeasurement(Pauli("XX")), [0, 1], [1])
expected.append(PauliProductMeasurement(Pauli("-Z")), [2], [2])
expected.append(PauliProductMeasurement(Pauli("XX")), [0, 3], [3])
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.

In this and in other new tests, how do we know that the expected circuit is correct? Without measures/PPMs I would suggest to add comparing Operators.

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.

this is correct from the example given in Litinski's paper. We can't compare the Operators in this case due to the measures

@alexanderivrii
Copy link
Copy Markdown
Member

After discussing with Shelly, a nice set of benchmarks would be to take the ASV benchmarks we already use for measuring the performance of LitinskiTransformation, apply ConvertToPauliRotations to them (hence obtaining a circuit with many PPRs including many Clifford-like PPRs), and then apply this extended LitinskiTransformation pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Added Add an "Added" entry in the GitHub Release changelog. fault tolerance related to fault tolerance compilation

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

Extend LitinskiTransformation generic Pauli rotations

5 participants