diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 10d9c3c0ec3..4411b25e69c 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -102,15 +102,19 @@

Bug fixes 🐛

+* Use vanilla NumPy arrays in `test_projector_expectation` to avoid differentiating `qml.Projector` with respect to the state attribute. + [(#5683)](https://github.com/PennyLaneAI/pennylane/pull/5683) + * `qml.Projector` is now compatible with jax-jit. [(#5595)](https://github.com/PennyLaneAI/pennylane/pull/5595) * Finite shot circuits with a `qml.probs` measurement, both with a `wires` or `op` argument, can now be compiled with `jax.jit`. [(#5619)](https://github.com/PennyLaneAI/pennylane/pull/5619) -* `param_shift`, `finite_diff`, `compile`, `merge_rotations`, and `transpile` now all work - with circuits with non-commuting measurements. +* `param_shift`, `finite_diff`, `compile`, `insert`, `merge_rotations`, and `transpile` now + all work with circuits with non-commuting measurements. [(#5424)](https://github.com/PennyLaneAI/pennylane/pull/5424) + [(#5681)](https://github.com/PennyLaneAI/pennylane/pull/5681) * A correction is added to `bravyi_kitaev` to call the correct function for a FermiSentence input. [(#5671)](https://github.com/PennyLaneAI/pennylane/pull/5671) diff --git a/pennylane/devices/tests/test_compare_default_qubit.py b/pennylane/devices/tests/test_compare_default_qubit.py index 728950b80a7..05b2b6509e8 100755 --- a/pennylane/devices/tests/test_compare_default_qubit.py +++ b/pennylane/devices/tests/test_compare_default_qubit.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. """Tests that a device gives the same output as the default device.""" +import numpy as np + # pylint: disable=no-self-use,no-member import pytest from flaky import flaky @@ -90,10 +92,10 @@ def workload(): [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], - pnp.array([1, 1, 0, 0]) / pnp.sqrt(2), - pnp.array([0, 1, 0, 1]) / pnp.sqrt(2), - pnp.array([1, 1, 1, 0]) / pnp.sqrt(3), - pnp.array([1, 1, 1, 1]) / 2, + np.array([1, 1, 0, 0]) / np.sqrt(2), + np.array([0, 1, 0, 1]) / np.sqrt(2), + np.array([1, 1, 1, 0]) / np.sqrt(3), + np.array([1, 1, 1, 1]) / 2, ], ) def test_projector_expectation(self, device, state, tol, benchmark): diff --git a/pennylane/transforms/insert_ops.py b/pennylane/transforms/insert_ops.py index 9d221079734..17a81beeec8 100644 --- a/pennylane/transforms/insert_ops.py +++ b/pennylane/transforms/insert_ops.py @@ -89,7 +89,6 @@ def insert( Raises: - QuantumFunctionError: if some observables in the tape are not qubit-wise commuting ValueError: if a single operation acting on multiple wires is passed to ``op`` ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or ``'all'`` OR PennyLane Operation @@ -100,9 +99,11 @@ def insert( .. code-block:: python3 + from functools import partial + dev = qml.device("default.mixed", wires=2) - @partial(qml.transforms.insert, qml.AmplitudeDamping, 0.2, position="end") + @partial(qml.transforms.insert, op=qml.AmplitudeDamping, op_args=0.2, position="end") @qml.qnode(dev) def f(w, x, y, z): qml.RX(w, wires=0) @@ -141,7 +142,7 @@ def op(x, y, wires): dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) - @qml.transforms.insert(op, [0.2, 0.3], position="end") + @partial(qml.transforms.insert, op=op, op_args=[0.2, 0.3], position="end") def f(w, x, y, z): qml.RX(w, wires=0) qml.RY(x, wires=1) @@ -175,7 +176,7 @@ def f(w, x, y, z): We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using: >>> from pennylane.transforms import insert - >>> noisy_tape = insert(tape, qml.AmplitudeDamping, 0.05, position="end") + >>> [noisy_tape], _ = insert(tape, qml.AmplitudeDamping, 0.05, position="end") >>> print(qml.drawer.tape_text(noisy_tape, decimals=2)) 0: ──RX(0.90)─╭●──RY(0.50)──AmplitudeDamping(0.05)─┤ ╭ 1: ──RY(0.40)─╰X──RX(0.60)──AmplitudeDamping(0.05)─┤ ╰ @@ -210,17 +211,18 @@ def f(w, x, y, z): >>> qnode_noisy(0.9, 0.4, 0.5, 0.6) tensor(0.72945434, requires_grad=True) """ - # decompose templates and their adjoints (which fixes a bug in the tutorial_error_mitigation demo) - # TODO: change this to be cleaner and more robust - try: - tape = tape.expand( - stop_at=lambda op: not hasattr(qml.templates, op.name) and not isinstance(op, Adjoint) - ) - except qml.QuantumFunctionError as e: - raise qml.QuantumFunctionError( - "The insert transform cannot transform a circuit measuring non-commuting observables. " - "Consider wrapping the gates in their own function and transforming only that function." - ) from e + + # decompose templates and their adjoints to fix a bug in the tutorial_error_mitigation demo + def stop_at(obj): + if not isinstance(obj, qml.operation.Operator): + return True + if not obj.has_decomposition: + return True + return not (hasattr(qml.templates, obj.name) or isinstance(obj, Adjoint)) + + error_type = (qml.operation.DecompositionUndefinedError,) + decompose = qml.devices.preprocess.decompose + [tape], _ = decompose(tape, stopping_condition=stop_at, name="insert", error=error_type) if not isinstance(op, FunctionType) and op.num_wires != 1: raise ValueError("Only single-qubit operations can be inserted into the circuit") diff --git a/requirements-ci.txt b/requirements-ci.txt index dafa106ffe4..9f7c3b39c77 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -8,7 +8,7 @@ autograd toml appdirs semantic_version -autoray +autoray>=0.6.1,<0.6.10 matplotlib requests tomli # Drop once minimum Python version is 3.11 diff --git a/requirements.txt b/requirements.txt index a734df40bba..ab01652d0e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ autograd~=1.4 toml~=0.10 appdirs~=1.4 semantic_version~=2.10 -autoray>=0.6.1 +autoray>=0.6.1,<0.6.10 matplotlib~=3.5 opt_einsum~=3.3 requests~=2.31.0 diff --git a/tests/transforms/test_insert_ops.py b/tests/transforms/test_insert_ops.py index 96e207f8c8d..a8c33f6cb6a 100644 --- a/tests/transforms/test_insert_ops.py +++ b/tests/transforms/test_insert_ops.py @@ -602,28 +602,32 @@ def f2(w1, w2): assert np.allclose(f1(w1, w2), f2(w1, w2)) -def test_insert_decorator_causes_custom_insert_error_non_qwc_obs(): +def test_insert_transform_works_with_non_qwc_obs(): """Test that the insert transform catches and reports errors from the enclosed function.""" - # pylint: disable=unused-argument - - def noise(noise_param, wires): + def op(noise_param, wires): + # pylint: disable=unused-argument qml.CRX(noise_param, wires=[0, 1]) qml.CNOT(wires=[1, 0]) - dev = qml.device("default.mixed", wires=2) + dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) - @partial(insert, op=noise, op_args=0.3, position="all") + @partial(insert, op=op, op_args=0.3, position="all") def noisy_circuit(circuit_param): qml.RY(circuit_param, wires=0) qml.Hadamard(wires=0) qml.T(wires=0) return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliY(0)), qml.expval(qml.PauliZ(0)) - # This tape's expansion fails, but shouldn't cause a downstream IndexError. See issue #3103 - with pytest.raises( - qml.QuantumFunctionError, - match="The insert transform cannot transform a circuit measuring non-commuting observables", - ): - noisy_circuit(0.4) + @qml.qnode(dev) + def explicit_circuit(circuit_param): + qml.RY(circuit_param, wires=0) + op(0.3, None) + qml.Hadamard(wires=0) + op(0.3, None) + qml.T(wires=0) + op(0.3, None) + return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliY(0)), qml.expval(qml.PauliZ(0)) + + assert np.allclose(noisy_circuit(0.4), explicit_circuit(0.4))