Repository: PennyLaneAI/pennylane
Pull request #4719 (2023-10-24 16:31:21)

Fix `LocalHilbertSchmidt` template
[sc-48521]


When run with a `QNode`, the `LocalHilbertSchmidt` template raises:
```
RuntimeError: No queuing context available to append operation to.
```

The decomposition uses `qml.apply` without checking that something is recording.  This failure was not found because there were no integration tests checking that the template worked in a `QNode`. 

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/4719?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:
> Comparison is base [(`5cd8354`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/5cd8354d218bf4fdfc6b280fc5288badec511308?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.64% compared to head [(`262ef19`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4719?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) 99.64%.

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


```diff
@@               Coverage Diff               @@
##           v0.33.0-rc0    #4719      +/-   ##
===============================================
- Coverage        99.64%   99.64%   -0.01%     
===============================================
  Files              380      380              
  Lines            34245    33985     -260     
===============================================
- Hits             34124    33863     -261     
- Misses             121      122       +1     
```


| [Files](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4719?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/templates/subroutines/hilbert\_schmidt.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4719?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RlbXBsYXRlcy9zdWJyb3V0aW5lcy9oaWxiZXJ0X3NjaG1pZHQucHk=) | `100.00% <100.00%> (ø)` | |

... and [42 files with indirect coverage changes](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4719/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4719?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-0.33.0.md b/doc/releases/changelog-0.33.0.md
index 771ba4c2a..553087421 100644
--- a/doc/releases/changelog-0.33.0.md
+++ b/doc/releases/changelog-0.33.0.md
@@ -537,6 +537,8 @@
 
 <h3>Bug fixes 🐛</h3>
 
+* Fixes `LocalHilbertSchmidt.compute_decomposition` so the template can be used in a qnode.
+
 * Providing `work_wires=None` to `qml.GroverOperator` no longer interprets `None` as a wire.
   [(#4668)](https://github.com/PennyLaneAI/pennylane/pull/4668)
 
diff --git a/pennylane/templates/subroutines/hilbert_schmidt.py b/pennylane/templates/subroutines/hilbert_schmidt.py
index f05920b5e..54e4b456e 100644
--- a/pennylane/templates/subroutines/hilbert_schmidt.py
+++ b/pennylane/templates/subroutines/hilbert_schmidt.py
@@ -248,7 +248,7 @@ class LocalHilbertSchmidt(HilbertSchmidt):
 
         Now that the cost function has been defined it can be called for specific parameters:
 
-        >>> cost_lhst([3*np.pi/2, 3*np.pi/2, np.pi/2], v_function = v_function, v_wires = [1], u_tape = u_tape)
+        >>> cost_lhst([3*np.pi/2, 3*np.pi/2, np.pi/2], v_function = v_function, v_wires = [2,3], u_tape = u_tape)
         0.5
     """
 
@@ -268,9 +268,10 @@ class LocalHilbertSchmidt(HilbertSchmidt):
         )
 
         # Unitary U
-        for op_u in u_tape.operations:
-            qml.apply(op_u)
-            decomp_ops.append(op_u)
+        if qml.QueuingManager.recording():
+            decomp_ops.extend(qml.apply(op_u) for op_u in u_tape.operations)
+        else:
+            decomp_ops.extend(u_tape.operations)
 
         # Unitary V conjugate
         decomp_ops.extend(qml.adjoint(qml.apply, lazy=False)(op_v) for op_v in v_tape.operations)


Test patch:
diff --git a/tests/templates/test_subroutines/test_hilbert_schmidt.py b/tests/templates/test_subroutines/test_hilbert_schmidt.py
index 8bb1ad4bd..9d3975f68 100644
--- a/tests/templates/test_subroutines/test_hilbert_schmidt.py
+++ b/tests/templates/test_subroutines/test_hilbert_schmidt.py
@@ -20,7 +20,8 @@ import pennylane as qml
 
 
 # pylint: disable=protected-access
-def test_flatten_unflatten():
+@pytest.mark.parametrize("op_type", (qml.HilbertSchmidt, qml.LocalHilbertSchmidt))
+def test_flatten_unflatten(op_type):
     """Test the flatten and unflatten methods."""
     u_tape = qml.tape.QuantumScript([qml.Hadamard("a"), qml.Identity("b")])
 
@@ -28,7 +29,7 @@ def test_flatten_unflatten():
         qml.RZ(params, wires=1)
 
     v_wires = qml.wires.Wires((0, 1))
-    op = qml.HilbertSchmidt([0.1], v_function=v_circuit, v_wires=v_wires, u_tape=u_tape)
+    op = op_type([0.1], v_function=v_circuit, v_wires=v_wires, u_tape=u_tape)
     data, metadata = op._flatten()
 
     assert data == (0.1,)
@@ -253,9 +254,15 @@ class TestLocalHilbertSchmidt:
         op = qml.LocalHilbertSchmidt([0.1], v_function=v_circuit, v_wires=[1], u_tape=U)
 
         with qml.queuing.AnnotatedQueue() as q_tape_dec:
-            op.decomposition()
+            decomp = op.decomposition()
+
+        unqueued_decomp = op.decomposition()
 
         tape_dec = qml.tape.QuantumScript.from_queue(q_tape_dec)
+
+        assert all(qml.equal(o1, o2) for o1, o2 in zip(decomp, tape_dec))
+        assert all(qml.equal(o1, o2) for o1, o2 in zip(decomp, unqueued_decomp))
+
         expected_operations = [
             qml.Hadamard(wires=[0]),
             qml.CNOT(wires=[0, 1]),
@@ -328,11 +335,46 @@ class TestLocalHilbertSchmidt:
             qml.CNOT(wires=[0, 2]),
             qml.Hadamard(wires=[0]),
         ]
+        assert tape_dec.operations == expected_operations
 
-        for i, j in zip(tape_dec.operations, expected_operations):
-            assert i.name == j.name
-            assert i.data == j.data
-            assert i.wires == j.wires
+    def test_qnode_integration(self):
+        """Test that the local hilbert schmidt template can be used inside a qnode."""
+
+        u_tape = qml.tape.QuantumTape([qml.CZ(wires=(0, 1))])
+
+        def v_function(params):
+            qml.RZ(params[0], wires=2)
+            qml.RZ(params[1], wires=3)
+            qml.CNOT(wires=[2, 3])
+            qml.RZ(params[2], wires=3)
+            qml.CNOT(wires=[2, 3])
+
+        dev = qml.device("default.qubit", wires=4)
+
+        @qml.qnode(dev)
+        def local_hilbert_test(v_params, v_function, v_wires, u_tape):
+            qml.LocalHilbertSchmidt(v_params, v_function=v_function, v_wires=v_wires, u_tape=u_tape)
+            return qml.probs(u_tape.wires + v_wires)
+
+        # pylint: disable=unsubscriptable-object
+        def cost_lhst(parameters, v_function, v_wires, u_tape):
+            return (
+                1
+                - local_hilbert_test(
+                    v_params=parameters, v_function=v_function, v_wires=v_wires, u_tape=u_tape
+                )[0]
+            )
+
+        res = cost_lhst(
+            [3 * qml.numpy.pi / 2, 3 * qml.numpy.pi / 2, qml.numpy.pi / 2],
+            v_function=v_function,
+            v_wires=[2, 3],
+            u_tape=u_tape,
+        )
+
+        assert qml.math.allclose(res, 0.5)
+        # the answer is currently 0.5, and I'm going to assume that's correct. This test will let us know
+        # if the answer changes.
 
     def test_v_not_quantum_function(self):
         """Test that we cannot pass a non quantum function to the HS operation"""