Repository: PennyLaneAI/pennylane
Pull request #4948 (2023-12-13 22:18:34)

`SymbolicOp`s are queued correctly with `qml.cond`
**Context:**
Any function that is conditioned with `qml.cond` and takes operators as arguments was being queued incorrectly. This is especially problematic for op_math functions, which generally allow operators as input, not just callables.

**Description of the Change:**
Added check to remove any operators from the queue that are present in the arguments to the conditioned function.

I initially thought about also checking for lists of operators, but that seems like a rabbit hole (why not also check for lists of lists of lists ... of operators). I also don't want to encourage users to create their own qfuncs that accept operators. This bug only exists because we have pennylane functions that accept operators, but creating qfuncs that accept operators will cause problems with queuing even outside `qml.cond` and should be avoided.

**Benefits:**
`op_math` functions work correctly with `qml.cond`.

**Possible Drawbacks:**

**Related GitHub Issues:**
#4822 

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.
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4948?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: `2 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`18608ef`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/18608ef0035b9b566a58f6dabd13fa2bde1c815f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.66% compared to head [(`62f67f5`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4948?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.66%.
> Report is 5 commits behind head on master.

| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4948?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/ops/op\_math/condition.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4948?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2NvbmRpdGlvbi5weQ==) | 66.66% | [2 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4948?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    #4948      +/-   ##
==========================================
- Coverage   99.66%   99.66%   -0.01%     
==========================================
  Files         388      388              
  Lines       35379    35155     -224     
==========================================
- Hits        35262    35036     -226     
- Misses        117      119       +2     
```



</details>

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

mudit2812: [sc-49921]

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index cc5365769..f18c9d4f4 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -548,6 +548,9 @@
 
 <h3>Bug fixes 🐛</h3>
 
+* `qml.cond` no longer incorrectly queues operators used as qfunc arguments.
+  [(#4948)](https://github.com/PennyLaneAI/pennylane/pull/4948)
+
 * `Attribute` objects now return `False` instead of raising a `TypeError` when checking if an object is inside
   the set.
   [(#4933)](https://github.com/PennyLaneAI/pennylane/pull/4933)
diff --git a/pennylane/ops/op_math/condition.py b/pennylane/ops/op_math/condition.py
index f78d9133b..fac518502 100644
--- a/pennylane/ops/op_math/condition.py
+++ b/pennylane/ops/op_math/condition.py
@@ -17,7 +17,8 @@ Contains the condition transform.
 from functools import wraps
 from typing import Type
 
-from pennylane.operation import AnyWires, Operation
+from pennylane import QueuingManager
+from pennylane.operation import AnyWires, Operation, Operator
 from pennylane.tape import make_qscript
 from pennylane.compiler import compiler
 
@@ -91,9 +92,9 @@ def cond(condition, true_fn, false_fn=None, elifs=()):
     Args:
         condition (.MeasurementValue): a conditional expression involving a mid-circuit
            measurement value (see :func:`.pennylane.measure`)
-        true_fn (callable): The quantum function of PennyLane operation to
+        true_fn (callable): The quantum function or PennyLane operation to
             apply if ``condition`` is ``True``
-        false_fn (callable): The quantum function of PennyLane operation to
+        false_fn (callable): The quantum function or PennyLane operation to
             apply if ``condition`` is ``False``
         elifs (List(Tuple(bool, callable))): A list of (bool, elif_fn) clauses. Can only
             be used when is decorated by :func:`~.qjit`.
@@ -333,6 +334,17 @@ def cond(condition, true_fn, false_fn=None, elifs=()):
         def wrapper(*args, **kwargs):
             # We assume that the callable is a quantum function
 
+            recorded_ops = [a for a in args if isinstance(a, Operator)] + [
+                k for k in kwargs.values() if isinstance(k, Operator)
+            ]
+
+            # This will dequeue all operators passed in as arguments to the qfunc that is
+            # being conditioned. These are queued incorrectly due to be fully constructed
+            # before the wrapper function is called.
+            if recorded_ops and QueuingManager.recording():
+                for op in recorded_ops:
+                    QueuingManager.remove(op)
+
             # 1. Apply true_fn conditionally
             qscript = make_qscript(true_fn)(*args, **kwargs)
 


Test patch:
diff --git a/tests/ops/op_math/test_condition.py b/tests/ops/op_math/test_condition.py
index d8a88da63..ef895866e 100644
--- a/tests/ops/op_math/test_condition.py
+++ b/tests/ops/op_math/test_condition.py
@@ -319,3 +319,35 @@ class TestOtherTransforms:
 
         assert len(tape.measurements) == 1
         assert tape.measurements[0] is terminal_measurement
+
+    @pytest.mark.parametrize(
+        "op_fn, fn_additional_args",
+        [
+            (qml.adjoint, ()),
+            (qml.ctrl, ([1, 2],)),
+            (qml.simplify, ()),
+            (qml.evolve, (1.5,)),
+            (qml.exp, (1.5,)),
+            (qml.pow, (3,)),
+            (qml.prod, (qml.prod(qml.PauliX(1), qml.PauliZ(1)),)),
+        ],
+    )
+    def test_ops_as_args(self, op_fn, fn_additional_args, terminal_measurement):
+        """Test that operations given are arguments to a conditioned function are not queued."""
+
+        # Need to construct now so that id is not random
+        mp = qml.measurements.MidMeasureMP(0, id="foo")
+        mv = qml.measurements.MeasurementValue([mp], lambda v: v)
+
+        def circuit():
+            qml.Hadamard(0)
+            qml.apply(mp)
+            qml.cond(mv, op_fn)(qml.T(0), *fn_additional_args)
+            return qml.apply(terminal_measurement)
+
+        tape = qml.tape.make_qscript(circuit)()
+        assert len(tape) == 4
+        assert tape[0] == qml.Hadamard(0)
+        assert tape[1] == mp
+        assert isinstance(tape[2], qml.ops.Conditional)
+        assert tape[3] == terminal_measurement