Repository: PennyLaneAI/pennylane
Pull request #4858 (2023-11-20 18:40:00)

Implement __eq__ comparison for TransformContainer and TransformProgram
Towards #4824.

I've completed the first draft of the __eq__ codes for TransformContainer and TransformProgram. I haven't written the testing code yet. Once you confirm these implementations, I'll proceed with the testing phase. Let me know your thoughts.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: `6 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`bce513e`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/bce513eaa121797dc3c97a7eb4ef152a8c844d43?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.65% compared to head [(`332b9ac`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.62%.
> Report is 10 commits behind head on master.

| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/transforms/core/transform\_dispatcher.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvY29yZS90cmFuc2Zvcm1fZGlzcGF0Y2hlci5weQ==) | 25.00% | [3 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) |
| [pennylane/transforms/core/transform\_program.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvY29yZS90cmFuc2Zvcm1fcHJvZ3JhbS5weQ==) | 25.00% | [3 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4858?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    #4858      +/-   ##
==========================================
- Coverage   99.65%   99.62%   -0.03%     
==========================================
  Files         382      382              
  Lines       34507    34248     -259     
==========================================
- Hits        34389    34121     -268     
- Misses        118      127       +9     
```



</details>

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

AnuravModak: @albi3ro , @rmoyard 
Hey, just a heads up—I've added those changes to the "improvements" section in the changelog.dev. If there's anything else you need or if there are more tasks coming up, just drop me a tag or assign it my way. Happy to tackle multiple things at once!

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index d36497602..2746a3c80 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -104,6 +104,9 @@
 * Added `ops.functions.assert_valid` for checking if an `Operator` class is defined correctly.
   [(#4764)](https://github.com/PennyLaneAI/pennylane/pull/4764)
 
+* Added `__eq__` method for TransformProgram and TransformContainers allowing the comparison of respective objects using `==` and `!=` operators.
+  [(#4858)](https://github.com/PennyLaneAI/pennylane/pull/4858)
+
 * `GlobalPhase` now decomposes to nothing, in case devices do not support global phases.
   [(#4855)](https://github.com/PennyLaneAI/pennylane/pull/4855)
 
diff --git a/pennylane/transforms/core/transform_dispatcher.py b/pennylane/transforms/core/transform_dispatcher.py
index f284f549f..dba4f9578 100644
--- a/pennylane/transforms/core/transform_dispatcher.py
+++ b/pennylane/transforms/core/transform_dispatcher.py
@@ -368,6 +368,18 @@ class TransformContainer:
             )
         )
 
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, TransformContainer):
+            return False
+        return (
+            self.args == other.args
+            and self.transform == other.transform
+            and self.kwargs == other.kwargs
+            and self.classical_cotransform == other.classical_cotransform
+            and self.is_informative == other.is_informative
+            and self.final_transform == other.final_transform
+        )
+
     @property
     def transform(self):
         """The stored quantum transform."""
diff --git a/pennylane/transforms/core/transform_program.py b/pennylane/transforms/core/transform_program.py
index c21037d28..ba175e91d 100644
--- a/pennylane/transforms/core/transform_program.py
+++ b/pennylane/transforms/core/transform_program.py
@@ -155,6 +155,12 @@ class TransformProgram:
         contents = ", ".join(f"{transform_c.transform.__name__}" for transform_c in self)
         return f"TransformProgram({contents})"
 
+    def __eq__(self, other):
+        if not isinstance(other, TransformProgram):
+            return False
+
+        return self._transform_program == other._transform_program
+
     def push_back(self, transform_container: TransformContainer):
         """Add a transform (container) to the end of the program.
 


Test patch:
diff --git a/tests/transforms/test_experimental/test_transform_dispatcher.py b/tests/transforms/test_experimental/test_transform_dispatcher.py
index 548b27631..61a4a0e80 100644
--- a/tests/transforms/test_experimental/test_transform_dispatcher.py
+++ b/tests/transforms/test_experimental/test_transform_dispatcher.py
@@ -17,7 +17,7 @@ from functools import partial
 
 import pytest
 import pennylane as qml
-from pennylane.transforms.core import transform, TransformError
+from pennylane.transforms.core import transform, TransformError, TransformContainer
 
 dev = qml.device("default.qubit", wires=2)
 
@@ -282,6 +282,40 @@ class TestTransformDispatcher:  # pylint: disable=too-many-public-methods
             qnode_circuit.transform_program.pop_front(), qml.transforms.core.TransformContainer
         )
 
+    def test_equality(self):
+        """Tests that we can compare TransformContainer objects with the '==' and '!=' operators."""
+
+        t1 = TransformContainer(
+            qml.transforms.compile.transform, kwargs={"num_passes": 2, "expand_depth": 1}
+        )
+        t2 = TransformContainer(
+            qml.transforms.compile.transform, kwargs={"num_passes": 2, "expand_depth": 1}
+        )
+        t3 = TransformContainer(
+            qml.transforms.transpile.transform, kwargs={"coupling_map": [(0, 1), (1, 2)]}
+        )
+        t4 = TransformContainer(
+            qml.transforms.compile.transform, kwargs={"num_passes": 2, "expand_depth": 2}
+        )
+
+        t5 = TransformContainer(qml.transforms.merge_rotations.transform, args=(1e-6,))
+        t6 = TransformContainer(qml.transforms.merge_rotations.transform, args=(1e-7,))
+
+        # test for equality of identical transformers
+        assert t1 == t2
+
+        # test for inequality of different transformers
+        assert t1 != t3
+        assert t2 != t3
+        assert t1 != 2
+        assert t1 != t4
+        assert t5 != t6
+        assert t5 != t1
+
+        # Test equality with the same args
+        t5_copy = TransformContainer(qml.transforms.merge_rotations.transform, args=(1e-6,))
+        assert t5 == t5_copy
+
     def test_queuing_qfunc_transform(self):
         """Test that queuing works with the transformed quantum function."""
 
diff --git a/tests/transforms/test_experimental/test_transform_program.py b/tests/transforms/test_experimental/test_transform_program.py
index bd0922a6a..4f89fe305 100644
--- a/tests/transforms/test_experimental/test_transform_program.py
+++ b/tests/transforms/test_experimental/test_transform_program.py
@@ -482,6 +482,35 @@ class TestTransformProgram:
         ):
             transform_program.push_back(transform2)
 
+    def test_equality(self):
+        """Tests that we can compare TransformProgram objects with the '==' and '!=' operators."""
+        t1 = TransformContainer(
+            qml.transforms.compile.transform, kwargs={"num_passes": 2, "expand_depth": 1}
+        )
+        t2 = TransformContainer(
+            qml.transforms.compile.transform, kwargs={"num_passes": 2, "expand_depth": 1}
+        )
+        t3 = TransformContainer(
+            qml.transforms.transpile.transform, kwargs={"coupling_map": [(0, 1), (1, 2)]}
+        )
+
+        p1 = TransformProgram([t1, t3])
+        p2 = TransformProgram([t2, t3])
+        p3 = TransformProgram([t3, t2])
+
+        # test for equality of identical objects
+        assert p1 == p2
+        # test for inequality of different objects
+        assert p1 != p3
+        assert p1 != t1
+
+        # Test inequality with different transforms
+        t4 = TransformContainer(
+            qml.transforms.transpile.transform, kwargs={"coupling_map": [(0, 1), (2, 3)]}
+        )
+        p4 = TransformProgram([t1, t4])
+        assert p1 != p4
+
 
 class TestTransformProgramCall:
     """Tests for calling a TransformProgram on a batch of quantum tapes."""