Skip to content

Commit f484b02

Browse files
sengthaijosh146dime10joeycarter
authored
ppm_compilation : Apply all sub-PPM compilation passes in one step. (#1750)
**Context:** Introduced the `ppm_compilation` pass as a top-level composition of existing transformation and optimization passes. This allows users to apply the transformation Clifford+T to Pauli product measurements (PPMs) in a single step without managing sub-pass sequencing manually. The sub-paseses comprise: * `to_ppr`: Converts gates into Pauli Product Rotations (PPRs) (Transformation) #1486 #1499 * `commute_ppr`: Commutes PPRs past non-Clifford PPRs (Optimization) #1563 * `merge_ppr_ppm`: Merges PPRs into PPMs (Optimization) #1580 * `ppr_to_ppm`: Decomposes PPRs into PPMs (Transformation) #1664 **Example:** ```python import pennylane as qml from catalyst import measure, passes, qjit pipe = [("pipe", ["enforce-runtime-invariants-pipeline"])] @qjit(pipelines=pipe, target="mlir") @passes.ppm_compilation(decompose_method="auto-corrected", avoid_y_measure=True, max_pauli_size=2) @qml.qnode(qml.device("null.qubit", wires=2)) def circuit(): qml.CNOT([0, 1]) qml.CNOT([1, 0]) qml.adjoint(qml.T)(0) qml.T(1) return measure(0), measure(1) print(circuit.mlir_opt) ``` **Benefits:** - Simplifies the user workflow for compilation. [[sc-90149]] --------- Co-authored-by: Josh Izaac <[email protected]> Co-authored-by: David Ittah <[email protected]> Co-authored-by: Joey Carter <[email protected]>
1 parent 3c66a23 commit f484b02

File tree

12 files changed

+706
-31
lines changed

12 files changed

+706
-31
lines changed

doc/releases/changelog-dev.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@
2323
* `decompose_non_clifford_ppr`: Decompose non-Cliford PPR (:math:`\theta = \tfrac{\pi}{8}`)
2424
into PPMs using a magic state.
2525

26+
* A new compilation pass called :func:`~.passes.ppm_compilation` has been added to Catalyst to
27+
transform Clifford+T gates into Pauli Product Measurements (PPMs). This high-level pass simplifies
28+
circuit transformation and optimization by combining multiple sub-passes into a single step.
29+
[(#1750)](https://github.com/PennyLaneAI/catalyst/pull/1750)
30+
31+
The sub-passes that make up the :func:`~.passes.ppm_compilation` pass are:
32+
* :func:`~.passes.to_ppr`: Converts gates into Pauli Product Rotations (PPRs).
33+
* :func:`~.passes.commute_ppr`: Commutes PPRs past non-Clifford PPRs.
34+
* :func:`~.passes.merge_ppr_ppm`: Merges Clifford PPRs into PPMs.
35+
* :func:`~.passes.ppr_to_ppm`: Decomposes non-Clifford and Clifford PPRs into PPMs.
36+
37+
Use this pass via the :func:`~.passes.ppm_compilation` decorator to compile circuits
38+
in a single pipeline.
39+
2640
* Support for :class:`qml.Snapshot <pennylane.Snapshot>` to capture quantum states at any
2741
point in a circuit has been added to Catalyst [(#1741)](https://github.com/PennyLaneAI/catalyst/pull/1741).
2842
For example, the code below is capturing
@@ -60,7 +74,6 @@
6074
Array([0.25, 0.25, 0.25, 0.25], dtype=float64))
6175
```
6276

63-
6477
<h3>Improvements 🛠</h3>
6578

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

109-
* `qunitary_p` is now `unitary_p` (unchanged)
110-
* `qmeasure_p` is now `measure_p` (unchanged)
111-
* `qdevice_p` is now `device_init_p` (unchanged)
112-
* `qdealloc_p` no longer releases the device, thus it can be used at any point of a quantum
122+
- `qunitary_p` is now `unitary_p` (unchanged)
123+
- `qmeasure_p` is now `measure_p` (unchanged)
124+
- `qdevice_p` is now `device_init_p` (unchanged)
125+
- `qdealloc_p` no longer releases the device, thus it can be used at any point of a quantum
113126
execution scope
114-
* `device_release_p` is a new primitive that must be used to mark the end of a quantum execution
127+
- `device_release_p` is a new primitive that must be used to mark the end of a quantum execution
115128
scope, which will release the quantum device
116129

117130
* Catalyst has removed the `experimental_capture` keyword from the `qjit` decorator in favour of
@@ -150,13 +163,13 @@
150163
[(#1729)](https://github.com/PennyLaneAI/catalyst/pull/1729)
151164

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

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

159-
* A new `debug_info` argument is added to `Jaxpr`, the `make_jaxpr`
172+
- A new `debug_info` argument is added to `Jaxpr`, the `make_jaxpr`
160173
functions, and `jax.extend.linear_util.wrap_init`.
161174
[(#1670)](https://github.com/PennyLaneAI/catalyst/pull/1670)
162175
[(#1671)](https://github.com/PennyLaneAI/catalyst/pull/1671)

frontend/catalyst/passes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
ions_decomposition,
4040
merge_ppr_ppm,
4141
merge_rotations,
42+
ppm_compilation,
4243
ppr_to_ppm,
4344
to_ppr,
4445
)
@@ -56,4 +57,5 @@
5657
"apply_pass_plugin",
5758
"merge_ppr_ppm",
5859
"ppr_to_ppm",
60+
"ppm_compilation",
5961
)

frontend/catalyst/passes/builtin_passes.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,3 +669,115 @@ def circuit():
669669
)
670670

671671
return PassPipelineWrapper(qnode, passes)
672+
673+
674+
def ppm_compilation(
675+
qnode=None, *, decompose_method="auto-corrected", avoid_y_measure=False, max_pauli_size=0
676+
):
677+
R"""
678+
Specify that the MLIR compiler pass for transforming
679+
clifford+T gates into Pauli Product Measurements (PPM) will be applied.
680+
681+
This pass combines multiple sub-passes:
682+
683+
- :func:`~.passes.to_ppr` : Converts gates into Pauli Product Rotations (PPRs)
684+
- :func:`~.passes.commute_ppr` : Commutes PPRs past non-Clifford PPRs
685+
- :func:`~.passes.merge_ppr_ppm` : Merges PPRs into Pauli Product Measurements (PPMs)
686+
- :func:`~.passes.ppr_to_ppm` : Decomposes PPRs into PPMs
687+
688+
The ``avoid_y_measure`` and ``decompose_method`` arguments are passed
689+
to the :func:`~.passes.ppr_to_ppm` pass.
690+
The ``max_pauli_size`` argument is passed to the :func:`~.passes.commute_ppr`
691+
and :func:`~.passes.merge_ppr_ppm` passes.
692+
693+
Args:
694+
qnode (QNode, optional): QNode to apply the pass to. If None, returns a decorator.
695+
decompose_method (str, optional): The method to use for decomposing non-Clifford PPRs.
696+
Options are ``"auto-corrected"`` and ``"clifford-corrected"``. Defaults to
697+
``"auto-corrected"``.
698+
``"auto-corrected"`` uses an additional measurement for correction.
699+
``"clifford-corrected"`` uses a Clifford rotation for correction.
700+
avoid_y_measure (bool): Rather than performing a Pauli-Y measurement for Clifford rotations
701+
(sometimes more costly), a :math:`Y` state (:math:`Y\vert 0 \rangle`) is used instead
702+
(requires :math:`Y` state preparation). Defaults to ``False``.
703+
max_pauli_size (int): The maximum size of the Pauli strings after commuting or merging.
704+
Defaults to 0 (no limit).
705+
706+
Returns:
707+
~.QNode or callable: Returns decorated QNode if qnode is provided,
708+
otherwise returns a decorator.
709+
710+
**Example**
711+
712+
If a merging resulted in a PPM acting on more than
713+
``max_pauli_size`` qubits (here, ``max_pauli_size = 2``), that merging would be skipped.
714+
However, when decomposed into PPMs, at least one qubit will be applied, so the final
715+
PPMs will act on at least one additional qubit.
716+
717+
.. code-block:: python
718+
719+
import pennylane as qml
720+
from catalyst import qjit, measure
721+
from catalyst.passes import ppm_compilation
722+
723+
pipeline = [("pipe", ["enforce-runtime-invariants-pipeline"])]
724+
method = "clifford-corrected"
725+
726+
@qjit(pipelines=pipeline, target="mlir")
727+
@ppm_compilation(decompose_method=method, avoid_y_measure=True, max_pauli_size=2)
728+
@qml.qnode(qml.device("null.qubit", wires=2))
729+
def circuit():
730+
qml.CNOT([0, 1])
731+
qml.CNOT([1, 0])
732+
qml.adjoint(qml.T)(0)
733+
qml.T(1)
734+
return measure(0), measure(1)
735+
736+
print(circuit.mlir_opt)
737+
738+
Example MLIR Representation:
739+
740+
.. code-block:: mlir
741+
742+
. . .
743+
%m, %out:3 = qec.ppm ["Z", "Z", "Z"] %1, %2, %4 : !quantum.bit, !quantum.bit, !quantum.bit
744+
%m_0, %out_1:2 = qec.ppm ["Z", "Y"] %3, %out#2 : !quantum.bit, !quantum.bit
745+
%m_2, %out_3 = qec.ppm ["X"] %out_1#1 : !quantum.bit
746+
%m_4, %out_5 = qec.select.ppm(%m, ["X"], ["Z"]) %out_1#0 : !quantum.bit
747+
%5 = arith.xori %m_0, %m_2 : i1
748+
%6:2 = qec.ppr ["Z", "Z"](2) %out#0, %out#1 cond(%5) : !quantum.bit, !quantum.bit
749+
quantum.dealloc_qb %out_5 : !quantum.bit
750+
quantum.dealloc_qb %out_3 : !quantum.bit
751+
%7 = quantum.alloc_qb : !quantum.bit
752+
%8 = qec.fabricate magic_conj : !quantum.bit
753+
%m_6, %out_7:2 = qec.ppm ["Z", "Z"] %6#1, %8 : !quantum.bit, !quantum.bit
754+
%m_8, %out_9:2 = qec.ppm ["Z", "Y"] %7, %out_7#1 : !quantum.bit, !quantum.bit
755+
%m_10, %out_11 = qec.ppm ["X"] %out_9#1 : !quantum.bit
756+
%m_12, %out_13 = qec.select.ppm(%m_6, ["X"], ["Z"]) %out_9#0 : !quantum.bit
757+
%9 = arith.xori %m_8, %m_10 : i1
758+
%10 = qec.ppr ["Z"](2) %out_7#0 cond(%9) : !quantum.bit
759+
quantum.dealloc_qb %out_13 : !quantum.bit
760+
quantum.dealloc_qb %out_11 : !quantum.bit
761+
%m_14, %out_15:2 = qec.ppm ["Z", "Z"] %6#0, %10 : !quantum.bit, !quantum.bit
762+
%from_elements = tensor.from_elements %m_14 : tensor<i1>
763+
%m_16, %out_17 = qec.ppm ["Z"] %out_15#1 : !quantum.bit
764+
. . .
765+
766+
"""
767+
passes = {
768+
"ppm_compilation": {
769+
"decompose-method": decompose_method,
770+
"avoid-y-measure": avoid_y_measure,
771+
"max-pauli-size": max_pauli_size,
772+
}
773+
}
774+
775+
if qnode is None:
776+
return functools.partial(
777+
ppm_compilation,
778+
decompose_method=decompose_method,
779+
avoid_y_measure=avoid_y_measure,
780+
max_pauli_size=max_pauli_size,
781+
)
782+
783+
return PassPipelineWrapper(qnode, passes)

frontend/catalyst/passes/pass_api.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,4 +378,5 @@ def _API_name_to_pass_name():
378378
"commute_ppr": "commute_ppr",
379379
"merge_ppr_ppm": "merge_ppr_ppm",
380380
"ppr_to_ppm": "ppr_to_ppm",
381+
"ppm_compilation": "ppm_compilation",
381382
}

0 commit comments

Comments
 (0)