Repository: PennyLaneAI/pennylane (python)

Issue:
[BUG] `CSWAP` and `CRot` are missing its `control_wires` attribute
`qml.CSWAP` and `qml.CRot`  are both controlled operations, but neither define their `control_wires` attribute.

Pull request description:
Standardize `control_wires` operation attribute
Fixes #1790 

`qml.CSWAP` and `qml.CRot` didn't have their `control_wires` attribute defined, even though they are controlled gates.  Also, the wires of `qml.SWAP` were control wires, even though it is not a controlled gate.

This PR fixes these issues and adds testing for the `control_wires` attribute.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 2da70eacc..75e0884e0 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -12,9 +12,14 @@
 
 <h3>Bug fixes</h3>
 
+* `qml.CSWAP` and `qml.CRot` now define `control_wires`, and `qml.SWAP` 
+  returns the default empty wires object.
+  [(#1830)](https://github.com/PennyLaneAI/pennylane/pull/1830)
+
 <h3>Documentation</h3>
 
 <h3>Contributors</h3>
 
 This release contains contributions from (in alphabetical order):
 
+Christina Lee
diff --git a/pennylane/ops/qubit/non_parametric_ops.py b/pennylane/ops/qubit/non_parametric_ops.py
index 3a210c662..7699de2c2 100644
--- a/pennylane/ops/qubit/non_parametric_ops.py
+++ b/pennylane/ops/qubit/non_parametric_ops.py
@@ -611,10 +611,6 @@ class SWAP(Operation):
     def _controlled(self, wire):
         CSWAP(wires=wire + self.wires)
 
-    @property
-    def control_wires(self):
-        return Wires(self.wires[:2])
-
 
 class ISWAP(Operation):
     r"""ISWAP(wires)
@@ -791,6 +787,10 @@ class CSWAP(Operation):
     def adjoint(self):
         return CSWAP(wires=self.wires)
 
+    @property
+    def control_wires(self):
+        return Wires(self.wires[0])
+
 
 class Toffoli(Operation):
     r"""Toffoli(wires)
diff --git a/pennylane/ops/qubit/parametric_ops.py b/pennylane/ops/qubit/parametric_ops.py
index f4622e202..b8a5ed97d 100644
--- a/pennylane/ops/qubit/parametric_ops.py
+++ b/pennylane/ops/qubit/parametric_ops.py
@@ -1143,6 +1143,10 @@ class CRot(Operation):
         phi, theta, omega = self.parameters
         return CRot(-omega, -theta, -phi, wires=self.wires)
 
+    @property
+    def control_wires(self):
+        return Wires(self.wires[0])
+
 
 class U1(Operation):
     r"""U1(phi)


Test patch:
diff --git a/tests/ops/qubit/test_matrix_ops.py b/tests/ops/qubit/test_matrix_ops.py
index d1b5e5089..96fffe230 100644
--- a/tests/ops/qubit/test_matrix_ops.py
+++ b/tests/ops/qubit/test_matrix_ops.py
@@ -410,3 +410,16 @@ def test_label(op, label):
     assert op.label(decimals=5) == label
     op.inv()
     assert op.label() == label + "⁻¹"
+
+
+control_data = [
+    (qml.QubitUnitary(X, wires=0), Wires([])),
+    (qml.DiagonalQubitUnitary([1, 1], wires=1), Wires([])),
+    (qml.ControlledQubitUnitary(X, control_wires=0, wires=1), Wires([0])),
+]
+
+
+@pytest.mark.parametrize("op, control_wires", control_data)
+def test_control_wires(op, control_wires):
+    """Test ``control_wires`` attribute for matrix operations."""
+    assert op.control_wires == control_wires
diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py
index 82989122f..e5edc74ab 100644
--- a/tests/ops/qubit/test_non_parametric_ops.py
+++ b/tests/ops/qubit/test_non_parametric_ops.py
@@ -738,3 +738,29 @@ def test_label_method(op, label1, label2):
 
     op.inv()
     assert op.label() == label2
+
+
+control_data = [
+    (qml.Hadamard(0), Wires([])),
+    (qml.PauliX(0), Wires([])),
+    (qml.PauliY(0), Wires([])),
+    (qml.S(wires=0), Wires([])),
+    (qml.T(wires=0), Wires([])),
+    (qml.SX(wires=0), Wires([])),
+    (qml.SWAP(wires=(0, 1)), Wires([])),
+    (qml.ISWAP(wires=(0, 1)), Wires([])),
+    (qml.SISWAP(wires=(0, 1)), Wires([])),
+    (qml.CNOT(wires=(0, 1)), Wires(0)),
+    (qml.CZ(wires=(0, 1)), Wires(0)),
+    (qml.CY(wires=(0, 1)), Wires(0)),
+    (qml.CSWAP(wires=(0, 1, 2)), Wires([0])),
+    (qml.Toffoli(wires=(0, 1, 2)), Wires([0, 1])),
+    (qml.MultiControlledX(control_wires=[0, 1, 2, 3], wires=4), Wires([0, 1, 2, 3])),
+]
+
+
+@pytest.mark.parametrize("op, control_wires", control_data)
+def test_control_wires(op, control_wires):
+    """Test ``control_wires`` attribute for non-parametrized operations."""
+
+    assert op.control_wires == control_wires
diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py
index b7dbd31dd..607b53b7a 100644
--- a/tests/ops/qubit/test_parametric_ops.py
+++ b/tests/ops/qubit/test_parametric_ops.py
@@ -1506,3 +1506,32 @@ class TestLabel:
 
         op3 = qml.Rot("x", "y", "z", wires=0)
         assert op3.label(decimals=0) == "Rot\n(x,y,z)"
+
+
+control_data = [
+    (qml.Rot(1, 2, 3, wires=0), Wires([])),
+    (qml.RX(1.23, wires=0), Wires([])),
+    (qml.RY(1.23, wires=0), Wires([])),
+    (qml.MultiRZ(1.234, wires=(0, 1, 2)), Wires([])),
+    (qml.PauliRot(1.234, "IXY", wires=(0, 1, 2)), Wires([])),
+    (qml.PhaseShift(1.234, wires=0), Wires([])),
+    (qml.U1(1.234, wires=0), Wires([])),
+    (qml.U2(1.234, 2.345, wires=0), Wires([])),
+    (qml.U3(1.234, 2.345, 3.456, wires=0), Wires([])),
+    (qml.IsingXX(1.234, wires=(0, 1)), Wires([])),
+    (qml.IsingYY(1.234, wires=(0, 1)), Wires([])),
+    (qml.IsingZZ(1.234, wires=(0, 1)), Wires([])),
+    ### Controlled Ops
+    (qml.ControlledPhaseShift(1.234, wires=(0, 1)), Wires(0)),
+    (qml.CPhase(1.234, wires=(0, 1)), Wires(0)),
+    (qml.CRX(1.234, wires=(0, 1)), Wires(0)),
+    (qml.CRY(1.234, wires=(0, 1)), Wires(0)),
+    (qml.CRZ(1.234, wires=(0, 1)), Wires(0)),
+    (qml.CRot(1.234, 2.2345, 3.456, wires=(0, 1)), Wires(0)),
+]
+
+
+@pytest.mark.parametrize("op, control_wires", control_data)
+def test_control_wires(op, control_wires):
+    """Test the ``control_wires`` attribute for parametrized operations."""
+    assert op.control_wires == control_wires