Repository: PennyLaneAI/pennylane
Pull request #7107 (2025-03-18 20:23:16)

Remove outdated Observable check in qml.equal
**Context:**

If the first operation in `qml.equal` is an adjoint of an Observable, it skips the "do they match" check because of an outdated clause that was meant to allow comparison of instances of the removed `Tensor` and `Hamiltonian` classes. This means that the second operation is not confirmed to also be an `Adjoint` before jumping to the function that assumes this check has already been performed, and leads to bugs like this:

```
>>> qml.equal(qml.adjoint(H(0)), qml.Y(0))
AttributeError: 'PauliY' object has no attribute 'base'
```

and

```
>>> qml.equal(qml.adjoint(X(0)), qml.exp(qml.X(0), 2))
True
```

This came up in a comparison of to operations in a decomposition when updating `assert_valid` (#7099).

**Description of the Change:**

Removed the exception for if `op1` is an `Observable`.

**Benefits:**

Fixes this edge-case bug for comparing operators where `op1` is the adjoint of an observable, and `op2` is not an `Adjoint`.

**Possible Drawbacks:**

If the tests and I are both missing some edge case where this is still relevant, I guess we might be breaking something.


[sc-86861]

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://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7107?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.60%. Comparing base [(`9c36698`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/9c36698c887e30abbbb0e73ec9187f9c1a397107?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`8eec248`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/8eec2487a9e87be5a6412ffac779e4b3b50c9242?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    #7107      +/-   ##
==========================================
- Coverage   99.60%   99.60%   -0.01%     
==========================================
  Files         496      496              
  Lines       47341    47341              
==========================================
- Hits        47154    47153       -1     
- Misses        187      188       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7107?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).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 085302ad6..398d5e85b 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -779,6 +779,10 @@
 * `qml.qchem.givens_decomposition` no longer raises a `RuntimeWarning` when the input is a zero matrix.
   [#7053)](https://github.com/PennyLaneAI/pennylane/pull/7053)
 
+* Comparing an adjoint of an `Observable` with another `Operation` using `qml.equal` no longer incorrectly 
+  skips the check ensuring that the operator types match.
+  [(#7107)](https://github.com/PennyLaneAI/pennylane/pull/7107)
+
 <h3>Contributors ✍️</h3>
 
 This release contains contributions from (in alphabetical order):
diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py
index f40769a7d..849959f7c 100644
--- a/pennylane/ops/functions/equal.py
+++ b/pennylane/ops/functions/equal.py
@@ -14,7 +14,7 @@
 """
 This module contains the qml.equal function.
 """
-# pylint: disable=too-many-arguments,too-many-return-statements,too-many-branches
+# pylint: disable=too-many-arguments,too-many-return-statements,too-many-branches, too-many-positional-arguments
 from collections.abc import Iterable
 from functools import singledispatch
 from typing import Union
@@ -26,7 +26,7 @@ from pennylane.measurements.counts import CountsMP
 from pennylane.measurements.mid_measure import MeasurementValue, MidMeasureMP
 from pennylane.measurements.mutual_info import MutualInfoMP
 from pennylane.measurements.vn_entropy import VnEntropyMP
-from pennylane.operation import Observable, Operator
+from pennylane.operation import Operator
 from pennylane.ops import Adjoint, CompositeOp, Conditional, Controlled, Exp, Pow, SProd
 from pennylane.pauli import PauliSentence, PauliWord
 from pennylane.pulse.parametrized_evolution import ParametrizedEvolution
@@ -219,7 +219,7 @@ def _equal(
     rtol=1e-5,
     atol=1e-9,
 ) -> Union[bool, str]:  # pylint: disable=unused-argument
-    if not isinstance(op2, type(op1)) and not isinstance(op1, Observable):
+    if not isinstance(op2, type(op1)):
         return f"op1 and op2 are of different types.  Got {type(op1)} and {type(op2)}."
 
     return _equal_dispatch(
@@ -297,10 +297,6 @@ def _equal_operators(
     atol=1e-9,
 ):
     """Default function to determine whether two Operator objects are equal."""
-    if not isinstance(
-        op2, type(op1)
-    ):  # clarifies cases involving PauliX/Y/Z (Observable/Operation)
-        return f"op1 and op2 are of different types. Got {type(op1)} and {type(op2)}"
 
     if isinstance(op1, qml.Identity):
         # All Identities are equivalent, independent of wires.


Test patch:
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index 98ed803a6..1bebfbe62 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -1739,6 +1739,7 @@ class TestSymbolicOpComparison:
             ):
                 assert_equal(op1, op2)
 
+    # pylint: disable=too-many-positional-arguments
     @pytest.mark.parametrize(
         ("wires1", "controls1", "wires2", "controls2", "res"),
         [
@@ -1832,6 +1833,18 @@ class TestSymbolicOpComparison:
         assert qml.equal(op1, op2, check_interface=False, check_trainability=False)
         assert not qml.equal(op1, op2, check_interface=False, check_trainability=True)
 
+    def test_adjoint_observable_with_non_adjoint(self):
+        """Test that comparing an adjoint observable with a non-adjoint operation returns False"""
+
+        base = qml.X(0)
+        adj_op = qml.adjoint(base)
+
+        # if we get rid of Observable or stop having adjoint(Observable) be an Observable,
+        # this test is no longer relevant. Ref: https://github.com/PennyLaneAI/pennylane/pull/7107/
+        assert isinstance(adj_op, qml.operation.Observable)
+
+        assert not qml.equal(adj_op, qml.exp(base, 2))
+
     @pytest.mark.parametrize(("wire1", "wire2", "res"), WIRES)
     def test_conditional_base_operator_wire_comparison(self, wire1, wire2, res):
         """Test that equal compares operator wires for Conditional operators"""