Repository: PennyLaneAI/pennylane
Pull request #4919 (2023-12-05 22:28:10)

Add comparison to BasisRotation
**Context:** Add comparison for `BasisRotation`

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:** As correctly stated in #4893, hashing a numpy array is not possible. One potential solution is to write a custom `_flatten()` and `_unflatten()` for BasisRotation, in which the matrix is serialized/deserialized to/from JSON. Specifically

```python
rotation_mat = np.array(
        [
             [-0.618452, -0.68369054 - 0.38740723j],
             [-0.78582258, 0.53807284 + 0.30489424j],
         ]
)
# serialize
u = ';'.join(str(__) for _ in rotation_mat for __ in _)   # gives '(-0.618452+0j);(-0.68369054-0.38740723j);(-0.78582258+0j);(0.53807284+0.30489424j)'
# deserialize
v = [complex(_) for _ in u.split(';')]
np.array(v).reshape([int(np.sqrt(len(v)))]*2)
```
This relies on the fact that rotational matrix is small and a square matrix
**Related GitHub Issues:** #4893

@albi3ro 


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4919?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: `1 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`73d2b28`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/73d2b2831d2e47a9ba4e5c962d754fbddc06da61?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.66% compared to head [(`235b1fd`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4919?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.65%.
> Report is 2 commits behind head on master.

| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/ops/functions/equal.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvZXF1YWwucHk=) | 96.15% | [1 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4919?src=pr&el=tree&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    #4919      +/-   ##
==========================================
- Coverage   99.66%   99.65%   -0.01%     
==========================================
  Files         387      387              
  Lines       35029    34790     -239     
==========================================
- Hits        34910    34671     -239     
  Misses        119      119              
```



</details>

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

minhtriet: > Thanks for opening this PR @minhtriet . I've noted down that we will need a follow-up task for fixing the `BasisRotation._flatten` and `BasisRotation._unflatten` methods, before removing the `xfail` from the "standard validity" tests.

Yes @albi3ro . If you look at the *Possible Drawbacks* part at the 1st post, I have proposed a potential solution for that. If it makes sense to you, then I would add this to the current PR.
minhtriet: Sorry for the unnecessary delay due to the docs. I have fixed it 

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 5e530ca27..8324791eb 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -260,8 +260,9 @@
 * `==` and `!=` operands can now be used with `TransformProgram` and `TransformContainers` instances.
   [(#4858)](https://github.com/PennyLaneAI/pennylane/pull/4858)
 
-* `qml.equal` now supports comparison of `QuantumScript` objects.
+* `qml.equal` now supports comparison of `QuantumScript` and `BasisRotation` objects
   [(#4902)](https://github.com/PennyLaneAI/pennylane/pull/4902)
+  [(#4919)](https://github.com/PennyLaneAI/pennylane/pull/4919)
 
 * The function ``qml.Snapshot`` now supports arbitrary measurements of type ``StateMeasurement``.
   [(#4876)](https://github.com/PennyLaneAI/pennylane/pull/4908)
@@ -559,4 +560,4 @@ Mudit Pandey,
 Matthew Silverman,
 Jay Soni,
 David Wierichs,
-Justin Woodring.
+Justin Woodring.
\ No newline at end of file
diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py
index 5e1a57da0..d030da4a4 100644
--- a/pennylane/ops/functions/equal.py
+++ b/pennylane/ops/functions/equal.py
@@ -18,6 +18,7 @@ This module contains the qml.equal function.
 from collections.abc import Iterable
 from functools import singledispatch
 from typing import Union
+
 import pennylane as qml
 from pennylane.measurements import MeasurementProcess
 from pennylane.measurements.classical_shadow import ShadowExpvalMP
@@ -485,3 +486,30 @@ def _equal_shadow_measurements(op1: ShadowExpvalMP, op2: ShadowExpvalMP, **_):
 @_equal.register
 def _equal_counts(op1: CountsMP, op2: CountsMP, **kwargs):
     return _equal_measurements(op1, op2, **kwargs) and op1.all_outcomes == op2.all_outcomes
+
+
+@_equal.register
+# pylint: disable=unused-argument
+def _equal_basis_rotation(
+    op1: qml.BasisRotation,
+    op2: qml.BasisRotation,
+    check_interface=True,
+    check_trainability=True,
+    rtol=1e-5,
+    atol=1e-9,
+):
+    if not qml.math.allclose(
+        op1.hyperparameters["unitary_matrix"],
+        op2.hyperparameters["unitary_matrix"],
+        atol=atol,
+        rtol=rtol,
+    ):
+        return False
+    if op1.wires != op2.wires:
+        return False
+    if check_interface:
+        if qml.math.get_interface(op1.hyperparameters["unitary_matrix"]) != qml.math.get_interface(
+            op2.hyperparameters["unitary_matrix"]
+        ):
+            return False
+    return True


Test patch:
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index 94778d70e..5c33a54c7 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -1866,3 +1866,44 @@ class TestQuantumScriptComparisons:
     @pytest.mark.parametrize("tape, other_tape", [(tape2, tape5)])
     def test_non_equal_training_params_comparison(self, tape, other_tape):
         assert qml.equal(tape, other_tape) is False
+
+
+class TestBasisRotation:
+    rotation_mat = np.array(
+        [
+            [-0.618452, -0.68369054 - 0.38740723j],
+            [-0.78582258, 0.53807284 + 0.30489424j],
+        ]
+    )
+    op1 = qml.BasisRotation(wires=range(2), unitary_matrix=rotation_mat)
+    op2 = qml.BasisRotation(wires=range(2), unitary_matrix=np.array(rotation_mat))
+    op3 = qml.BasisRotation(wires=range(2), unitary_matrix=rotation_mat + 1e-7)
+    op4 = qml.BasisRotation(wires=range(2, 4), unitary_matrix=rotation_mat)
+
+    @pytest.mark.parametrize("op, other_op", [(op1, op3)])
+    def test_different_tolerances_comparison(self, op, other_op):
+        assert qml.equal(op, other_op, atol=1e-5)
+        assert qml.equal(op, other_op, rtol=0, atol=1e-9) is False
+
+    @pytest.mark.parametrize("op, other_op", [(op1, op2)])
+    def test_non_equal_training_params_comparison(self, op, other_op):
+        assert qml.equal(op, other_op)
+
+    @pytest.mark.parametrize("op, other_op", [(op1, op4)])
+    def test_non_equal_training_wires(self, op, other_op):
+        assert qml.equal(op, other_op) is False
+
+    @pytest.mark.jax
+    @pytest.mark.parametrize("op", [op1])
+    def test_non_equal_interfaces(self, op):
+        import jax
+
+        rotation_mat_jax = jax.numpy.array(
+            [
+                [-0.618452, -0.68369054 - 0.38740723j],
+                [-0.78582258, 0.53807284 + 0.30489424j],
+            ]
+        )
+        other_op = qml.BasisRotation(wires=range(2), unitary_matrix=rotation_mat_jax)
+        assert qml.equal(op, other_op, check_interface=False)
+        assert qml.equal(op, other_op) is False