Skip to content

Conversation

@joeycarter
Copy link
Contributor

@joeycarter joeycarter commented Nov 11, 2025

Context: A Pauli frame tracker [1] is a system that tracks gates from the Pauli group in classical electronics instead of physically applying them to qubits on the device. Doing so reduces the number of quantum operations applied to the system, thus reducing the probability of errors. This PR adds a Pauli frame tracking dialect to Catalyst and includes a set of abstractions and operations for interacting with an external Pauli frame tracking library.

[1] Knill, E. Quantum computing with realistically noisy devices. Nature 434, 39-44 (2005). https://doi.org/10.1038/nature03350.

Description of the Change: The PauliFrame dialect includes the following operations:

  • pauli_frame.init, which initializes the Pauli record of the target qubit(s) to I.
  • pauli_frame.init_qreg, same as pauli_frame.init except it initializes theh Pauli records of all the qubits in a quantum register.
  • pauli_frame.update, which updates the Pauli record of the target qubit(s) given a new Pauli record.
  • pauli_frame.read, which reads the Pauli record of the target qubit.
  • pauli_frame.correct_measurement, which corrects a measurement result given a Pauli record.
  • pauli_frame.flush, which flush the Pauli record(s) of the target qubit(s) (flushing means physically applying the Pauli gates in the record and re-initializing).
  • pauli_frame.set, an extra op which sets the Pauli record of the target qubit(s).

Note that this PR only adds the dialect itself. Future PRs will add compilation passes to insert these ops into the quantum program IR and to lower them to runtime stubs that call into a (thus far) mocked out Pauli frame tracking runtime library.

[sc-103530]

@joeycarter joeycarter marked this pull request as ready for review November 12, 2025 21:36
@joeycarter joeycarter requested review from a team November 12, 2025 21:37
Copy link
Member

@multiphaseCFD multiphaseCFD left a comment

Choose a reason for hiding this comment

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

Thanks @joeycarter for the nice work! Looks good to me. Just left a few minor comments/question.

Copy link
Contributor

@dime10 dime10 left a comment

Choose a reason for hiding this comment

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

Love it! 😍

The one thing I wonder is whether the pauli frame ops should generate new qubits 🤔 After all, they don't write to the qubits (they read/write from pauli frame which is a hidden state (no value for it in the ir)?), the only exception is the flush op so there it makes sense.

@joeycarter
Copy link
Contributor Author

Love it! 😍

Thanks!

The one thing I wonder is whether the pauli frame ops should generate new qubits 🤔 After all, they don't write to the qubits (they read/write from pauli frame which is a hidden state (no value for it in the ir)?), the only exception is the flush op so there it makes sense.

I was wondering this as well when I was building the interface. The reason I opted for this design was to easily replace Pauli gates with calls to the Pauli frame tracker. For example, given then program,

%0 = quantum.alloc( 1) : !quantum.reg
%1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit
%2 = quantum.custom "Hadamard"() %1 : !quantum.bit
%3 = quantum.custom "PauliX"() %2 : !quantum.bit
%4 = quantum.custom "Hadamard"() %3 : !quantum.bit

we will eventually want to have a pass that replaces the PauliX gate with a pauli_frame.update [true, false] %in_qubit op:

%0 = quantum.alloc( 1) : !quantum.reg
%1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit
%2 = quantum.custom "Hadamard"() %1 : !quantum.bit
%3 = pauli_frame.update [true, false] %2 : !quantum.bit
%4 = quantum.custom "Hadamard"() %3 : !quantum.bit

This way it's easy to replace ops without extra management of the qubit SSA values. We'll also need to consider commutation relations at some point, so returning an updated qubit value helps keep track of the operation order (although we're still working out the mechanics of how this will work).

Do you see an alternative design that might work here instead?

@dime10
Copy link
Contributor

dime10 commented Nov 13, 2025

The reason I opted for this design was to easily replace Pauli gates with calls to the Pauli frame tracker.

Replacing (or erasing) a quantum gate is actually quite easy, you would just call reaplaceAllUses on the output qubits with the input qubits.

We'll also need to consider commutation relations at some point, so returning an updated qubit value helps keep track of the operation order (although we're still working out the mechanics of how this will work).

That's a good point, if the pauli frame op consumes the same qubit value as another gate, there is no defined ordering between the two (at least on the SSA graph), the ordering would only be defined when considering the absolute ordering in the block's op list. MLIR passes won't mess with this ordering if the op is effectful, but our transforms might if we don't pay attention. In this case your current design might be safer then 👍

@codecov
Copy link

codecov bot commented Nov 13, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.48%. Comparing base (c5f86eb) to head (e61ffc6).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2188      +/-   ##
==========================================
+ Coverage   97.40%   97.48%   +0.07%     
==========================================
  Files          93       93              
  Lines       10684    10690       +6     
  Branches     1027     1027              
==========================================
+ Hits        10407    10421      +14     
+ Misses        216      209       -7     
+ Partials       61       60       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@joeycarter
Copy link
Contributor Author

The reason I opted for this design was to easily replace Pauli gates with calls to the Pauli frame tracker.

Replacing (or erasing) a quantum gate is actually quite easy, you would just call reaplaceAllUses on the output qubits with the input qubits.

We'll also need to consider commutation relations at some point, so returning an updated qubit value helps keep track of the operation order (although we're still working out the mechanics of how this will work).

That's a good point, if the pauli frame op consumes the same qubit value as another gate, there is no defined ordering between the two (at least on the SSA graph), the ordering would only be defined when considering the absolute ordering in the block's op list. MLIR passes won't mess with this ordering if the op is effectful, but our transforms might if we don't pay attention. In this case your current design might be safer then 👍

Yes I agree, it might not be strictly necessary to return the qubit, but it's safer for the time being, given our current knowledge of the system. In that case I propose we leave it as.

Copy link
Member

@multiphaseCFD multiphaseCFD left a comment

Choose a reason for hiding this comment

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

Thanks @joeycarter ! LGTM

@lillian542
Copy link
Contributor

How does it handle updates like "commute current record past Hadamard"?

@multiphaseCFD
Copy link
Member

multiphaseCFD commented Nov 18, 2025

How does it handle updates like "commute current record past Hadamard"?

That is an excellent point @lillian542 , this feature seems to be currently missing. I believe we could change the signature of .update operation by adding an arg for quantum.customOp or str_type. With this arg, we could cook the data (apply commute rules) in the update op.

@joeycarter
Copy link
Contributor Author

Moving this PR back to draft while we discuss design choices.

@joeycarter joeycarter marked this pull request as draft November 18, 2025 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants