Repository: PennyLaneAI/pennylane
Pull request #6926 (2025-02-04 22:30:16)

Fix double QubitUnitary bug from Catalyst
**Context:**
A recent deprecation https://github.com/PennyLaneAI/pennylane/pull/6840/ deprecated the usage of `QubitUnitary` type input `base` for instantiation of `ControlledQubitUnitary`.
However, a modification that was equivalent in PennyLane caused unexpected behavior in Catalyst, https://github.com/PennyLaneAI/catalyst/issues/1494.
After investigation, it appears to be the issue with the private method `_try_wrap_in_custom_ctrl_op` of `pennylane/ops/op_math/controlled.py`, since it used to call the deprecated `init` to directly use base as `QubitUnitary`, and to safely deprecate it we decided to locally replace with equivalent `ControlledQubitUnitary(base=op.matrix(), ...)`, this caused Catalyst to received double `QubitUnitary`.

It is also noteable that the call `ControlledQubitUnitary` should not appear in this script, since logically all the classes living in `controlled_ops` depend on `controlled.py`, which means that from the very beginning the affected private method served as a tricky hack with cyclic dependence.

Therefore, we would like to replace the `ControlledQubitUnitary` call with a direct `ControlledOp`.

**Description of the Change:**
source files: as mentioned above.

tests: necessary improvements were made to correctly reflect the assertion results.

**Benefits:**
 - Remove bugs
 - Less cyclic dependency

**Possible Drawbacks:**
 - Probably there're more that depend on this method, which might be affected by this fix

**Related GitHub Issues:**
https://github.com/PennyLaneAI/catalyst/issues/1494

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.
JerryChen97: @erick-xanadu we can try to test the affected tmrw.
On my end it looks fixed
![image](https://github.com/user-attachments/assets/e2a35a73-2e71-4f51-bcb2-105011412725)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6926?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.59%. Comparing base [(`8cefc9a`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/8cefc9a7d549b0a1644ff1f719d8f3c6fcf9bf8d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`fd8ee4c`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/fd8ee4cc54b9fda37ef379b72fb6035d4f7b1cd7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 3 commits behind head on master.

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


```diff
@@           Coverage Diff           @@
##           master    #6926   +/-   ##
=======================================
  Coverage   99.59%   99.59%           
=======================================
  Files         480      480           
  Lines       45474    45497   +23     
=======================================
+ Hits        45289    45312   +23     
  Misses        185      185           
```

</details>

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

erick-xanadu: @JerryChen97, yes I also tested it and it works! Thanks! I'm just running Catalyst tests to see if anything else breaks but I don't think so. I would also like to spend a little bit of time see if we can fix this in Catalyst. But it is good to know that there is a fix that solves it.
JerryChen97: Do not merge until concerns were cleared
albi3ro: > Can we just add it to `ops_with_custom_ctrl_op` and remove the special `QubitUnitary` logic? Looks to like it it should be able to fit that mold now.

Never mind, I just realized `ControlledQubitUnitary` allows any number of control wires, so it does not fit the mold.
JerryChen97: Added full test suite to see if we broke any other pipelines
rashidnhm: Hello, from a quick pull request filter, it seems this is currently the only PR that needs gpu test suite run on it. 

I have changed the gpu test suite as part of #6927 . Please pull in latest master to ensure the gpu tests continue to be run.

I will be disabling the existing gpu runners shortly.
JerryChen97: > Hello, from a quick pull request filter, it seems this is currently the only PR that needs gpu test suite run on it.
> 
> I have changed the gpu test suite as part of #6927 . Please pull in latest master to ensure the gpu tests continue to be run.
> 
> I will be disabling the existing gpu runners shortly.

No wry! After confirming nothing else broken we don't need GPU tests any more. I'll move labels
JerryChen97: > Can we also add a test to make sure the bug is really fixed? Maybe something like:
> 
> ```
> with qml.queuing.AnnotatedQueue() as q:
>     qml.ctrl(qml.QubitUnitary(np.eye(2), 0), 1)
> 
> assert len(q.queue) == 1
> ```

@albi3ro Done!

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 14a792a77..66b91b496 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -192,7 +192,9 @@
 <h3>Deprecations 👋</h3>
 
 * The ``ControlledQubitUnitary`` will stop accepting `QubitUnitary` objects as arguments as its ``base``. Instead, use ``qml.ctrl`` to construct a controlled `QubitUnitary`.
+  A folllow-on PR fixed accidental double-queuing when using `qml.ctrl` with `QubitUnitary`.
   [(#6840)](https://github.com/PennyLaneAI/pennylane/pull/6840)
+  [(#6926)](https://github.com/PennyLaneAI/pennylane/pull/6926)
 
 * The `control_wires` argument in `qml.ControlledQubitUnitary` has been deprecated.
   Instead, use the `wires` argument as the second positional argument.
diff --git a/pennylane/ops/op_math/controlled.py b/pennylane/ops/op_math/controlled.py
index b690ff11c..b3221a689 100644
--- a/pennylane/ops/op_math/controlled.py
+++ b/pennylane/ops/op_math/controlled.py
@@ -14,6 +14,7 @@
 """
 This submodule defines the symbolic operation that indicates the control of an operator.
 """
+# pylint: disable=too-many-positional-arguments
 import functools
 import warnings
 from collections.abc import Callable, Sequence
@@ -338,6 +339,7 @@ def _try_wrap_in_custom_ctrl_op(op, control, control_values=None, work_wires=Non
         return ops_with_custom_ctrl_ops[custom_key](*op.data, control + op.wires)
 
     if isinstance(op, qml.QubitUnitary):
+        qml.QueuingManager.remove(op)
         return qml.ControlledQubitUnitary(
             op.matrix(),
             wires=control + op.wires,


Test patch:
diff --git a/tests/ops/op_math/test_controlled.py b/tests/ops/op_math/test_controlled.py
index d647815e8..d016ffc29 100644
--- a/tests/ops/op_math/test_controlled.py
+++ b/tests/ops/op_math/test_controlled.py
@@ -1671,6 +1671,13 @@ custom_ctrl_ops = [
 class TestCtrl:
     """Tests for the ctrl transform."""
 
+    def test_no_redundant_queue(self):
+        """Test that the ctrl transform does not add redundant operations to the queue. https://github.com/PennyLaneAI/pennylane/pull/6926"""
+        with qml.queuing.AnnotatedQueue() as q:
+            qml.ctrl(qml.QubitUnitary(np.eye(2), 0), 1)
+
+        assert len(q.queue) == 1
+
     def test_invalid_input_error(self):
         """Test that a ValueError is raised upon invalid inputs."""
         with pytest.raises(ValueError, match=r"<class 'int'> is not an Operator or callable."):