-
Notifications
You must be signed in to change notification settings - Fork 47
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
base: main
Are you sure you want to change the base?
Changes from all commits
dd110d1
907957d
9de681b
a37ab39
dc78cfb
57e1276
6aa10bf
0994d63
3bd3292
a26289b
44c7f90
d2c2e26
2fb6a99
d23eddd
9b84432
914843a
a5a55bd
055681c
f663816
6191c9c
1b917e6
a511f45
e3874c2
7521430
3beb2f3
fb28104
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,6 +2,20 @@ | |||||
|
||||||
<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) into | ||||||
Pauli product measurements (PPMs). Non-Clifford (pi/8) rotations require the consumption of a | ||||||
magic state, while Clifford rotations will not. The methods are implemented as described in | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
[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: | ||||||
sengthai marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
* :func:`~.passes.ppr_to_ppm`: Decomposes all PPRs into PPMs (except Pauli rotations). | ||||||
|
||||||
Or via the Catalyst CLI as two separate passes: | ||||||
* `decompose_clifford_ppr`: Decompose pi/4 rotations into PPMs. | ||||||
* `decompose_non_clifford_ppr`: Decompose pi/8 rotations 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 | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -499,3 +499,83 @@ def circuit(): | |
|
||
""" | ||
return PassPipelineWrapper(qnode, "merge_ppr_ppm") | ||
|
||
|
||
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) | ||
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 (pi/8) are decomposed first, and then Clifford PPRs (pi/4) are decomposed. Non-Clifford | ||
decomposition can be performed in one of two ways: "clifford-corrected" or "auto-corrected", | ||
Comment on lines
+509
to
+510
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See changelog entry suggestion. I'd do the same here! |
||
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". | ||
isaacdevlugt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"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 | ||
sengthai marked this conversation as resolved.
Show resolved
Hide resolved
|
||
@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}, | ||
"decompose_clifford_ppr": {"avoid-y-measure": avoid_y_measure}, | ||
} | ||
|
||
def decorator(qnode_func): | ||
return PassPipelineWrapper(qnode_func, passes) | ||
|
||
if qnode is None: | ||
return decorator | ||
return decorator(qnode) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,8 +18,15 @@ | |
import pennylane as qml | ||
import pytest | ||
|
||
from catalyst import pipeline, qjit | ||
from catalyst.passes import cancel_inverses, merge_rotations, to_ppr | ||
from catalyst import measure, pipeline, qjit | ||
from catalyst.passes import ( | ||
cancel_inverses, | ||
commute_ppr, | ||
merge_ppr_ppm, | ||
merge_rotations, | ||
ppr_to_ppm, | ||
to_ppr, | ||
) | ||
|
||
# pylint: disable=missing-function-docstring | ||
|
||
|
@@ -189,8 +196,6 @@ def test_chained_apply_passes_workflow(x: float): | |
assert "merge-rotations" in test_chained_apply_passes_workflow.mlir | ||
|
||
|
||
test_chained_passes() | ||
|
||
# | ||
# to_ppr | ||
# | ||
|
@@ -221,7 +226,153 @@ def f(): | |
assert "qec.ppr" in optimized_ir | ||
|
||
|
||
test_convert_clifford_to_ppr() | ||
# | ||
# commute_ppr | ||
# | ||
Comment on lines
+229
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here as well I would avoid these comments, you should use docstrings to document individual tests, and classes to group similar tests together. That's how we organize all our other pytests as well :) In this case the headers don't really do much anyway since they just repeat the name of the test function. |
||
|
||
|
||
def test_commute_ppr(): | ||
""" | ||
Test commute_ppr | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A good docstring will say what you are testing and possibly additional information like what you are expecting (if that isn't obvious). |
||
""" | ||
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 | ||
|
||
|
||
# | ||
# merge_ppr_ppm | ||
# | ||
|
||
|
||
def test_merge_ppr_ppm(): | ||
""" | ||
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 | ||
|
||
|
||
# | ||
# ppr_to_ppm | ||
# | ||
|
||
|
||
def test_ppr_to_ppm(): | ||
""" | ||
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 "qec.fabricate zero" in optimized_ir | ||
assert "qec.fabricate magic" in optimized_ir | ||
assert "qec.select.ppm" in optimized_ir | ||
assert 'qec.ppr ["X"]' in optimized_ir | ||
|
||
|
||
# | ||
# ppr_to_ppm with clifford-corrected | ||
# | ||
|
||
|
||
def test_ppr_to_ppm_inject_magic_state(): | ||
""" | ||
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(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 | ||
|
||
|
||
if __name__ == "__main__": | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
x, y, z?
The expression is also missing an indication that the exponential argument is a product.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know that these rendered forms add that much, unless we say we have to define the term PPR mathematically whenever we use it. It's also harder to read unrendered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do something to clarify what the "pi/8" means, especially because, in PL, we take the convention of the factor of 1/2 being in the exponent already! I found that to be a pesky blocker when trying to roughly implement some things in the paper in PL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about defining PPR once when it is first mentioned? Then you can say
Non-Clifford (Θ=pi/8) rotations
assuming theta was the variable used in the definition