Repository: PennyLaneAI/pennylane
Pull request #3796 (2023-02-21 17:04:16)

Fix `Evolution.generator`
- Generalize `np.real` to `qml.math.real` to make sure it works with all interfaces.
- Cast coefficients to real to avoid a warning.
- ~Readd features removed in https://github.com/PennyLaneAI/pennylane/pull/3797~

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/3796?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3796](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3796?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (ec1bd23) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/fbd5f6d847829f5bca06c99b9a7a0efafe1c6575?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (fbd5f6d) will **decrease** coverage by `0.01%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master    #3796      +/-   ##
==========================================
- Coverage   99.72%   99.72%   -0.01%     
==========================================
  Files         336      336              
  Lines       29455    29454       -1     
==========================================
- Hits        29375    29374       -1     
  Misses         80       80              
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3796?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/3796?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2V2b2x1dGlvbi5weQ==) | `97.43% <100.00%> (-0.07%)` | :arrow_down: |
| [pennylane/ops/op\_math/exp.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3796?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2V4cC5weQ==) | `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)

rmoyard: ```
    @property
    def grad_method(self):
        return self.base.grad_method if hasattr(self.base, "grad_method") else super().grad_method
```
Is it needed in this PR @albi3ro ?
timmysilv: I'm not sure what prompted this PR, can someone give an example of what causes this warning? if it's because the user provided something that might result in something that requires truncating some produced real part of a coefficient, idk if hiding a warning is the best move. If it's just because of weird typing things and a result of pennylane implementation details, this lgtm 👍 

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 48ddc5df3..28ccfe689 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -146,6 +146,9 @@
 
 <h3>Bug fixes</h3>
 
+* Fixed a bug where calling `Evolution.generator` with `coeff` being a complex ArrayBox raised an error.
+  [(#3796)](https://github.com/PennyLaneAI/pennylane/pull/3796)
+  
 * `MeasurementProcess.hash` now uses the hash property of the observable. The property now depends on all
   properties that affect the behaviour of the object, such as `VnEntropyMP.log_base` or the distribution of wires between
   the two subsystems in `MutualInfoMP`.
@@ -158,7 +161,7 @@
   to work with non-numeric wire labels.  `sum_expand` should now return correct results and not treat some products as the same
   operation.
   [(#3898)](https://github.com/PennyLaneAI/pennylane/pull/3898)
-
+  
 * Fixed bug where the coefficients where not ordered correctly when summing a `ParametrizedHamiltonian`
   with other operators.
   [(#3749)](https://github.com/PennyLaneAI/pennylane/pull/3749)
diff --git a/pennylane/ops/op_math/evolution.py b/pennylane/ops/op_math/evolution.py
index c0571134a..9092e5853 100644
--- a/pennylane/ops/op_math/evolution.py
+++ b/pennylane/ops/op_math/evolution.py
@@ -17,8 +17,6 @@ This submodule defines the Evolution class.
 import warnings
 from warnings import warn
 
-import numpy as np
-
 import pennylane as qml
 from pennylane import math
 from pennylane.operation import GeneratorUndefinedError
@@ -88,6 +86,13 @@ class Evolution(Exp):
         super().__init__(generator, coeff=1j * param, num_steps=num_steps, do_queue=do_queue, id=id)
         self._data = [param]
 
+    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 param(self):
         """A real coefficient with ``1j`` factored out."""
@@ -114,7 +119,7 @@ class Evolution(Exp):
     # pylint: disable=arguments-renamed, invalid-overridden-method
     @property
     def has_generator(self):
-        return not np.real(self.coeff)
+        return not qml.math.real(self.coeff)
 
     def generator(self):
         r"""Generator of an operator that is in single-parameter-form.
@@ -134,7 +139,7 @@ class Evolution(Exp):
         """
         if not self.base.is_hermitian:
             warn(f"The base {self.base} may not be hermitian.")
-        if np.real(self.coeff):
+        if qml.math.real(self.coeff):
             raise GeneratorUndefinedError(
                 f"The operator coefficient {self.coeff} is not imaginary; the expected format is exp(ixG)."
                 f"The generator is not defined."
diff --git a/pennylane/ops/op_math/exp.py b/pennylane/ops/op_math/exp.py
index 7cf3cdd88..ce2de0a78 100644
--- a/pennylane/ops/op_math/exp.py
+++ b/pennylane/ops/op_math/exp.py
@@ -306,7 +306,9 @@ class Exp(ScalarSymbolicOp, Operation):
                 new_g = qml.map_wires(g, dict(zip(g.wires, base.wires)))
 
                 if qml.equal(base, new_g) and math.real(coeff) == 0:
-                    coeff *= -1j / c  # cancel the coefficients added by the generator
+                    coeff = math.real(
+                        -1j / c * coeff
+                    )  # cancel the coefficients added by the generator
                     return [op_class(coeff, g.wires)]
 
         if qml.pauli.is_pauli_word(base) and math.real(coeff) == 0:
@@ -330,7 +332,9 @@ class Exp(ScalarSymbolicOp, Operation):
         Returns:
             List[Operator]: list containing the PauliRot operator
         """
-        coeff = 2j * coeff  # need to cancel the coefficients added by PauliRot and Ising gates
+        coeff = math.real(
+            2j * coeff
+        )  # need to cancel the coefficients added by PauliRot and Ising gates
         pauli_word = qml.pauli.pauli_word_to_string(base)
         if pauli_word == "I" * base.num_wires:
             return []


Test patch:
diff --git a/tests/ops/op_math/test_evolution.py b/tests/ops/op_math/test_evolution.py
index a956024f7..e814b1dfb 100644
--- a/tests/ops/op_math/test_evolution.py
+++ b/tests/ops/op_math/test_evolution.py
@@ -83,6 +83,25 @@ class TestEvolution:
         assert op.coeff == 1j * op.data[0]
         assert op.param == op.data[0]
 
+    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",
         [