Repository: PennyLaneAI/pennylane

Issue:
[BUG] Derivatives of `qml.CommutingEvolution`
In cleaning warnings from the test suite, I found this warning:

```
templates/test_subroutines/test_commuting_evolution.py::TestGradients::test_differentiable_hamiltonian
  /Users/christina/Prog/pl/lib/python3.8/site-packages/autograd/tracer.py:14: UserWarning: Output seems independent of input.
    warnings.warn("Output seems independent of input.")
```

The effect of `CommutingEvolution` on the state did not change with the time.  
This problems seems to stem from [commuting_evolution.py Line 160](https://github.com/PennyLaneAI/pennylane/blob/e55f463bcbca1832505d9440d2f5144543f944fe/pennylane/templates/subroutines/commuting_evolution.py#L160):
```
        return qml.templates.ApproxTimeEvolution(hamiltonian, time_and_coeffs[1], 1).decomposition()
```
This line incorrectly indexes into `time_and_coeffs` to try and extract the time.  It should instead use `time_and_coeffs[0]`.

Upon fixing that bug, we now find that parameter shift no longer works for `CommutingEvolution`.  

These two tests start to fail:
```
==================================================================================== short test summary info ====================================================================================
FAILED templates/test_subroutines/test_commuting_evolution.py::TestGradients::test_two_term_case - assert False
FAILED templates/test_subroutines/test_commuting_evolution.py::TestGradients::test_four_term_case - assert False
```

The tests that fail manually specify the frequencies. If the frequencies are specified, the operation sets its own `grad_recipe`.  This `grad_recipe` gives incorrect results.  I have yet to track down the problem with the `grad_recipe` property.


In summary, fixing the bug in the creation of the `ApproxTimeEvolution` operation allows finite difference derivatives and derivatives for operations without specified frequencies to work correctly.  Since the finite-difference derivatives are now correct, we can see that the parameter-shift gradients never worked correctly.




Discussion:
Oof, really nice find @albi3ro. Since `qml.CommutingEvolution` is really integral to QAOA workflows, I would say that this is a very high priority bug.
In fact the evaluation of `qml.CommutingEvolution` is now incorrect. Running the example code in the comment at https://github.com/PennyLaneAI/pennylane/blob/master/pennylane/templates/subroutines/commuting_evolution.py#L103
```python
import pennylane as qml
n_wires = 2
dev = qml.device('default.qubit', wires=n_wires)
coeffs = [1, -1]
obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)]
hamiltonian = qml.Hamiltonian(coeffs, obs)
frequencies = (2, 4)
@qml.qnode(dev)
def circuit(time):
    qml.PauliX(0)
    qml.CommutingEvolution(hamiltonian, time, frequencies)
    return qml.expval(qml.PauliZ(0))
>>> circuit(1)
0.6536436208636115
```
        
Gives `0.14550003` instead.
I can confirm that in version 0.21 of PennyLane, the above example gives `0.6536436208636115`. In version 0.22, it gives `0.14550003`
I can also confirm that the expectation value of `<ψ|Z⊗I|ψ> = -cos(4) ≈ 0.653644` where `|ψ> = exp(-i(X⊗Y-Y⊗X))|0>`, so the old result was the correct one
After more digging: both v0.21 and v0.22 produce the _same_ decomposition for the `ApproxTimeEvolution`:

```
[PauliRot(tensor(2., requires_grad=False), 'XY', wires=[0, 1]), PauliRot(tensor(-2., requires_grad=False), 'YX', wires=[0, 1])]
```

So it looks like the bug could be related to the decomposition of the `PauliRot` operation?

v0.21 final decomposition:

```
PauliX(wires=[0]) [] <Wires = [0]>
Hadamard(wires=[0]) [] <Wires = [0]>
RX(1.5707963267948966, wires=[1]) [1.5707963267948966] <Wires = [1]>
MultiRZ(tensor(2., requires_grad=False), wires=[0, 1]) [tensor(2., requires_grad=False)] <Wires = [0, 1]>
Hadamard(wires=[0]) [] <Wires = [0]>
RX(-1.5707963267948966, wires=[1]) [-1.5707963267948966] <Wires = [1]>
RX(1.5707963267948966, wires=[0]) [1.5707963267948966] <Wires = [0]>
Hadamard(wires=[1]) [] <Wires = [1]>
MultiRZ(tensor(-2., requires_grad=False), wires=[0, 1]) [tensor(-2., requires_grad=False)] <Wires = [0, 1]>
RX(-1.5707963267948966, wires=[0]) [-1.5707963267948966] <Wires = [0]>
Hadamard(wires=[1]) [] <Wires = [1]>
```

v0.22 decomposition:

```
PauliX(wires=[0]) [] <Wires = [0]>
Hadamard(wires=[0]) [] <Wires = [0]>
RX(1.5707963267948966, wires=[1]) [1.5707963267948966] <Wires = [1]>
MultiRZ(tensor(2., requires_grad=False), wires=[0, 1]) [tensor(2., requires_grad=False)] <Wires = [0, 1]>
Hadamard(wires=[0]) [] <Wires = [0]>
RX(-1.5707963267948966, wires=[1]) [-1.5707963267948966] <Wires = [1]>
RX(1.5707963267948966, wires=[0]) [1.5707963267948966] <Wires = [0]>
Hadamard(wires=[1]) [] <Wires = [1]>
MultiRZ(tensor(-2., requires_grad=False), wires=[0, 1]) [tensor(-2., requires_grad=False)] <Wires = [0, 1]>
RX(-1.5707963267948966, wires=[0]) [-1.5707963267948966] <Wires = [0]>
Hadamard(wires=[1]) [] <Wires = [1]>
Hadamard(wires=[0]) [] <Wires = [0]>
RX(1.5707963267948966, wires=[1]) [1.5707963267948966] <Wires = [1]>
MultiRZ(tensor(2., requires_grad=False), wires=[0, 1]) [tensor(2., requires_grad=False)] <Wires = [0, 1]>
Hadamard(wires=[0]) [] <Wires = [0]>
RX(-1.5707963267948966, wires=[1]) [-1.5707963267948966] <Wires = [1]>
RX(1.5707963267948966, wires=[0]) [1.5707963267948966] <Wires = [0]>
Hadamard(wires=[1]) [] <Wires = [1]>
MultiRZ(tensor(-2., requires_grad=False), wires=[0, 1]) [tensor(-2., requires_grad=False)] <Wires = [0, 1]>
RX(-1.5707963267948966, wires=[0]) [-1.5707963267948966] <Wires = [0]>
Hadamard(wires=[1]) [] <Wires = [1]>
```

So in v0.22, the decomposition of PauliRot is a lot longer
Ah!!!! If you ignore the initial `PauliX`, it looks like v0.22 is _repeating the decomposition twice_ for some reason
This diff solves the bug:

```diff
diff --git a/pennylane/templates/subroutines/commuting_evolution.py b/pennylane/templates/subroutines/commuting_evolution.py
index 0ce68ed2f..e426d8d07 100644
--- a/pennylane/templates/subroutines/commuting_evolution.py
+++ b/pennylane/templates/subroutines/commuting_evolution.py
@@ -158,7 +158,8 @@ class CommutingEvolution(Operation):
         """
         # uses standard PauliRot decomposition through ApproxTimeEvolution.
         hamiltonian = qml.Hamiltonian(time_and_coeffs[1:], hamiltonian.ops)
-        return qml.templates.ApproxTimeEvolution(hamiltonian, time_and_coeffs[1], 1).decomposition()
+        op = qml.tape.stop_recording()(qml.ApproxTimeEvolution)
+        return op(hamiltonian, time_and_coeffs[0], 1).decomposition()
 
     def adjoint(self):  # pylint: disable=arguments-differ
```
I'm having a look at the tests now, and unfortunately they suffer from the fact that the 'expected value' that the test is comparing to is generated from PennyLane as well.

So that's why the tests never failed; when the test result became incorrect, the 'expected' result also changed to match it.

We should have had a test that compared to a known analytic value, e.g., `-cos(4)` as in the example above.
@josh146 
Instead of using `stop_recording` or querying the `decomposition`, we could do:
```
return qml.ApproxTimeEvolution(hamiltonian, time_and_coeffs[0], 1)
```
which would then itself be decomposed on a subsequent pass.

We could also do:
```
return qml.ApproxTimeEvolution(hamiltonian, time_and_coeffs[0], 1, do_queue=False).decomposition()
```
@albi3ro I like your first suggestion!

Resolving patch:
diff --git a/pennylane/templates/subroutines/commuting_evolution.py b/pennylane/templates/subroutines/commuting_evolution.py
--- a/pennylane/templates/subroutines/commuting_evolution.py
+++ b/pennylane/templates/subroutines/commuting_evolution.py
@@ -135,7 +135,7 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, do_queue=Tr
 
     @staticmethod
     def compute_decomposition(
-        *time_and_coeffs, wires, hamiltonian, **kwargs
+        time, *coeffs, wires, hamiltonian, **kwargs
     ):  # pylint: disable=arguments-differ,unused-argument
         r"""Representation of the operator as a product of other operators.
 
@@ -157,8 +157,8 @@ def compute_decomposition(
             list[.Operator]: decomposition of the operator
         """
         # uses standard PauliRot decomposition through ApproxTimeEvolution.
-        hamiltonian = qml.Hamiltonian(time_and_coeffs[1:], hamiltonian.ops)
-        return qml.templates.ApproxTimeEvolution(hamiltonian, time_and_coeffs[1], 1).decomposition()
+        hamiltonian = qml.Hamiltonian(coeffs, hamiltonian.ops)
+        return qml.ApproxTimeEvolution(hamiltonian, time, 1)
 
     def adjoint(self):  # pylint: disable=arguments-differ
 


Test patch:
diff --git a/tests/templates/test_subroutines/test_commuting_evolution.py b/tests/templates/test_subroutines/test_commuting_evolution.py
--- a/tests/templates/test_subroutines/test_commuting_evolution.py
+++ b/tests/templates/test_subroutines/test_commuting_evolution.py
@@ -18,6 +18,8 @@
 from pennylane import numpy as np
 import pennylane as qml
 
+from scipy.linalg import expm
+
 
 def test_adjoint():
     """Tests the CommutingEvolution.adjoint method provides the correct adjoint operation."""
@@ -51,6 +53,58 @@ def evolution_circuit(time):
     assert all(np.isclose(dev1.state, dev2.state))
 
 
+def test_decomposition_expand():
+    """Test that the decomposition of CommutingEvolution is an ApproxTimeEvolution with one step."""
+
+    hamiltonian = 0.5 * qml.PauliX(0) @ qml.PauliY(1)
+    time = 2.345
+
+    op = qml.CommutingEvolution(hamiltonian, time)
+
+    decomp = op.decomposition()
+
+    assert isinstance(decomp, qml.ApproxTimeEvolution)
+    assert all(decomp.hyperparameters["hamiltonian"].coeffs == hamiltonian.coeffs)
+    assert decomp.hyperparameters["n"] == 1
+
+    tape = op.expand()
+    assert len(tape) == 1
+    assert isinstance(tape[0], qml.ApproxTimeEvolution)
+
+
+def test_matrix():
+    """Test that the matrix of commuting evolution is the same as exponentiating -1j * t the hamiltonian."""
+
+    h = 2.34 * qml.PauliX(0)
+    time = 0.234
+    op = qml.CommutingEvolution(h, time)
+
+    mat = qml.matrix(op)
+
+    expected = expm(-1j * time * qml.matrix(h))
+
+    assert qml.math.allclose(mat, expected)
+
+
+def test_forward_execution():
+    """Compare the foward execution to an exactly known result."""
+    dev = qml.device("default.qubit", wires=2)
+
+    H = qml.PauliX(0) @ qml.PauliY(1) - 1.0 * qml.PauliY(0) @ qml.PauliX(1)
+    freq = (2, 4)
+
+    @qml.qnode(dev, diff_method=None)
+    def circuit(time):
+        qml.PauliX(0)
+        qml.CommutingEvolution(H, time, freq)
+        return qml.expval(qml.PauliZ(0))
+
+    t = 1.0
+    res = circuit(t)
+    expected = -np.cos(4)
+    assert np.allclose(res, expected)
+
+
 class TestInputs:
     """Tests for input validation of `CommutingEvolution`."""