Repository: PennyLaneAI/pennylane
Pull request #5856 (2024-06-14 14:21:09)

Fix wire handling on special control wires
**Context:**

A user was doing `qml.CRY(0.1, wires=((0,1), 2)`.  In this case, the control wire should be a single wire with a value `(0,1)`.  Yes, this is weird, but this is how wires work in pennylane.  Instead, it was being interpretted as having two control wires, even though `CRY` is only for singly-controlled `RY` gates.

**Description of the Change:**

Some minor slicing changes in how we handle special controlled ops.  

Now we have:
```
>>> qml.CZ(wires=((0,1), 2))
CZ(wires=[(0, 1), 2])
```

Which is consistent with how we treat wires elsewhere in pennylane.

**Benefits:**

Don't end up with a two qubit gate on three qubits.

**Possible Drawbacks:**

Still weird, and still not what the user was looking for when they provided wires like that.  But at least now they'll get a better error.

**Related GitHub Issues:**


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5856?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.67%. Comparing base [(`9b3060f`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/9b3060fd56e1e65a0d69a27b8a13583ce716a538?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`61a0c85`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/61a0c85f05315daf8b2058055dfcc7d3d45321fe?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


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


```diff
@@            Coverage Diff             @@
##           master    #5856      +/-   ##
==========================================
- Coverage   99.67%   99.67%   -0.01%     
==========================================
  Files         421      421              
  Lines       40189    39890     -299     
==========================================
- Hits        40060    39760     -300     
- Misses        129      130       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5856?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 847bcf3d6..11e431fa3 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -341,6 +341,9 @@
 
 <h3>Bug fixes 🐛</h3>
 
+* Fixes a bug in the wire handling on special controlled ops.
+  [(#5856)](https://github.com/PennyLaneAI/pennylane/pull/5856)
+
 * Fixes a bug where `Sum`'s with repeated identical operations ended up with the same hash as
   `Sum`'s with different numbers of repeats.
   [(#5851)](https://github.com/PennyLaneAI/pennylane/pull/5851)
diff --git a/pennylane/ops/op_math/controlled_ops.py b/pennylane/ops/op_math/controlled_ops.py
index c1953ba6d..c3a052087 100644
--- a/pennylane/ops/op_math/controlled_ops.py
+++ b/pennylane/ops/op_math/controlled_ops.py
@@ -329,8 +329,7 @@ class CY(ControlledOp):
         return cls._primitive.bind(*wires, n_wires=2)
 
     def __init__(self, wires, id=None):
-        control_wire, wire = wires
-        super().__init__(qml.Y(wire), control_wire, id=id)
+        super().__init__(qml.Y(wires[1:]), wires[:1], id=id)
 
     def __repr__(self):
         return f"CY(wires={self.wires.tolist()})"
@@ -436,8 +435,7 @@ class CZ(ControlledOp):
         return cls._primitive.bind(*wires, n_wires=2)
 
     def __init__(self, wires, id=None):
-        control_wire, wire = wires
-        super().__init__(qml.Z(wires=wire), control_wire, id=id)
+        super().__init__(qml.Z(wires=wires[1:]), wires[:1], id=id)
 
     def __repr__(self):
         return f"CZ(wires={self.wires.tolist()})"
@@ -791,8 +789,7 @@ class CNOT(ControlledOp):
         return cls._primitive.bind(*wires, n_wires=2)
 
     def __init__(self, wires, id=None):
-        control_wire, wire = wires
-        super().__init__(qml.PauliX(wires=wire), control_wire, id=id)
+        super().__init__(qml.PauliX(wires=wires[1:]), wires[:1], id=id)
 
     def __repr__(self):
         return f"CNOT(wires={self.wires.tolist()})"
@@ -1267,7 +1264,7 @@ class CRX(ControlledOp):
     parameter_frequencies = [(0.5, 1.0)]
 
     def __init__(self, phi, wires, id=None):
-        super().__init__(qml.RX(phi, wires=wires[1:]), control_wires=wires[0], id=id)
+        super().__init__(qml.RX(phi, wires=wires[1:]), control_wires=wires[:1], id=id)
 
     def __repr__(self):
         return f"CRX({self.data[0]}, wires={self.wires.tolist()})"
@@ -1420,7 +1417,7 @@ class CRY(ControlledOp):
     parameter_frequencies = [(0.5, 1.0)]
 
     def __init__(self, phi, wires, id=None):
-        super().__init__(qml.RY(phi, wires=wires[1:]), control_wires=wires[0], id=id)
+        super().__init__(qml.RY(phi, wires=wires[1:]), control_wires=wires[:1], id=id)
 
     def __repr__(self):
         return f"CRY({self.data[0]}, wires={self.wires.tolist()}))"
@@ -1573,7 +1570,7 @@ class CRZ(ControlledOp):
     parameter_frequencies = [(0.5, 1.0)]
 
     def __init__(self, phi, wires, id=None):
-        super().__init__(qml.RZ(phi, wires=wires[1:]), control_wires=wires[0], id=id)
+        super().__init__(qml.RZ(phi, wires=wires[1:]), control_wires=wires[:1], id=id)
 
     def __repr__(self):
         return f"CRZ({self.data[0]}, wires={self.wires})"
@@ -1758,7 +1755,9 @@ class CRot(ControlledOp):
     parameter_frequencies = [(0.5, 1.0), (0.5, 1.0), (0.5, 1.0)]
 
     def __init__(self, phi, theta, omega, wires, id=None):  # pylint: disable=too-many-arguments
-        super().__init__(qml.Rot(phi, theta, omega, wires=wires[1]), control_wires=wires[0], id=id)
+        super().__init__(
+            qml.Rot(phi, theta, omega, wires=wires[1:]), control_wires=wires[:1], id=id
+        )
 
     def __repr__(self):
         params = ", ".join([repr(p) for p in self.parameters])
@@ -1924,7 +1923,7 @@ class ControlledPhaseShift(ControlledOp):
     parameter_frequencies = [(1,)]
 
     def __init__(self, phi, wires, id=None):
-        super().__init__(qml.PhaseShift(phi, wires=wires[1:]), control_wires=wires[0], id=id)
+        super().__init__(qml.PhaseShift(phi, wires=wires[1:]), control_wires=wires[:1], id=id)
 
     def __repr__(self):
         return f"ControlledPhaseShift({self.data[0]}, wires={self.wires})"


Test patch:
diff --git a/tests/ops/op_math/test_controlled_ops.py b/tests/ops/op_math/test_controlled_ops.py
index 415502e96..9719f14e3 100644
--- a/tests/ops/op_math/test_controlled_ops.py
+++ b/tests/ops/op_math/test_controlled_ops.py
@@ -749,3 +749,17 @@ def test_controlled_method(base, cbase):
 def test_controlling_a_controlled_operation(control, control_values, base_op):
     """Test that a controlled op can be controlled again."""
     qml.ctrl(base_op, control=control, control_values=control_values)
+
+
+@pytest.mark.parametrize("op_type", (qml.CH, qml.CY, qml.CZ, qml.CNOT))
+def test_tuple_control_wires_non_parametric_ops(op_type):
+    """Test that tuples can be provided as control wire labels."""
+
+    assert op_type([(0, 1), 2]).wires == qml.wires.Wires([(0, 1), 2])
+
+
+@pytest.mark.parametrize("op_type", (qml.CRX, qml.CRY, qml.CRZ, qml.CPhase))
+def test_tuple_control_wires_parametric_ops(op_type):
+    """Test that tuples can be provided as control wire labels."""
+
+    assert op_type(0.123, [(0, 1), 2]).wires == qml.wires.Wires([(0, 1), 2])