Skip to content

Commit

Permalink
[Code together] Make QFT a template instead of an operation issue (#1548
Browse files Browse the repository at this point in the history
)

* changes for issue 1477

* Update CHANGELOG.md

* formatted by black

* update test_qft

* Update test_qft.py

* Update test_qft.py

* Update test_qft.py

* disable ops qft & its tests

* disable ops qft

* Update non_parametric_ops.py

* fix qft

* fix qft

* Update qmc.py

* Update test_qubit_ops.py

* Update pennylane/ops/qubit/non_parametric_ops.py

Co-authored-by: Romain <rmoyard@gmail.com>

* Update pennylane/ops/qubit/non_parametric_ops.py

Co-authored-by: Romain <rmoyard@gmail.com>

* Update tests/templates/test_subroutines/test_qft.py

Co-authored-by: Romain <rmoyard@gmail.com>

* Update non_parametric_ops.py

* change the import

* change qml.QFT to qml.templates.QFT

* Update operations.rst

* Update non_parametric_ops.py

* Update non_parametric_ops.py

* Update CHANGELOG.md

* Update CHANGELOG.md

Co-authored-by: antalszava <antalszava@gmail.com>
Co-authored-by: Romain <rmoyard@gmail.com>
  • Loading branch information
3 people committed Aug 19, 2021
1 parent a0d3606 commit c403e27
Show file tree
Hide file tree
Showing 16 changed files with 1,150 additions and 169 deletions.
6 changes: 5 additions & 1 deletion .github/CHANGELOG.md
Expand Up @@ -136,9 +136,13 @@
and requirements-ci.txt (unpinned). This latter would be used by the CI.
[(#1535)](https://github.com/PennyLaneAI/pennylane/pull/1535)

* The QFT operation is moved to template
[(#1548)](https://github.com/PennyLaneAI/pennylane/pull/1548)

* The `qml.ResetError` is now supported for `default.mixed` device.
[(#1541)](https://github.com/PennyLaneAI/pennylane/pull/1541)


<h3>Breaking changes</h3>

* The class `qml.Interferometer` is deprecated and will be renamed `qml.InterferometerUnitary`
Expand Down Expand Up @@ -1925,7 +1929,7 @@ fully differentiable.
@qml.qnode(dev)
def circuit_qft(basis_state):
qml.BasisState(basis_state, wires=range(3))
qml.QFT(wires=range(3))
qml.templates.QFT(wires=range(3))
return qml.state()
```

Expand Down
921 changes: 921 additions & 0 deletions doc/_static/templates/subroutines/qft.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion doc/introduction/operations.rst
Expand Up @@ -89,7 +89,6 @@ Qubit gates
~pennylane.ControlledQubitUnitary
~pennylane.MultiControlledX
~pennylane.DiagonalQubitUnitary
~pennylane.QFT
~pennylane.SingleExcitation
~pennylane.SingleExcitationPlus
~pennylane.SingleExcitationMinus
Expand Down
5 changes: 5 additions & 0 deletions doc/introduction/templates.rst
Expand Up @@ -213,6 +213,11 @@ of other templates.
:description: AllSinglesDoubles
:figure: ../_static/templates/subroutines/all_singles_doubles.png

.. customgalleryitem::
:link: ../code/api/pennylane.templates.subroutines.QuantumFourierTransform.html
:description: QuantumFourierTransform
:figure: ../_static/templates/subroutines/qft.svg

.. raw:: html

<div style='clear:both'></div>
Expand Down
2 changes: 1 addition & 1 deletion pennylane/devices/tests/test_gates.py
Expand Up @@ -68,7 +68,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]),
"QFT": qml.templates.QFT(wires=[0, 1, 2]),
"IsingXX": qml.IsingXX(0, wires=[0, 1]),
"IsingYY": qml.IsingYY(0, wires=[0, 1]),
"IsingZZ": qml.IsingZZ(0, wires=[0, 1]),
Expand Down
1 change: 0 additions & 1 deletion pennylane/ops/qubit/__init__.py
Expand Up @@ -77,7 +77,6 @@
"ControlledQubitUnitary",
"MultiControlledX",
"DiagonalQubitUnitary",
"QFT",
"SingleExcitation",
"SingleExcitationPlus",
"SingleExcitationMinus",
Expand Down
95 changes: 0 additions & 95 deletions pennylane/ops/qubit/non_parametric_ops.py
Expand Up @@ -17,7 +17,6 @@
"""
# pylint:disable=abstract-method,arguments-differ,protected-access
import cmath
import functools
import numpy as np

import pennylane as qml
Expand Down Expand Up @@ -987,97 +986,3 @@ def _decomposition_with_one_worker(control_wires, target_wire, work_wire):
]

return gates


# TODO: this should be moved to a template
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
**Example**
The quantum Fourier transform is applied by specifying the corresponding wires:
.. code-block::
wires = 3
dev = qml.device('default.qubit',wires=wires)
@qml.qnode(dev)
def circuit_qft(basis_state):
qml.BasisState(basis_state, wires=range(wires))
qml.QFT(wires=range(wires))
return qml.state()
circuit_qft([1.0, 0.0, 0.0])
"""
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
mat = self._matrix(len(self.wires))
if self.inverse:
mat = mat.conj()
return mat

@classmethod
@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)

for m in range(dimension):
for n in range(dimension):
mat[m, n] = omega ** (m * n)

return mat / np.sqrt(dimension)

@staticmethod
def decomposition(wires):
num_wires = len(wires)
shifts = [2 * np.pi * 2 ** -i for i in range(2, num_wires + 1)]

decomp_ops = []
for i, wire in enumerate(wires):
decomp_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])
decomp_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])
decomp_ops.append(swap)

return decomp_ops

def adjoint(self):
return QFT(wires=self.wires).inv()
1 change: 1 addition & 0 deletions pennylane/templates/subroutines/__init__.py
Expand Up @@ -27,3 +27,4 @@
from .qmc import QuantumMonteCarlo
from .all_singles_doubles import AllSinglesDoubles
from .grover import GroverOperator
from .qft import QFT
116 changes: 116 additions & 0 deletions pennylane/templates/subroutines/qft.py
@@ -0,0 +1,116 @@
# 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.
"""
This submodule contains the template for QFT.
"""
# pylint:disable=abstract-method,arguments-differ,protected-access

import functools
import numpy as np

import pennylane as qml
from pennylane.operation import AnyWires, Operation


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
**Example**
The quantum Fourier transform is applied by specifying the corresponding wires:
.. code-block::
wires = 3
dev = qml.device('default.qubit',wires=wires)
@qml.qnode(dev)
def circuit_qft(basis_state):
qml.BasisState(basis_state, wires=range(wires))
qml.templates.QFT(wires=range(wires))
return qml.state()
circuit_qft([1.0, 0.0, 0.0])
"""
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
mat = self._matrix(len(self.wires))
if self.inverse:
mat = mat.conj()
return mat

@classmethod
@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)

for m in range(dimension):
for n in range(dimension):
mat[m, n] = omega ** (m * n)

return mat / np.sqrt(dimension)

@staticmethod
def decomposition(wires):
num_wires = len(wires)
shifts = [2 * np.pi * 2 ** -i for i in range(2, num_wires + 1)]

decomp_ops = []
for i, wire in enumerate(wires):
decomp_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])
decomp_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])
decomp_ops.append(swap)

return decomp_ops

def adjoint(self):
return QFT(wires=self.wires).inv()
4 changes: 2 additions & 2 deletions pennylane/templates/subroutines/qpe.py
Expand Up @@ -17,7 +17,7 @@
# pylint: disable=too-many-arguments
import pennylane as qml
from pennylane.operation import AnyWires, Operation
from pennylane.ops import Hadamard, ControlledQubitUnitary, QFT
from pennylane.ops import Hadamard, ControlledQubitUnitary


class QuantumPhaseEstimation(Operation):
Expand Down Expand Up @@ -136,6 +136,6 @@ def expand(self):
unitary_powers.pop(), control_wires=wire, wires=self.target_wires
)

QFT(wires=self.estimation_wires).inv()
qml.templates.QFT(wires=self.estimation_wires).inv()

return tape
3 changes: 2 additions & 1 deletion pennylane/transforms/qmc.py
Expand Up @@ -15,9 +15,10 @@
Contains the quantum_monte_carlo transform.
"""
from functools import wraps
from pennylane import PauliX, Hadamard, MultiControlledX, CZ, QFT
from pennylane import PauliX, Hadamard, MultiControlledX, CZ
from pennylane.wires import Wires
from pennylane.transforms import adjoint
from pennylane.templates import QFT


def _apply_controlled_z(wires, control_wire, work_wires):
Expand Down
64 changes: 0 additions & 64 deletions tests/ops/test_qubit_ops.py
Expand Up @@ -521,70 +521,6 @@ def test_adjoint_error_exception(self, op, tol):
with pytest.raises(qml.ops.AdjointError):
op.adjoint()

@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)

@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)

@pytest.mark.parametrize("n_qubits", range(2, 6))
def test_QFT_adjoint_identity(self, n_qubits, tol):
"""Test if the QFT adjoint operation is the inverse of QFT."""

dev = qml.device("default.qubit", wires=n_qubits)

@qml.qnode(dev)
def circ(n_qubits):
qml.adjoint(qml.QFT)(wires=range(n_qubits))
qml.QFT(wires=range(n_qubits))
return qml.state()

assert np.allclose(1, circ(n_qubits)[0], tol)

for i in range(1, n_qubits):
assert np.allclose(0, circ(n_qubits)[i], tol)

@pytest.mark.parametrize("n_qubits", range(2, 6))
def test_QFT_adjoint_decomposition(self, n_qubits, tol):
"""Test if the QFT adjoint operation has the right decomposition"""

# QFT adjoint has right decompositions
qft = qml.QFT(wires=range(n_qubits))
qft_dec = qft.expand().operations

expected_op = [x.adjoint() for x in qft_dec]
expected_op.reverse()

adj = qml.QFT(wires=range(n_qubits)).adjoint()
op = adj.expand().operations

for j in range(0, len(op)):
assert op[j].name == expected_op[j].name
assert op[j].wires == expected_op[j].wires
assert op[j].parameters == expected_op[j].parameters

def test_x_decomposition(self, tol):
"""Tests that the decomposition of the PauliX is correct"""
op = qml.PauliX(wires=0)
Expand Down

0 comments on commit c403e27

Please sign in to comment.