Repository: PennyLaneAI/pennylane (python)

Issue:
Allow specifying `basis_set` as an empty set to `qml.compile`
We currently have a `basis_set` keyword argument to `qml.compile` that specifies which operations to not decompose.  If an operation is not in the basis set and does not have a decomposition, we leave it be.

We may sometimes want to decompose anything that can potentially be decomposed:

```
@partial(qml.compile, basis_set={})
@qml.qnode(qml.device('default.qubit'))
def circuit():
    qml.U2(0.5, 0.6, 0)
    return qml.expval(qml.Z(0))
```

The above syntax should indicate "as long as it has a decomposition, decompose it".

Right now we interpret `{}` as Falsy, and change the basis set to `qml.ops.__all__`. Instead, we should interpret `{}`, `()`, and `[]` (any non-`None`) object, as an actual basis set. An empty basis set should be a valid basis set.

Pull request:
Allow empty `basis_set` in `qml.compile`
**Context:**
The `basis_set` keyword argument to `qml.compile` allows us to specify which operations not to decompose. Currently, passing an empty `basis_set` practically results in no decomposition, as the basis is set to `qml.ops.__all__`. An empty `basis_set` should be seen as valid by `qml.compile`, indicating that operations should be decomposed as long as they have a decomposition. 

**Description of the Change:**
- Treat `{}`, `()`, and `[]` (any non-`None`) object as a valid basis set.
- This means an empty basis set now triggers full decomposition of operations with known decompositions.

**Benefits:**
- Allows users to explicitly request full decomposition by passing an empty basis set.
- Makes the behavior more intuitive and consistent with user expectations.

**Possible Drawbacks:**
- None identified.

**Related GitHub Issues:**
Closes #6793 

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 963683936..941ea5a86 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -15,6 +15,9 @@
 * The coefficients of observables now have improved differentiability.
   [(#6598)](https://github.com/PennyLaneAI/pennylane/pull/6598)
 
+* An empty basis set in `qml.compile` is now recognized as valid, resulting in decomposition of all operators that can be decomposed. 
+   [(#6821)](https://github.com/PennyLaneAI/pennylane/pull/6821)
+
 * An informative error is raised when a `QNode` with `diff_method=None` is differentiated.
   [(#6770)](https://github.com/PennyLaneAI/pennylane/pull/6770)
 
@@ -52,5 +55,6 @@ This release contains contributions from (in alphabetical order):
 
 Yushao Chen,
 Diksha Dhawan,
+Marcus Gisslén,
 Christina Lee,
 Andrija Paurevic
diff --git a/pennylane/transforms/compile.py b/pennylane/transforms/compile.py
index 8c65052ce..9205ab9d1 100644
--- a/pennylane/transforms/compile.py
+++ b/pennylane/transforms/compile.py
@@ -54,7 +54,9 @@ def compile(
             expansion will continue until gates in the specific set are
             reached. If no basis set is specified, a default of
             ``pennylane.ops.__all__`` will be used. This decomposes templates and
-            operator arithmetic.
+            operator arithmetic. If an empty basis set (e.g. ``[]``, ``()``, or
+            ``{}``) is provided, all operations that can be decomposed will be
+            decomposed.
         num_passes (int): The number of times to apply the set of transforms in
             ``pipeline``. The default is to perform each transform once;
             however, doing so may produce a new circuit where applying the set
@@ -180,7 +182,8 @@ def compile(
     # don't queue anything as a result of the expansion or transform pipeline
 
     with QueuingManager.stop_recording():
-        basis_set = basis_set or all_ops
+        if basis_set is None:
+            basis_set = all_ops
 
         def stop_at(obj):
             if not isinstance(obj, qml.operation.Operator):


Test patch:
diff --git a/tests/transforms/test_compile.py b/tests/transforms/test_compile.py
index 9f9dd3d4c..e8090ebf0 100644
--- a/tests/transforms/test_compile.py
+++ b/tests/transforms/test_compile.py
@@ -201,6 +201,22 @@ class TestCompileIntegration:
         [compiled_tape], _ = qml.compile(tape)
         assert compiled_tape.operations == [qml.PauliX(0), qml.CNOT([0, 1])]
 
+    def test_compile_empty_basis_set(self):
+        """Test that compiling with empty basis set decomposes any decomposable operation."""
+        ops = (
+            qml.RX(0.1, 0),
+            qml.H(1),
+            qml.Barrier([0, 1]),
+            qml.CNOT([1, 0]),
+            qml.PauliY(0),
+            qml.CY([0, 1]),
+        )
+        tape = qml.tape.QuantumScript(ops)
+        decomposable_ops = {op.name for op in tape.operations if op.has_decomposition}
+
+        [transformed_tape], _ = qml.compile(tape, basis_set=[])
+        assert not any(op.name in decomposable_ops for op in transformed_tape.operations)
+
     @pytest.mark.parametrize(("wires"), [["a", "b", "c"], [0, 1, 2], [3, 1, 2], [0, "a", 4]])
     def test_compile_pipeline_with_non_default_arguments(self, wires):
         """Test that using non-default arguments returns the correct results."""