Repository: PennyLaneAI/pennylane
Pull request #1955 (2021-11-30 16:51:39)

`qml.QFT` `inv()` and adjoint() issues
**Context:** Trying to solve [(#1940)](https://github.com/PennyLaneAI/pennylane/issues/1940) I've found different issues between the `adjoint` and `inv` functions and the and calls in and out of the quantum function. Examples:

```python
import pennylane as qml

op = qml.RX(2, wires = 0).inv()
dev = qml.device("default.qubit", wires=0)
@qml.qnode(dev)
def circuit():
    qml.apply(op)
    return qml.state()

print(qml.draw(circuit)())
```
>>>  0: ──RX(2)──┤ State

As we can see, if we define the inverse outside the function, it is not detected when we apply it inside it.
This problem was solved but then something else was discovered. The `adjoint` operator did not detect inverses. This is because for example the `adjoint` method of RX(a) returns a new RX(-a) so if you had the `inverse` parameter set to `True`, it is lost.
This problem has also been solved, but with an important note: we must see the difference between adjoint of an operator with its inverse:
- inv(): The same operator marks it as inverse.
- adjoint(): Returns new operations equivalent to the inverse.

we should avoid mixing these concepts as they are here:

```python
class QFT:



    def adjoint(self):
        return QFT(wires=self.wires).inv()
```


**Description of the Change:**

The three problems mentioned above have been solved.

**Possible Drawbacks:**
- It may have looked nice to see qml.adjoint(QFT) as QFT^-1, but now it breaks it down. If we want to see it as QFT^-1 we would have to put QFT.inv()

- It must be ensured that in all operators and templates the adjoint does not return the inverse.

**Related GitHub Issues:**
[(#1940)](https://github.com/PennyLaneAI/pennylane/issues/1940)

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1955](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (e744f3f) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/4b62d0619c9e0b331e5ae0e9093df14bea2c5575?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (4b62d06) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

> :exclamation: Current head e744f3f differs from pull request most recent head 530ee6c. Consider uploading reports for the commit 530ee6c to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955/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/1955?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1955   +/-   ##
=======================================
  Coverage   98.81%   98.81%           
=======================================
  Files         225      225           
  Lines       17187    17188    +1     
=======================================
+ Hits        16983    16984    +1     
  Misses        204      204           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/templates/subroutines/qft.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9zdWJyb3V0aW5lcy9xZnQucHk=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955?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/1955?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [4b62d06...530ee6c](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1955?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).


Patch:
diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py
index 3d2ab1dff..63f02cc9c 100644
--- a/pennylane/tape/tape.py
+++ b/pennylane/tape/tape.py
@@ -422,7 +422,7 @@ class QuantumTape(AnnotatedQueue):
                     )
 
                 # invert the operation if required
-                obj.inverse = info.get("inverse", False)
+                obj.inverse = info.get("inverse", obj.inverse)
 
                 if isinstance(obj, STATE_PREP_OPS):
                     if self._ops:
diff --git a/pennylane/templates/subroutines/qft.py b/pennylane/templates/subroutines/qft.py
index 3adc8501f..67a13a4d5 100644
--- a/pennylane/templates/subroutines/qft.py
+++ b/pennylane/templates/subroutines/qft.py
@@ -115,4 +115,6 @@ class QFT(Operation):
         return decomp_ops
 
     def adjoint(self):
-        return QFT(wires=self.wires).inv()
+        adjoint_op = QFT(wires=self.wires)
+        adjoint_op.inverse = not self.inverse
+        return adjoint_op


Test patch:
diff --git a/tests/templates/test_subroutines/test_qft.py b/tests/templates/test_subroutines/test_qft.py
index 7a1baa3f6..8c520d18f 100644
--- a/tests/templates/test_subroutines/test_qft.py
+++ b/tests/templates/test_subroutines/test_qft.py
@@ -33,6 +33,20 @@ class TestQFT:
         exp = QFT.conj().T if inverse else QFT
         assert np.allclose(res, exp)
 
+    @pytest.mark.parametrize("num_inversions", [1, 2, 3])
+    def test_QFT_adjoint_method(self, num_inversions):
+        """Test the adjoint method of the QFT class"""
+        op = qml.QFT(wires=range(3))
+
+        for _ in range(num_inversions):
+            op = op.adjoint()
+
+        res = op.matrix
+        inverse = num_inversions % 2 == 1
+        exp = QFT.conj().T if inverse else QFT
+        assert np.allclose(res, exp)
+        assert op.inverse is inverse
+
     @pytest.mark.parametrize("n_qubits", range(2, 6))
     def test_QFT_decomposition(self, n_qubits):
         """Test if the QFT operation is correctly decomposed"""
@@ -55,7 +69,8 @@ class TestQFT:
 
     @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."""
+        """Test if using the qml.adjoint transform the resulting operation is
+        the inverse of QFT."""
 
         dev = qml.device("default.qubit", wires=n_qubits)
 
@@ -72,7 +87,8 @@ class TestQFT:
 
     @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"""
+        """Test if using the qml.adjoint transform results in the right
+        decomposition."""
 
         # QFT adjoint has right decompositions
         qft = qml.QFT(wires=range(n_qubits))
diff --git a/tests/templates/test_subroutines/test_qpe.py b/tests/templates/test_subroutines/test_qpe.py
index ef504aaae..084b4fbf1 100644
--- a/tests/templates/test_subroutines/test_qpe.py
+++ b/tests/templates/test_subroutines/test_qpe.py
@@ -130,6 +130,33 @@ class TestDecomposition:
         # lower
         assert np.allclose(estimates[-1], phase, rtol=1e-2)
 
+    def test_adjoint(self):
+        """Test that the QPE adjoint works."""
+        dev = qml.device("default.qubit", wires=3)
+
+        @qml.qnode(dev)
+        def qpe_circuit():
+
+            qml.Hadamard(wires=0)
+            qml.PauliX(wires=1)
+            qml.QuantumPhaseEstimation(
+                qml.PauliX.matrix,
+                target_wires=[0],
+                estimation_wires=[1, 2],
+            )
+
+            qml.adjoint(qml.QuantumPhaseEstimation)(
+                qml.PauliX.matrix,
+                target_wires=[0],
+                estimation_wires=[1, 2],
+            )
+            qml.Hadamard(wires=0)
+            qml.PauliX(wires=1)
+
+            return qml.state()
+
+        assert qml.math.isclose(qpe_circuit()[0], 1)
+
 
 class TestInputs:
     """Test inputs and pre-processing."""