From 46745da8f78dcef4c8e8e8f4a4d41e66d87371eb Mon Sep 17 00:00:00 2001 From: Matthew Silverman Date: Tue, 1 Aug 2023 12:26:06 -0400 Subject: [PATCH] fix `has_decomposition` for ControlledQubitUnitary (#4407) * fix has_decomposition for ControlledQubitUnitary * changelog * add comment; add test when super returns False --- doc/releases/changelog-dev.md | 4 ++++ pennylane/ops/op_math/controlled_ops.py | 13 +++++++++++ .../op_math/test_controlled_decompositions.py | 22 +++++++++++++++++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 5dc84ff6a3e..3aaf6183a07 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -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) +

Contributors ✍️

This release contains contributions from (in alphabetical order): diff --git a/pennylane/ops/op_math/controlled_ops.py b/pennylane/ops/op_math/controlled_ops.py index 2e8fdffa48f..b9b8c601f09 100644 --- a/pennylane/ops/op_math/controlled_ops.py +++ b/pennylane/ops/op_math/controlled_ops.py @@ -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) diff --git a/tests/ops/op_math/test_controlled_decompositions.py b/tests/ops/op_math/test_controlled_decompositions.py index 4c92f8d72bf..907d73b03ac 100644 --- a/tests/ops/op_math/test_controlled_decompositions.py +++ b/tests/ops/op_math/test_controlled_decompositions.py @@ -224,7 +224,7 @@ 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 @@ -232,7 +232,7 @@ def test_zyz_decomp_no_control_values(self, test_expand): 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( [ @@ -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()