Repository: PennyLaneAI/pennylane
Pull request #7139 (2025-03-24 16:25:56)

Fix the issue that ctrl a sparse QubitUnitary doesn't work
**Context:**
After implementing the sparse `QubitUnitary`, the `qml.ctrl` over a sparse QU does not work:

```python
>>> op = qml.QubitUnitary(sparse.eye(2), wires=2)
>>> qml.ctrl(op, 3)
MatrixUndefinedError: U is sparse matrix. Use sparse_matrix method instead.
```

**Description of the Change:**
Added a conditional branch in `ctrl`

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


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.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 22382f923..5c1b6936d 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -222,6 +222,7 @@
   
 * `default.qubit` now supports the sparse matrices to be applied to the state vector. Specifically, `QubitUnitary` initialized with a sparse matrix can now be applied to the state vector in the `default.qubit` device.
   [(#6883)](https://github.com/PennyLaneAI/pennylane/pull/6883)
+  [(#7139)](https://github.com/PennyLaneAI/pennylane/pull/7139)
 
 * `merge_rotations` now correctly simplifies merged `qml.Rot` operators whose angles yield the identity operator.
   [(#7011)](https://github.com/PennyLaneAI/pennylane/pull/7011)
diff --git a/pennylane/ops/op_math/controlled.py b/pennylane/ops/op_math/controlled.py
index cbc2025f4..6a51b38a1 100644
--- a/pennylane/ops/op_math/controlled.py
+++ b/pennylane/ops/op_math/controlled.py
@@ -321,7 +321,7 @@ def _try_wrap_in_custom_ctrl_op(op, control, control_values=None, work_wires=Non
     if isinstance(op, qml.QubitUnitary):
         qml.QueuingManager.remove(op)
         return qml.ControlledQubitUnitary(
-            op.matrix(),
+            op.matrix() if op.has_matrix else op.sparse_matrix(),
             wires=control + op.wires,
             control_values=control_values,
             work_wires=work_wires,


Test patch:
diff --git a/tests/ops/op_math/test_controlled.py b/tests/ops/op_math/test_controlled.py
index c31df9693..895e97624 100644
--- a/tests/ops/op_math/test_controlled.py
+++ b/tests/ops/op_math/test_controlled.py
@@ -18,6 +18,7 @@ from functools import partial
 
 import numpy as np
 import pytest
+import scipy as sp
 from gate_data import (
     CCZ,
     CH,
@@ -1705,6 +1706,18 @@ custom_ctrl_ops = [
 class TestCtrl:
     """Tests for the ctrl transform."""
 
+    def test_sparse_qubit_unitary(self):
+        """Test that the controlled sparse QubitUnitary works correctly"""
+        data = sp.sparse.eye(2)
+        op = qml.QubitUnitary(data, wires=2)
+        c_op = qml.ctrl(op, 3)
+
+        data_dense = data.toarray()
+        op_dense = qml.QubitUnitary(data_dense, wires=2)
+        c_op_dense = qml.ctrl(op_dense, 3)
+
+        assert qml.math.allclose(c_op.sparse_matrix(), c_op_dense.matrix())
+
     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: