Repository: PennyLaneAI/pennylane
Pull request #6720 (2024-12-13 18:48:41)

Fix possibility of falsy zero for `work_wires` in templates
**Context:**

Follow up to #6713. 

Previously, `work_wires=0` was incorrectly being mapped to an empty list,
```python
>>> qml.Adder(1, x_wires=(), mod=1, work_wires=0)
TypeError: object of type 'int' has no len()
```

Now, we have it properly handled,
```python
>>> qml.Adder(1, x_wires=(), mod=1, work_wires=0)
Adder(wires=[0])
>>> qml.Adder(1, x_wires=(), mod=1, work_wires=None)
ValueError: Adder: wrong number of wires. At least one wire has to be given.
```

**Description of the Change:**

**Source Code**

Change the faulty logic of `work_wires = work_wires or ()` which will fail if `work_wires=0`.

**Test Suite**

Added tests to templates that allow single work wires, ensuring `work_wires=0` can be passed.

**Benefits:** Fixes possibility of falsy zero logic in `work_wires`.

**Possible Drawbacks:** None.

[[sc-80454](https://app.shortcut.com/xanaduai/story/80454)]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6720?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.61%. Comparing base [(`41cb22a`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/41cb22a8d21785cf2762f7ed3c3cd9103e2e7cdb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`f8929d1`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/f8929d1b522e6e4a0907e48645e461e72a5612c4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 1 commits behind head on master.

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


```diff
@@           Coverage Diff           @@
##           master    #6720   +/-   ##
=======================================
  Coverage   99.61%   99.61%           
=======================================
  Files         472      473    +1     
  Lines       44312    44347   +35     
=======================================
+ Hits        44143    44178   +35     
  Misses        169      169           
```

</details>

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


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 1e90b2392..45b66490b 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -494,6 +494,7 @@ same information.
 
 * The `Wires` object throws a `TypeError` if `wires=None`. 
   [(#6713)](https://github.com/PennyLaneAI/pennylane/pull/6713)
+  [(#6720)](https://github.com/PennyLaneAI/pennylane/pull/6720)
 
 * The `qml.Hermitian` class no longer checks that the provided matrix is hermitian.
   The reason for this removal is to allow for faster execution and avoid incompatibilities with `jax.jit`.
diff --git a/pennylane/ops/qubit/state_preparation.py b/pennylane/ops/qubit/state_preparation.py
index 04a4ac8cc..d51a4f55f 100644
--- a/pennylane/ops/qubit/state_preparation.py
+++ b/pennylane/ops/qubit/state_preparation.py
@@ -71,8 +71,9 @@ class BasisState(StatePrepBase):
     [0.+0.j 0.+0.j 0.+0.j 1.+0.j]
     """
 
-    def __init__(self, state, wires, id=None):
+    def __init__(self, state, wires: WiresLike, id=None):
 
+        wires = Wires(wires)
         if isinstance(state, list):
             state = qml.math.stack(state)
 
@@ -86,7 +87,6 @@ class BasisState(StatePrepBase):
             bin = 2 ** math.arange(len(wires))[::-1]
             state = qml.math.where((state & bin) > 0, 1, 0)
 
-        wires = Wires(wires)
         shape = qml.math.shape(state)
 
         if len(shape) != 1:
diff --git a/pennylane/templates/subroutines/adder.py b/pennylane/templates/subroutines/adder.py
index 5b1951876..ade18bdad 100644
--- a/pennylane/templates/subroutines/adder.py
+++ b/pennylane/templates/subroutines/adder.py
@@ -105,8 +105,7 @@ class Adder(Operation):
     ):  # pylint: disable=too-many-arguments
 
         x_wires = qml.wires.Wires(x_wires)
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
 
         num_works_wires = len(work_wires)
 
@@ -125,15 +124,11 @@ class Adder(Operation):
                 f"with len(x_wires)={len(x_wires)} is {2 ** len(x_wires)}, but received {mod}."
             )
 
-        all_wires = (
-            qml.wires.Wires(x_wires) + qml.wires.Wires(work_wires)
-            if work_wires
-            else qml.wires.Wires(x_wires)
-        )
+        all_wires = x_wires + work_wires
 
         self.hyperparameters["k"] = k
         self.hyperparameters["mod"] = mod
-        self.hyperparameters["work_wires"] = qml.wires.Wires(work_wires)
+        self.hyperparameters["work_wires"] = work_wires
         self.hyperparameters["x_wires"] = x_wires
 
         super().__init__(wires=all_wires, id=id)
diff --git a/pennylane/templates/subroutines/mod_exp.py b/pennylane/templates/subroutines/mod_exp.py
index a77c4f660..103e22a91 100644
--- a/pennylane/templates/subroutines/mod_exp.py
+++ b/pennylane/templates/subroutines/mod_exp.py
@@ -117,8 +117,8 @@ class ModExp(Operation):
     ):  # pylint: disable=too-many-arguments
 
         output_wires = qml.wires.Wires(output_wires)
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
+
         if len(work_wires) == 0:
             raise ValueError("Work wires must be specified for ModExp")
 
diff --git a/pennylane/templates/subroutines/multiplier.py b/pennylane/templates/subroutines/multiplier.py
index 5175a11d9..1ebe6dde7 100644
--- a/pennylane/templates/subroutines/multiplier.py
+++ b/pennylane/templates/subroutines/multiplier.py
@@ -122,8 +122,7 @@ class Multiplier(Operation):
     ):  # pylint: disable=too-many-arguments
 
         x_wires = qml.wires.Wires(x_wires)
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
         if len(work_wires) == 0:
             raise ValueError("Work wires must be specified for Multiplier")
 
@@ -148,9 +147,9 @@ class Multiplier(Operation):
 
         self.hyperparameters["k"] = k
         self.hyperparameters["mod"] = mod
-        self.hyperparameters["work_wires"] = qml.wires.Wires(work_wires)
-        self.hyperparameters["x_wires"] = qml.wires.Wires(x_wires)
-        all_wires = qml.wires.Wires(x_wires) + qml.wires.Wires(work_wires)
+        self.hyperparameters["work_wires"] = work_wires
+        self.hyperparameters["x_wires"] = x_wires
+        all_wires = x_wires + work_wires
         super().__init__(wires=all_wires, id=id)
 
     @property
diff --git a/pennylane/templates/subroutines/out_adder.py b/pennylane/templates/subroutines/out_adder.py
index 1c0094abc..edf091572 100644
--- a/pennylane/templates/subroutines/out_adder.py
+++ b/pennylane/templates/subroutines/out_adder.py
@@ -151,8 +151,7 @@ class OutAdder(Operation):
         x_wires = qml.wires.Wires(x_wires)
         y_wires = qml.wires.Wires(y_wires)
         output_wires = qml.wires.Wires(output_wires)
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
 
         num_work_wires = len(work_wires)
 
diff --git a/pennylane/templates/subroutines/out_multiplier.py b/pennylane/templates/subroutines/out_multiplier.py
index 9147eadf0..58ecc1c61 100644
--- a/pennylane/templates/subroutines/out_multiplier.py
+++ b/pennylane/templates/subroutines/out_multiplier.py
@@ -152,8 +152,7 @@ class OutMultiplier(Operation):
         x_wires = qml.wires.Wires(x_wires)
         y_wires = qml.wires.Wires(y_wires)
         output_wires = qml.wires.Wires(output_wires)
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
 
         num_work_wires = len(work_wires)
 
diff --git a/pennylane/templates/subroutines/out_poly.py b/pennylane/templates/subroutines/out_poly.py
index 16ae661ea..4472bfb49 100644
--- a/pennylane/templates/subroutines/out_poly.py
+++ b/pennylane/templates/subroutines/out_poly.py
@@ -273,8 +273,7 @@ class OutPoly(Operation):
 
         registers_wires = [*input_registers, output_wires]
 
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
         num_work_wires = len(work_wires)
         if mod is None:
             mod = 2 ** len(registers_wires[-1])
@@ -302,7 +301,7 @@ class OutPoly(Operation):
 
         self.hyperparameters["polynomial_function"] = polynomial_function
         self.hyperparameters["mod"] = mod
-        self.hyperparameters["work_wires"] = qml.wires.Wires(work_wires)
+        self.hyperparameters["work_wires"] = work_wires
 
         wires_vars = [len(w) for w in registers_wires[:-1]]
 
diff --git a/pennylane/templates/subroutines/phase_adder.py b/pennylane/templates/subroutines/phase_adder.py
index 8dc8ae32e..722dfad97 100644
--- a/pennylane/templates/subroutines/phase_adder.py
+++ b/pennylane/templates/subroutines/phase_adder.py
@@ -127,8 +127,7 @@ class PhaseAdder(Operation):
         self, k, x_wires: WiresLike, mod=None, work_wire: WiresLike = (), id=None
     ):  # pylint: disable=too-many-arguments
 
-        work_wire = work_wire or ()
-        work_wire = qml.wires.Wires(work_wire)
+        work_wire = qml.wires.Wires(() if work_wire is None else work_wire)
         x_wires = qml.wires.Wires(x_wires)
 
         num_work_wires = len(work_wire)
diff --git a/pennylane/templates/subroutines/qrom.py b/pennylane/templates/subroutines/qrom.py
index 700cae206..1c667be74 100644
--- a/pennylane/templates/subroutines/qrom.py
+++ b/pennylane/templates/subroutines/qrom.py
@@ -117,8 +117,7 @@ class QROM(Operation):
         control_wires = qml.wires.Wires(control_wires)
         target_wires = qml.wires.Wires(target_wires)
 
-        work_wires = work_wires or ()
-        work_wires = qml.wires.Wires(work_wires)
+        work_wires = qml.wires.Wires(() if work_wires is None else work_wires)
 
         self.hyperparameters["bitstrings"] = tuple(bitstrings)
         self.hyperparameters["control_wires"] = control_wires


Test patch:
diff --git a/tests/templates/test_subroutines/test_phase_adder.py b/tests/templates/test_subroutines/test_phase_adder.py
index 96b462949..ee30e530c 100644
--- a/tests/templates/test_subroutines/test_phase_adder.py
+++ b/tests/templates/test_subroutines/test_phase_adder.py
@@ -32,6 +32,16 @@ def test_standard_validity_Phase_Adder():
     qml.ops.functions.assert_valid(op)
 
 
+def test_falsy_zero_as_work_wire():
+    """Test that work wire is not treated as a falsy zero."""
+    k = 6
+    mod = 11
+    x_wires = [1, 2, 3, 4]
+    work_wire = 0
+    op = qml.PhaseAdder(k, x_wires=x_wires, mod=mod, work_wire=work_wire)
+    qml.ops.functions.assert_valid(op)
+
+
 def test_add_k_fourier():
     """Test the private _add_k_fourier function."""
 
diff --git a/tests/templates/test_subroutines/test_qrom.py b/tests/templates/test_subroutines/test_qrom.py
index fc0ebe8f6..4482c5647 100644
--- a/tests/templates/test_subroutines/test_qrom.py
+++ b/tests/templates/test_subroutines/test_qrom.py
@@ -29,6 +29,12 @@ def test_assert_valid_qrom():
     qml.ops.functions.assert_valid(op)
 
 
+def test_falsy_zero_as_work_wire():
+    """Test that work wire is not treated as a falsy zero."""
+    op = qml.QROM(["1", "0", "0", "1"], control_wires=[1, 2], target_wires=[3], work_wires=0)
+    qml.ops.functions.assert_valid(op)
+
+
 class TestQROM:
     """Test the qml.QROM template."""