Skip to content

Conversation

ShellyGarion
Copy link
Member

@ShellyGarion ShellyGarion commented Oct 5, 2025

Summary

Adds rust and python support for PauliProductMeasurement instructions. This is a fundamental operation in fault-tolerant quantum computing [1].

Close #15104
Co-authored by @alexanderivrii

References:

Details and comments

The implementation is similar to the existing handling of unitary gates.

In rust:

  • There is a new variant PauliProductMeasurement for PackedOperationType. This increases the total number of types to 7.
  • There is a new struct PauliProductMeasurement defined in operations.rs. Currently it stores
/// This class represents a PauliProductMeasurement instruction.
#[derive(Clone, Debug)]
#[repr(align(8))]
pub struct PauliProductMeasurement {
    /// The z-component of the pauli.
    pub z: Vec<bool>,
    /// The x-component of the pauli.
    pub x: Vec<bool>,
    /// The phase of the pauli. This is an integer modulo 4.
    pub phase: u8,
}

but we are considering bitpacking the data structure.

  • There is a new variant PPM of OperationRef.

  • The synthesis crate contains a function synthesize_ppm, which is used in HighLevelSynthesis transpiler pass to synthesize PauliProductMeasurement instructions (this is very similar to handling unitary gates) and is also exposed to Python (allowing to extract definition on the python side).

In python:

  • There is a "mirror" class PauliProductMeasurement, which can be instantiated from a Pauli object:
ppm = PauliProductMeasurement(Pauli("-XYIZ"))

qc = QuantumCircuit(6, 2)
qc.append(ppm, [0, 1, 2, 3], [0])
  • Internally, the pauli data (pauli.z, pauli.x, pauli.phase) is stored in the params field, allowing to easily pass it down to rust.

  • QPY serialization works

  • Visualization is done using the label attribute. Currently this just draws a box over relevant quantum and classical bits.

  • Default and custom labels are preserved correct;y

Note that we support pauli product measurement instructions based on Paulis with some "I"-terms (except for all-"I"), since we believe that this might simplify user workflows. However, for efficiency it would be better for the users to avoid creating "I"s, and we will try to document this clearly both in the docstring and the release note

ToDo (in this PR)

  • Write release note once the implementation is a bit more stable.
  • Port the class to rust, the quantum circuit generation should be based on [Pauli Evolution].(https://github.com/Qiskit/qiskit/blob/main/crates/circuit_library/src/pauli_evolution.rs).
  • Add Tests to check that transpiler passes that handle measurements do not fail.
  • Minor fix to visualization (to print “PauliProductMeasurement” and not “Pauliproductmeasurement”).
  • Handle QPY serialization.
  • Improve the PauliProductMeasurement data structure (at least in Rust).
  • Double-check that the endianness of Pauli is consistent with the one in SparseObservable.

ToDo (in follow-ups)

  • Let visualization draw the double-wire down to the classical register, like the standard measurements do.
  • Update the LitinskiTransformation pass to produce PPMs in addition to PPRs (pauli product rotations).
  • Rethink and improve transpiler passes involving measurement instructions
  • Qasm?

@ShellyGarion ShellyGarion added this to the 2.3.0 milestone Oct 5, 2025
@ShellyGarion ShellyGarion added the fault tolerance related to fault tolerance compilation label Oct 5, 2025
@coveralls
Copy link

coveralls commented Oct 5, 2025

Pull Request Test Coverage Report for Build 18442497358

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 192 of 257 (74.71%) changed or added relevant lines in 18 files are covered.
  • 186 unchanged lines in 10 files lost coverage.
  • Overall coverage remained the same at 88.222%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/circuit/src/converters.rs 0 1 0.0%
crates/transpiler/src/passes/basis_translator/mod.rs 1 2 50.0%
crates/transpiler/src/passes/high_level_synthesis.rs 1 2 50.0%
crates/circuit/src/circuit_data.rs 0 2 0.0%
crates/circuit/src/dag_node.rs 0 2 0.0%
crates/transpiler/src/target/mod.rs 0 2 0.0%
qiskit/qpy/binary_io/circuits.py 4 6 66.67%
qiskit/circuit/library/pauli_product_measurement.py 34 37 91.89%
crates/circuit/src/circuit_instruction.rs 22 28 78.57%
crates/synthesis/src/pauli_product_measurement.rs 87 93 93.55%
Files with Coverage Reduction New Missed Lines %
crates/circuit/src/dag_circuit.rs 1 84.53%
crates/circuit/src/packed_instruction.rs 1 91.64%
crates/circuit/src/parameter/parameter_expression.rs 1 81.99%
crates/circuit/src/parameter/symbol_expr.rs 1 72.82%
crates/qasm2/src/lex.rs 1 92.54%
crates/transpiler/src/passes/unitary_synthesis.rs 1 92.4%
crates/transpiler/src/passes/optimize_1q_gates_decomposition.rs 15 91.4%
qiskit/visualization/circuit/text.py 17 93.62%
qiskit/circuit/quantumcircuit.py 73 94.34%
crates/transpiler/src/passes/basis_translator/mod.rs 75 82.9%
Totals Coverage Status
Change from base Build 18229727033: 0.0%
Covered Lines: 93380
Relevant Lines: 105847

💛 - Coveralls

* Conversions between rust and python classes

* Synthesis of PauliProductMeasurement instructions

* Exposing synthesis function to python

* Usage of synthesis function in HLS

* QPY support

* Some additional python tests
@ShellyGarion ShellyGarion added the Rust This PR or issue is related to Rust code in the repository label Oct 12, 2025
Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

The overall structure LGTM, my main comment is about the internal Pauli representation. Do we want to generally adopt a dense ZX formulation for Cliffords and PBC? If yes, we (1) need to ensure we can build the PPMs from the string output of the Litinski transformation, and (2) it would be nice to investigate bitpacking (e.g. bitvec package?).

"""
Args:
pauli: A tensor product of Pauli operators defining the measurement,
for example ``Pauli("XY")`` or ``Pauli("-XYIZ)``.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for example ``Pauli("XY")`` or ``Pauli("-XYIZ)``.
for example ``Pauli("XY")`` or ``Pauli("-XYIZ")``.

Comment on lines +58 to +59
While Paulis involving "I"-terms are fully supported, it is recommended to remove
"I"-terms from the Pauli when creating a ``PauliProductMeasurement`` instruction,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
While Paulis involving "I"-terms are fully supported, it is recommended to remove
"I"-terms from the Pauli when creating a ``PauliProductMeasurement`` instruction,
While Paulis involving ``"I"``-terms are fully supported, it is recommended to remove
``"I"``-terms from the Pauli when creating a ``PauliProductMeasurement`` instruction,

"""

if not isinstance(pauli, Pauli):
Copy link
Contributor

Choose a reason for hiding this comment

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

We generally don't have these type checks in the circuit library -- the type hint is clear here so it's on the user to provide the right type.

with self.assertRaises(CircuitError):
_ = PauliProductMeasurement(Pauli(p))

@data("", "II", "-III")
Copy link
Contributor

Choose a reason for hiding this comment

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

Arguably "" is not the identity string 😉 A separate error here seems better.

qc3.append(PauliProductMeasurement(Pauli("XZ")), [4, 1], [0])

qc4 = QuantumCircuit(5, 2)
qc4.append(PauliProductMeasurement(Pauli("ZX")), [4, 1], [1])
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be good to add a case here that checks that reverting the Pauli labels and qubit indices evaluates to True.

Instruction(&'a PyInstruction),
Operation(&'a PyOperation),
Unitary(&'a UnitaryGate),
PPM(&'a PauliProductMeasurement),
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we spell PPM out? No other gates are abbreviated and it'll be more consistent with PauliEvolutionGate or PauliRotationGate.

#[repr(align(8))]
pub struct PauliProductMeasurement {
/// The z-component of the pauli.
pub z: Vec<bool>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Are there alternatives to storing boolean vectors, i.e. 1 byte per term if we'd only need 1 bit? E.g. the bitvec package seems to have a convenient interface. I assume @jakelishman has some opinions here 🙂


/// This class represents a PauliProductMeasurement instruction.
#[derive(Clone, Debug)]
#[repr(align(8))]
Copy link
Contributor

Choose a reason for hiding this comment

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

In the current format I don't think we need this, since bool and u8 are both a byte long. But if we change to bitvec or some bitpacking we might need to keep it?

/// This class represents a PauliProductMeasurement instruction.
#[derive(Clone, Debug)]
#[repr(align(8))]
pub struct PauliProductMeasurement {
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll need to construct the PPMs in the Rust Litinski transformation code, which currently outputs the Pauli in a sparse string format (sign, Paulistring, indices). If we use ZX bits as internal representation we'll also need a converter from Pauli string to ZX in Rust.

with self.assertRaises(CircuitError):
_ = PauliProductMeasurement(Pauli("XYZ")).inverse()

def test_qpy(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for thinking of qpy 👍🏻 I guess for qasm export it'll just define the instruction?

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

Labels

fault tolerance related to fault tolerance compilation Rust This PR or issue is related to Rust code in the repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add PauliProductMeasurement

4 participants