Skip to content

Commit

Permalink
Fix handling of cumulative_angles in merge_rotations (#7011)
Browse files Browse the repository at this point in the history
**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
#7010

[sc-85327]

---------

Co-authored-by: David Wierichs <[email protected]>
Co-authored-by: Christina Lee <[email protected]>
  • Loading branch information
3 people authored Feb 28, 2025
1 parent a0f6f71 commit 4690439
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 1 deletion.
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 9 additions & 1 deletion pennylane/transforms/optimization/merge_rotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ def stop_at(obj):

# 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:
Expand All @@ -194,9 +195,16 @@ def stop_at(obj):
# 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
Expand All @@ -210,7 +218,7 @@ def stop_at(obj):
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(
Expand Down
11 changes: 11 additions & 0 deletions tests/transforms/test_optimization/test_merge_rotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ def qfunc():
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"),
[
Expand Down

0 comments on commit 4690439

Please sign in to comment.