Repository: PennyLaneAI/pennylane
Pull request #3304 (2022-11-09 21:00:33)

Use singledispatch for qml.equal
**Description of the Change:**
Slightly rearrange `qml.equal` to use `singledispatch` to replace type-based conditional logic

**Benefits:**
Moves some conditional logic out of the function and into `singledispatch`

**Related GitHub Issues:**
N/A

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

```diff
@@            Coverage Diff             @@
##           master    #3304      +/-   ##
==========================================
- Coverage   99.70%   99.69%   -0.01%     
==========================================
  Files         299      299              
  Lines       26121    26126       +5     
==========================================
+ Hits        26044    26047       +3     
- Misses         77       79       +2     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3304?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/ops/functions/equal.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3304/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvZXF1YWwucHk=) | `96.29% <86.66%> (-3.71%)` | :arrow_down: |

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/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py
index 00c38f3d1..d75b6bfdc 100644
--- a/pennylane/ops/functions/equal.py
+++ b/pennylane/ops/functions/equal.py
@@ -17,14 +17,15 @@ This module contains the qml.equal function.
 # pylint: disable=too-many-arguments,too-many-return-statements
 from typing import Union
 
+from functools import singledispatch
 import pennylane as qml
 from pennylane.measurements import MeasurementProcess, ShadowMeasurementProcess
 from pennylane.operation import Operator
 
 
 def equal(
-    op1: Union[Operator, MeasurementProcess, ShadowMeasurementProcess],
-    op2: Union[Operator, MeasurementProcess, ShadowMeasurementProcess],
+    op1: Union[Operator, MeasurementProcess],
+    op2: Union[Operator, MeasurementProcess],
     check_interface=True,
     check_trainability=True,
     rtol=1e-5,
@@ -33,8 +34,8 @@ def equal(
     r"""Function for determining operator or measurement equality.
 
     Args:
-        op1 (.Operator, .MeasurementProcess, or .ShadowMeasurementProcess): First object to compare
-        op2 (.Operator, .MeasurementProcess, or .ShadowMeasurementProcess): Second object to compare
+        op1 (.Operator or .MeasurementProcess): First object to compare
+        op2 (.Operator or .MeasurementProcess): Second object to compare
         check_interface (bool, optional): Whether to compare interfaces. Default: ``True``
         check_trainability (bool, optional): Whether to compare trainability status. Default: ``True``
         rtol (float, optional): Relative tolerance for parameters
@@ -52,12 +53,14 @@ def equal(
     >>> qml.equal(op1, op1), qml.equal(op1, op2)
     (True, False)
 
-    >>> qml.equal(qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(0)))
-    >>> True
-    >>> qml.equal(qml.probs(wires=(0,1)), qml.probs(wires=(1,2)))
-    >>> False
-    >>> qml.equal(qml.classical_shadow(wires=[0,1]), qml.classical_shadow(wires=[0,1]))
-    >>> True
+
+    >>> qml.equal(qml.expval(qml.PauliX(0)), qml.expval(qml.PauliX(0)) )
+    True
+    >>> qml.equal(qml.probs(wires=(0,1)), qml.probs(wires=(1,2)) )
+    False
+    >>> qml.equal(qml.classical_shadow(wires=[0,1]), qml.classical_shadow(wires=[0,1]) )
+    True
+
 
     .. details::
         :title: Usage Details
@@ -83,20 +86,41 @@ def equal(
         True
     """
 
-    if op1.__class__ is not op2.__class__:
+    if not isinstance(op2, type(op1)):
         return False
 
-    if isinstance(op1, ShadowMeasurementProcess):
-        return equal_shadow_measurements(op1, op2)
+    return _equal(
+        op1,
+        op2,
+        check_interface=check_interface,
+        check_trainability=check_trainability,
+        atol=atol,
+        rtol=rtol,
+    )
 
-    if isinstance(op1, MeasurementProcess):
-        return equal_measurements(op1, op2)
 
-    return equal_operator(op1, op2, check_interface, check_trainability, rtol, atol)
+@singledispatch
+def _equal(
+    op1,
+    op2,
+    **kwargs,
+):
+
+    raise NotImplementedError(f"Comparison between {type(op1)} and {type(op2)} not implemented.")
 
 
-def equal_operator(op1, op2, check_interface, check_trainability, rtol, atol):
+@_equal.register
+def _equal_operator(
+    op1: Operator,
+    op2: Operator,
+    check_interface=True,
+    check_trainability=True,
+    rtol=1e-5,
+    atol=1e-9,
+):
+
     """Determine whether two Operator objects are equal"""
+
     if op1.arithmetic_depth != op2.arithmetic_depth:
         return False
 
@@ -110,9 +134,9 @@ def equal_operator(op1, op2, check_interface, check_trainability, rtol, atol):
         return False
     if op1.wires != op2.wires:
         return False
-    for kwarg in op1.hyperparameters:
-        if op1.hyperparameters[kwarg] != op2.hyperparameters[kwarg]:
-            return False
+
+    if op1.hyperparameters != op2.hyperparameters:
+        return False
 
     if check_trainability:
         for params_1, params_2 in zip(op1.data, op2.data):
@@ -127,8 +151,11 @@ def equal_operator(op1, op2, check_interface, check_trainability, rtol, atol):
     return getattr(op1, "inverse", False) == getattr(op2, "inverse", False)
 
 
-def equal_measurements(op1, op2):
+@_equal.register
+# pylint: disable=unused-argument
+def _equal_measurements(op1: MeasurementProcess, op2: MeasurementProcess, **kwargs):
     """Determine whether two MeasurementProcess objects are equal"""
+
     return_types_match = op1.return_type == op2.return_type
     if op1.obs is not None and op2.obs is not None:
         observables_match = equal(op1.obs, op2.obs)
@@ -148,8 +175,13 @@ def equal_measurements(op1, op2):
     )
 
 
-def equal_shadow_measurements(op1, op2):
+@_equal.register
+# pylint: disable=unused-argument
+def _equal_shadow_measurements(
+    op1: ShadowMeasurementProcess, op2: ShadowMeasurementProcess, **kwargs
+):
     """Determine whether two ShadowMeasurementProcess objects are equal"""
+
     return_types_match = op1.return_type == op2.return_type
     wires_match = op1.wires == op2.wires
     H_match = op1.H == op2.H


Test patch:
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index edae8712f..c06bad31e 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -167,6 +167,10 @@ class TestEqual:
         """Test different operators return False"""
         assert not qml.equal(ops[0], ops[1], check_trainability=False, check_interface=False)
 
+    def test_equal_unsupported_op_type(self):
+        with pytest.raises(NotImplementedError, match="Comparison between "):
+            qml.equal(3, 4)
+
     @pytest.mark.parametrize("op1", PARAMETRIZED_OPERATIONS)
     def test_equal_simple_same_op(self, op1):
         """Test same operators return True"""