Repository: PennyLaneAI/pennylane
Pull request #7011 (2025-02-26 19:08:48)

Fix handling of `cumulative_angles` in `merge_rotations`
**Context:**

For the `Rot` gate, the angles can cancel in a non-trivial way (e.g. Rot(φ,0,-φ) = RZ(φ) RY(0) RZ(-φ) = RZ(0) = I). 

Unlike `single_qubit_fusion`, this was not being handled correctly and only the trivial case where all angles are zero was being checked. 

**Description of the Change:**

- Create a boolean flag `angles_cancel` that determines whether the operator is the identity and can be ignored in the transformed tape.
- Added a test to cover the use case.

**Benefits:** Correct behaviour.

**Possible Drawbacks:** None identified.

**Related GitHub Issues:** Fixes https://github.com/PennyLaneAI/pennylane/issues/7010

[sc-85327]

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.
andrijapau: @albi3ro, @dwierichs

I've deleted the `xfail`d test associated with this issue https://github.com/PennyLaneAI/pennylane/issues/7009 after the discussion that this is expected behaviour for the `merge_rotations` transform. 👍🏼 
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7011?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 [(`09ea76b`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/09ea76bf59b96c6fa69fd2a0a67c5a09773e4d32?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`e101a6a`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/e101a6a7903b4dffc2376ed16a483f4bb722bf68?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 1 commits behind head on master.

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


```diff
@@           Coverage Diff           @@
##           master    #7011   +/-   ##
=======================================
  Coverage   99.60%   99.60%           
=======================================
  Files         484      484           
  Lines       46193    46197    +4     
=======================================
+ Hits        46009    46013    +4     
  Misses        184      184           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7011?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 643eacc8e..df4c80f8a 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -42,6 +42,9 @@
 
 <h3>Improvements 🛠</h3>
 
+* `merge_rotations` now correctly simplifies merged `qml.Rot` operators whose angles yield the identity operator.
+  [(#7011)](https://github.com/PennyLaneAI/pennylane/pull/7011)
+  
 * Bump `rng_salt` to `v0.40.0`.
   [(#6854)](https://github.com/PennyLaneAI/pennylane/pull/6854)
 
diff --git a/pennylane/transforms/optimization/merge_rotations.py b/pennylane/transforms/optimization/merge_rotations.py
index 601e1a704..50e8e0c4c 100644
--- a/pennylane/transforms/optimization/merge_rotations.py
+++ b/pennylane/transforms/optimization/merge_rotations.py
@@ -177,6 +177,7 @@ def merge_rotations(
 
         # We need to use stack to get this to work and be differentiable in all interfaces
         cumulative_angles = qml.math.stack(current_gate.parameters)
+        angles_cancel = False
         interface = qml.math.get_interface(cumulative_angles)
         # As long as there is a valid next gate, check if we can merge the angles
         while next_gate_idx is not None:
@@ -194,9 +195,16 @@ def merge_rotations(
                 # The Rot gate must be treated separately
                 if isinstance(current_gate, qml.Rot):
                     cumulative_angles = fuse_rot_angles(cumulative_angles, next_params)
+                    # For the Rot gate, the angles can cancel in a non-trivial way
+                    # e.g. Rot(φ,0,-φ) = RZ(φ) RY(0) RZ(-φ) = RZ(0) = I.
+                    test_angles = qml.math.stack(
+                        [cumulative_angles[0] + cumulative_angles[2], cumulative_angles[1]]
+                    )
                 # Other, single-parameter rotation gates just have the angle summed
                 else:
                     cumulative_angles = cumulative_angles + next_params
+                    test_angles = cumulative_angles
+                angles_cancel = qml.math.allclose(test_angles, 0.0, atol=atol, rtol=0)
             # If it is not, we need to stop
             else:
                 break
@@ -210,7 +218,7 @@ def merge_rotations(
         if (
             qml.math.is_abstract(cumulative_angles)
             or qml.math.requires_grad(cumulative_angles)
-            or not qml.math.allclose(cumulative_angles, 0.0, atol=atol, rtol=0)
+            or not angles_cancel
         ):
             with QueuingManager.stop_recording():
                 new_operations.append(


Test patch:
diff --git a/tests/transforms/test_optimization/test_merge_rotations.py b/tests/transforms/test_optimization/test_merge_rotations.py
index 4de97e4b9..0cca24b10 100644
--- a/tests/transforms/test_optimization/test_merge_rotations.py
+++ b/tests/transforms/test_optimization/test_merge_rotations.py
@@ -55,6 +55,17 @@ class TestMergeRotations:
             assert op_obtained.name == op_expected.name
             assert np.allclose(op_obtained.parameters, op_expected.parameters)
 
+    def test_rot_gate_cancel(self):
+        """Test that two rotation gates get merged to the identity operator (cancel)."""
+
+        def qfunc():
+            qml.Rot(-1, 0, 1, wires=0)
+            qml.Rot(-1, 0, 1, wires=0)
+
+        transformed_qfunc = merge_rotations(qfunc)
+        ops = qml.tape.make_qscript(transformed_qfunc)().operations
+        assert not ops
+
     @pytest.mark.parametrize(
         ("theta_1", "theta_2", "expected_ops"),
         [