Repository: PennyLaneAI/pennylane
Pull request #5877 (2024-06-19 14:12:19)

Fix bug in Tensor equal check
Fixes bug where `qml.equal` incorrectly returns `True` when a `Tensor` is not equal with another non-`Tensor` observable.

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.
astralcai: [sc-66438]
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5877?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.67%. Comparing base [(`ab8b6d5`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/ab8b6d574d8bf2472832cdbc57f08f3d15f6479f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`0973662`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/0973662c63f5a72a405b7e022e1494fe0df30f38?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5877      +/-   ##
==========================================
- Coverage   99.68%   99.67%   -0.01%     
==========================================
  Files         421      421              
  Lines       40499    40199     -300     
==========================================
- Hits        40370    40069     -301     
- Misses        129      130       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5877?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index fb62fcde1..caee5bf67 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -314,7 +314,8 @@
   [(#5502)](https://github.com/PennyLaneAI/pennylane/pull/5502)
  
 * Implement support in `assert_equal` for `Operator`, `Controlled`, `Adjoint`, `Pow`, `Exp`, `SProd`, `ControlledSequence`, `Prod`, `Sum`, `Tensor` and `Hamiltonian`
- [(#5780)](https://github.com/PennyLaneAI/pennylane/pull/5780)
+  [(#5780)](https://github.com/PennyLaneAI/pennylane/pull/5780)
+  [(#5877)](https://github.com/PennyLaneAI/pennylane/pull/5877)
 
 * `qml.QutritChannel` has been added, enabling the specification of noise using a collection of (3x3) Kraus matrices on the `default.qutrit.mixed` device.
   [(#5793)](https://github.com/PennyLaneAI/pennylane/issues/5793)
diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py
index 7a89ed4de..f159ae3bf 100644
--- a/pennylane/ops/functions/equal.py
+++ b/pennylane/ops/functions/equal.py
@@ -597,31 +597,33 @@ def _equal_sprod(op1: SProd, op2: SProd, **kwargs):
 # pylint: disable=unused-argument
 def _equal_tensor(op1: Tensor, op2: Observable, **kwargs):
     """Determine whether a Tensor object is equal to a Hamiltonian/Tensor"""
+
     if not isinstance(op2, Observable):
         return f"{op2} is not of type Observable"
 
     if isinstance(op2, (Hamiltonian, LinearCombination, Hermitian)):
-        if not op2.compare(op1):
-            return f"'{op1}' and '{op2}' are not same"
+        return (
+            op2.compare(op1) or f"'{op1}' and '{op2}' are not the same for an unspecified reason."
+        )
 
     if isinstance(op2, Tensor):
-        if not op1._obs_data() == op2._obs_data():  # pylint: disable=protected-access
-            return "op1 and op2 have different _obs_data outputs"
+        return (
+            op1._obs_data() == op2._obs_data()  # pylint: disable=protected-access
+            or f"{op1} and {op2} have different _obs_data outputs"
+        )
 
-    return True
+    return f"{op1} is of type {type(op1)} and {op2} is of type {type(op2)}"
 
 
 @_equal_dispatch.register
 # pylint: disable=unused-argument
 def _equal_hamiltonian(op1: Hamiltonian, op2: Observable, **kwargs):
     """Determine whether a Hamiltonian object is equal to a Hamiltonian/Tensor objects"""
+
     if not isinstance(op2, Observable):
         return f"{op2} is not of type Observable"
 
-    if not op1.compare(op2):
-        return f"'{op1}' and '{op2}' are not same"
-
-    return True
+    return op1.compare(op2) or f"'{op1}' and '{op2}' are not the same for an unspecified reason"
 
 
 @_equal_dispatch.register


Test patch:
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index 62c2fa7be..3e24d9bb9 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -1467,7 +1467,7 @@ class TestObservablesComparisons:
         assert qml.equal(H1, H2) == qml.equal(H2, H1)
         assert qml.equal(H1, H2) == res
         if not res:
-            error_message_pattern = re.compile(r"'([^']+)' and '([^']+)' are not same")
+            error_message_pattern = re.compile(r"'([^']+)' and '([^']+)' are not the same")
             with pytest.raises(AssertionError, match=error_message_pattern):
                 assert_equal(H1, H2)
 
@@ -1481,7 +1481,7 @@ class TestObservablesComparisons:
         """Tensors are not equal because of different observable data"""
         op1 = qml.operation.Tensor(qml.X(0), qml.Y(1))
         op2 = qml.operation.Tensor(qml.Y(0), qml.X(1))
-        with pytest.raises(AssertionError, match="op1 and op2 have different _obs_data outputs"):
+        with pytest.raises(AssertionError, match="have different _obs_data outputs"):
             assert_equal(op1, op2)
 
     @pytest.mark.parametrize(("H", "T", "res"), equal_hamiltonians_and_tensors)
@@ -1522,13 +1522,22 @@ class TestObservablesComparisons:
         with pytest.raises(AssertionError, match="is not of type Observable"):
             assert_equal(op1, op2)
 
+    def test_tensor_and_observable_not_equal(self):
+        """Tests that comparing a Tensor with an Observable that is not a Tensor returns False"""
+        op1 = qml.PauliX(0) @ qml.PauliY(1)
+        op2 = qml.Z(0)
+        assert qml.equal(op1, op2) is False
+        assert qml.equal(op2, op1) is False
+        with pytest.raises(AssertionError, match="is of type <class 'pennylane.operation.Tensor'>"):
+            assert_equal(op1, op2)
+
     def test_tensor_and_unsupported_observable_returns_false(self):
         """Tests that trying to compare a Tensor to something other than another Tensor or a Hamiltonian returns False"""
         op1 = qml.PauliX(0) @ qml.PauliY(1)
         op2 = qml.Hermitian([[0, 1], [1, 0]], 0)
 
         assert not qml.equal(op1, op2)
-        error_message_pattern = re.compile(r"'([^']+)' and '([^']+)' are not same")
+        error_message_pattern = re.compile(r"'([^']+)' and '([^']+)' are not the same")
         with pytest.raises(AssertionError, match=error_message_pattern):
             assert_equal(op1, op2)