-
Notifications
You must be signed in to change notification settings - Fork 575
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
Adds elementary gate decomposition for single-qubit QubitUnitary
#1427
Changes from all commits
acb3357
62863e1
5dbe2c0
2772e52
bb6f7f0
c90439a
f3c5bd0
3fef91d
4d66997
5f723fb
b8a524b
60a0b1e
f8d41ce
dccdba7
f457347
284e65f
85eb1ab
c6ad65b
9cc9ffe
6986829
5fddce4
e4591c9
0fe201e
b09e6d8
91ae536
ef09356
b9480be
b621546
a40f05b
3b6c694
97ee4db
7232294
262fbaf
1489d81
4e2925e
0cd62fa
379ea3f
0dd4ce0
8c41a0d
513a4d6
40fdf67
8a20067
26575ea
03bd4dd
070bc97
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 |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Copyright 2018-2021 Xanadu Quantum Technologies Inc. | ||
|
||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
|
||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
r"""This module contains decompositions for (numerically-specified) arbitrary | ||
unitary operations into sequences of elementary operations. | ||
Comment on lines
+14
to
+15
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. I can imagine this being really useful going forward! Both for devs, and external contributors that want to contribute new decompositions to PL without the overhead of having to learn the |
||
""" | ||
|
||
from .single_qubit_unitary import zyz_decomposition |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# Copyright 2018-2021 Xanadu Quantum Technologies Inc. | ||
|
||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
|
||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Contains transforms and helpers functions for decomposing arbitrary unitary | ||
operations into elementary gates. | ||
""" | ||
import pennylane as qml | ||
from pennylane import math | ||
|
||
|
||
def _convert_to_su2(U): | ||
r"""Check unitarity of a matrix and convert it to :math:`SU(2)` if possible. | ||
|
||
Args: | ||
U (array[complex]): A matrix, presumed to be :math:`2 \times 2` and unitary. | ||
|
||
Returns: | ||
array[complex]: A :math:`2 \times 2` matrix in :math:`SU(2)` that is | ||
equivalent to U up to a global phase. | ||
""" | ||
# Check unitarity | ||
if not math.allclose(math.dot(U, math.T(math.conj(U))), math.eye(2), atol=1e-7): | ||
raise ValueError("Operator must be unitary.") | ||
|
||
# Compute the determinant | ||
det = U[0, 0] * U[1, 1] - U[0, 1] * U[1, 0] | ||
|
||
# Convert to SU(2) if it's not close to 1 | ||
if not math.allclose(det, [1.0]): | ||
exp_angle = -1j * math.cast_like(math.angle(det), 1j) / 2 | ||
U = math.cast_like(U, exp_angle) * math.exp(exp_angle) | ||
|
||
return U | ||
|
||
|
||
def zyz_decomposition(U, wire): | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
r"""Recover the decomposition of a single-qubit matrix :math:`U` in terms of | ||
elementary operations. | ||
|
||
Diagonal operations will be converted to a single :class:`.RZ` gate, while non-diagonal | ||
operations will be converted to a :class:`.Rot` gate that implements the original operation | ||
up to a global phase in the form :math:`RZ(\omega) RY(\theta) RZ(\phi)`. | ||
|
||
Args: | ||
U (tensor): A 2 x 2 unitary matrix. | ||
wire (Union[Wires, Sequence[int] or int]): The wire on which to apply the operation. | ||
|
||
Returns: | ||
list[qml.Operation]: A ``Rot`` gate on the specified wire that implements ``U`` | ||
up to a global phase, or an equivalent ``RZ`` gate if ``U`` is diagonal. | ||
|
||
**Example** | ||
|
||
Suppose we would like to apply the following unitary operation: | ||
|
||
.. code-block:: python3 | ||
|
||
U = np.array([ | ||
[-0.28829348-0.78829734j, 0.30364367+0.45085995j], | ||
[ 0.53396245-0.10177564j, 0.76279558-0.35024096j] | ||
]) | ||
|
||
For PennyLane devices that cannot natively implement ``QubitUnitary``, we | ||
can instead recover a ``Rot`` gate that implements the same operation, up | ||
to a global phase: | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
>>> decomp = zyz_decomposition(U, 0) | ||
>>> decomp | ||
[Rot(-0.24209529417800013, 1.14938178234275, 1.7330581433950871, wires=[0])] | ||
""" | ||
U = _convert_to_su2(U) | ||
|
||
# Check if the matrix is diagonal; only need to check one corner. | ||
# If it is diagonal, we don't need a full Rot, just return an RZ. | ||
if math.allclose(U[0, 1], [0.0]): | ||
omega = 2 * math.angle(U[1, 1]) | ||
return [qml.RZ(omega, wires=wire)] | ||
|
||
# If not diagonal, compute the angle of the RY | ||
cos2_theta_over_2 = math.abs(U[0, 0] * U[1, 1]) | ||
theta = 2 * math.arccos(math.sqrt(cos2_theta_over_2)) | ||
|
||
# If the top left element is 0, can only use the off-diagonal elements We | ||
# have to be very careful with the math here to ensure things that get | ||
# multiplied together are of the correct type in the different interfaces. | ||
if math.allclose(U[0, 0], [0.0]): | ||
phi = 1j * math.log(U[0, 1] / U[1, 0]) | ||
omega = -phi - math.cast_like(2 * math.angle(U[1, 0]), phi) | ||
else: | ||
el_division = U[0, 0] / U[1, 0] | ||
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. El Division sounds like a math-parody mariachi band 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. 🤣 🤣 |
||
tan_part = math.cast_like(math.tan(theta / 2), el_division) | ||
omega = 1j * math.log(tan_part * el_division) | ||
phi = -omega - math.cast_like(2 * math.angle(U[0, 0]), omega) | ||
|
||
return [qml.Rot(math.real(phi), math.real(theta), math.real(omega), wires=wire)] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Copyright 2018-2021 Xanadu Quantum Technologies Inc. | ||
|
||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
|
||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
""" | ||
A transform for decomposing arbitrary single-qubit QubitUnitary gates into elementary gates. | ||
""" | ||
import pennylane as qml | ||
from pennylane.transforms import qfunc_transform | ||
from pennylane.transforms.decompositions import zyz_decomposition | ||
|
||
|
||
@qfunc_transform | ||
def unitary_to_rot(tape): | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Quantum function transform to decomposes all instances of single-qubit :class:`~.QubitUnitary` | ||
operations to parametrized single-qubit operations. | ||
|
||
Diagonal operations will be converted to a single :class:`.RZ` gate, while non-diagonal | ||
operations will be converted to a :class:`.Rot` gate that implements the original operation | ||
up to a global phase. | ||
|
||
Args: | ||
qfunc (function): a quantum function | ||
|
||
**Example** | ||
|
||
Suppose we would like to apply the following unitary operation: | ||
|
||
.. code-block:: python3 | ||
|
||
U = np.array([ | ||
[-0.17111489+0.58564875j, -0.69352236-0.38309524j], | ||
[ 0.25053735+0.75164238j, 0.60700543-0.06171855j] | ||
]) | ||
|
||
The ``unitary_to_rot`` transform enables us to decompose | ||
such numerical operations (as well as unitaries that may be defined by parameters | ||
within the QNode, and instantiated therein), while preserving differentiability. | ||
|
||
|
||
.. code-block:: python3 | ||
|
||
def qfunc(): | ||
qml.QubitUnitary(U, wires=0) | ||
return qml.expval(qml.PauliZ(0)) | ||
|
||
The original circuit is: | ||
|
||
>>> dev = qml.device('default.qubit', wires=1) | ||
>>> qnode = qml.QNode(qfunc, dev) | ||
>>> print(qml.draw(qnode)()) | ||
0: ──U0──┤ ⟨Z⟩ | ||
U0 = | ||
[[-0.17111489+0.58564875j -0.69352236-0.38309524j] | ||
[ 0.25053735+0.75164238j 0.60700543-0.06171855j]] | ||
|
||
We can use the transform to decompose the gate: | ||
|
||
>>> transformed_qfunc = unitary_to_rot(qfunc) | ||
>>> transformed_qnode = qml.QNode(transformed_qfunc, dev) | ||
>>> print(qml.draw(transformed_qnode)()) | ||
0: ──Rot(-1.35, 1.83, -0.606)──┤ ⟨Z⟩ | ||
|
||
""" | ||
josh146 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for op in tape.operations + tape.measurements: | ||
if isinstance(op, qml.QubitUnitary): | ||
# Only act on single-qubit unitary operations | ||
if qml.math.shape(op.parameters[0]) != (2, 2): | ||
continue | ||
|
||
zyz_decomposition(op.parameters[0], op.wires[0]) | ||
else: | ||
qml.apply(op) |
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.
Not sure why this isn't getting caught; the corresponding test is on line 1593 of the
test_qubit_ops
file.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.
Ah, I think this is a well-known coverage bug. Fine to ignore.