From b6250aa1e4e0ecd740783ee0145ce78b51100740 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 13:24:15 -0500 Subject: [PATCH 01/25] Add QFT --- pennylane/ops/qubit.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 28e47f4a1b2..01cfc35afa1 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1580,6 +1580,61 @@ def decomposition(D, wires): return [QubitUnitary(np.diag(D), wires=wires)] +class QFT(Operation): + r"""QFT(wires) + Apply a quantum Fourier transform (QFT). + + For the :math:`N`-qubit computational basis state :math:`|m\rangle`, the QFT performs the + transformation + + .. math:: + + |m\rangle \rightarrow \frac{1}{\sqrt{2^{N}}}\sum_{n=0}^{2^{N} - 1}\omega_{N}^{mn} |n\rangle, + + where :math:`\omega_{N} = e^{\frac{2 \pi i}{2^{N}}}` is the :math:`2^{N}`-th root of unity. + + **Details:** + + * Number of wires: Any (the operation can act on any number of wires) + * Number of parameters: 0 + * Gradient recipe: None + + Args: + wires (int or Iterable[Number, str]]): the wire(s) the operation acts on + """ + num_params = 0 + num_wires = AnyWires + par_domain = None + grad_method = None + + @property + def matrix(self): + # Redefine the property here to allow for a custom _matrix signature + return self._matrix(len(self.wires), self.inverse) + + @classmethod + @functools.lru_cache + def _matrix(cls, num_wires, inverse): + dimension = 2 ** num_wires + + mat = np.zeros((dimension, dimension)) + omega = np.exp(2 * np.pi * 1j / dimension) + if inverse: + omega = 1 / omega + + for i in range(dimension): + for j in range(dimension): + mat[i, j] = omega ** (i * j) + + return mat + + # @staticmethod + # def decomposition(D, wires): + # return [QubitUnitary(np.diag(D), wires=wires)] + + + + # ============================================================================= # State preparation # ============================================================================= From e88c3c58b359816b6ececf63136d9a021e72c3c4 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 13:25:45 -0500 Subject: [PATCH 02/25] Update notation --- pennylane/ops/qubit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 01cfc35afa1..28623ebee05 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1622,9 +1622,9 @@ def _matrix(cls, num_wires, inverse): if inverse: omega = 1 / omega - for i in range(dimension): - for j in range(dimension): - mat[i, j] = omega ** (i * j) + for m in range(dimension): + for n in range(dimension): + mat[m, n] = omega ** (m * n) return mat From 34e845a17a9ed0f3771996b81ad7ab0214529553 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 13:32:59 -0500 Subject: [PATCH 03/25] Add complex dtype --- pennylane/ops/qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 28623ebee05..c613e5d74f7 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1617,7 +1617,7 @@ def matrix(self): def _matrix(cls, num_wires, inverse): dimension = 2 ** num_wires - mat = np.zeros((dimension, dimension)) + mat = np.zeros((dimension, dimension), dtype=np.complex128) omega = np.exp(2 * np.pi * 1j / dimension) if inverse: omega = 1 / omega From af7ae8975e38682e19b6034f7235f1f39ede9a3a Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 14:10:44 -0500 Subject: [PATCH 04/25] Add test --- pennylane/ops/qubit.py | 14 ++++++++------ tests/gate_data.py | 14 ++++++++++++++ tests/ops/test_qubit_ops.py | 10 +++++++++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index c613e5d74f7..c71893f45c5 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1610,23 +1610,24 @@ class QFT(Operation): @property def matrix(self): # Redefine the property here to allow for a custom _matrix signature - return self._matrix(len(self.wires), self.inverse) + mat = self._matrix(len(self.wires)) + if self.inverse: + mat = mat.conj() + return mat @classmethod - @functools.lru_cache - def _matrix(cls, num_wires, inverse): + @functools.lru_cache() + def _matrix(cls, num_wires): dimension = 2 ** num_wires mat = np.zeros((dimension, dimension), dtype=np.complex128) omega = np.exp(2 * np.pi * 1j / dimension) - if inverse: - omega = 1 / omega for m in range(dimension): for n in range(dimension): mat[m, n] = omega ** (m * n) - return mat + return mat / np.sqrt(dimension) # @staticmethod # def decomposition(D, wires): @@ -1827,6 +1828,7 @@ def diagonalizing_gates(self): "QubitStateVector", "QubitUnitary", "DiagonalQubitUnitary", + "QFT", } diff --git a/tests/gate_data.py b/tests/gate_data.py index 9c8007b6b76..2f8c569f198 100644 --- a/tests/gate_data.py +++ b/tests/gate_data.py @@ -37,6 +37,20 @@ Toffoli = np.diag([1 for i in range(8)]) Toffoli[6:8, 6:8] = np.array([[0, 1], [1, 0]]) +w = np.exp(2 * np.pi * 1j / 8) +QFT = np.array( + [ + [1, 1, 1, 1, 1, 1, 1, 1], + [1, w, w ** 2, w ** 3, w ** 4, w ** 5, w ** 6, w ** 7], + [1, w ** 2, w ** 4, w ** 6, 1, w ** 2, w ** 4, w ** 6], + [1, w ** 3, w ** 6, w, w ** 4, w ** 7, w ** 2, w ** 5], + [1, w ** 4, 1, w ** 4, 1, w ** 4, 1, w ** 4], + [1, w ** 5, w ** 2, w ** 7, w ** 4, w, w ** 6, w ** 3], + [1, w ** 6, w ** 4, w ** 2, 1, w ** 6, w ** 4, w ** 2], + [1, w ** 7, w ** 6, w ** 5, w ** 4, w ** 3, w ** 2, w], + ] +) / np.sqrt(8) + # ======================================================== # parametrized gates # ======================================================== diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index 49cd9f3c47f..a6d71a75179 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -22,7 +22,7 @@ import pennylane as qml from pennylane.wires import Wires -from gate_data import I, X, Y, Z, H, CNOT, SWAP, CZ, S, T, CSWAP, Toffoli +from gate_data import I, X, Y, Z, H, CNOT, SWAP, CZ, S, T, CSWAP, Toffoli, QFT # Standard observables, their matrix representation, and eigenvlaues @@ -329,6 +329,14 @@ def test_matrices(self, ops, mat, tol): res = op.matrix assert np.allclose(res, mat, atol=tol, rtol=0) + @pytest.mark.parametrize("inverse", [True, False]) + def test_QFT(self, inverse): + """Test if the QFT matrix is equal to a manually-calculated version for 3 qubits""" + op = qml.QFT(wires=range(3)).inv() if inverse else qml.QFT(wires=range(3)) + res = op.matrix + exp = QFT.conj().T if inverse else QFT + assert np.allclose(res, exp) + def test_x_decomposition(self, tol): """Tests that the decomposition of the PauliX is correct""" op = qml.PauliX(wires=0) From fd49f75e6b8f94e671fc6417b99cd06112311134 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 15:09:37 -0500 Subject: [PATCH 05/25] Add --- pennylane/ops/qubit.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index c71893f45c5..7ff2a9bf8dc 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1629,12 +1629,6 @@ def _matrix(cls, num_wires): return mat / np.sqrt(dimension) - # @staticmethod - # def decomposition(D, wires): - # return [QubitUnitary(np.diag(D), wires=wires)] - - - # ============================================================================= # State preparation From 247c7e41df3d6e9f015e7f143475135044bd4302 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 15:36:09 -0500 Subject: [PATCH 06/25] Add controlled phase --- pennylane/ops/qubit.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 7ff2a9bf8dc..409399cc47e 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -722,6 +722,58 @@ def decomposition(phi, wires): return decomp_ops +class ControlledPhaseShift(DiagonalOperation): + r"""ControlledPhaseShift(phi, wires) + A controlled phase shift + + .. math:: CR_\phi(\phi) = \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i\phi} + \end{bmatrix}. + + .. note:: The first wire provided corresponds to the **control qubit**. + + **Details:** + + * Number of wires: 2 + * Number of parameters: 1 + * Gradient recipe: :math:`\frac{d}{d\phi}f(CR_\phi(\phi)) = \frac{1}{2}\left[f(CR_\phi(\phi+\pi/2)) - f(CR_\phi(\phi-\pi/2))\right]` + where :math:`f` is an expectation value depending on :math:`CR_{\phi}(\phi)`. + + Args: + phi (float): rotation angle :math:`\phi` + wires (Sequence[int] or int): the wire the operation acts on + """ + num_params = 1 + num_wires = 2 + par_domain = "R" + grad_method = "A" + generator = [np.array([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1]]), 1] + + @classmethod + def _matrix(cls, *params): + phi = params[0] + return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, cmath.exp(1j * phi)]]) + + @classmethod + def _eigvals(cls, *params): + phi = params[0] + return np.array([1, 1, 1, cmath.exp(1j * phi)]) + + @staticmethod + def decomposition(phi, wires): + decomp_ops = [ + qml.PhaseShift(phi / 2, wires=wires[0]), + qml.CNOT(wires=[0, 1]), + qml.PhaseShift(-phi / 2, wires=wires[1]), + qml.CNOT(wires=[0, 1]), + qml.PhaseShift(phi / 2, wires=wires[1]), + ] + return decomp_ops + + class Rot(Operation): r"""Rot(phi, theta, omega, wires) Arbitrary single qubit rotation From d84cbf42495151e4330d865d506350240f458c57 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 15:57:35 -0500 Subject: [PATCH 07/25] Add ControlledPhaseShift --- pennylane/ops/qubit.py | 1 + tests/gate_data.py | 12 ++++++++++++ tests/ops/test_qubit_ops.py | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 409399cc47e..714d14f4b54 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1862,6 +1862,7 @@ def diagonalizing_gates(self): "RY", "RZ", "PhaseShift", + "ControlledPhaseShift", "Rot", "CRX", "CRY", diff --git a/tests/gate_data.py b/tests/gate_data.py index 2f8c569f198..752b3a2a26f 100644 --- a/tests/gate_data.py +++ b/tests/gate_data.py @@ -221,3 +221,15 @@ def MultiRZ2(theta): [0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, np.exp(-1j * theta / 2)], ] ) + + +def ControlledPhaseShift(phi): + r"""Controlled phase shift. + + Args: + phi (float): rotation angle + + Returns: + array: the two-wire controlled-phase matrix + """ + return np.diag([1, 1, 1, np.exp(1j * phi)]) diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index a6d71a75179..5e5345a0bcc 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -22,7 +22,7 @@ import pennylane as qml from pennylane.wires import Wires -from gate_data import I, X, Y, Z, H, CNOT, SWAP, CZ, S, T, CSWAP, Toffoli, QFT +from gate_data import I, X, Y, Z, H, CNOT, SWAP, CZ, S, T, CSWAP, Toffoli, QFT, ControlledPhaseShift # Standard observables, their matrix representation, and eigenvlaues @@ -747,6 +747,38 @@ def test_qubit_unitary_exceptions(self): with pytest.raises(ValueError, match="must be unitary"): qml.QubitUnitary(U3, wires=0).matrix + @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5]) + def test_controlled_phase_shift_matrix_and_eigvals(self, phi): + """Tests that the ControlledPhaseShift operation calculates the correct matrix and + eigenvalues""" + op = qml.ControlledPhaseShift(phi, wires=[0, 1]) + res = op.matrix + exp = ControlledPhaseShift(phi) + assert np.allclose(res, exp) + + res = op.eigvals + assert np.allclose(res, np.diag(exp)) + + @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5]) + def test_controlled_phase_shift_decomp(self, phi): + """Tests that the ControlledPhaseShift operation calculates the correct decomposition""" + op = qml.ControlledPhaseShift(phi, wires=[0, 1]) + decomp = op.decomposition(phi, wires=[0, 1]) + + mats = [] + for i in reversed(decomp): + if i.wires.tolist() == [0]: + mats.append(np.kron(i.matrix, np.eye(2))) + elif i.wires.tolist() == [1]: + mats.append(np.kron(np.eye(2), i.matrix)) + else: + mats.append(i.matrix) + + decomposed_matrix = np.linalg.multi_dot(mats) + exp = ControlledPhaseShift(phi) + + assert np.allclose(decomposed_matrix, exp) + PAULI_ROT_PARAMETRIC_MATRIX_TEST_DATA = [ ( From aa79522a64f74731e0f608503b6835ea67ebf5a9 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 16:10:20 -0500 Subject: [PATCH 08/25] Add decomposition --- pennylane/ops/qubit.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 714d14f4b54..a0ab23dca47 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1681,6 +1681,21 @@ def _matrix(cls, num_wires): return mat / np.sqrt(dimension) + @staticmethod + def decomposition(wires): + num_wires = len(wires) + shifts = [np.exp(2 * np.pi * 1j * 2 ** -i) for i in range(2, num_wires + 1)] + + ops = [] + for i, wire in enumerate(wires): + ops.append(qml.Hadamard(wire)) + + for shift, control_wire in zip(shifts[:len(shifts) - i], wires[i + 1:]): + op = qml.ControlledPhaseShift(shift, wires=[control_wire, wire]) + ops.append(op) + + return ops + # ============================================================================= # State preparation From f1e1d86c3bc136280117a7a1f3eadc9e76543f2c Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 17:21:36 -0500 Subject: [PATCH 09/25] Add tests --- pennylane/ops/qubit.py | 9 ++++++++- tests/ops/test_qubit_ops.py | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index a0ab23dca47..a516e820d4f 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1684,7 +1684,7 @@ def _matrix(cls, num_wires): @staticmethod def decomposition(wires): num_wires = len(wires) - shifts = [np.exp(2 * np.pi * 1j * 2 ** -i) for i in range(2, num_wires + 1)] + shifts = [2 * np.pi * 2 ** -i for i in range(2, num_wires + 1)] ops = [] for i, wire in enumerate(wires): @@ -1694,6 +1694,13 @@ def decomposition(wires): op = qml.ControlledPhaseShift(shift, wires=[control_wire, wire]) ops.append(op) + first_half_wires = wires[:num_wires // 2] + last_half_wires = wires[-(num_wires // 2):] + + for wire1, wire2 in zip(first_half_wires, reversed(last_half_wires)): + swap = qml.SWAP(wires=[wire1, wire2]) + ops.append(swap) + return ops diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index 5e5345a0bcc..a4de12393bf 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -337,6 +337,26 @@ def test_QFT(self, inverse): exp = QFT.conj().T if inverse else QFT assert np.allclose(res, exp) + @pytest.mark.parametrize("n_qubits", range(2, 6)) + def test_QFT_decomposition(self, n_qubits): + """Test if the QFT operation is correctly decomposed""" + op = qml.QFT(wires=range(n_qubits)) + decomp = op.decomposition(wires=range(n_qubits)) + + dev = qml.device("default.qubit", wires=n_qubits) + + out_states = [] + for state in np.eye(2 ** n_qubits): + dev.reset() + ops = [qml.QubitStateVector(state, wires=range(n_qubits))] + decomp + dev.apply(ops) + out_states.append(dev.state) + + reconstructed_unitary = np.array(out_states).T + expected_unitary = qml.QFT(wires=range(n_qubits)).matrix + + assert np.allclose(reconstructed_unitary, expected_unitary) + def test_x_decomposition(self, tol): """Tests that the decomposition of the PauliX is correct""" op = qml.PauliX(wires=0) From 1e22db71eb72e670ed79199f5726328c063e9ce3 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 17:26:35 -0500 Subject: [PATCH 10/25] Apply linters --- pennylane/ops/qubit.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index a516e820d4f..dae3bf297be 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -15,17 +15,20 @@ This module contains the available built-in discrete-variable quantum operations supported by PennyLane, as well as their conventions. """ -# pylint:disable=abstract-method,arguments-differ,protected-access -import math import cmath import functools +# pylint:disable=abstract-method,arguments-differ,protected-access +import math + import numpy as np -from pennylane.templates import template -from pennylane.operation import AnyWires, Observable, Operation, DiagonalOperation -from pennylane.templates.state_preparations import BasisStatePreparation, MottonenStatePreparation -from pennylane.utils import pauli_eigs, expand import pennylane as qml +from pennylane.operation import (AnyWires, DiagonalOperation, Observable, + Operation) +from pennylane.templates import template +from pennylane.templates.state_preparations import (BasisStatePreparation, + MottonenStatePreparation) +from pennylane.utils import expand, pauli_eigs INV_SQRT2 = 1 / math.sqrt(2) @@ -957,7 +960,7 @@ class PauliRot(Operation): } def __init__(self, *params, wires=None, do_queue=True): - super().__init__(*params, wires=wires, do_queue=True) + super().__init__(*params, wires=wires, do_queue=do_queue) pauli_word = params[1] @@ -1686,22 +1689,22 @@ def decomposition(wires): num_wires = len(wires) shifts = [2 * np.pi * 2 ** -i for i in range(2, num_wires + 1)] - ops = [] + decomp_ops = [] for i, wire in enumerate(wires): - ops.append(qml.Hadamard(wire)) + decomp_ops.append(qml.Hadamard(wire)) - for shift, control_wire in zip(shifts[:len(shifts) - i], wires[i + 1:]): + for shift, control_wire in zip(shifts[: len(shifts) - i], wires[i + 1 :]): op = qml.ControlledPhaseShift(shift, wires=[control_wire, wire]) - ops.append(op) + decomp_ops.append(op) - first_half_wires = wires[:num_wires // 2] - last_half_wires = wires[-(num_wires // 2):] + first_half_wires = wires[: num_wires // 2] + last_half_wires = wires[-(num_wires // 2) :] for wire1, wire2 in zip(first_half_wires, reversed(last_half_wires)): swap = qml.SWAP(wires=[wire1, wire2]) - ops.append(swap) + decomp_ops.append(swap) - return ops + return decomp_ops # ============================================================================= From 4d1a615d40f655e43294531f936489212f95ee68 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 17:35:18 -0500 Subject: [PATCH 11/25] Add to docs --- doc/introduction/operations.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst index 325e463bd57..c8b6aa6c30d 100644 --- a/doc/introduction/operations.rst +++ b/doc/introduction/operations.rst @@ -66,6 +66,7 @@ Qubit gates ~pennylane.MultiRZ ~pennylane.PauliRot ~pennylane.PhaseShift + ~pennylane.ControlledPhaseShift ~pennylane.CNOT ~pennylane.CZ ~pennylane.CY @@ -81,6 +82,7 @@ Qubit gates ~pennylane.CSWAP ~pennylane.QubitUnitary ~pennylane.DiagonalQubitUnitary + ~pennylane.QFT :html:`` From c39835847f34346155ee265c0b6ddd093783371d Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 17:39:55 -0500 Subject: [PATCH 12/25] Add to changelog --- .github/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 21489f11db5..f4a4c789ba3 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -4,6 +4,10 @@

Improvements

+- Added the `ControlledPhaseShift` gate as well as the `QFT` gate for applying quantum Fourier + transforms. + [(#1064)](https://github.com/PennyLaneAI/pennylane/pull/1064) +

Breaking changes

Bug fixes

@@ -14,7 +18,7 @@ This release contains contributions from (in alphabetical order): - +Thomas Bromley # Release 0.14.0 (current release) From 1c4d3314356d7a8b3048f1611fc3d26dec65aa22 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 17:41:21 -0500 Subject: [PATCH 13/25] Add clarification --- pennylane/ops/qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index dae3bf297be..446c0ad09ea 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -727,7 +727,7 @@ def decomposition(phi, wires): class ControlledPhaseShift(DiagonalOperation): r"""ControlledPhaseShift(phi, wires) - A controlled phase shift + A qubit controlled phase shift. .. math:: CR_\phi(\phi) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ From 4d4c06cd717923df0e20a858566412ed03698720 Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 17:56:22 -0500 Subject: [PATCH 14/25] Add device support --- pennylane/devices/autograd_ops.py | 12 ++++++++++++ pennylane/devices/default_mixed.py | 2 ++ pennylane/devices/default_qubit.py | 2 ++ pennylane/devices/default_qubit_autograd.py | 1 + pennylane/devices/default_qubit_jax.py | 1 + pennylane/devices/default_qubit_tf.py | 1 + pennylane/devices/jax_ops.py | 12 ++++++++++++ pennylane/devices/tf_ops.py | 13 +++++++++++++ tests/tape/tapes/test_reversible.py | 1 + 9 files changed, 45 insertions(+) diff --git a/pennylane/devices/autograd_ops.py b/pennylane/devices/autograd_ops.py index c662c2a66d0..d13361cd218 100644 --- a/pennylane/devices/autograd_ops.py +++ b/pennylane/devices/autograd_ops.py @@ -50,6 +50,18 @@ def PhaseShift(phi): return np.array([1.0, np.exp(1j * phi)]) +def ControlledPhaseShift(phi): + r"""Two-qubit controlled phase shift. + + Args: + phi (float): phase shift angle + + Returns: + array[complex]: diagonal part of the controlled phase shift matrix + """ + return np.array([1.0, 1.0, 1.0, np.exp(1j * phi)]) + + def RX(theta): r"""One-qubit rotation about the x axis. diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py index 6d7526b2aae..facaf5dc41e 100644 --- a/pennylane/devices/default_mixed.py +++ b/pennylane/devices/default_mixed.py @@ -77,6 +77,7 @@ class DefaultMixed(QubitDevice): "Toffoli", "CZ", "PhaseShift", + "ControlledPhaseShift", "RX", "RY", "RZ", @@ -92,6 +93,7 @@ class DefaultMixed(QubitDevice): "BitFlip", "PhaseFlip", "QubitChannel", + "QFT", } def __init__(self, wires, *, shots=1000, analytic=True, cache=0): diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py index fa55c90b7fa..9f246d9c459 100644 --- a/pennylane/devices/default_qubit.py +++ b/pennylane/devices/default_qubit.py @@ -113,6 +113,7 @@ class DefaultQubit(QubitDevice): "CY", "CZ", "PhaseShift", + "ControlledPhaseShift", "RX", "RY", "RZ", @@ -121,6 +122,7 @@ class DefaultQubit(QubitDevice): "CRY", "CRZ", "CRot", + "QFT", } observables = {"PauliX", "PauliY", "PauliZ", "Hadamard", "Hermitian", "Identity"} diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py index d5cb1883ab9..6ddf70344e6 100644 --- a/pennylane/devices/default_qubit_autograd.py +++ b/pennylane/devices/default_qubit_autograd.py @@ -85,6 +85,7 @@ class DefaultQubitAutograd(DefaultQubit): parametric_ops = { "PhaseShift": autograd_ops.PhaseShift, + "ControlledPhaseShift": autograd_ops.ControlledPhaseShift, "RX": autograd_ops.RX, "RY": autograd_ops.RY, "RZ": autograd_ops.RZ, diff --git a/pennylane/devices/default_qubit_jax.py b/pennylane/devices/default_qubit_jax.py index 439197f50d1..6759c45a572 100644 --- a/pennylane/devices/default_qubit_jax.py +++ b/pennylane/devices/default_qubit_jax.py @@ -136,6 +136,7 @@ def circuit(): parametric_ops = { "PhaseShift": jax_ops.PhaseShift, + "ControlledPhaseShift": jax_ops.ControlledPhaseShift, "RX": jax_ops.RX, "RY": jax_ops.RY, "RZ": jax_ops.RZ, diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py index 7be29983530..7a3828fb596 100644 --- a/pennylane/devices/default_qubit_tf.py +++ b/pennylane/devices/default_qubit_tf.py @@ -133,6 +133,7 @@ class DefaultQubitTF(DefaultQubit): parametric_ops = { "PhaseShift": tf_ops.PhaseShift, + "ControlledPhaseShift": tf_ops.ControlledPhaseShift, "RX": tf_ops.RX, "RY": tf_ops.RY, "RZ": tf_ops.RZ, diff --git a/pennylane/devices/jax_ops.py b/pennylane/devices/jax_ops.py index 82af606b8b2..0e7e6a29c6a 100644 --- a/pennylane/devices/jax_ops.py +++ b/pennylane/devices/jax_ops.py @@ -50,6 +50,18 @@ def PhaseShift(phi): return jnp.array([1.0, jnp.exp(1j * phi)]) +def ControlledPhaseShift(phi): + r"""Two-qubit controlled phase shift. + + Args: + phi (float): phase shift angle + + Returns: + array[complex]: diagonal part of the controlled phase shift matrix + """ + return jnp.array([1.0, 1.0, 1.0, jnp.exp(1j * phi)]) + + def RX(theta): r"""One-qubit rotation about the x axis. diff --git a/pennylane/devices/tf_ops.py b/pennylane/devices/tf_ops.py index 75a536b64a9..f01563f5a7f 100644 --- a/pennylane/devices/tf_ops.py +++ b/pennylane/devices/tf_ops.py @@ -51,6 +51,19 @@ def PhaseShift(phi): return tf.convert_to_tensor([1.0, tf.exp(1j * phi)]) +def ControlledPhaseShift(phi): + r"""Two-qubit controlled phase shift. + + Args: + phi (float): phase shift angle + + Returns: + tf.Tensor[complex]: diagonal part of the controlled phase shift matrix + """ + phi = tf.cast(phi, dtype=C_DTYPE) + return tf.convert_to_tensor([1.0, 1.0, 1.0, tf.exp(1j * phi)]) + + def RX(theta): r"""One-qubit rotation about the x axis. diff --git a/tests/tape/tapes/test_reversible.py b/tests/tape/tapes/test_reversible.py index 28e0f2d5c40..2486076790b 100644 --- a/tests/tape/tapes/test_reversible.py +++ b/tests/tape/tapes/test_reversible.py @@ -280,6 +280,7 @@ def test_multiple_rx_gradient(self, tol): qml.CRZ, qml.CRot, qml.PhaseShift, + qml.ControlledPhaseShift, qml.PauliRot, qml.MultiRZ, qml.U1, From 37c40101d67251c03910d3ca71b8be69e759937a Mon Sep 17 00:00:00 2001 From: trbromley Date: Wed, 3 Feb 2021 18:01:02 -0500 Subject: [PATCH 15/25] Add to tests --- tests/devices/test_default_qubit_tf.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index 3cb86794485..d6e37dd86e7 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -49,6 +49,7 @@ CRot3, MultiRZ1, MultiRZ2, + ControlledPhaseShift, ) np.random.seed(42) @@ -74,7 +75,14 @@ ##################################################### single_qubit = [(qml.S, S), (qml.T, T), (qml.PauliX, X), (qml.PauliY, Y), (qml.PauliZ, Z), (qml.Hadamard, H)] -single_qubit_param = [(qml.PhaseShift, Rphi), (qml.RX, Rotx), (qml.RY, Roty), (qml.RZ, Rotz), (qml.MultiRZ, MultiRZ1)] +single_qubit_param = [ + (qml.PhaseShift, Rphi), + (qml.ControlledPhaseShift, ControlledPhaseShift), + (qml.RX, Rotx), + (qml.RY, Roty), + (qml.RZ, Rotz), + (qml.MultiRZ, MultiRZ1) +] two_qubit = [(qml.CZ, CZ), (qml.CNOT, CNOT), (qml.SWAP, SWAP)] two_qubit_param = [(qml.CRX, CRotx), (qml.CRY, CRoty), (qml.CRZ, CRotz), (qml.MultiRZ, MultiRZ2)] three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP)] From e62dc18bd6c8934846588942d4eb8e2fd2ca8570 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 09:00:46 -0500 Subject: [PATCH 16/25] Add to device tests --- pennylane/devices/tests/test_gates.py | 2 ++ tests/devices/test_default_qubit_tf.py | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py index 4b59530385b..4db4dab0a5f 100755 --- a/pennylane/devices/tests/test_gates.py +++ b/pennylane/devices/tests/test_gates.py @@ -53,6 +53,7 @@ "PauliY": qml.PauliY(wires=[0]), "PauliZ": qml.PauliZ(wires=[0]), "PhaseShift": qml.PhaseShift(0, wires=[0]), + "ControlledPhaseShift": qml.ControlledPhaseShift(0, wires=[0]), "QubitStateVector": qml.QubitStateVector(np.array([1.0, 0.0]), wires=[0]), "QubitUnitary": qml.QubitUnitary(np.eye(2), wires=[0]), "RX": qml.RX(0, wires=[0]), @@ -64,6 +65,7 @@ "T": qml.T(wires=[0]), "SX": qml.SX(wires=[0]), "Toffoli": qml.Toffoli(wires=[0, 1, 2]), + "QFT": qml.QFT(wires=[0, 1, 2]), } all_ops = ops.keys() diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py index d6e37dd86e7..bc68471751e 100644 --- a/tests/devices/test_default_qubit_tf.py +++ b/tests/devices/test_default_qubit_tf.py @@ -77,14 +77,19 @@ single_qubit = [(qml.S, S), (qml.T, T), (qml.PauliX, X), (qml.PauliY, Y), (qml.PauliZ, Z), (qml.Hadamard, H)] single_qubit_param = [ (qml.PhaseShift, Rphi), - (qml.ControlledPhaseShift, ControlledPhaseShift), (qml.RX, Rotx), (qml.RY, Roty), (qml.RZ, Rotz), - (qml.MultiRZ, MultiRZ1) + (qml.MultiRZ, MultiRZ1), ] two_qubit = [(qml.CZ, CZ), (qml.CNOT, CNOT), (qml.SWAP, SWAP)] -two_qubit_param = [(qml.CRX, CRotx), (qml.CRY, CRoty), (qml.CRZ, CRotz), (qml.MultiRZ, MultiRZ2)] +two_qubit_param = [ + (qml.CRX, CRotx), + (qml.CRY, CRoty), + (qml.CRZ, CRotz), + (qml.MultiRZ, MultiRZ2), + (qml.ControlledPhaseShift, ControlledPhaseShift), +] three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP)] From bbdf9dadb3af2ca594d48bbe9555b3b2b7c08fb0 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 09:05:09 -0500 Subject: [PATCH 17/25] Apply black --- pennylane/ops/qubit.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index 446c0ad09ea..d259e3c359c 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -17,17 +17,16 @@ """ import cmath import functools + # pylint:disable=abstract-method,arguments-differ,protected-access import math import numpy as np import pennylane as qml -from pennylane.operation import (AnyWires, DiagonalOperation, Observable, - Operation) +from pennylane.operation import AnyWires, DiagonalOperation, Observable, Operation from pennylane.templates import template -from pennylane.templates.state_preparations import (BasisStatePreparation, - MottonenStatePreparation) +from pennylane.templates.state_preparations import BasisStatePreparation, MottonenStatePreparation from pennylane.utils import expand, pauli_eigs INV_SQRT2 = 1 / math.sqrt(2) From b9045a7e39e0bbe90fe9fd77ffcf8e71542dc914 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 09:05:57 -0500 Subject: [PATCH 18/25] Fix wires --- pennylane/devices/tests/test_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py index 4db4dab0a5f..d6d35da6548 100755 --- a/pennylane/devices/tests/test_gates.py +++ b/pennylane/devices/tests/test_gates.py @@ -53,7 +53,7 @@ "PauliY": qml.PauliY(wires=[0]), "PauliZ": qml.PauliZ(wires=[0]), "PhaseShift": qml.PhaseShift(0, wires=[0]), - "ControlledPhaseShift": qml.ControlledPhaseShift(0, wires=[0]), + "ControlledPhaseShift": qml.ControlledPhaseShift(0, wires=[0, 1]), "QubitStateVector": qml.QubitStateVector(np.array([1.0, 0.0]), wires=[0]), "QubitUnitary": qml.QubitUnitary(np.eye(2), wires=[0]), "RX": qml.RX(0, wires=[0]), From 552824c72967deae7c3d41ec6c198ec7121615c2 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 09:31:22 -0500 Subject: [PATCH 19/25] Remove line --- tests/ops/test_qubit_ops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index a4de12393bf..373677755ac 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -781,7 +781,8 @@ def test_controlled_phase_shift_matrix_and_eigvals(self, phi): @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5]) def test_controlled_phase_shift_decomp(self, phi): - """Tests that the ControlledPhaseShift operation calculates the correct decomposition""" + """Tests that the ControlledPhaseShift operation calculates the correct + decomposition""" op = qml.ControlledPhaseShift(phi, wires=[0, 1]) decomp = op.decomposition(phi, wires=[0, 1]) From 358bcb07d9b524de0c46f80516955f91990c2db0 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 09:31:35 -0500 Subject: [PATCH 20/25] Add --- tests/ops/test_qubit_ops.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index 373677755ac..a4de12393bf 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -781,8 +781,7 @@ def test_controlled_phase_shift_matrix_and_eigvals(self, phi): @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5]) def test_controlled_phase_shift_decomp(self, phi): - """Tests that the ControlledPhaseShift operation calculates the correct - decomposition""" + """Tests that the ControlledPhaseShift operation calculates the correct decomposition""" op = qml.ControlledPhaseShift(phi, wires=[0, 1]) decomp = op.decomposition(phi, wires=[0, 1]) From 0733f46ae23893687469010a464f9cd2eb42b6bb Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 10:03:18 -0500 Subject: [PATCH 21/25] docstring --- tests/ops/test_qubit_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index a4de12393bf..9ae0a6964d5 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -781,7 +781,7 @@ def test_controlled_phase_shift_matrix_and_eigvals(self, phi): @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5]) def test_controlled_phase_shift_decomp(self, phi): - """Tests that the ControlledPhaseShift operation calculates the correct decomposition""" + """Tests that the ControlledPhaseShift operation calculates the correctdecomposition""" op = qml.ControlledPhaseShift(phi, wires=[0, 1]) decomp = op.decomposition(phi, wires=[0, 1]) From 0f874fc97f60900e424edc4632c66f4b913c6096 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 10:03:22 -0500 Subject: [PATCH 22/25] docstring --- tests/ops/test_qubit_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py index 9ae0a6964d5..a4de12393bf 100644 --- a/tests/ops/test_qubit_ops.py +++ b/tests/ops/test_qubit_ops.py @@ -781,7 +781,7 @@ def test_controlled_phase_shift_matrix_and_eigvals(self, phi): @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5]) def test_controlled_phase_shift_decomp(self, phi): - """Tests that the ControlledPhaseShift operation calculates the correctdecomposition""" + """Tests that the ControlledPhaseShift operation calculates the correct decomposition""" op = qml.ControlledPhaseShift(phi, wires=[0, 1]) decomp = op.decomposition(phi, wires=[0, 1]) From c2e7673c5a1d19b46ef3b3d30dffb9029b9d041f Mon Sep 17 00:00:00 2001 From: Tom Bromley <49409390+trbromley@users.noreply.github.com> Date: Thu, 4 Feb 2021 13:27:13 -0500 Subject: [PATCH 23/25] Update .github/CHANGELOG.md Co-authored-by: Olivia Di Matteo <2068515+glassnotes@users.noreply.github.com> --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index f4a4c789ba3..e1a2a9c5244 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -4,7 +4,7 @@

Improvements

-- Added the `ControlledPhaseShift` gate as well as the `QFT` gate for applying quantum Fourier +- Added the `ControlledPhaseShift` gate as well as the `QFT` operation for applying quantum Fourier transforms. [(#1064)](https://github.com/PennyLaneAI/pennylane/pull/1064) From 16d116e7819cf3b9924903f40aa9d24d14840e62 Mon Sep 17 00:00:00 2001 From: trbromley Date: Thu, 4 Feb 2021 14:06:05 -0500 Subject: [PATCH 24/25] Add example --- pennylane/ops/qubit.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index d259e3c359c..c2dbda6736c 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1647,6 +1647,22 @@ class QFT(Operation): where :math:`\omega_{N} = e^{\frac{2 \pi i}{2^{N}}}` is the :math:`2^{N}`-th root of unity. + **Example** + + The quantum Fourier transform is applied by specifying the corresponding wires: + + .. code-block:: + + wires = 3 + + @qml.qnode(dev) + def circuit_qft(basis_state): + qml.BasisState(basis_state, wires=range(wires)) + qml.QFT(wires=range(wires)) + return qml.state() + + The inverse quantum Fourier transform is accessed using ``qml.QFT(wires).inv()``. + **Details:** * Number of wires: Any (the operation can act on any number of wires) From 2566d5274ae89406b57d9c098555d416ba487600 Mon Sep 17 00:00:00 2001 From: trbromley Date: Fri, 5 Feb 2021 07:29:25 -0500 Subject: [PATCH 25/25] Updates from code review --- .github/CHANGELOG.md | 4 ++-- pennylane/ops/qubit.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index e1a2a9c5244..0c8544a55f4 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,12 +2,12 @@

New features since last release

-

Improvements

- - Added the `ControlledPhaseShift` gate as well as the `QFT` operation for applying quantum Fourier transforms. [(#1064)](https://github.com/PennyLaneAI/pennylane/pull/1064) +

Improvements

+

Breaking changes

Bug fixes

diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py index c2dbda6736c..7b3b113dbd0 100644 --- a/pennylane/ops/qubit.py +++ b/pennylane/ops/qubit.py @@ -1647,6 +1647,15 @@ class QFT(Operation): where :math:`\omega_{N} = e^{\frac{2 \pi i}{2^{N}}}` is the :math:`2^{N}`-th root of unity. + **Details:** + + * Number of wires: Any (the operation can act on any number of wires) + * Number of parameters: 0 + * Gradient recipe: None + + Args: + wires (int or Iterable[Number, str]]): the wire(s) the operation acts on + **Example** The quantum Fourier transform is applied by specifying the corresponding wires: @@ -1662,15 +1671,6 @@ def circuit_qft(basis_state): return qml.state() The inverse quantum Fourier transform is accessed using ``qml.QFT(wires).inv()``. - - **Details:** - - * Number of wires: Any (the operation can act on any number of wires) - * Number of parameters: 0 - * Gradient recipe: None - - Args: - wires (int or Iterable[Number, str]]): the wire(s) the operation acts on """ num_params = 0 num_wires = AnyWires