Skip to content

Commit

Permalink
Allow tuple-valued control_values with qml.ctrl on a Controlled
Browse files Browse the repository at this point in the history
… instance. (#5725)

**Context:**
By default, `control_values` of `Controlled` instances are lists.
`qml.defer_measurements` passes a tuple as `control_values` to `ctrl`.
`create_controlled_op`, called by `ctrl`, tries to merge
`control_values` of the base operation and the provided
`control_values`.
The consequence is an attempted addition of `list` and `tuple`, if
`qml.defer_measurements` hits a `qml.cond` of a `Controlled` instance,
leading to #5717

**Description of the Change:**
This PR fixes the above problem in two (redundant!) ways: 
- tuples are allowed to be passed as `control_values` to `ctrl`, which
casts them to a `list`.
- `defer_measurements` passes a `list` itself.

Clearly those changes are redundant for the bug #5717, with the former
also providing users with the option of using `tuple`s for
`control_values`. We could skip the change to `defer_measurements`,
thus.

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**
Fixes #5717 

[sc-63645]
  • Loading branch information
dwierichs authored May 24, 2024
1 parent fdca352 commit 85ff62b
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 3 deletions.
6 changes: 6 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

<h3>Improvements 🛠</h3>

* `ctrl` now works with tuple-valued `control_values` when applied to any already controlled operation.
[(#5725)](https://github.com/PennyLaneAI/pennylane/pull/5725)

* Add support for 3 new pytest markers: `unit`, `integration` and `system`.
[(#5517)](https://github.com/PennyLaneAI/pennylane/pull/5517)

Expand Down Expand Up @@ -141,6 +144,9 @@

<h3>Bug fixes 🐛</h3>

* `qml.cond` can now be applied to `ControlledOp` operations when deferring measurements.
[(#5725)](https://github.com/PennyLaneAI/pennylane/pull/5725)

* The legacy `Tensor` class can now handle a `Projector` with abstract tracer input.
[(#5720)](https://github.com/PennyLaneAI/pennylane/pull/5720)

Expand Down
2 changes: 2 additions & 0 deletions pennylane/ops/op_math/controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ def create_controlled_op(op, control, control_values=None, work_wires=None):
control_values = [control_values]
elif control_values is None:
control_values = [True] * len(control)
elif isinstance(control_values, tuple):
control_values = list(control_values)

ctrl_op = _try_wrap_in_custom_ctrl_op(
op, control, control_values=control_values, work_wires=work_wires
Expand Down
11 changes: 11 additions & 0 deletions tests/ops/op_math/test_controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ def test_zero_one_control_values(self):
op = Controlled(self.temp_op, (0, 1), control_values=[0, 1])
assert op.control_values == [False, True]

@pytest.mark.parametrize("control_values", [True, False, 0, 1])
def test_scalar_control_values(self, control_values):
"""Test assignment of provided control_values."""
op = Controlled(self.temp_op, 0, control_values=control_values)
assert op.control_values == [control_values]

def test_tuple_control_values(self):
"""Test assignment of provided control_values."""
op = Controlled(self.temp_op, (0, 1), control_values=(0, 1))
assert op.control_values == [False, True]

def test_non_boolean_control_values(self):
"""Test control values are converted to booleans."""
op = Controlled(self.temp_op, (0, 1, 2), control_values=["", None, 5])
Expand Down
12 changes: 12 additions & 0 deletions tests/ops/op_math/test_controlled_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,3 +737,15 @@ def test_controlled_method(base, cbase):
"""Tests the _controlled method for parametric ops."""
# pylint: disable=protected-access
assert qml.equal(base._controlled("a"), cbase)


@pytest.mark.parametrize(
"control, control_values",
[([0, 1], [True, False]), ([10, "a"], (0, 0)), ([2], 1), (2, (True,))],
)
@pytest.mark.parametrize(
"base_op", [qml.CRX(0.2, [21, 22]), qml.CNOT([21, 22]), qml.CPhase(0.6, [21, 22])]
)
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)
8 changes: 5 additions & 3 deletions tests/transforms/test_defer_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ def qnode():
)
def test_cond_qfunc(self, device):
"""Test that a qfunc can also used with qml.cond."""
dev = qml.device(device, wires=3)
dev = qml.device(device, wires=4)

r = 2.324

Expand All @@ -1074,20 +1074,22 @@ def normal_circuit(rads):
qml.CNOT(wires=[0, 1])
qml.CRY(rads, wires=[0, 1])
qml.CZ(wires=[0, 1])
return qml.probs(wires=1)
qml.ctrl(qml.CRX, control=0, control_values=[1])(0.5, [1, 2])
return qml.probs(wires=[1, 2])

def f(x):
qml.PauliX(1)
qml.RY(x, wires=1)
qml.PauliZ(1)
qml.CRX(0.5, [1, 2])

@qml.defer_measurements
@qml.qnode(dev)
def quantum_control_circuit(r):
qml.Hadamard(0)
m_0 = qml.measure(0)
qml.cond(m_0, f)(r)
return qml.probs(wires=1)
return qml.probs(wires=[1, 2])

exp = normal_circuit(r)
cond_probs = quantum_control_circuit(r)
Expand Down

0 comments on commit 85ff62b

Please sign in to comment.