Skip to content

Implement a decomposition of PPRs into PPMs #1664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
dd110d1
Add the `qec.prepare` operation for initializing the logical qubits.
sengthai Apr 16, 2025
907957d
- Introduced `SelectPPMeasurementOp` for conditional Pauli product me…
sengthai Apr 22, 2025
9de681b
Add convenience builder
sengthai Apr 22, 2025
a37ab39
Implement new pass that includes `auto-corrected` and `inject-magic-s…
sengthai Apr 23, 2025
dc78cfb
Added test case
sengthai Apr 23, 2025
57e1276
Refactor decomposition method
sengthai Apr 24, 2025
6aa10bf
Fixed memory buffer caused by using `ArrayRef`
sengthai Apr 24, 2025
0994d63
Added verifier to
sengthai Apr 24, 2025
3bd3292
Supported decomposition of
sengthai Apr 27, 2025
a26289b
Merge branch 'main' into ppr-to-ppm
sengthai Apr 27, 2025
44c7f90
Supported pass in frontend
sengthai Apr 28, 2025
d2c2e26
Add tests
sengthai Apr 29, 2025
2fb6a99
Separate decomposition of pi/4 and pi/8 PPR into two MLIR passes
sengthai Apr 29, 2025
d23eddd
codefactor
sengthai Apr 29, 2025
9b84432
Updated test in
sengthai Apr 30, 2025
914843a
Formated
sengthai Apr 30, 2025
a5a55bd
Added test case
sengthai Apr 30, 2025
055681c
Apply suggestions from code review
sengthai May 7, 2025
f663816
Update docstring
sengthai May 7, 2025
6191c9c
- Modified `state-prep` to `avoid-y-measure`
sengthai May 8, 2025
1b917e6
Update frontend/catalyst/passes/builtin_passes.py
sengthai May 8, 2025
a511f45
Added more info on
sengthai May 8, 2025
e3874c2
Removed function call in pytest
sengthai May 8, 2025
7521430
Update docstring for `ppr_to_ppm` function to clarify decomposition m…
sengthai May 8, 2025
3beb2f3
- Added `qec.fabricate` to QEC dialect
sengthai May 8, 2025
fb28104
Supported `qec.fabricate` in `decompose_non_clifford_ppr`
sengthai May 8, 2025
9d942c9
Update mlir/include/QEC/IR/QECDialect.td
sengthai May 13, 2025
e134941
Removed unnesessary comment
sengthai May 13, 2025
0aff695
Refactor `decompose_auto_corrected_pi_over_eight` to use boolean for …
sengthai May 13, 2025
8243b2d
Refactor `SelectPPMeasurementOp` to use `select_switch` for clarity a…
sengthai May 13, 2025
8a95e18
Add `AllocQubitOp` and `DeallocQubitOp` to QuantumOps.td; update deco…
sengthai May 13, 2025
b5b7008
Refactored `alloc_qb` and `dealloc_qb`.
sengthai May 15, 2025
ffc213f
Removed memory effect in `AllocQubitOp`
sengthai May 15, 2025
589e1b1
Enhance description of`PrepareStateOp` and `FabricateOp` to clarify s…
sengthai May 15, 2025
2a362c4
Enchanced docstring in PrepareStateOp
sengthai May 15, 2025
63cc5b0
Updated changelog
sengthai May 15, 2025
2ad7e59
Updated docstring in ppr_to_ppm pass
sengthai May 15, 2025
937e942
Merge branch 'main' into ppr-to-ppm
sengthai May 15, 2025
ec77938
Formated
sengthai May 15, 2025
e6d5bd2
Updated ppr_to_ppm in frontend
sengthai May 15, 2025
ed98d01
Update changelog and improve ppr_to_ppm function signature
sengthai May 16, 2025
7157c46
Apply suggestions from code review
sengthai May 20, 2025
9065f4a
Enhance DecomposeNonCliffordPPR pass to include `avoid-y-measure` option
sengthai May 20, 2025
ed178d4
Merge branch 'main' into ppr-to-ppm
sengthai May 20, 2025
8cff4bd
Add PPR decomposition utility functions
sengthai May 20, 2025
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
33 changes: 25 additions & 8 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

<h3>New features since last release</h3>

* A new compilation pass called :func:`~.passes.ppr_to_ppm` has been added to Catalyst
to decompose Pauli product rotations (PPRs), :math:`\exp(-iP_{\{x, y, z\}} \theta)`, into
Pauli product measurements (PPMs). Non-Clifford PPR (:math:`\theta = \tfrac{\pi}{8}`) requires
the consumption of a magic state, while Clifford PPR (:math:`\theta = \tfrac{\pi}{4}`) will not.
The methods are implemented as described in [arXiv:1808.02892](https://arxiv.org/abs/1808.02892v3).
[(#1664)](https://github.com/PennyLaneAI/catalyst/pull/1664)

The new compilation pass can be accessed in the frontend from the :mod:`~.passes` module:
* :func:`~.passes.ppr_to_ppm`: Decomposes all PPRs into PPMs
(except PPR (:math:`\theta = \tfrac{\pi}{2}`)).

Or via the Catalyst CLI as two separate passes:
* `decompose_clifford_ppr`: Decompose Clifford PPR (:math:`\theta = \tfrac{\pi}{4}`)
rotations into PPMs.
* `decompose_non_clifford_ppr`: Decompose non-Cliford PPR (:math:`\theta = \tfrac{\pi}{8}`)
into PPMs using a magic state.

<h3>Improvements 🛠</h3>

* The behaviour of measurement processes executed on `null.qubit` with QJIT is now more in line with
Expand Down Expand Up @@ -47,12 +64,12 @@
deallocation primitive has been split into deallocation and a separate device release primitive.
[(#1720)](https://github.com/PennyLaneAI/catalyst/pull/1720)

- `qunitary_p` is now `unitary_p` (unchanged)
- `qmeasure_p` is now `measure_p` (unchanged)
- `qdevice_p` is now `device_init_p` (unchanged)
- `qdealloc_p` no longer releases the device, thus it can be used at any point of a quantum
* `qunitary_p` is now `unitary_p` (unchanged)
* `qmeasure_p` is now `measure_p` (unchanged)
* `qdevice_p` is now `device_init_p` (unchanged)
* `qdealloc_p` no longer releases the device, thus it can be used at any point of a quantum
execution scope
- `device_release_p` is a new primitive that must be used to mark the end of a quantum execution
* `device_release_p` is a new primitive that must be used to mark the end of a quantum execution
scope, which will release the quantum device

* Catalyst has removed the `experimental_capture` keyword from the `qjit` decorator in favour of
Expand Down Expand Up @@ -91,13 +108,13 @@
[(#1729)](https://github.com/PennyLaneAI/catalyst/pull/1729)

Several internal changes were made for this update.
- LAPACK kernels are updated to adhere to the new JAX lowering rules for external functions.
* LAPACK kernels are updated to adhere to the new JAX lowering rules for external functions.
[(#1685)](https://github.com/PennyLaneAI/catalyst/pull/1685)

- The trace stack is removed and replaced with a tracing context manager.
* The trace stack is removed and replaced with a tracing context manager.
[(#1662)](https://github.com/PennyLaneAI/catalyst/pull/1662)

- A new `debug_info` argument is added to `Jaxpr`, the `make_jaxpr`
* A new `debug_info` argument is added to `Jaxpr`, the `make_jaxpr`
functions, and `jax.extend.linear_util.wrap_init`.
[(#1670)](https://github.com/PennyLaneAI/catalyst/pull/1670)
[(#1671)](https://github.com/PennyLaneAI/catalyst/pull/1671)
Expand Down
2 changes: 2 additions & 0 deletions frontend/catalyst/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
ions_decomposition,
merge_ppr_ppm,
merge_rotations,
ppr_to_ppm,
to_ppr,
)
from catalyst.passes.pass_api import Pass, PassPlugin, apply_pass, apply_pass_plugin
Expand All @@ -54,4 +55,5 @@
"apply_pass",
"apply_pass_plugin",
"merge_ppr_ppm",
"ppr_to_ppm",
)
87 changes: 86 additions & 1 deletion frontend/catalyst/passes/builtin_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,9 +578,94 @@ def circuit():
. . .

"""

if qnode is None:
return functools.partial(merge_ppr_ppm, max_pauli_size=max_pauli_size)

merge_ppr_ppm_pass = {"merge_ppr_ppm": {"max-pauli-size": max_pauli_size}}
return PassPipelineWrapper(qnode, merge_ppr_ppm_pass)


def ppr_to_ppm(qnode=None, *, decompose_method="auto-corrected", avoid_y_measure=False):
R"""Specify that the MLIR compiler passes for decomposing Pauli Product rotations (PPR)
, :math:`\exp(-iP\theta)`, into Pauli Pauli measurements (PPM) will be applied.

This pass is used to decompose both non-Clifford and Clifford PPRs into PPMs. The non-Clifford
PPRs (:math:`\theta = \tfrac{\pi}{8}`) are decomposed first, and then Clifford PPRs
(:math:`\theta = \tfrac{\pi}{4}`) are decomposed.
Non-Clifford decomposition can be performed in one of two ways:
``"clifford-corrected"`` or ``"auto-corrected"``, by default the latter is used.
Both methods are based on `A Game of Surface Codes <https://arxiv.org/abs/1808.02892>`__,
figures 7 and 17(b) respectively.

Args:
qnode (QNode, optional): QNode to apply the pass to. If None, returns a decorator.
decompose_method (str, optional): The method to use for decomposing non-Clifford PPRs.
Options are ``"auto-corrected"`` and ``"clifford-corrected"``.
Defaults to ``"auto-corrected"``.
``"auto-corrected"`` uses an additional measurement for correction.
``"clifford-corrected"`` uses a Clifford rotation for correction.
avoid_y_measure (bool): Rather than performing a Pauli-Y measurement for Clifford rotations
(sometimes more costly), a :math:`Y` state (:math:`Y\vert 0 \rangle`) is used instead
(requires :math:`Y` state preparation). Defaults to ``False``.

Returns:
~.QNode or callable: Returns decorated QNode if qnode is provided,
otherwise returns a decorator.

**Example**

This example shows the sequence of passes that will be applied. The last pass
will convert the non-Clifford PPR into Pauli Product Measurements.

.. code-block:: python

import pennylane as qml
from catalyst import qjit, measure
from catalyst.passes import to_ppr, commute_ppr, merge_ppr_ppm, ppr_to_ppm

pipeline = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipeline, target="mlir")
@to_ppr
@commute_ppr
@merge_ppr_ppm
@ppr_to_ppm
@qml.qnode(qml.device("null.qubit", wires=2))
def circuit():
qml.H(0)
qml.T(0)
qml.CNOT([0, 1])
return measure(0), measure(1)

print(circuit.mlir_opt)

Example MLIR Representation:

.. code-block:: mlir

. . .
%5 = qec.fabricate zero : !quantum.bit
%6 = qec.fabricate magic : !quantum.bit
%mres, %out_qubits:2 = qec.ppm ["X", "Z"] %1, %6 : !quantum.bit, !quantum.bit
%mres_0, %out_qubits_1:2 = qec.ppm ["Z", "Y"] %5, %out_qubits#1 : !quantum.bit, !quantum.bit
%mres_2, %out_qubits_3 = qec.ppm ["X"] %out_qubits_1#1 : !quantum.bit
%mres_4, %out_qubits_5 = qec.select.ppm(%mres, ["X"], ["Z"]) %out_qubits_1#0 : !quantum.bit
%7 = arith.xori %mres_0, %mres_2 : i1
%8 = qec.ppr ["X"](2) %out_qubits#0 cond(%7) : !quantum.bit
. . .

"""
passes = {
"decompose_non_clifford_ppr": {
"decompose-method": decompose_method,
"avoid-y-measure": avoid_y_measure,
},
"decompose_clifford_ppr": {"avoid-y-measure": avoid_y_measure},
}

if qnode is None:
return functools.partial(
ppr_to_ppm, decompose_method=decompose_method, avoid_y_measure=avoid_y_measure
)

return PassPipelineWrapper(qnode, passes)
1 change: 1 addition & 0 deletions frontend/catalyst/passes/pass_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,4 +377,5 @@ def _API_name_to_pass_name():
"to_ppr": "to_ppr",
"commute_ppr": "commute_ppr",
"merge_ppr_ppm": "merge_ppr_ppm",
"ppr_to_ppm": "ppr_to_ppm",
}
138 changes: 131 additions & 7 deletions frontend/test/pytest/test_peephole_optimizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
import pytest

from catalyst import measure, pipeline, qjit
from catalyst.passes import cancel_inverses, commute_ppr, merge_ppr_ppm, merge_rotations, to_ppr
from catalyst.passes import (
cancel_inverses,
commute_ppr,
merge_ppr_ppm,
merge_rotations,
ppr_to_ppm,
to_ppr,
)

# pylint: disable=missing-function-docstring

Expand Down Expand Up @@ -180,9 +187,7 @@ def test_chained_apply_passes_workflow(x: float):


def test_convert_clifford_to_ppr():
"""
Test convert_clifford_to_ppr
"""

pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipe, target="mlir")
Expand All @@ -204,10 +209,129 @@ def f():
assert "qec.ppr" in optimized_ir


def test_commute_ppr():

pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipe, target="mlir")
def test_commute_ppr_workflow():

@to_ppr
@commute_ppr
@qml.qnode(qml.device("lightning.qubit", wires=2))
def f():
qml.H(0)
qml.S(1)
qml.T(0)
qml.CNOT([0, 1])
return measure(0), measure(1)

return f()

assert 'transform.apply_registered_pass "commute_ppr"' in test_commute_ppr_workflow.mlir
optimized_ir = test_commute_ppr_workflow.mlir_opt
assert 'transform.apply_registered_pass "commute_ppr"' not in optimized_ir
assert "qec.ppr" in optimized_ir
assert "qec.ppm" in optimized_ir


def test_merge_ppr_ppm():

pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipe, target="mlir")
def test_merge_ppr_ppm_workflow():

@to_ppr
@merge_ppr_ppm
@qml.qnode(qml.device("lightning.qubit", wires=2))
def f():
qml.H(0)
qml.S(1)
qml.CNOT([0, 1])
return measure(0), measure(1)

return f()

assert 'transform.apply_registered_pass "merge_ppr_ppm"' in test_merge_ppr_ppm_workflow.mlir
optimized_ir = test_merge_ppr_ppm_workflow.mlir_opt
assert 'transform.apply_registered_pass "merge_ppr_ppm"' not in optimized_ir
assert 'qec.ppm ["Z", "X"]' in optimized_ir
assert 'qec.ppm ["X"]' in optimized_ir


def test_ppr_to_ppm():

pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipe, target="mlir")
def test_ppr_to_ppm_workflow():

@to_ppr
@ppr_to_ppm
@qml.qnode(qml.device("lightning.qubit", wires=2))
def f():
qml.H(0)
qml.S(1)
qml.T(0)
qml.CNOT([0, 1])
return measure(0), measure(1)

return f()

assert (
'transform.apply_registered_pass "decompose_non_clifford_ppr"'
in test_ppr_to_ppm_workflow.mlir
)
assert (
'transform.apply_registered_pass "decompose_clifford_ppr"' in test_ppr_to_ppm_workflow.mlir
)
optimized_ir = test_ppr_to_ppm_workflow.mlir_opt
assert 'transform.apply_registered_pass "decompose_non_clifford_ppr"' not in optimized_ir
assert 'transform.apply_registered_pass "decompose_clifford_ppr"' not in optimized_ir
assert "quantum.alloc_qb" in optimized_ir
assert "qec.fabricate magic" in optimized_ir
assert "qec.select.ppm" in optimized_ir
assert 'qec.ppr ["X"]' in optimized_ir


def test_ppr_to_ppm_inject_magic_state():

pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipe, target="mlir")
def test_ppr_to_ppm_workflow():

@to_ppr
@ppr_to_ppm(decompose_method="clifford-corrected", avoid_y_measure=True)
@qml.qnode(qml.device("lightning.qubit", wires=2))
def f():
qml.H(0)
qml.S(1)
qml.T(0)
qml.CNOT([0, 1])
return measure(0), measure(1)

return f()

assert (
'transform.apply_registered_pass "decompose_non_clifford_ppr"'
in test_ppr_to_ppm_workflow.mlir
)
assert (
'transform.apply_registered_pass "decompose_clifford_ppr"' in test_ppr_to_ppm_workflow.mlir
)
optimized_ir = test_ppr_to_ppm_workflow.mlir_opt
assert 'transform.apply_registered_pass "decompose_non_clifford_ppr"' not in optimized_ir
assert 'transform.apply_registered_pass "decompose_clifford_ppr"' not in optimized_ir
assert "qec.fabricate magic" in optimized_ir
assert "qec.fabricate plus_i" in optimized_ir
assert 'qec.ppm ["X", "Z"]' in optimized_ir
assert 'qec.ppr ["X"]' in optimized_ir


def test_commute_ppr_and_merge_ppr_ppm_with_max_pauli_size():
"""
Test commute_ppr
"""

pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])]

@qjit(pipelines=pipe, target="mlir")
Expand Down
7 changes: 7 additions & 0 deletions mlir/include/QEC/IR/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@ add_mlir_dialect(QECDialect qec)
add_mlir_interface(QECOpInterfaces)
add_mlir_doc(QECDialect QECDialect QEC/ -gen-dialect-doc -gen-op-doc)
add_mlir_doc(QECOpInterfaces QECOpInterfaces QEC/ -gen-op-interface-docs)

set(LLVM_TARGET_DEFINITIONS QECDialect.td)
mlir_tablegen(QECEnums.h.inc -gen-enum-decls)
mlir_tablegen(QECEnums.cpp.inc -gen-enum-defs)
mlir_tablegen(QECAttributes.h.inc -gen-attrdef-decls)
mlir_tablegen(QECAttributes.cpp.inc -gen-attrdef-defs)
add_public_tablegen_target(MLIRQECEnumsIncGen)
13 changes: 13 additions & 0 deletions mlir/include/QEC/IR/QECDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@
#define GET_TYPEDEF_CLASSES
#include "QEC/IR/QECDialectTypes.h.inc"

//===----------------------------------------------------------------------===//
// QEC enum definitions.
//===----------------------------------------------------------------------===//

#include "QEC/IR/QECEnums.h.inc"

//===----------------------------------------------------------------------===//
// QEC attribute declarations.
//===----------------------------------------------------------------------===//

#define GET_ATTRDEF_CLASSES
#include "QEC/IR/QECAttributes.h.inc"

//===----------------------------------------------------------------------===//
// QEC ops declarations.
//===----------------------------------------------------------------------===//
Expand Down
Loading