Repository: PennyLaneAI/pennylane
Pull request #1451 (2021-07-06 13:19:06)

Add adjoint to the operation `QFT` and fix the `qml.adjoint()` function
**Context:**
The method `.adjoint()` of the operation `QFT` was not implemented. Furthermore It should have worked without it, as the `qml.adjoint` function was designed such that if the adjoint does not exist, it decomposes the operation and then applies adjoint on each part. 

**Description of the Change:**
1)
The operation `QFT` has now an adjoint function that can be used to take the inverse of it, e.g.
```
N=2
dev = qml.device('default.qubit', wires=N)
@qml.qnode(dev)
def circ():
    qml.adjoint(qml.QFT)(wires=range(N))
    qml.QFT(wires = range(N))
    return qml.state()

print(qml.draw(circ)())
print(circ())
```

 0: ──╭QFT⁻¹──╭QFT──╭┤ State 
 1: ──╰QFT⁻¹──╰QFT──╰┤ State 

[ 1.00000000e+00+0.00000000e+00j -4.59242550e-17+5.55111512e-17j
  0.00000000e+00+6.12323400e-17j  8.22616137e-17+8.32667268e-17j]

2)
The function `qml.adjoint` is fixed and also works for operations with no defined adjoint method.

**Related GitHub Issues:**

This problem was mentioned here https://github.com/PennyLaneAI/pennylane/issues/1448 .


codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1451](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (43ef61c) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/8cb8d04b092682abdeb92fea6f8b728ed4672f8f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (8cb8d04) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1451   +/-   ##
=======================================
  Coverage   98.24%   98.24%           
=======================================
  Files         160      160           
  Lines       12027    12029    +2     
=======================================
+ Hits        11816    11818    +2     
  Misses        211      211           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/ops/qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC5weQ==) | `98.73% <100.00%> (+<0.01%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [8cb8d04...43ef61c](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1451?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

josh146: Thanks for this @rmoyard! It might be good to think about how we can avoid this issue in the future; whether that is raising a better error message, or making `adjoint()` an abstract method 🤔 
antalszava: Hmm, it looks like some ops would have to be exceptions and we might also be missing a def for some templates :thinking: Got the following when set `adjoint` abstract and initialized running the tests:
```pythontests/circuit_drawer/test_representation_resolver.py
  File "/home/antal/xanadu/pennylane/tests/circuit_drawer/test_representation_resolver.py", line 38, in <module>
    class TestRepresentationResolver:
  File "/home/antal/xanadu/pennylane/tests/circuit_drawer/test_representation_resolver.py", line 114, in TestRepresentationResolver
    (qml.ThermalState(3, wires=[1]), 1, "Thermal(3)"),
TypeError: Can't instantiate abstract class ThermalState with abstract methods adjoint

tests/devices/test_default_mixed.py
Traceback (most recent call last):
  File "/home/antal/xanadu/pennylane/tests/devices/test_default_mixed.py", line 216, in <module>
    class TestKrausOps:
  File "/home/antal/xanadu/pennylane/tests/devices/test_default_mixed.py", line 252, in TestKrausOps
    AmplitudeDamping(p, wires=0),
TypeError: Can't instantiate abstract class AmplitudeDamping with abstract methods adjoint

ERROR collecting tests/templates/test_layer.py
Traceback (most recent call last):
  File "/home/antal/xanadu/pennylane/tests/templates/test_layer.py", line 102, in <module>
    qml.templates.BasicEntanglerLayers([[0.5, 0.4]], wires=[0, 1]),
TypeError: Can't instantiate abstract class BasicEntanglerLayers with abstract methods adjoint

```
josh146: @antalszava @rmoyard I realize something is bugging me (😆) about this bug.

If you have a look at the logic for `qml.adjoint()`, it turns out that _operations do not have to have `adjoint()` defined_:

https://github.com/PennyLaneAI/pennylane/blob/8cb8d04b092682abdeb92fea6f8b728ed4672f8f/pennylane/transforms/adjoint.py#L121

Instead, if `adjoint()` is not defined, the transform simply decomposes the operation and calls `adjoint()` on each operation in the decomposition.

So why did we need to add `QFT.adjoint()`? Shouldn't it work even without?
antalszava: [ch6963]

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index a3f567fe4..5407dae5b 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -97,6 +97,10 @@
 
 <h3>Bug fixes</h3>
 
+* Fixes a bug where the adjoint of `qml.QFT` when using the `qml.adjoint` function
+  was not correctly computed.
+  [(#1451)](https://github.com/PennyLaneAI/pennylane/pull/1451)
+
 * Fixes the differentiability of the operation`IsingYY` for Autograd, Jax and Tensorflow.
   [(#1425)](https://github.com/PennyLaneAI/pennylane/pull/1425)
   
diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py
index e6303effd..b2442c12d 100644
--- a/pennylane/ops/qubit.py
+++ b/pennylane/ops/qubit.py
@@ -2646,6 +2646,9 @@ class QFT(Operation):
 
         return decomp_ops
 
+    def adjoint(self):
+        return QFT(wires=self.wires).inv()
+
 
 # =============================================================================
 # Quantum chemistry
diff --git a/pennylane/transforms/adjoint.py b/pennylane/transforms/adjoint.py
index fbdb6e578..d0dfcba5b 100644
--- a/pennylane/transforms/adjoint.py
+++ b/pennylane/transforms/adjoint.py
@@ -115,7 +115,11 @@ def adjoint(fn):
     def wrapper(*args, **kwargs):
         with get_active_tape().stop_recording(), QuantumTape() as tape:
             fn(*args, **kwargs)
-        for op in reversed(tape.queue):
+
+        if not tape.operations:
+            tape = fn(*args, **kwargs)
+
+        for op in reversed(tape.operations):
             try:
                 op.adjoint()
             except NotImplementedError:


Test patch:
diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py
index 047fd52f7..cd2777f6d 100644
--- a/tests/ops/test_qubit_ops.py
+++ b/tests/ops/test_qubit_ops.py
@@ -54,7 +54,6 @@ from gate_data import (
     DoubleExcitationMinus,
 )
 
-
 # Standard observables, their matrix representation, and eigenvalues
 OBSERVABLES = [
     (qml.PauliX, X, [1, -1]),
@@ -500,7 +499,6 @@ class TestOperations:
     @pytest.mark.parametrize(
         "op_builder",
         [
-            lambda: qml.QFT(wires=[1, 2, 3]),
             lambda: qml.QubitCarry(wires=[0, 1, 2, 3]),
         ],
     )
@@ -551,6 +549,42 @@ class TestOperations:
 
         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)
@@ -3373,8 +3407,10 @@ class TestArithmetic:
             ([2, 0, 1], [0, 0, 0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], True),
             ([1, 2, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], True),
             ([0, 1, 2], [0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0], [0.5, 0, 0, 0.5, 0, 0.5, 0.5, 0], True),
-            ([0, 1, 2], [np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8)],
-            [np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8)], True),
+            ([0, 1, 2], [np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8),
+                         np.sqrt(1 / 8), np.sqrt(1 / 8)],
+             [np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8),
+              np.sqrt(1 / 8), np.sqrt(1 / 8)], True),
             ([0, 1, 2], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], False),
             ([0, 1, 2], [0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], False),
             ([0, 1, 2], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], False),
@@ -3386,8 +3422,10 @@ class TestArithmetic:
             ([2, 0, 1], [0, 0, 0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], False),
             ([1, 2, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], False),
             ([0, 1, 2], [0.5, 0, 0.5, 0, 0.5, 0, 0.5, 0], [0.5, 0, 0, 0.5, 0, 0.5, 0.5, 0], False),
-            ([0, 1, 2], [np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8)],
-            [np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8), np.sqrt(1/8)], False),
+            ([0, 1, 2], [np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8),
+                         np.sqrt(1 / 8), np.sqrt(1 / 8)],
+             [np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8), np.sqrt(1 / 8),
+              np.sqrt(1 / 8), np.sqrt(1 / 8)], False),
         ],
     )
     # fmt: on