Skip to content

Commit

Permalink
Recreates AQFT PR (#4715)
Browse files Browse the repository at this point in the history
**Context:**
Recreates AQFT PR 
**Description of the Change:**
Adds AQFT template
**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**

Closes #4641.

---------

Co-authored-by: soranjh <[email protected]>
Co-authored-by: Jay Soni <[email protected]>
Co-authored-by: Guillermo Alonso-Linaje <[email protected]>
  • Loading branch information
4 people authored Oct 31, 2023
1 parent 28d255a commit 099bc38
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 1 deletion.
Binary file added doc/_static/templates/subroutines/aqft.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions doc/introduction/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ Other useful templates which do not belong to the previous categories can be fou
:description: :doc:`QuantumFourierTransform <../code/api/pennylane.QFT>`
:figure: _static/templates/subroutines/qft.svg

.. gallery-item::
:description: :doc:`Approximate QFT<../code/api/pennylane.AQFT>`
:figure: _static/templates/subroutines/aqft.png

.. gallery-item::
:description: :doc:`CommutingEvolution <../code/api/pennylane.CommutingEvolution>`
:figure: _static/templates/subroutines/commuting_evolution.png
Expand Down
6 changes: 5 additions & 1 deletion doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

<h3>New features since last release</h3>

* Approximate Quantum Fourier Transform (AQFT) is now available from `qml.AQFT`.
[(#4656)](https://github.com/PennyLaneAI/pennylane/pull/4656)

<h3>Improvements 🛠</h3>

* Updates to some relevant Pytests to enable its use as a suite of benchmarks.
Expand Down Expand Up @@ -31,6 +34,7 @@
This release contains contributions from (in alphabetical order):

Amintor Dusko,
Ankit Khandelwal,
Anurav Modak,
David Wierichs,
Justin Woodring
Justin Woodring,
1 change: 1 addition & 0 deletions pennylane/templates/subroutines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@
from .qdrift import QDrift
from .controlled_sequence import ControlledSequence
from .trotter import TrotterProduct
from .aqft import AQFT
189 changes: 189 additions & 0 deletions pennylane/templates/subroutines/aqft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Copyright 2018-2023 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 AQFT.
"""

import warnings
import numpy as np

import pennylane as qml
from pennylane.operation import Operation


class AQFT(Operation):
r"""AQFT(order, wires)
Apply an approximate quantum Fourier transform (AQFT).
The `AQFT <https://arxiv.org/abs/1803.04933>`_ method helps to reduce the number of ``ControlledPhaseShift`` operations required
for QFT by only using a maximum of ``order`` number of ``ControlledPhaseShift`` gates per qubit.
.. seealso:: :class:`~.QFT`
Args:
order (int): the order of approximation
wires (int or Iterable[Number, str]]): the wire(s) the operation acts on
**Example**
The approximate quantum Fourier transform is applied by specifying the corresponding wires and
the order of approximation:
.. code-block::
wires = 3
dev = qml.device('default.qubit', wires=wires)
@qml.qnode(dev)
def circuit_aqft():
qml.PauliX(0)
qml.Hadamard(1)
qml.AQFT(order=1,wires=range(wires))
return qml.state()
.. code-block:: pycon
>>> circuit_aqft()
[ 0.5 +0.j -0.25-0.25j 0. +0.j -0.25+0.25j 0.5 +0.j -0.25-0.25j 0. +0.j -0.25+0.25j]
.. details::
:title: Usage Details
**Order**
The order of approximation must be a whole number less than :math:`n-1`
where :math:`n` is the number of wires the operation is being applied on.
This creates four cases for different ``order`` values:
* ``order`` :math:`< 0`
This will raise a ``ValueError``
* ``order`` :math:`= 0`
This will warn the user that only a Hadamard transform is being applied.
.. code-block::
@qml.qnode(dev)
def circ():
qml.AQFT(order=0, wires=range(6))
return qml.probs()
The resulting circuit is:
>>> print(qml.draw(circ, expansion_strategy='device')())
UserWarning: order=0, applying Hadamard transform warnings.warn("order=0, applying Hadamard transform")
0: ──H─╭SWAP─────────────┤ ╭Probs
1: ──H─│─────╭SWAP───────┤ ├Probs
2: ──H─│─────│─────╭SWAP─┤ ├Probs
3: ──H─│─────│─────╰SWAP─┤ ├Probs
4: ──H─│─────╰SWAP───────┤ ├Probs
5: ──H─╰SWAP─────────────┤ ╰Probs
* :math:`0 <` ``order`` :math:`< n-1`
This is the intended AQFT use case.
.. code-block::
@qml.qnode(dev)
def circ():
qml.AQFT(order=2, wires=range(4))
return qml.probs()
The resulting circuit is:
>>> print(qml.draw(circ, expansion_strategy='device')())
0: ──H─╭Rϕ(1.57)─╭Rϕ(0.79)────────────────────────────────────────╭SWAP───────┤ ╭Probs
1: ────╰●────────│──────────H─╭Rϕ(1.57)─╭Rϕ(0.79)─────────────────│─────╭SWAP─┤ ├Probs
2: ──────────────╰●───────────╰●────────│──────────H─╭Rϕ(1.57)────│─────╰SWAP─┤ ├Probs
3: ─────────────────────────────────────╰●───────────╰●─────────H─╰SWAP───────┤ ╰Probs
* ``order`` :math:`\geq n-1`
Using the QFT class is recommended in this case. The AQFT operation here is
equivalent to QFT.
"""

def __init__(self, order, wires=None, id=None):
n_wires = len(wires)

if not isinstance(order, int):
warnings.warn(f"The order must be an integer. Using order = {round(order)}")
order = round(order)

if order >= n_wires - 1:
warnings.warn(
f"The order ({order}) is >= to the number of wires - 1 ({n_wires-1}). Using the QFT class is recommended in this case."
)
order = n_wires - 1

if order < 0:
raise ValueError("Order can not be less than 0")

if order == 0:
warnings.warn("order=0, applying Hadamard transform")

self.hyperparameters["order"] = order
super().__init__(wires=wires, id=id)

@property
def num_params(self):
return 0

@staticmethod
def compute_decomposition(wires, order): # pylint: disable=arguments-differ
r"""Representation of the operator as a product of other operators (static method).
.. math:: O = O_1 O_2 \dots O_n.
.. seealso:: :meth:`~.AQFT.decomposition`.
Args:
wires (Iterable, Wires): wires that the operator acts on
order (int): order of approximation
Returns:
list[Operator]: decomposition of the operator
**Example:**
>>> qml.AQFT.compute_decomposition((0, 1, 2), 3, order=1)
[Hadamard(wires=[0]), ControlledPhaseShift(1.5707963267948966, wires=[1, 0]), Hadamard(wires=[1]), ControlledPhaseShift(1.5707963267948966, wires=[2, 1]), Hadamard(wires=[2]), SWAP(wires=[0, 2])]
"""
n_wires = len(wires)
shifts = [2 * np.pi * 2**-i for i in range(2, n_wires + 1)]

decomp_ops = []
for i, wire in enumerate(wires):
decomp_ops.append(qml.Hadamard(wire))
counter = 0

for shift, control_wire in zip(shifts[: len(shifts) - i], wires[i + 1 :]):
if counter >= order:
break

op = qml.ControlledPhaseShift(shift, wires=[control_wire, wire])
decomp_ops.append(op)
counter = counter + 1

first_half_wires = wires[: n_wires // 2]
last_half_wires = wires[-(n_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
88 changes: 88 additions & 0 deletions tests/templates/test_subroutines/test_aqft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2018-2023 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.
"""
Unit tests for the aqft template.
"""
import pytest

import numpy as np
import pennylane as qml


class TestAQFT:
"""Tests for the aqft operations"""

@pytest.mark.parametrize("order,n_qubits", [(o, w) for w in range(2, 10) for o in range(1, w)])
def test_AQFT_adjoint_identity(self, order, n_qubits, tol):
"""Test if after using the qml.adjoint transform the resulting operation is
the inverse of AQFT."""

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

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

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

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

@pytest.mark.parametrize("order", [-1, -5.4])
def test_negative_order(self, order):
"""Test if ValueError is raised for negative orders"""
with pytest.raises(ValueError, match="Order can not be less than 0"):
qml.AQFT(order=order, wires=range(5))

@pytest.mark.parametrize("order", [1.2, 4.6])
def test_float_order(self, order):
"""Test if float order is handled correctly"""
with pytest.warns(UserWarning, match="The order must be an integer"):
op = qml.AQFT(order=order, wires=range(9))
assert op.hyperparameters["order"] == round(order)

@pytest.mark.parametrize("wires", range(3, 10))
def test_zero_order(self, wires):
"""Test if Hadamard transform is applied for zero order"""
with pytest.warns(UserWarning, match="order=0"):
op = qml.AQFT(order=0, wires=range(wires))
for gate in op.decomposition()[: -wires // 2]:
assert gate.name == "Hadamard"

@pytest.mark.parametrize("order", [4, 5, 6])
def test_higher_order(self, order):
"""Test if higher order recommends using QFT"""
with pytest.warns(UserWarning, match="Using the QFT class is recommended in this case"):
qml.AQFT(order=order, wires=range(5))

@pytest.mark.parametrize("wires", [3, 4, 5, 6, 7, 8, 9])
def test_matrix_higher_order(self, wires):
"""Test if the matrix from AQFT and QFT are same for higher order"""

m1 = qml.matrix(qml.AQFT(order=10, wires=range(wires)))
m2 = qml.matrix(qml.QFT(wires=range(wires)))

assert np.allclose(m1, m2)

@pytest.mark.parametrize("order,wires", [(o, w) for w in range(2, 10) for o in range(1, w)])
def test_gates(self, order, wires):
"""Test if the AQFT operation consists of only 3 type of gates"""

op = qml.AQFT(order=order, wires=range(wires))
decomp = op.decomposition()

for gate in decomp:
assert gate.name in ["Hadamard", "ControlledPhaseShift", "SWAP"]

0 comments on commit 099bc38

Please sign in to comment.