Repository: PennyLaneAI/pennylane
Pull request #5728 (2024-05-22 21:15:25)

`Controlled.wires` does not contain work wires
**Context:**
`Controlled.wires` contained `work_wires` that are typically used for the decomposition. This was inconsistent with the general usage of the attributes. The expected wires behaviour was in the `active_wires`.

**Description of the Change:**
`active_wires` was renamed to `wires`, and `work_wires` has been removed from the returned wires for `self.wires` 

**Benefits:**
- More consistent behaviour of wiring attributes

[[55898](https://app.shortcut.com/xanaduai/story/55898)]

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 3b03629e1..c995c32d2 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -137,6 +137,10 @@
   returning a list of `QuantumTape`s and a post-processing function instead of simply the transformed circuit.
   [(#5693)](https://github.com/PennyLaneAI/pennylane/pull/5693)
 
+* `Controlled.wires` does not include `self.work_wires` anymore. That can be accessed separately through `Controlled.work_wires`.
+  Consequently, `Controlled.active_wires` has been removed in favour of the more common `Controlled.wires`.
+  [(#5728)](https://github.com/PennyLaneAI/pennylane/pull/5728)
+  
 * `qml.QutritAmplitudeDamping` channel has been added, allowing for noise processes modelled by amplitude damping to be simulated on the `default.qutrit.mixed` device.
   [(#5503)](https://github.com/PennyLaneAI/pennylane/pull/5503)
 
diff --git a/pennylane/drawer/tape_mpl.py b/pennylane/drawer/tape_mpl.py
index e08bba2d3..e113805d5 100644
--- a/pennylane/drawer/tape_mpl.py
+++ b/pennylane/drawer/tape_mpl.py
@@ -114,7 +114,7 @@ def _(op: ops.Toffoli, drawer, layer, _):
 
 @_add_operation_to_drawer.register
 def _(op: ops.MultiControlledX, drawer, layer, _):
-    drawer.CNOT(layer, op.active_wires, control_values=op.control_values)
+    drawer.CNOT(layer, op.wires, control_values=op.control_values)
 
 
 @_add_operation_to_drawer.register
diff --git a/pennylane/ops/op_math/controlled.py b/pennylane/ops/op_math/controlled.py
index 33d23ca4e..7d901db0d 100644
--- a/pennylane/ops/op_math/controlled.py
+++ b/pennylane/ops/op_math/controlled.py
@@ -517,14 +517,9 @@ class Controlled(SymbolicOp):
         """Additional wires that can be used in the decomposition. Not modified by the operation."""
         return self.hyperparameters["work_wires"]
 
-    @property
-    def active_wires(self):
-        """Wires modified by the operator. This is the control wires followed by the target wires."""
-        return self.control_wires + self.target_wires
-
     @property
     def wires(self):
-        return self.control_wires + self.target_wires + self.work_wires
+        return self.control_wires + self.target_wires
 
     def map_wires(self, wire_map: dict):
         new_base = self.base.map_wires(wire_map=wire_map)
@@ -584,9 +579,7 @@ class Controlled(SymbolicOp):
             canonical_matrix = self._compute_matrix_from_base()
 
         wire_order = wire_order or self.wires
-        return qml.math.expand_matrix(
-            canonical_matrix, wires=self.active_wires, wire_order=wire_order
-        )
+        return qml.math.expand_matrix(canonical_matrix, wires=self.wires, wire_order=wire_order)
 
     # pylint: disable=arguments-differ
     def sparse_matrix(self, wire_order=None, format="csr"):
@@ -743,16 +736,16 @@ def _decompose_pauli_x_based_no_control_values(op: Controlled):
     """Decomposes a PauliX-based operation"""
 
     if isinstance(op.base, qml.PauliX) and len(op.control_wires) == 1:
-        return [qml.CNOT(wires=op.active_wires)]
+        return [qml.CNOT(wires=op.wires)]
 
     if isinstance(op.base, qml.PauliX) and len(op.control_wires) == 2:
-        return qml.Toffoli.compute_decomposition(wires=op.active_wires)
+        return qml.Toffoli.compute_decomposition(wires=op.wires)
 
     if isinstance(op.base, qml.CNOT) and len(op.control_wires) == 1:
-        return qml.Toffoli.compute_decomposition(wires=op.active_wires)
+        return qml.Toffoli.compute_decomposition(wires=op.wires)
 
     return qml.MultiControlledX.compute_decomposition(
-        wires=op.active_wires,
+        wires=op.wires,
         work_wires=op.work_wires,
     )
 
@@ -766,7 +759,7 @@ def _decompose_custom_ops(op: Controlled) -> List["operation.Operator"]:
     custom_key = (type(op.base), len(op.control_wires))
     if custom_key in ops_with_custom_ctrl_ops:
         custom_op_cls = ops_with_custom_ctrl_ops[custom_key]
-        return custom_op_cls.compute_decomposition(*op.data, op.active_wires)
+        return custom_op_cls.compute_decomposition(*op.data, op.wires)
     if isinstance(op.base, pauli_x_based_ctrl_ops):
         # has some special case handling of its own for further decomposition
         return _decompose_pauli_x_based_no_control_values(op)
diff --git a/pennylane/ops/op_math/controlled_ops.py b/pennylane/ops/op_math/controlled_ops.py
index 4a6e3c78c..a2c75c8b4 100644
--- a/pennylane/ops/op_math/controlled_ops.py
+++ b/pennylane/ops/op_math/controlled_ops.py
@@ -44,8 +44,7 @@ class ControlledQubitUnitary(ControlledOp):
     * ``control_wires``: wires that act as control for the operation
     * ``control_values``: the state on which to apply the controlled operation (see below)
     * ``target_wires``: the wires the unitary matrix will be applied to
-    * ``active_wires``: Wires modified by the operator. This is the control wires followed
-        by the target wires.
+    * ``work_wires``: wires made use of during the decomposition of the operation into native operations
 
     **Details:**
 
@@ -66,6 +65,8 @@ class ControlledQubitUnitary(ControlledOp):
             control on (default is the all 1s state)
         unitary_check (bool): whether to check whether an array U is unitary when creating the
             operator (default False)
+        work_wires (Union[Wires, Sequence[int], or int]): ancillary wire(s) that may be utilized in during
+            the decomposition of the operator into native operations.
 
     **Example**
 
@@ -1028,7 +1029,7 @@ class MultiControlledX(ControlledOp):
     name = "MultiControlledX"
 
     def _flatten(self):
-        return (), (self.active_wires, tuple(self.control_values), self.work_wires)
+        return (), (self.wires, tuple(self.control_values), self.work_wires)
 
     @classmethod
     def _unflatten(cls, _, metadata):
@@ -1079,11 +1080,13 @@ class MultiControlledX(ControlledOp):
         )
 
     def __repr__(self):
-        return f"MultiControlledX(wires={self.active_wires.tolist()}, control_values={self.control_values})"
+        return (
+            f"MultiControlledX(wires={self.wires.tolist()}, control_values={self.control_values})"
+        )
 
     @property
     def wires(self):
-        return self.active_wires
+        return self.control_wires + self.target_wires
 
     # pylint: disable=unused-argument, arguments-differ
     @staticmethod
@@ -1126,9 +1129,7 @@ class MultiControlledX(ControlledOp):
     def matrix(self, wire_order=None):
         canonical_matrix = self.compute_matrix(self.control_wires, self.control_values)
         wire_order = wire_order or self.wires
-        return qml.math.expand_matrix(
-            canonical_matrix, wires=self.active_wires, wire_order=wire_order
-        )
+        return qml.math.expand_matrix(canonical_matrix, wires=self.wires, wire_order=wire_order)
 
     # pylint: disable=unused-argument, arguments-differ
     @staticmethod
@@ -1189,7 +1190,7 @@ class MultiControlledX(ControlledOp):
         return flips1 + decomp + flips2
 
     def decomposition(self):
-        return self.compute_decomposition(self.active_wires, self.work_wires, self.control_values)
+        return self.compute_decomposition(self.wires, self.work_wires, self.control_values)
 
 
 class CRX(ControlledOp):


Test patch:
diff --git a/tests/ops/op_math/test_controlled.py b/tests/ops/op_math/test_controlled.py
index 2d4234a77..225bfb76b 100644
--- a/tests/ops/op_math/test_controlled.py
+++ b/tests/ops/op_math/test_controlled.py
@@ -132,7 +132,7 @@ class TestControlledInit:
         assert op.base is self.temp_op
         assert op.hyperparameters["base"] is self.temp_op
 
-        assert op.wires == Wires((0, 1, "a", "aux"))
+        assert op.wires == Wires((0, 1, "a"))
 
         assert op.control_wires == Wires((0, 1))
         assert op.hyperparameters["control_wires"] == Wires((0, 1))
@@ -151,7 +151,7 @@ class TestControlledInit:
         assert op.parameters == []  # pylint: disable=use-implicit-booleaness-not-comparison
         assert op.data == ()
 
-        assert op.num_wires == 4
+        assert op.num_wires == 3
 
     def test_default_control_values(self):
         """Test assignment of default control_values."""
@@ -355,7 +355,7 @@ class TestControlledProperties:
         base = qml.IsingXX(1.234, wires=(0, 1))
         op = Controlled(base, (3, 4), work_wires="aux")
 
-        assert op.wires == Wires((3, 4, 0, 1, "aux"))
+        assert op.wires == Wires((3, 4, 0, 1))
 
         op = op.map_wires(wire_map={3: "a", 4: "b", 0: "c", 1: "d", "aux": "extra"})
 
@@ -740,7 +740,7 @@ class TestMatrix:
             work_wires="aux",
         )
         mat = op.matrix()
-        assert mat.shape == (8, 8)
+        assert mat.shape == (4, 4)
 
     def test_wire_order(self):
         """Test that the ``wire_order`` keyword argument alters the matrix as expected."""