Skip to content

Commit

Permalink
fix has_decomposition for ControlledQubitUnitary (#4407)
Browse files Browse the repository at this point in the history
* fix has_decomposition for ControlledQubitUnitary

* changelog

* add comment; add test when super returns False
  • Loading branch information
timmysilv authored Aug 1, 2023
1 parent 81c8477 commit 46745da
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 2 deletions.
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,10 @@
trainable parameters of the expanded tape.
[(#4365)](https://github.com/PennyLaneAI/pennylane/pull/4365)

* `qml.ControlledQubitUnitary` no longer reports `has_decomposition` as `True` when it does
not really have a decomposition.
[(#4407)](https://github.com/PennyLaneAI/pennylane/pull/4407)

<h3>Contributors ✍️</h3>

This release contains contributions from (in alphabetical order):
Expand Down
13 changes: 13 additions & 0 deletions pennylane/ops/op_math/controlled_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,19 @@ def _controlled(self, wire):
work_wires=self.work_wires,
)

@property
def has_decomposition(self):
if not super().has_decomposition:
return False
with qml.QueuingManager.stop_recording():
# we know this is using try-except as logical control, but are favouring
# certainty in it being correct over explicitness in an edge case.
try:
self.decomposition()
except qml.operation.DecompositionUndefinedError:
return False
return True


class CY(ControlledOp):
r"""CY(wires)
Expand Down
22 changes: 20 additions & 2 deletions tests/ops/op_math/test_controlled_decompositions.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,15 +224,15 @@ def test_zyz_decomp_no_control_values(self, test_expand):
decomp = (
op.expand().expand().circuit if test_expand else op.decomposition()[0].decomposition()
)
expected = qml.ops.ctrl_decomp_zyz(base, (0,))
expected = qml.ops.ctrl_decomp_zyz(base, (0,)) # pylint:disable=no-member
assert equal_list(decomp, expected)

@pytest.mark.xfail
@pytest.mark.parametrize("test_expand", [False, True])
def test_zyz_decomp_control_values(self, test_expand):
"""Test that the ZYZ decomposition is used for single qubit target operations
when other decompositions aren't available and control values are present."""

# pylint:disable=no-member
base = qml.QubitUnitary(
np.array(
[
Expand Down Expand Up @@ -663,3 +663,21 @@ def test_decomposition_matrix(self, op, control_wires, tol):
expected = expected_op.matrix()

assert np.allclose(res, expected, atol=tol, rtol=tol)


def test_ControlledQubitUnitary_has_decomposition_correct():
"""Test that ControlledQubitUnitary reports has_decomposition=False if it is False"""
U = qml.Toffoli(wires=[0, 1, 2]).matrix()
op = qml.ControlledQubitUnitary(U, wires=[1, 2, 3], control_wires=[0])

assert not op.has_decomposition
with pytest.raises(qml.operation.DecompositionUndefinedError):
op.decomposition()


def test_ControlledQubitUnitary_has_decomposition_super_False(mocker):
"""Test that has_decomposition returns False if super() returns False"""
spy = mocker.spy(qml.QueuingManager, "stop_recording")
op = qml.ControlledQubitUnitary(np.diag((1.0,) * 8), wires=[2, 3, 4], control_wires=[0, 1])
assert not op.has_decomposition
spy.assert_not_called()

0 comments on commit 46745da

Please sign in to comment.