Repository: PennyLaneAI/pennylane
Pull request #3755 (2023-02-13 09:35:25)

Deprecate ApproxTimeEvolution
1. Deprecate `ApproxTimeEvolution`, and use `qml.evolve` instead.
2. Add `Evolution.__repr__` method.
3. Add `Evolution.grad_method` static attribute.

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.
AlbertMitjans: [sc-33650]
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3755](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (7e5b8fb) into [evolution-decompositions-2](https://codecov.io/gh/PennyLaneAI/pennylane/commit/c519ab4befff49b55399cfe9883c9b7fe9007e26?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (c519ab4) will **decrease** coverage by `0.01%`.
> The diff coverage is `100.00%`.

```diff
@@                      Coverage Diff                       @@
##           evolution-decompositions-2    #3755      +/-   ##
==============================================================
- Coverage                       99.82%   99.82%   -0.01%     
==============================================================
  Files                             330      330              
  Lines                           29002    29009       +7     
==============================================================
+ Hits                            28951    28957       +6     
- Misses                             51       52       +1     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/ops/op\_math/evolution.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2V2b2x1dGlvbi5weQ==) | `97.50% <100.00%> (-2.50%)` | :arrow_down: |
| [pennylane/optimize/lie\_algebra.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wdGltaXplL2xpZV9hbGdlYnJhLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/qaoa/layers.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3Fhb2EvbGF5ZXJzLnB5) | `100.00% <100.00%> (ø)` | |
| [...ane/templates/subroutines/approx\_time\_evolution.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9zdWJyb3V0aW5lcy9hcHByb3hfdGltZV9ldm9sdXRpb24ucHk=) | `100.00% <100.00%> (ø)` | |
| [...ylane/templates/subroutines/commuting\_evolution.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3755?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9zdWJyb3V0aW5lcy9jb21tdXRpbmdfZXZvbHV0aW9uLnB5) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


Patch:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 1d6dbb83e..e27ddcc20 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -77,6 +77,11 @@ Pending deprecations
   - Deprecated in v0.29
   - Removed in v0.31
 
+* ``ApproxTimeEvolution`` has been deprecated.
+
+  - Deprecated in v0.29
+  - Will be removed in v0.31
+
 Completed deprecation cycles
 ----------------------------
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 2c0752238..e29226639 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -711,6 +711,9 @@
   This new function changes the sign of the given parameter.
   [(#3706)](https://github.com/PennyLaneAI/pennylane/pull/3706)
 
+* `ApproxTimeEvolution` has been deprecated. Please use `qml.evolve` instead.
+  [(#3755)](https://github.com/PennyLaneAI/pennylane/pull/3755)
+
 <h3>Documentation</h3>
 
 * Organizes the module for documentation for ``operation``.
diff --git a/pennylane/ops/op_math/evolution.py b/pennylane/ops/op_math/evolution.py
index b3d1c312b..a78ce2880 100644
--- a/pennylane/ops/op_math/evolution.py
+++ b/pennylane/ops/op_math/evolution.py
@@ -93,6 +93,13 @@ class Evolution(Exp):
         """A real coefficient with ``1j`` factored out."""
         return self.data[0]
 
+    def __repr__(self):
+        return (
+            f"Evolution({self.coeff} {self.base})"
+            if self.base.arithmetic_depth > 0
+            else f"Evolution({self.coeff} {self.base.name})"
+        )
+
     @property
     def coeff(self):
         return 1j * self.data[0]
@@ -105,6 +112,10 @@ class Evolution(Exp):
         )
         return base_label or f"Exp({param}j {self.base.label(decimals=decimals, cache=cache)})"
 
+    @property
+    def grad_method(self):
+        return self.base.grad_method if hasattr(self.base, "grad_method") else super().grad_method
+
     def simplify(self):
         new_base = self.base.simplify()
         if isinstance(new_base, qml.ops.op_math.SProd):  # pylint: disable=no-member
diff --git a/pennylane/optimize/lie_algebra.py b/pennylane/optimize/lie_algebra.py
index 9329ccbc2..e9e990040 100644
--- a/pennylane/optimize/lie_algebra.py
+++ b/pennylane/optimize/lie_algebra.py
@@ -16,6 +16,7 @@ import warnings
 
 import numpy as np
 from scipy.sparse.linalg import expm
+
 import pennylane as qml
 
 
@@ -63,7 +64,7 @@ def append_time_evolution(tape, riemannian_gradient, t, n, exact=False):
             wires=range(len(riemannian_gradient.wires)),
         )
     else:
-        qml.templates.ApproxTimeEvolution(riemannian_gradient, t, n)
+        qml.evolve(riemannian_gradient, coeff=t, num_steps=n)
 
     for obj in tape.measurements:
         qml.apply(obj)
diff --git a/pennylane/qaoa/layers.py b/pennylane/qaoa/layers.py
index e2625e617..56193c847 100644
--- a/pennylane/qaoa/layers.py
+++ b/pennylane/qaoa/layers.py
@@ -72,7 +72,7 @@ def cost_layer(gamma, hamiltonian):
 
         .. code-block:: python
 
-            dev = qml.device('default.qubit', wires=2)
+            dev = qml.device('default.mixed', wires=2)
 
             @qml.qnode(dev)
             def circuit(gamma):
@@ -87,11 +87,11 @@ def cost_layer(gamma, hamiltonian):
         which gives us a circuit of the form:
 
         >>> print(qml.draw(circuit)(0.5))
-        0: ──H─╭ApproxTimeEvolution(1.00,1.00,0.50)─┤  <Z>
-        1: ──H─╰ApproxTimeEvolution(1.00,1.00,0.50)─┤  <Z>
+        0: ──H─╭Exp(-0.50j 𝓗(1.00,1.00))─┤  <Z>
+        1: ──H─╰Exp(-0.50j 𝓗(1.00,1.00))─┤  <Z>
         >>> print(qml.draw(circuit, expansion_strategy="device")(0.5))
-        0: ──H──MultiRZ(1.00)─╭MultiRZ(1.00)─┤  <Z>
-        1: ──H────────────────╰MultiRZ(1.00)─┤  <Z>
+        0: ──H──RZ(1.00+0.00j)─╭●─────────────────╭●─┤  <Z>
+        1: ──H─────────────────╰X──RZ(1.00+0.00j)─╰X─┤  <Z>
 
     """
     if not isinstance(hamiltonian, qml.Hamiltonian):
@@ -102,7 +102,7 @@ def cost_layer(gamma, hamiltonian):
     if not _diagonal_terms(hamiltonian):
         raise ValueError("hamiltonian must be written only in terms of PauliZ and Identity gates")
 
-    qml.templates.ApproxTimeEvolution(hamiltonian, gamma, 1)
+    qml.evolve(hamiltonian, coeff=gamma, num_steps=1)
 
 
 def mixer_layer(alpha, hamiltonian):
@@ -134,7 +134,7 @@ def mixer_layer(alpha, hamiltonian):
 
         .. code-block:: python
 
-            dev = qml.device('default.qubit', wires=2)
+            dev = qml.device('default.mixed', wires=2)
 
             @qml.qnode(dev)
             def circuit(alpha):
@@ -149,11 +149,11 @@ def mixer_layer(alpha, hamiltonian):
         which gives us a circuit of the form:
 
         >>> print(qml.draw(circuit)(0.5))
-        0: ──H─╭ApproxTimeEvolution(1.00,1.00,0.50)─┤  <Z>
-        1: ──H─╰ApproxTimeEvolution(1.00,1.00,0.50)─┤  <Z>
+        0: ──H─╭Exp(-0.50j 𝓗(1.00,1.00))─┤  <Z>
+        1: ──H─╰Exp(-0.50j 𝓗(1.00,1.00))─┤  <Z>
         >>> print(qml.draw(circuit, expansion_strategy="device")(0.5))
-        0: ──H──H──MultiRZ(1.00)──H──H─╭MultiRZ(1.00)──H─┤  <Z>
-        1: ──H──H──────────────────────╰MultiRZ(1.00)──H─┤  <Z>
+        0: ──H──RX(1.00+0.00j)─╭●──RX(1.00+0.00j)─╭●─┤  <Z>
+        1: ──H─────────────────╰X─────────────────╰X─┤  <Z>
 
 
     """
@@ -162,4 +162,4 @@ def mixer_layer(alpha, hamiltonian):
             f"hamiltonian must be of type pennylane.Hamiltonian, got {type(hamiltonian).__name__}"
         )
 
-    qml.templates.ApproxTimeEvolution(hamiltonian, alpha, 1)
+    qml.evolve(hamiltonian, coeff=alpha, num_steps=1)
diff --git a/pennylane/templates/subroutines/approx_time_evolution.py b/pennylane/templates/subroutines/approx_time_evolution.py
index c1eca87d7..c300d8cad 100644
--- a/pennylane/templates/subroutines/approx_time_evolution.py
+++ b/pennylane/templates/subroutines/approx_time_evolution.py
@@ -14,9 +14,11 @@
 r"""
 Contains the ApproxTimeEvolution template.
 """
+import warnings
+
 # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access
 import pennylane as qml
-from pennylane.operation import Operation, AnyWires
+from pennylane.operation import AnyWires, Operation
 from pennylane.ops import PauliRot
 
 
@@ -102,6 +104,10 @@ class ApproxTimeEvolution(Operation):
     grad_method = None
 
     def __init__(self, hamiltonian, time, n, do_queue=True, id=None):
+        warnings.warn(
+            "The ApproxTimeEvolution class is deprecated. Please use `qml.evolve` instead.",
+            UserWarning,
+        )
         if not isinstance(hamiltonian, qml.Hamiltonian):
             raise ValueError(
                 f"hamiltonian must be of type pennylane.Hamiltonian, got {type(hamiltonian).__name__}"
diff --git a/pennylane/templates/subroutines/commuting_evolution.py b/pennylane/templates/subroutines/commuting_evolution.py
index 6d3233b58..24078f445 100644
--- a/pennylane/templates/subroutines/commuting_evolution.py
+++ b/pennylane/templates/subroutines/commuting_evolution.py
@@ -16,7 +16,7 @@ Contains the CommutingEvolution template.
 """
 # pylint: disable-msg=too-many-arguments,import-outside-toplevel
 import pennylane as qml
-from pennylane.operation import Operation, AnyWires
+from pennylane.operation import AnyWires, Operation
 
 
 class CommutingEvolution(Operation):
@@ -48,7 +48,7 @@ class CommutingEvolution(Operation):
 
     .. warning::
 
-       This template uses the :class:`~.ApproxTimeEvolution` operation with ``n=1`` in order to
+       This template uses the :func:`pennylane.evolve` function with ``num_steps=1`` in order to
        implement the time evolution, as a single-step Trotterization is exact for a commuting
        Hamiltonian.
 
@@ -108,9 +108,7 @@ class CommutingEvolution(Operation):
 
     def __init__(self, hamiltonian, time, frequencies=None, shifts=None, do_queue=True, id=None):
         # pylint: disable=import-outside-toplevel
-        from pennylane.gradients.general_shift_rules import (
-            generate_shift_rule,
-        )
+        from pennylane.gradients.general_shift_rules import generate_shift_rule
 
         if not isinstance(hamiltonian, qml.Hamiltonian):
             type_name = type(hamiltonian).__name__
@@ -156,9 +154,9 @@ class CommutingEvolution(Operation):
         Returns:
             list[.Operator]: decomposition of the operator
         """
-        # uses standard PauliRot decomposition through ApproxTimeEvolution.
+        # uses standard PauliRot decomposition through `qml.evolve`.
         hamiltonian = qml.Hamiltonian(coeffs, hamiltonian.ops)
-        return qml.ApproxTimeEvolution(hamiltonian, time, 1)
+        return qml.evolve(hamiltonian, coeff=time, num_steps=1)
 
     def adjoint(self):
         hamiltonian = qml.Hamiltonian(self.parameters[1:], self.hyperparameters["hamiltonian"].ops)


Test patch:
diff --git a/tests/ops/op_math/test_evolution.py b/tests/ops/op_math/test_evolution.py
index f850d6bdd..50a5549ad 100644
--- a/tests/ops/op_math/test_evolution.py
+++ b/tests/ops/op_math/test_evolution.py
@@ -70,6 +70,33 @@ class TestEvolution:
         assert op.coeff == 1j * op.data[0]
         assert op.param == op.data[0]
 
+    @pytest.mark.parametrize(
+        "base", [qml.Hamiltonian([1], [qml.PauliX(0)]), qml.pow(qml.PauliX(0))]
+    )
+    def test_grad_method(self, base):
+        """Test that the grad_method of the Evolution operator corresponds to the grad_method of the base operator."""
+        ev = Evolution(base)
+        assert ev.grad_method == base.grad_method
+
+    def test_repr_paulix(self):
+        """Test the __repr__ method when the base is a simple observable."""
+        op = Evolution(qml.PauliX(0), 3)
+        assert repr(op) == "Evolution(3j PauliX)"
+
+    def test_repr_tensor(self):
+        """Test the __repr__ method when the base is a tensor."""
+        t = qml.PauliX(0) @ qml.PauliX(1)
+        isingxx = Evolution(t, 0.25)
+
+        assert repr(isingxx) == "Evolution(0.25j PauliX(wires=[0]) @ PauliX(wires=[1]))"
+
+    def test_repr_deep_operator(self):
+        """Test the __repr__ method when the base is any operator with arithmetic depth > 0."""
+        base = qml.S(0) @ qml.PauliX(0)
+        op = Evolution(base, 3)
+
+        assert repr(op) == "Evolution(3j S(wires=[0]) @ PauliX(wires=[0]))"
+
     @pytest.mark.parametrize(
         "op,decimals,expected",
         [
diff --git a/tests/templates/test_subroutines/test_commuting_evolution.py b/tests/templates/test_subroutines/test_commuting_evolution.py
index d115ac7da..7dbdf3a39 100644
--- a/tests/templates/test_subroutines/test_commuting_evolution.py
+++ b/tests/templates/test_subroutines/test_commuting_evolution.py
@@ -15,11 +15,11 @@
 Tests for the CommutingEvolution template.
 """
 import pytest
-from pennylane import numpy as np
-import pennylane as qml
-
 from scipy.linalg import expm
 
+import pennylane as qml
+from pennylane import numpy as np
+
 
 def test_adjoint():
     """Tests the CommutingEvolution.adjoint method provides the correct adjoint operation."""
@@ -63,13 +63,13 @@ def test_decomposition_expand():
 
     decomp = op.decomposition()
 
-    assert isinstance(decomp, qml.ApproxTimeEvolution)
-    assert all(decomp.hyperparameters["hamiltonian"].coeffs == hamiltonian.coeffs)
-    assert decomp.hyperparameters["n"] == 1
+    assert isinstance(decomp, qml.ops.Exp)
+    assert decomp.base.coeffs == hamiltonian.coeffs
+    assert decomp.num_steps == 1
 
     tape = op.expand()
     assert len(tape) == 1
-    assert isinstance(tape[0], qml.ApproxTimeEvolution)
+    assert isinstance(tape[0], qml.ops.Exp)
 
 
 def test_matrix():
diff --git a/tests/test_qaoa.py b/tests/test_qaoa.py
index fce83c9fd..6bd2e40fb 100644
--- a/tests/test_qaoa.py
+++ b/tests/test_qaoa.py
@@ -14,32 +14,30 @@
 """
 Unit tests for the :mod:`pennylane.qaoa` submodule.
 """
-import pytest
 import itertools
-import numpy as np
 
 import networkx as nx
-from networkx import Graph
+import numpy as np
+import pytest
 import retworkx as rx
+from networkx import Graph
+from scipy.linalg import expm
+from scipy.sparse import csc_matrix, kron
 
 import pennylane as qml
 from pennylane import qaoa
-
 from pennylane.qaoa.cycle import (
-    edges_to_wires,
-    wires_to_edges,
     _inner_net_flow_constraint_hamiltonian,
-    net_flow_constraint,
-    loss_hamiltonian,
+    _inner_out_flow_constraint_hamiltonian,
+    _partial_cycle_mixer,
     _square_hamiltonian_terms,
     cycle_mixer,
-    _partial_cycle_mixer,
+    edges_to_wires,
+    loss_hamiltonian,
+    net_flow_constraint,
     out_flow_constraint,
-    _inner_out_flow_constraint_hamiltonian,
+    wires_to_edges,
 )
-from scipy.linalg import expm
-from scipy.sparse import csc_matrix, kron
-
 
 #####################################################
 
@@ -1127,17 +1125,17 @@ class TestLayers:
         [
             [
                 qml.Hamiltonian([1, 1], [qml.PauliX(0), qml.PauliX(1)]),
-                [qml.PauliRot(2, "X", wires=[0]), qml.PauliRot(2, "X", wires=[1])],
+                [qml.RX(2, wires=[0]), qml.RX(2, wires=[1])],
             ],
             [
                 qaoa.xy_mixer(Graph([(0, 1), (1, 2), (2, 0)])),
                 [
-                    qml.PauliRot(1, "XX", wires=[0, 1]),
-                    qml.PauliRot(1, "YY", wires=[0, 1]),
-                    qml.PauliRot(1, "XX", wires=[0, 2]),
-                    qml.PauliRot(1, "YY", wires=[0, 2]),
-                    qml.PauliRot(1, "XX", wires=[1, 2]),
-                    qml.PauliRot(1, "YY", wires=[1, 2]),
+                    qml.IsingXX(1, wires=[0, 1]),
+                    qml.IsingYY(1, wires=[0, 1]),
+                    qml.IsingXX(1, wires=[0, 2]),
+                    qml.IsingYY(1, wires=[0, 2]),
+                    qml.IsingXX(1, wires=[1, 2]),
+                    qml.IsingYY(1, wires=[1, 2]),
                 ],
             ],
         ],
@@ -1163,14 +1161,14 @@ class TestLayers:
         [
             [
                 qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]),
-                [qml.PauliRot(2, "Z", wires=[0]), qml.PauliRot(2, "Z", wires=[1])],
+                [qml.RZ(2, wires=[0]), qml.RZ(2, wires=[1])],
             ],
             [
                 qaoa.maxcut(Graph([(0, 1), (1, 2), (2, 0)]))[0],
                 [
-                    qml.PauliRot(1, "ZZ", wires=[0, 1]),
-                    qml.PauliRot(1, "ZZ", wires=[0, 2]),
-                    qml.PauliRot(1, "ZZ", wires=[1, 2]),
+                    qml.IsingZZ(1, wires=[0, 1]),
+                    qml.IsingZZ(1, wires=[0, 2]),
+                    qml.IsingZZ(1, wires=[1, 2]),
                 ],
             ],
         ],