From 1cbaeeccf554b0b8e5a3f5e9746f2a48b6ab6758 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 13 Sep 2024 16:20:14 -0400 Subject: [PATCH 01/14] Fix failing test in legacy opmath (#6272) A follow up of https://github.com/PennyLaneAI/pennylane/pull/6252, does the same but with a different test. --- .../test_diagonalize_measurements.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/transforms/test_diagonalize_measurements.py b/tests/transforms/test_diagonalize_measurements.py index abaeef466f8..9c6c1fa7929 100644 --- a/tests/transforms/test_diagonalize_measurements.py +++ b/tests/transforms/test_diagonalize_measurements.py @@ -35,6 +35,8 @@ null_postprocessing, ) +# pylint: disable=protected-access + class TestDiagonalizeObservable: """Tests for the _diagonalize_observable method""" @@ -315,13 +317,9 @@ def test_diagonalize_all_measurements(self, to_eigvals): assert isinstance(new_tape.measurements[1], VarianceMP) assert new_tape.measurements[0].wires == qml.wires.Wires([0]) assert new_tape.measurements[1].wires == qml.wires.Wires([1, 2]) + assert qml.math.allclose(sorted(new_tape.measurements[0]._eigvals), [-1.0, 1.0]) assert qml.math.allclose( - sorted(new_tape.measurements[0]._eigvals), # pylint: disable=protected-access - [-1.0, 1.0], - ) - assert qml.math.allclose( - sorted(new_tape.measurements[1]._eigvals), # pylint: disable=protected-access - [-2.0, 0.0, 0.0, 2.0], + sorted(new_tape.measurements[1]._eigvals), [-2.0, 0.0, 0.0, 2.0] ) else: assert new_tape.measurements == [qml.expval(Z(0)), qml.var(Z(1) + Z(2))] @@ -448,11 +446,16 @@ def test_with_duplicate_measurements(self, to_eigvals, supported_base_obs): new_tape = tapes[0] if to_eigvals: - assert new_tape.measurements == [ - ExpectationMP(eigvals=[1.0, -1], wires=[0]), - VarianceMP(eigvals=[2.0, 0.0, 0.0, -2.0], wires=[1, 2]), - SampleMP(eigvals=[1.0, -1.0, -1.0, 1.0], wires=[0, 2]), - ] + assert len(new_tape.measurements) == 3 + assert isinstance(new_tape.measurements[0], ExpectationMP) + assert isinstance(new_tape.measurements[1], VarianceMP) + assert isinstance(new_tape.measurements[2], SampleMP) + assert new_tape.measurements[0].wires == qml.wires.Wires([0]) + assert new_tape.measurements[1].wires == qml.wires.Wires([1, 2]) + assert new_tape.measurements[2].wires == qml.wires.Wires([0, 2]) + assert np.allclose(sorted(new_tape.measurements[0]._eigvals), [-1.0, 1]) + assert np.allclose(sorted(new_tape.measurements[1]._eigvals), [-2.0, 0, 0, 2.0]) + assert np.allclose(sorted(new_tape.measurements[2]._eigvals), [-1.0, -1.0, 1.0, 1.0]) else: assert new_tape.measurements == [ qml.expval(Z(0)), From 08961ddf5c6a42c62b427c2ccbfcf6f0ca7d7520 Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Fri, 13 Sep 2024 16:54:07 -0400 Subject: [PATCH 02/14] remove default.qubit.autograd (#6210) [sc-72795] --------- Co-authored-by: Pietropaolo Frisoni --- Makefile | 2 - doc/development/deprecations.rst | 24 +- doc/releases/changelog-dev.md | 5 +- pennylane/devices/__init__.py | 4 - pennylane/devices/default_qubit_autograd.py | 141 - pennylane/devices/default_qubit_legacy.py | 4 +- pennylane/devices/legacy_facade.py | 94 +- pennylane/devices/tests/conftest.py | 1 - pennylane/tape/qscript.py | 2 +- pennylane/workflow/execution.py | 2 +- setup.py | 1 - tests/devices/test_default_qubit_autograd.py | 786 ------ tests/devices/test_default_qubit_legacy.py | 17 +- .../test_default_qubit_legacy_broadcasting.py | 2024 -------------- tests/devices/test_legacy_facade.py | 74 - .../test_parameter_shift_shot_vec.py | 1 - .../test_autograd_legacy.py | 1367 ---------- .../test_autograd_qnode_legacy.py | 2365 ----------------- .../test_autograd_qnode_shot_vector_legacy.py | 658 ----- tests/interfaces/test_execute.py | 13 + tests/test_qnode_legacy.py | 15 - tests/test_qubit_device.py | 5 +- 22 files changed, 39 insertions(+), 7566 deletions(-) delete mode 100644 pennylane/devices/default_qubit_autograd.py delete mode 100644 tests/devices/test_default_qubit_autograd.py delete mode 100644 tests/devices/test_default_qubit_legacy_broadcasting.py delete mode 100644 tests/interfaces/legacy_devices_integration/test_autograd_legacy.py delete mode 100644 tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py delete mode 100644 tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py diff --git a/Makefile b/Makefile index 7f36c24c698..8b3bec9be42 100644 --- a/Makefile +++ b/Makefile @@ -60,12 +60,10 @@ clean-docs: test: $(PYTHON) $(TESTRUNNER) - $(PYTHON) $(PLUGIN_TESTRUNNER) --device=default.qubit.autograd coverage: @echo "Generating coverage report..." $(PYTHON) $(TESTRUNNER) $(COVERAGE) - $(PYTHON) $(PLUGIN_TESTRUNNER) --device=default.qubit.autograd $(COVERAGE) --cov-append .PHONY:format format: diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index ffd490f09ba..967417ad71b 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -15,18 +15,6 @@ Pending deprecations - Deprecated in v0.39 - Will be removed in v0.40 -* All of the legacy devices (any with the name ``default.qubit.{autograd,torch,tf,jax,legacy}``) are deprecated. Use ``default.qubit`` instead, - as it supports backpropagation for the many backends the legacy devices support. - - - Deprecated in v0.38 - - Will be removed in v0.39 - -* The logic for internally switching a device for a different backpropagation - compatible device is now deprecated, as it was in place for the deprecated ``default.qubit.legacy``. - - - Deprecated in v0.38 - - Will be removed in v0.39 - * The ``decomp_depth`` argument in ``qml.device`` is deprecated. - Deprecated in v0.38 @@ -88,6 +76,18 @@ Other deprecations Completed deprecation cycles ---------------------------- +* All of the legacy devices (any with the name ``default.qubit.{autograd,torch,tf,jax,legacy}``) are removed. Use ``default.qubit`` instead, + as it supports backpropagation for the many backends the legacy devices support. + + - Deprecated in v0.38 + - Removed in v0.39 + +* The logic for internally switching a device for a different backpropagation + compatible device is removed, as it was in place for removed ``default.qubit.legacy``. + + - Deprecated in v0.38 + - Removed in v0.39 + * `Operator.expand` is now removed. Use `qml.tape.QuantumScript(op.deocomposition())` instead. - Deprecated in v0.38 diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 90dbe86a773..7b9416d62c3 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -59,10 +59,12 @@ * Remove support for Python 3.9. [(#6223)](https://github.com/PennyLaneAI/pennylane/pull/6223) -* `DefaultQubitTF`, `DefaultQubitTorch`, and `DefaultQubitJax` are removed. Please use `default.qubit` for all interfaces. +* `DefaultQubitTF`, `DefaultQubitTorch`, `DefaultQubitJax`, and `DefaultQubitAutograd` are removed. + Please use `default.qubit` for all interfaces. [(#6207)](https://github.com/PennyLaneAI/pennylane/pull/6207) [(#6208)](https://github.com/PennyLaneAI/pennylane/pull/6208) [(#6209)](https://github.com/PennyLaneAI/pennylane/pull/6209) + [(#6210)](https://github.com/PennyLaneAI/pennylane/pull/6210) * `expand_fn`, `max_expansion`, `override_shots`, and `device_batch_transform` are removed from the signature of `qml.execute`. @@ -81,6 +83,7 @@ * `Operator.expand` is now removed. Use `qml.tape.QuantumScript(op.deocomposition())` instead. [(#6227)](https://github.com/PennyLaneAI/pennylane/pull/6227) +

Deprecations πŸ‘‹

* `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` and `QNode.get_gradient_fn` instead. diff --git a/pennylane/devices/__init__.py b/pennylane/devices/__init__.py index 823f2b7f41d..a542ba7df1d 100644 --- a/pennylane/devices/__init__.py +++ b/pennylane/devices/__init__.py @@ -27,7 +27,6 @@ default_qubit default_qubit_legacy - default_qubit_autograd default_gaussian default_mixed default_qutrit @@ -153,9 +152,6 @@ def execute(self, circuits, execution_config = qml.devices.DefaultExecutionConfi from .default_qubit import DefaultQubit from .legacy_facade import LegacyDeviceFacade -# DefaultQubitTF and DefaultQubitAutograd not imported here since this -# would lead to an automatic import of tensorflow and autograd, which are -# not PennyLane core dependencies. # DefaultTensor is not imported here to avoid warnings # from quimb in case it is installed on the system. from .default_qubit_legacy import DefaultQubitLegacy diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py deleted file mode 100644 index abcc6e0452f..00000000000 --- a/pennylane/devices/default_qubit_autograd.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright 2018-2021 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""This module contains an autograd implementation of the :class:`~.DefaultQubitLegacy` -reference plugin. -""" -import warnings - -from pennylane import PennyLaneDeprecationWarning -from pennylane import numpy as pnp -from pennylane.devices import DefaultQubitLegacy - - -class DefaultQubitAutograd(DefaultQubitLegacy): - r"""Simulator plugin based on ``"default.qubit.legacy"``, written using Autograd. - - **Short name:** ``default.qubit.autograd`` - - This device provides a pure-state qubit simulator written using Autograd. As a result, it - supports classical backpropagation as a means to compute the gradient. This can be faster than - the parameter-shift rule for analytic quantum gradients when the number of parameters to be - optimized is large. - - To use this device, you will need to install Autograd: - - .. code-block:: console - - pip install autograd - - .. warning:: - This device is deprecated. Use :class:`~pennylane.devices.DefaultQubit` instead; for example through ``qml.device("default.qubit")``, which now supports backpropagation. - - **Example** - - The ``default.qubit.autograd`` is designed to be used with end-to-end classical backpropagation - (``diff_method="backprop"``) with the Autograd interface. This is the default method of - differentiation when creating a QNode with this device. - - Using this method, the created QNode is a 'white-box', and is - tightly integrated with your Autograd computation: - - >>> dev = qml.device("default.qubit.autograd", wires=1) - >>> @qml.qnode(dev, interface="autograd", diff_method="backprop") - ... def circuit(x): - ... qml.RX(x[1], wires=0) - ... qml.Rot(x[0], x[1], x[2], wires=0) - ... return qml.expval(qml.Z(0)) - >>> weights = np.array([0.2, 0.5, 0.1], requires_grad=True) - >>> grad_fn = qml.grad(circuit) - >>> print(grad_fn(weights)) - array([-2.2526717e-01 -1.0086454e+00 1.3877788e-17]) - - There are a couple of things to keep in mind when using the ``"backprop"`` - differentiation method for QNodes: - - * You must use the ``"autograd"`` interface for classical backpropagation, as Autograd is - used as the device backend. - - * Only exact expectation values, variances, and probabilities are differentiable. - When instantiating the device with ``analytic=False``, differentiating QNode - outputs will result in an error. - - Args: - wires (int): the number of wires to initialize the device with - shots (None, int): How many times the circuit should be evaluated (or sampled) to estimate - the expectation values. Defaults to ``None`` if not specified, which means that the device - returns analytical results. - analytic (bool): Indicates if the device should calculate expectations - and variances analytically. In non-analytic mode, the ``diff_method="backprop"`` - QNode differentiation method is not supported and it is recommended to consider - switching device to ``default.qubit`` and using ``diff_method="parameter-shift"``. - """ - - name = "Default qubit (Autograd) PennyLane plugin" - short_name = "default.qubit.autograd" - - _dot = staticmethod(pnp.dot) - _abs = staticmethod(pnp.abs) - _reduce_sum = staticmethod(lambda array, axes: pnp.sum(array, axis=tuple(axes))) - _reshape = staticmethod(pnp.reshape) - _flatten = staticmethod(lambda array: array.flatten()) - _einsum = staticmethod(pnp.einsum) - _cast = staticmethod(pnp.asarray) - _transpose = staticmethod(pnp.transpose) - _tensordot = staticmethod(pnp.tensordot) - _conj = staticmethod(pnp.conj) - _real = staticmethod(pnp.real) - _imag = staticmethod(pnp.imag) - _roll = staticmethod(pnp.roll) - _stack = staticmethod(pnp.stack) - _size = staticmethod(pnp.size) - _ndim = staticmethod(pnp.ndim) - - @staticmethod - def _asarray(array, dtype=None): - return pnp.asarray(array, dtype=dtype) - - @staticmethod - def _const_mul(constant, array): - return constant * array - - def __init__(self, wires, *, shots=None, analytic=None): - warnings.warn( - f"Use of '{self.short_name}' is deprecated. Instead, use 'default.qubit', " - "which supports backpropagation. " - "If you experience issues, reach out to the PennyLane team on " - "the discussion forum: https://discuss.pennylane.ai/", - PennyLaneDeprecationWarning, - ) - - r_dtype = pnp.float64 - c_dtype = pnp.complex128 - super().__init__(wires, shots=shots, r_dtype=r_dtype, c_dtype=c_dtype, analytic=analytic) - - # prevent using special apply methods for these gates due to slowdown in Autograd - # implementation - del self._apply_ops["PauliY"] - del self._apply_ops["Hadamard"] - del self._apply_ops["CZ"] - - @classmethod - def capabilities(cls): - capabilities = super().capabilities().copy() - capabilities.update(passthru_interface="autograd") - return capabilities - - @staticmethod - def _scatter(indices, array, new_dimensions): - new_array = pnp.zeros(new_dimensions, dtype=array.dtype.type) - new_array[indices] = array - return new_array diff --git a/pennylane/devices/default_qubit_legacy.py b/pennylane/devices/default_qubit_legacy.py index bdcfd1ad1da..868c426d47f 100644 --- a/pennylane/devices/default_qubit_legacy.py +++ b/pennylane/devices/default_qubit_legacy.py @@ -713,9 +713,7 @@ def capabilities(cls): supports_analytic_computation=True, supports_broadcasting=True, returns_state=True, - passthru_devices={ - "autograd": "default.qubit.autograd", - }, + passthru_devices={}, ) return capabilities diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index a35aa4b25f5..41c1e0dea2c 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -15,7 +15,6 @@ Defines a LegacyDeviceFacade class for converting legacy devices to the new interface. """ -import warnings # pylint: disable=not-callable, unused-argument from contextlib import contextmanager @@ -318,79 +317,6 @@ def supports_derivatives(self, execution_config=None, circuit=None) -> bool: return False - # pylint: disable=protected-access - def _create_temp_device(self, batch): - """Create a temporary device for use in a backprop execution.""" - params = [] - for t in batch: - params.extend(t.get_parameters(trainable_only=False)) - interface = qml.math.get_interface(*params) - if interface == "numpy": - return self._device - - mapped_interface = qml.workflow.execution.INTERFACE_MAP.get(interface, interface) - - backprop_interface = self._device.capabilities().get("passthru_interface", None) - if mapped_interface == backprop_interface: - return self._device - - backprop_devices = self._device.capabilities().get("passthru_devices", None) - - if backprop_devices is None: - raise qml.DeviceError(f"Device {self} does not support backpropagation.") - - if backprop_devices[mapped_interface] == self._device.short_name: - return self._device - - if self.target_device.short_name != "default.qubit.legacy": - warnings.warn( - "The switching of devices for backpropagation is now deprecated in v0.38 and " - "will be removed in v0.39, as this behavior was developed purely for the " - "deprecated default.qubit.legacy.", - qml.PennyLaneDeprecationWarning, - ) - - # create new backprop device - expand_fn = self._device.expand_fn - batch_transform = self._device.batch_transform - if hasattr(self._device, "_debugger"): - debugger = self._device._debugger - else: - debugger = "No debugger" - tracker = self._device.tracker - - with warnings.catch_warnings(): - warnings.filterwarnings( - action="ignore", - category=qml.PennyLaneDeprecationWarning, - message=r"use 'default.qubit'", - ) - # we already warned about backprop device switching - new_device = qml.device( - backprop_devices[mapped_interface], - wires=self._device.wires, - shots=self._device.shots, - ).target_device - - new_device.expand_fn = expand_fn - new_device.batch_transform = batch_transform - if debugger != "No debugger": - new_device._debugger = debugger - new_device.tracker = tracker - - return new_device - - # pylint: disable=protected-access - def _update_original_device(self, temp_device): - """After performing an execution with a backprop device, update the state of the original device.""" - # Update for state vector simulators that have the _pre_rotated_state attribute - if hasattr(self._device, "_pre_rotated_state"): - self._device._pre_rotated_state = temp_device._pre_rotated_state - - # Update for state vector simulators that have the _state attribute - if hasattr(self._device, "_state"): - self._device._state = temp_device._state - def _validate_backprop_method(self, tape): if tape.shots: return False @@ -441,11 +367,7 @@ def _validate_device_method(self, _): return self._device.capabilities().get("provides_jacobian", False) def execute(self, circuits, execution_config=DefaultExecutionConfig): - dev = ( - self._create_temp_device(circuits) - if execution_config.gradient_method == "backprop" - else self._device - ) + dev = self.target_device kwargs = {} if dev.capabilities().get("supports_mid_measure", False): @@ -453,16 +375,10 @@ def execute(self, circuits, execution_config=DefaultExecutionConfig): first_shot = circuits[0].shots if all(t.shots == first_shot for t in circuits): - results = _set_shots(dev, first_shot)(dev.batch_execute)(circuits, **kwargs) - else: - results = tuple( - _set_shots(dev, t.shots)(dev.batch_execute)((t,), **kwargs)[0] for t in circuits - ) - - if dev is not self._device: - self._update_original_device(dev) - - return results + return _set_shots(dev, first_shot)(dev.batch_execute)(circuits, **kwargs) + return tuple( + _set_shots(dev, t.shots)(dev.batch_execute)((t,), **kwargs)[0] for t in circuits + ) def execute_and_compute_derivatives(self, circuits, execution_config=DefaultExecutionConfig): first_shot = circuits[0].shots diff --git a/pennylane/devices/tests/conftest.py b/pennylane/devices/tests/conftest.py index 8e5e9740da1..5ea86da0aae 100755 --- a/pennylane/devices/tests/conftest.py +++ b/pennylane/devices/tests/conftest.py @@ -36,7 +36,6 @@ # List of all devices that are included in PennyLane LIST_CORE_DEVICES = { "default.qubit", - "default.qubit.autograd", } diff --git a/pennylane/tape/qscript.py b/pennylane/tape/qscript.py index 9758e8fc185..7cc6809ff82 100644 --- a/pennylane/tape/qscript.py +++ b/pennylane/tape/qscript.py @@ -555,7 +555,7 @@ def trainable_params(self) -> list[int]: .. note:: For devices that support native backpropagation (such as - ``default.qubit.tf`` and ``default.qubit.autograd``), this + ``default.qubit`` and ``default.mixed``), this property contains no relevant information when using backpropagation to compute gradients. diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index 2465c70e481..7445bcea2b7 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -165,7 +165,7 @@ def _get_ml_boundary_execute( else: from .interfaces.jax import jax_jvp_execute as ml_boundary - except ImportError as e: # pragma: no-cover + except ImportError as e: # pragma: no cover raise qml.QuantumFunctionError( f"{mapped_interface} not found. Please install the latest " f"version of {mapped_interface} to enable the '{mapped_interface}' interface." diff --git a/setup.py b/setup.py index ec97eea8503..e13673fb1fa 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,6 @@ "default.qubit = pennylane.devices:DefaultQubit", "default.qubit.legacy = pennylane.devices:DefaultQubitLegacy", "default.gaussian = pennylane.devices:DefaultGaussian", - "default.qubit.autograd = pennylane.devices.default_qubit_autograd:DefaultQubitAutograd", "default.mixed = pennylane.devices.default_mixed:DefaultMixed", "null.qubit = pennylane.devices.null_qubit:NullQubit", "default.qutrit = pennylane.devices.default_qutrit:DefaultQutrit", diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py deleted file mode 100644 index 87d402cab03..00000000000 --- a/tests/devices/test_default_qubit_autograd.py +++ /dev/null @@ -1,786 +0,0 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Integration tests for the ``default.qubit.autograd`` device. -""" -import pytest - -import pennylane as qml -from pennylane import DeviceError -from pennylane import numpy as np -from pennylane.devices.default_qubit_autograd import DefaultQubitAutograd - - -@pytest.mark.autograd -def test_analytic_deprecation(): - """Tests if the kwarg `analytic` is used and displays error message.""" - msg = "The analytic argument has been replaced by shots=None. " - msg += "Please use shots=None instead of analytic=True." - - with pytest.raises( - DeviceError, - match=msg, - ): - qml.device("default.qubit.autograd", wires=1, shots=1, analytic=True) - - -@pytest.mark.autograd -class TestQNodeIntegration: - """Integration tests for default.qubit.autograd. This test ensures it integrates - properly with the PennyLane UI, in particular the new QNode.""" - - def test_defines_correct_capabilities(self): - """Test that the device defines the right capabilities""" - - dev = qml.device("default.qubit.autograd", wires=1) - cap = dev.capabilities() - capabilities = { - "model": "qubit", - "supports_finite_shots": True, - "supports_tensor_observables": True, - "returns_probs": True, - "returns_state": True, - "supports_inverse_operations": True, - "supports_analytic_computation": True, - "passthru_interface": "autograd", - "supports_broadcasting": True, - "passthru_devices": { - "autograd": "default.qubit.autograd", - }, - } - assert cap == capabilities - - def test_load_device(self): - """Test that the plugin device loads correctly""" - dev = qml.device("default.qubit.autograd", wires=2) - assert dev.num_wires == 2 - assert dev.shots == qml.measurements.Shots(None) - assert dev.short_name == "default.qubit.autograd" - assert dev.capabilities()["passthru_interface"] == "autograd" - - def test_qubit_circuit(self, tol): - """Test that the device provides the correct - result for a simple circuit.""" - p = np.array(0.543) - - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd") - def circuit(x): - qml.RX(x, wires=0) - return qml.expval(qml.PauliY(0)) - - expected = -np.sin(p) - assert np.isclose(circuit(p), expected, atol=tol, rtol=0) - - def test_qubit_circuit_broadcasted(self, tol): - """Test that the device provides the correct - result for a simple broadcasted circuit.""" - p = np.array([0.543, 0.21, 1.5]) - - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd") - def circuit(x): - qml.RX(x, wires=0) - return qml.expval(qml.PauliY(0)) - - expected = -np.sin(p) - - assert np.allclose(circuit(p), expected, atol=tol, rtol=0) - - def test_correct_state(self, tol): - """Test that the device state is correct after applying a - quantum function on the device""" - - dev = qml.device("default.qubit.autograd", wires=2) - - state = dev.state - expected = np.array([1, 0, 0, 0]) - assert np.allclose(state, expected, atol=tol, rtol=0) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(): - qml.Hadamard(wires=0) - qml.RZ(np.pi / 4, wires=0) - return qml.expval(qml.PauliZ(0)) - - circuit() - state = dev.state - - amplitude = np.exp(-1j * np.pi / 8) / np.sqrt(2) - - expected = np.array([amplitude, 0, np.conj(amplitude), 0]) - assert np.allclose(state, expected, atol=tol, rtol=0) - - def test_correct_state_broadcasted(self, tol): - """Test that the device state is correct after applying a - broadcasted quantum function on the device""" - - dev = qml.device("default.qubit.autograd", wires=2) - - state = dev.state - expected = np.array([1, 0, 0, 0]) - assert np.allclose(state, expected, atol=tol, rtol=0) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(): - qml.Hadamard(wires=0) - qml.RZ(np.array([np.pi / 4, np.pi / 2]), wires=0) - return qml.expval(qml.PauliZ(0)) - - circuit() - state = dev.state - - phase = np.exp(-1j * np.pi / 8) - - expected = np.array( - [ - [phase / np.sqrt(2), 0, np.conj(phase) / np.sqrt(2), 0], - [phase**2 / np.sqrt(2), 0, np.conj(phase) ** 2 / np.sqrt(2), 0], - ] - ) - assert np.allclose(state, expected, atol=tol, rtol=0) - - -@pytest.mark.autograd -class TestDtypePreserved: - """Test that the user-defined dtype of the device is preserved for QNode - evaluation""" - - @pytest.mark.parametrize("r_dtype", [np.float32, np.float64]) - @pytest.mark.parametrize( - "measurement", - [ - qml.expval(qml.PauliY(0)), - qml.var(qml.PauliY(0)), - qml.probs(wires=[1]), - qml.probs(wires=[2, 0]), - ], - ) - def test_real_dtype(self, r_dtype, measurement): - """Test that the default qubit plugin returns the correct - real data type for a simple circuit""" - p = 0.543 - - dev = qml.device("default.qubit.autograd", wires=3) - dev.target_device.R_DTYPE = r_dtype - - @qml.qnode(dev, diff_method="backprop") - def circuit(x): - qml.RX(x, wires=0) - return qml.apply(measurement) - - res = circuit(p) - assert res.dtype == r_dtype - - @pytest.mark.parametrize("r_dtype", [np.float32, np.float64]) - @pytest.mark.parametrize( - "measurement", - [ - qml.expval(qml.PauliY(0)), - qml.var(qml.PauliY(0)), - qml.probs(wires=[1]), - qml.probs(wires=[2, 0]), - ], - ) - def test_real_dtype_broadcasted(self, r_dtype, measurement): - """Test that the default qubit plugin returns the correct - real data type for a simple broadcasted circuit""" - p = np.array([0.543, 0.21, 1.6]) - - dev = qml.device("default.qubit.autograd", wires=3) - dev.target_device.R_DTYPE = r_dtype - - @qml.qnode(dev, diff_method="backprop") - def circuit(x): - qml.RX(x, wires=0) - return qml.apply(measurement) - - res = circuit(p) - assert res.dtype == r_dtype - - @pytest.mark.parametrize("c_dtype_name", ["complex64", "complex128"]) - @pytest.mark.parametrize( - "measurement", - [qml.state(), qml.density_matrix(wires=[1]), qml.density_matrix(wires=[2, 0])], - ) - def test_complex_dtype(self, c_dtype_name, measurement): - """Test that the default qubit plugin returns the correct - complex data type for a simple circuit""" - p = 0.543 - c_dtype = np.dtype(c_dtype_name) - - dev = qml.device("default.qubit.autograd", wires=3) - dev.target_device.C_DTYPE = c_dtype - - @qml.qnode(dev, diff_method="backprop") - def circuit(x): - qml.RX(x, wires=0) - return qml.apply(measurement) - - res = circuit(p) - assert res.dtype == c_dtype - - @pytest.mark.parametrize("c_dtype_name", ["complex64", "complex128"]) - def test_complex_dtype_broadcasted(self, c_dtype_name): - """Test that the default qubit plugin returns the correct - complex data type for a simple broadcasted circuit""" - p = np.array([0.543, 0.21, 1.6]) - c_dtype = np.dtype(c_dtype_name) - - dev = qml.device("default.qubit.autograd", wires=3) - dev.target_device.C_DTYPE = c_dtype - - measurement = qml.state() - - @qml.qnode(dev, diff_method="backprop") - def circuit(x): - qml.RX(x, wires=0) - return qml.apply(measurement) - - res = circuit(p) - assert res.dtype == c_dtype - - -@pytest.mark.autograd -class TestPassthruIntegration: - """Tests for integration with the PassthruQNode""" - - def test_jacobian_variable_multiply(self, tol): - """Test that jacobian of a QNode with an attached default.qubit.autograd device - gives the correct result in the case of parameters multiplied by scalars""" - x = 0.43316321 - y = 0.2162158 - z = 0.75110998 - weights = np.array([x, y, z], requires_grad=True) - - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(p): - qml.RX(3 * p[0], wires=0) - qml.RY(p[1], wires=0) - qml.RX(p[2] / 2, wires=0) - return qml.expval(qml.PauliZ(0)) - - res = circuit(weights) - - expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2) - assert np.allclose(res, expected, atol=tol, rtol=0) - - grad_fn = qml.jacobian(circuit, 0) - res = grad_fn(np.array(weights)) - - expected = np.array( - [ - -3 * (np.sin(3 * x) * np.cos(y) * np.cos(z / 2) + np.cos(3 * x) * np.sin(z / 2)), - -np.cos(3 * x) * np.sin(y) * np.cos(z / 2), - -0.5 * (np.sin(3 * x) * np.cos(z / 2) + np.cos(3 * x) * np.cos(y) * np.sin(z / 2)), - ] - ) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_jacobian_variable_multiply_broadcasted(self, tol): - """Test that jacobian of a QNode with an attached default.qubit.autograd device - gives the correct result in the case of broadcasted parameters multiplied by scalars""" - x = np.array([0.43316321, 92.1, -0.5129]) - y = np.array([0.2162158, 0.241, -0.51]) - z = np.array([0.75110998, 0.12512, 9.12]) - weights = np.array([x, y, z], requires_grad=True) - - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(p): - qml.RX(3 * p[0], wires=0) - qml.RY(p[1], wires=0) - qml.RX(p[2] / 2, wires=0) - return qml.expval(qml.PauliZ(0)) - - res = circuit(weights) - - expected = np.cos(3 * x) * np.cos(y) * np.cos(z / 2) - np.sin(3 * x) * np.sin(z / 2) - assert np.allclose(res, expected, atol=tol, rtol=0) - - grad_fn = qml.jacobian(circuit, 0) - res = grad_fn(np.array(weights)) - - expected = np.array( - [ - -3 * (np.sin(3 * x) * np.cos(y) * np.cos(z / 2) + np.cos(3 * x) * np.sin(z / 2)), - -np.cos(3 * x) * np.sin(y) * np.cos(z / 2), - -0.5 * (np.sin(3 * x) * np.cos(z / 2) + np.cos(3 * x) * np.cos(y) * np.sin(z / 2)), - ] - ) - - assert all(np.allclose(res[i, :, i], expected[:, i], atol=tol, rtol=0) for i in range(3)) - - def test_jacobian_repeated(self, tol): - """Test that jacobian of a QNode with an attached default.qubit.autograd device - gives the correct result in the case of repeated parameters""" - x = 0.43316321 - y = 0.2162158 - z = 0.75110998 - p = np.array([x, y, z], requires_grad=True) - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(x): - qml.RX(x[1], wires=0) - qml.Rot(x[0], x[1], x[2], wires=0) - return qml.expval(qml.PauliZ(0)) - - res = circuit(p) - - expected = np.cos(y) ** 2 - np.sin(x) * np.sin(y) ** 2 - assert np.allclose(res, expected, atol=tol, rtol=0) - - grad_fn = qml.jacobian(circuit, 0) - res = grad_fn(p) - - expected = np.array( - [-np.cos(x) * np.sin(y) ** 2, -2 * (np.sin(x) + 1) * np.sin(y) * np.cos(y), 0] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_jacobian_repeated_broadcasted(self, tol): - """Test that jacobian of a QNode with an attached default.qubit.autograd device - gives the correct result in the case of repeated broadcasted parameters""" - x = np.array([0.43316321, 92.1, -0.5129]) - y = np.array([0.2162158, 0.241, -0.51]) - z = np.array([0.75110998, 0.12512, 9.12]) - p = np.array([x, y, z], requires_grad=True) - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(x): - qml.RX(x[1], wires=0) - qml.Rot(x[0], x[1], x[2], wires=0) - return qml.expval(qml.PauliZ(0)) - - res = circuit(p) - - expected = np.cos(y) ** 2 - np.sin(x) * np.sin(y) ** 2 - assert np.allclose(res, expected, atol=tol, rtol=0) - - grad_fn = qml.jacobian(circuit, 0) - res = grad_fn(p) - - expected = np.array( - [ - -np.cos(x) * np.sin(y) ** 2, - -2 * (np.sin(x) + 1) * np.sin(y) * np.cos(y), - np.zeros_like(x), - ] - ) - assert all(np.allclose(res[i, :, i], expected[:, i], atol=tol, rtol=0) for i in range(3)) - - def test_jacobian_agrees_backprop_parameter_shift(self, tol): - """Test that jacobian of a QNode with an attached default.qubit.autograd device - gives the correct result with respect to the parameter-shift method""" - p = np.array([0.43316321, 0.2162158, 0.75110998, 0.94714242], requires_grad=True) - - def circuit(x): - for i in range(0, len(p), 2): - qml.RX(x[i], wires=0) - qml.RY(x[i + 1], wires=1) - for i in range(2): - qml.CNOT(wires=[i, i + 1]) - return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)) - - dev1 = qml.device("default.qubit.legacy", wires=3) - dev2 = qml.device("default.qubit.legacy", wires=3) - - def cost(x): - return qml.math.stack(circuit(x)) - - circuit1 = qml.QNode(cost, dev1, diff_method="backprop", interface="autograd") - circuit2 = qml.QNode(cost, dev2, diff_method="parameter-shift") - - res = circuit1(p) - - assert np.allclose(res, circuit2(p), atol=tol, rtol=0) - - grad_fn = qml.jacobian(circuit1, 0) - res = grad_fn(p) - assert np.allclose(res, qml.jacobian(circuit2)(p), atol=tol, rtol=0) - - @pytest.mark.parametrize("wires", [[0], ["abc"]]) - def test_state_differentiability(self, wires, tol): - """Test that the device state can be differentiated""" - dev = qml.device("default.qubit.autograd", wires=wires) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(a): - qml.RY(a, wires=wires[0]) - return qml.state() - - a = np.array(0.54, requires_grad=True) - - def cost(a): - """A function of the device quantum state, as a function - of input QNode parameters.""" - res = np.abs(circuit(a)) ** 2 - return res[1] - res[0] - - grad = qml.grad(cost)(a) - expected = np.sin(a) - assert np.allclose(grad, expected, atol=tol, rtol=0) - - def test_state_differentiability_broadcasted(self, tol): - """Test that the broadcasted device state can be differentiated""" - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(a): - qml.RY(a, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array([0.54, 0.32, 1.2], requires_grad=True) - - def cost(a): - """A function of the device quantum state, as a function - of input QNode parameters.""" - circuit(a) - res = np.abs(dev.state) ** 2 - return res[:, 1] - res[:, 0] - - grad = qml.jacobian(cost)(a) - expected = np.diag(np.sin(a)) - assert np.allclose(grad, expected, atol=tol, rtol=0) - - def test_prob_differentiability(self, tol): - """Test that the device probability can be differentiated""" - dev = qml.device("default.qubit.autograd", wires=2) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(a, b): - qml.RX(a, wires=0) - qml.RY(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.probs(wires=[1]) - - a = np.array(0.54, requires_grad=True) - b = np.array(0.12, requires_grad=True) - - def cost(a, b): - prob_wire_1 = circuit(a, b) - return prob_wire_1[1] - prob_wire_1[0] # pylint:disable=unsubscriptable-object - - res = cost(a, b) - expected = -np.cos(a) * np.cos(b) - assert np.allclose(res, expected, atol=tol, rtol=0) - - grad = qml.grad(cost)(a, b) - expected = [np.sin(a) * np.cos(b), np.cos(a) * np.sin(b)] - assert np.allclose(grad, expected, atol=tol, rtol=0) - - def test_prob_differentiability_broadcasted(self, tol): - """Test that the broadcasted device probability can be differentiated""" - dev = qml.device("default.qubit.autograd", wires=2) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(a, b): - qml.RX(a, wires=0) - qml.RY(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.probs(wires=[1]) - - a = np.array([0.54, 0.32, 1.2], requires_grad=True) - b = np.array(0.12, requires_grad=True) - - def cost(a, b): - prob_wire_1 = circuit(a, b) - return prob_wire_1[:, 1] - prob_wire_1[:, 0] # pylint:disable=unsubscriptable-object - - res = cost(a, b) - expected = -np.cos(a) * np.cos(b) - assert np.allclose(res, expected, atol=tol, rtol=0) - - jac = qml.jacobian(cost)(a, b) - expected = np.array([np.sin(a) * np.cos(b), np.cos(a) * np.sin(b)]) - expected = (np.diag(expected[0]), expected[1]) # Only first parameter is broadcasted - assert all(np.allclose(j, e, atol=tol, rtol=0) for j, e in zip(jac, expected)) - - def test_backprop_gradient(self, tol): - """Tests that the gradient of the qnode is correct""" - dev = qml.device("default.qubit.autograd", wires=2) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(a, b): - qml.RX(a, wires=0) - qml.CRX(b, wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - a = np.array(-0.234, requires_grad=True) - b = np.array(0.654, requires_grad=True) - - res = circuit(a, b) - expected_cost = 0.5 * (np.cos(a) * np.cos(b) + np.cos(a) - np.cos(b) + 1) - assert np.allclose(res, expected_cost, atol=tol, rtol=0) - - res = qml.grad(circuit)(a, b) - expected_grad = np.array( - [-0.5 * np.sin(a) * (np.cos(b) + 1), 0.5 * np.sin(b) * (1 - np.cos(a))] - ) - assert np.allclose(res, expected_grad, atol=tol, rtol=0) - - def test_backprop_gradient_broadcasted(self, tol): - """Tests that the gradient of the broadcasted qnode is correct""" - dev = qml.device("default.qubit.autograd", wires=2) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(a, b): - qml.RX(a, wires=0) - qml.CRX(b, wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - - a = np.array(0.12, requires_grad=True) - b = np.array([0.54, 0.32, 1.2], requires_grad=True) - - res = circuit(a, b) - expected_cost = 0.5 * (np.cos(a) * np.cos(b) + np.cos(a) - np.cos(b) + 1) - assert np.allclose(res, expected_cost, atol=tol, rtol=0) - - res = qml.jacobian(circuit)(a, b) - expected = np.array([-0.5 * np.sin(a) * (np.cos(b) + 1), 0.5 * np.sin(b) * (1 - np.cos(a))]) - expected = (expected[0], np.diag(expected[1])) - assert all(np.allclose(r, e, atol=tol, rtol=0) for r, e in zip(res, expected)) - - @pytest.mark.parametrize( - "x, shift", - [np.array((0.0, 0.0), requires_grad=True), np.array((0.5, -0.5), requires_grad=True)], - ) - def test_hessian_at_zero(self, x, shift): - """Tests that the Hessian at vanishing state vector amplitudes - is correct.""" - dev = qml.device("default.qubit.autograd", wires=1) - - @qml.qnode(dev, interface="autograd", diff_method="backprop") - def circuit(x): - qml.RY(shift, wires=0) - qml.RY(x, wires=0) - return qml.expval(qml.PauliZ(0)) - - assert qml.math.isclose(qml.jacobian(circuit)(x), 0.0) - assert qml.math.isclose(qml.jacobian(qml.jacobian(circuit))(x), -1.0) - assert qml.math.isclose(qml.grad(qml.grad(circuit))(x), -1.0) - - @pytest.mark.parametrize("operation", [qml.U3, qml.U3.compute_decomposition]) - @pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "finite-diff"]) - def test_autograd_interface_gradient(self, operation, diff_method, tol): - """Tests that the gradient of an arbitrary U3 gate is correct - using the Autograd interface, using a variety of differentiation methods.""" - dev = qml.device("default.qubit.autograd", wires=1) - state = np.array(1j * np.array([1, -1]) / np.sqrt(2), requires_grad=False) - - @qml.qnode(dev, diff_method=diff_method, interface="autograd") - def circuit(x, weights, w): - """In this example, a mixture of scalar - arguments, array arguments, and keyword arguments are used.""" - qml.StatePrep(state, wires=w) - operation(x, weights[0], weights[1], wires=w) - return qml.expval(qml.PauliX(w)) - - def cost(params): - """Perform some classical processing""" - return circuit(params[0], params[1:], w=0) ** 2 - - theta = 0.543 - phi = -0.234 - lam = 0.654 - - params = np.array([theta, phi, lam], requires_grad=True) - - res = cost(params) - expected_cost = (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) ** 2 - assert np.allclose(res, expected_cost, atol=tol, rtol=0) - - res = qml.grad(cost)(params) - expected_grad = ( - np.array( - [ - np.sin(theta) * np.cos(lam) * np.cos(phi), - np.cos(theta) * np.cos(lam) * np.sin(phi) + np.sin(lam) * np.cos(phi), - np.cos(theta) * np.sin(lam) * np.cos(phi) + np.cos(lam) * np.sin(phi), - ] - ) - * 2 - * (np.sin(lam) * np.sin(phi) - np.cos(theta) * np.cos(lam) * np.cos(phi)) - ) - assert np.allclose(res, expected_grad, atol=tol, rtol=0) - - @pytest.mark.xfail(reason="Not applicable anymore.") - @pytest.mark.parametrize("interface", ["tf", "torch"]) - def test_error_backprop_wrong_interface(self, interface): - """Tests that an error is raised if diff_method='backprop' but not using - the Autograd interface""" - dev = qml.device("default.qubit.autograd", wires=1) - - def circuit(x, w=None): - qml.RZ(x, wires=w) - return qml.expval(qml.PauliX(w)) - - with pytest.raises( - qml.QuantumFunctionError, - match="default.qubit.autograd only supports diff_method='backprop' when using the autograd interface", - ): - qml.qnode(dev, diff_method="backprop", interface=interface)(circuit) - - -@pytest.mark.autograd -class TestHighLevelIntegration: - """Tests for integration with higher level components of PennyLane.""" - - def test_do_not_split_analytic_autograd(self, mocker): - """Tests that the Hamiltonian is not split for shots=None using the autograd device.""" - dev = qml.device("default.qubit.autograd", wires=2) - H = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)]) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(): - return qml.expval(H) - - spy = mocker.spy(dev.target_device, "expval") - - circuit() - # evaluated one expval altogether - assert spy.call_count == 1 - - def test_do_not_split_analytic_autograd_broadcasted(self, mocker): - """Tests that the Hamiltonian is not split for shots=None - and broadcasting using the autograd device.""" - dev = qml.device("default.qubit.autograd", wires=2) - H = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)]) - - @qml.qnode(dev, diff_method="backprop", interface="autograd") - def circuit(): - qml.RX(np.zeros(5), 0) - return qml.expval(H) - - spy = mocker.spy(dev.target_device, "expval") - - circuit() - # evaluated one expval altogether - assert spy.call_count == 1 - - def test_template_integration(self): - """Test that a PassthruQNode default.qubit.autograd works with templates.""" - dev = qml.device("default.qubit.autograd", wires=2) - - @qml.qnode(dev, diff_method="backprop") - def circuit(weights): - qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - shape = qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=2) - weights = np.random.random(shape, requires_grad=True) - - grad = qml.grad(circuit)(weights) - assert grad.shape == weights.shape - - -# pylint: disable=protected-access -@pytest.mark.autograd -class TestOps: - """Unit tests for operations supported by the default.qubit.autograd device""" - - def test_multirz_jacobian(self): - """Test that the patched numpy functions are used for the MultiRZ - operation and the jacobian can be computed.""" - wires = 4 - dev = qml.device("default.qubit.autograd", wires=wires) - - @qml.qnode(dev, diff_method="backprop") - def circuit(param): - qml.MultiRZ(param, wires=[0, 1]) - return qml.probs(wires=list(range(wires))) - - param = np.array(0.3, requires_grad=True) - res = qml.jacobian(circuit)(param) - assert np.allclose(res, np.zeros(wires**2)) - - def test_full_subsystem(self, mocker): - """Test applying a state vector to the full subsystem""" - dev = DefaultQubitAutograd(wires=["a", "b", "c"]) - state = np.array([1, 0, 0, 0, 1, 0, 1, 1]) / 2.0 - state_wires = qml.wires.Wires(["a", "b", "c"]) - - spy = mocker.spy(dev, "_scatter") - dev._apply_state_vector(state=state, device_wires=state_wires) - - assert np.all(dev._state.flatten() == state) - spy.assert_not_called() - - def test_partial_subsystem(self, mocker): - """Test applying a state vector to a subset of wires of the full subsystem""" - - dev = DefaultQubitAutograd(wires=["a", "b", "c"]) - state = np.array([1, 0, 1, 0]) / np.sqrt(2.0) - state_wires = qml.wires.Wires(["a", "c"]) - - spy = mocker.spy(dev, "_scatter") - dev._apply_state_vector(state=state, device_wires=state_wires) - res = np.sum(dev._state, axis=(1,)).flatten() - - assert np.all(res == state) - spy.assert_called() - - -@pytest.mark.autograd -class TestOpsBroadcasted: - """Unit tests for broadcasted operations supported by the default.qubit.autograd device""" - - def test_multirz_jacobian_broadcasted(self): - """Test that the patched numpy functions are used for the MultiRZ - operation and the jacobian can be computed.""" - wires = 4 - dev = qml.device("default.qubit.autograd", wires=wires) - - @qml.qnode(dev, diff_method="backprop") - def circuit(param): - qml.MultiRZ(param, wires=[0, 1]) - return qml.probs(wires=list(range(wires))) - - param = np.array([0.3, 0.9, -4.3], requires_grad=True) - res = qml.jacobian(circuit)(param) - assert np.allclose(res, np.zeros((3, wires**2, 3))) - - def test_full_subsystem_broadcasted(self, mocker): - """Test applying a state vector to the full subsystem""" - dev = DefaultQubitAutograd(wires=["a", "b", "c"]) - state = np.array([[1, 0, 0, 0, 1, 0, 1, 1], [0, 0, 0, 1, 1, 1, 1, 0]]) / 2.0 - state_wires = qml.wires.Wires(["a", "b", "c"]) - - spy = mocker.spy(dev, "_scatter") - dev._apply_state_vector(state=state, device_wires=state_wires) - - assert np.all(dev._state.reshape((2, 8)) == state) - spy.assert_not_called() - - def test_partial_subsystem_broadcasted(self, mocker): - """Test applying a state vector to a subset of wires of the full subsystem""" - - dev = DefaultQubitAutograd(wires=["a", "b", "c"]) - state = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [1, 1, 0, 0]]) / np.sqrt(2.0) - state_wires = qml.wires.Wires(["a", "c"]) - - spy = mocker.spy(dev, "_scatter") - dev._apply_state_vector(state=state, device_wires=state_wires) - res = np.sum(dev._state, axis=(2,)).reshape((3, 4)) - - assert np.allclose(res, state) - spy.assert_called() diff --git a/tests/devices/test_default_qubit_legacy.py b/tests/devices/test_default_qubit_legacy.py index 3ab933495fe..f3c47f90e23 100644 --- a/tests/devices/test_default_qubit_legacy.py +++ b/tests/devices/test_default_qubit_legacy.py @@ -1006,9 +1006,7 @@ def test_defines_correct_capabilities(self): "supports_inverse_operations": True, "supports_analytic_computation": True, "supports_broadcasting": True, - "passthru_devices": { - "autograd": "default.qubit.autograd", - }, + "passthru_devices": {}, } assert cap == capabilities @@ -2389,19 +2387,6 @@ def test_super_expval_not_called(self, is_state_batched, mocker): assert np.isclose(dev.expval(obs), 0.2) spy.assert_not_called() - @pytest.mark.autograd - def test_trainable_autograd(self, is_state_batched): - """Tests that coeffs passed to a sum are trainable with autograd.""" - if is_state_batched: - pytest.skip( - reason="Broadcasting, qml.jacobian and new return types do not work together" - ) - dev = qml.device("default.qubit.legacy", wires=1) - qnode = qml.QNode(self.circuit, dev, interface="autograd") - y, z = np.array([1.1, 2.2]) - actual = qml.grad(qnode, argnum=[0, 1])(y, z, is_state_batched) - assert np.allclose(actual, self.expected_grad(is_state_batched)) - class TestGetBatchSize: """Tests for the helper method ``_get_batch_size`` of ``QubitDevice``.""" diff --git a/tests/devices/test_default_qubit_legacy_broadcasting.py b/tests/devices/test_default_qubit_legacy_broadcasting.py deleted file mode 100644 index 50ef31d0294..00000000000 --- a/tests/devices/test_default_qubit_legacy_broadcasting.py +++ /dev/null @@ -1,2024 +0,0 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the :class:`pennylane.devices.DefaultQubitLegacy` device when using broadcasting. -""" -# pylint: disable=protected-access,cell-var-from-loop,too-many-arguments -import math -from itertools import product - -import pytest -from gate_data import ( - CNOT, - CSWAP, - CZ, - ISWAP, - SISWAP, - SWAP, - CRot3, - CRotx, - CRoty, - CRotz, - H, - I, - IsingXX, - IsingYY, - IsingZZ, - MultiRZ1, - MultiRZ2, - Rot3, - Rotx, - Roty, - Rotz, - Rphi, - S, - T, - Toffoli, - X, - Y, - Z, -) - -import pennylane as qml -from pennylane import DeviceError -from pennylane import numpy as np -from pennylane.devices.default_qubit_legacy import DefaultQubitLegacy - -THETA = np.linspace(0.11, 1, 3) -PHI = np.linspace(0.32, 1, 3) -VARPHI = np.linspace(0.02, 1, 3) - -INVSQ2 = 1 / math.sqrt(2) -T_PHASE = np.exp(1j * np.pi / 4) -T_PHASE_C = np.exp(-1j * np.pi / 4) - -# Variant of diag that does not take the diagonal of a 2d array, but broadcasts diag. -diag = lambda x: np.array([np.diag(_x) for _x in x]) if np.ndim(x) == 2 else np.diag(x) - - -def mat_vec(mat, vec, par=None, inv=False): - if par is not None: - scalar = [np.isscalar(p) for p in par] - if not all(scalar): - batch_size = len(par[scalar.index(False)]) - par = [tuple(p if s else p[i] for p, s in zip(par, scalar)) for i in range(batch_size)] - mat = np.array([mat(*_par) for _par in par]) - else: - mat = mat(*par) - - if inv: - mat = np.moveaxis(mat.conj(), -2, -1) - - return np.einsum("...ij,...j->...i", mat, vec) - - -class TestApplyBroadcasted: - """Tests that operations and inverses of certain operations are applied to a broadcasted - state/with broadcasted parameters (or both) correctly, or that the proper errors are raised. - """ - - triple_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]]) - test_data_no_parameters = [ - (qml.PauliX, triple_state, mat_vec(X, triple_state)), - (qml.PauliY, triple_state, mat_vec(Y, triple_state)), - (qml.PauliZ, triple_state, mat_vec(Z, triple_state)), - (qml.S, triple_state, mat_vec(S, triple_state)), - (qml.T, triple_state, mat_vec(T, triple_state)), - (qml.Hadamard, triple_state, mat_vec(H, triple_state)), - (qml.Identity, triple_state, triple_state), - ] - - @pytest.mark.parametrize("operation,input,expected_output", test_data_no_parameters) - def test_apply_operation_single_wire_no_parameters_broadcasted( - self, qubit_device_1_wire, tol, operation, input, expected_output - ): - """Tests that applying an operation yields the expected output state for single wire - operations that have no parameters.""" - - qubit_device_1_wire.target_device._state = np.array( - input, dtype=qubit_device_1_wire.C_DTYPE - ) - qubit_device_1_wire.apply([operation(wires=[0])]) - - assert np.allclose(qubit_device_1_wire._state, np.array(expected_output), atol=tol, rtol=0) - assert qubit_device_1_wire._state.dtype == qubit_device_1_wire.C_DTYPE - - single_state = np.array([[0, 0.6, 0, 0.8]]) - triple_state = np.array([[1, 0, 0, 0], [0, 0, INVSQ2, -INVSQ2], [0, 0.6, 0, 0.8]]) - - test_data_two_wires_no_param = [ - (qml_op, state, mat_vec(mat_op, state)) - for (qml_op, mat_op), state in product( - zip( - [qml.CNOT, qml.SWAP, qml.CZ, qml.ISWAP, qml.SISWAP, qml.SQISW], - [CNOT, SWAP, CZ, ISWAP, SISWAP, SISWAP], - ), - [single_state, triple_state], - ) - ] - - @pytest.mark.parametrize("operation,input,expected_output", test_data_two_wires_no_param) - def test_apply_operation_two_wires_no_parameters_broadcasted( - self, qubit_device_2_wires, tol, operation, input, expected_output - ): - """Tests that applying an operation yields the expected output state for two wire - operations that have no parameters.""" - - qubit_device_2_wires.target_device._state = np.array( - input, dtype=qubit_device_2_wires.C_DTYPE - ).reshape((-1, 2, 2)) - qubit_device_2_wires.apply([operation(wires=[0, 1])]) - - assert np.allclose( - qubit_device_2_wires._state.reshape((-1, 4)), - np.array(expected_output), - atol=tol, - rtol=0, - ) - assert qubit_device_2_wires._state.dtype == qubit_device_2_wires.C_DTYPE - - quad_state = np.array( - [ - [0.6, 0, 0, 0, 0, 0, 0.8, 0], - [-INVSQ2, INVSQ2, 0, 0, 0, 0, 0, 0], - [0, 0, 0.5, 0.5, 0.5, -0.5, 0, 0], - [0, 0, 0.5, 0, 0.5, -0.5, 0, 0.5], - ] - ) - test_data_three_wires_no_parameters = [(qml.CSWAP, quad_state, mat_vec(CSWAP, quad_state))] - - @pytest.mark.parametrize("operation,input,expected_output", test_data_three_wires_no_parameters) - def test_apply_operation_three_wires_no_parameters_broadcasted( - self, qubit_device_3_wires, tol, operation, input, expected_output - ): - """Tests that applying an operation yields the expected output state for three wire - operations that have no parameters.""" - - qubit_device_3_wires.target_device._state = np.array( - input, dtype=qubit_device_3_wires.C_DTYPE - ).reshape((-1, 2, 2, 2)) - qubit_device_3_wires.apply([operation(wires=[0, 1, 2])]) - - assert np.allclose( - qubit_device_3_wires._state.reshape((-1, 8)), - np.array(expected_output), - atol=tol, - rtol=0, - ) - assert qubit_device_3_wires._state.dtype == qubit_device_3_wires.C_DTYPE - - single_state = np.array([[0, 0, 1, 0]]) - triple_state = np.array( - [ - [0, 0, 1, 0], - [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], - [0.5, -0.5, 0.5j, -0.5j], - ] - ) - - # TODO[dwierichs]: add tests with qml.BaisState once `_apply_basis_state` supports broadcasting - @pytest.mark.parametrize( - "operation,expected_output,par", - [(qml.StatePrep, s, s) for s in [single_state, triple_state]], - ) - def test_apply_operation_state_preparation_broadcasted( - self, qubit_device_2_wires, tol, operation, expected_output, par - ): - """Tests that applying an operation yields the expected output state for single wire - operations that have no parameters.""" - - par = np.array(par) - qubit_device_2_wires.reset() - qubit_device_2_wires.apply([operation(par, wires=[0, 1])]) - - assert np.allclose( - qubit_device_2_wires._state.reshape((-1, 4)), - np.array(expected_output), - atol=tol, - rtol=0, - ) - - # Collect test cases for single-scalar-parameter single-wire operations and their inverses - # For each operation, we choose broadcasted state, broadcasted params, or both - state_1 = np.array([0.6, 0.8j]) - state_5 = np.array([[INVSQ2, INVSQ2], [0.6, 0.8], [0, 1j], [-1, 0], [-INVSQ2, INVSQ2]]) - scalar_par_1 = [np.pi / 2] - scalar_par_5 = [[np.pi / 3, np.pi, 0.5, -1.2, -3 * np.pi / 2]] - test_data_single_wire_with_parameters = [ - (qml_op, state, mat_vec(mat_op, state, par=par), par) - for (qml_op, mat_op), (state, par) in product( - zip( - [qml.PhaseShift, qml.RX, qml.RY, qml.RZ, qml.MultiRZ], - [Rphi, Rotx, Roty, Rotz, MultiRZ1], - ), - [(state_1, scalar_par_5), (state_5, scalar_par_1), (state_5, scalar_par_5)], - ) - ] - - # Add qml.QubitUnitary test cases - matrix_1_par_1 = [np.array([[1, 1j], [1j, 1]]) * INVSQ2] - matrix_1_par_5 = [ - np.array( - [ - np.array([[1, -1j], [-1j, 1]]) * INVSQ2, - np.array([[1, -1], [1, 1]]) * INVSQ2, - np.array([[T_PHASE_C, 0], [0, T_PHASE]]), - np.array([[1, 0], [0, -1]]), - T, - ] - ) - ] - test_data_single_wire_with_parameters += [ - (qml.QubitUnitary, s, mat_vec(par[0], s), par) - for s, par in [ - (state_1, matrix_1_par_5), - (state_5, matrix_1_par_1), - (state_5, matrix_1_par_5), - ] - ] - - # Add qml.DiagonalQubitUnitary test cases - diag_par_1 = [[np.exp(1j * 0.1), np.exp(1j * np.pi)]] - diag_par_5 = [ - np.array( - [ - [1, -1j], - [np.exp(1j * 0.4), np.exp(1j * -0.4)], - [np.exp(1j * 0.1), np.exp(1j * np.pi)], - [1.0, np.exp(1j * np.pi / 2)], - [1, 1], - ] - ) - ] - test_data_single_wire_with_parameters += [ - (qml.DiagonalQubitUnitary, s, mat_vec(diag(par[0]), s), par) - for s, par in [(state_1, diag_par_5), (state_5, diag_par_1), (state_5, diag_par_5)] - ] - - # Add qml.SpecialUnitary test cases - theta_1_par_1 = [np.array([np.pi / 2, 0, 0])] - theta_1_par_5 = [ - np.array( - [[np.pi / 2, 0, 0], [0, np.pi / 2, 0], [0, 0, np.pi / 2], [0.3, 0, 0], [0.4, 0.2, 1.2]] - ) - ] - test_data_single_wire_with_parameters += [ - (qml.SpecialUnitary, s, mat_vec(qml.SpecialUnitary.compute_matrix(par[0], 1), s), par) - for s, par in [(state_1, theta_1_par_5), (state_5, theta_1_par_1), (state_5, theta_1_par_5)] - ] - - # Add qml.Rot test cases - multi_par_1 = { - "rz_0": [0.632, 0, 0], - "ry": [0, 0.632, 0], - "rz_1": [0, 0, 0.632], - "mixed": [0.12, -2.468, 0.812], - } - multi_par_5 = { - "rz_0": [[np.pi / 2 * i for i in range(5)], 0, 0], - "ry": [0, [np.pi / 2 * i for i in range(5)], 0], - "rz_1": [0, 0, [np.pi / 2 * i for i in range(5)]], - "mixed": [[np.pi / 2 * i for i in range(5)], [np.pi / 2 * i for i in range(5)], np.pi], - } - for like in ["rz_0", "ry", "rz_1", "mixed"]: - states_and_pars = [ - (state_1, multi_par_5[like]), - (state_5, multi_par_1[like]), - (state_5, multi_par_5[like]), - ] - test_data_single_wire_with_parameters += [ - (qml.Rot, s, mat_vec(Rot3, s, par=par), par) for s, par in states_and_pars - ] - - @pytest.mark.parametrize( - "operation,input,expected_output,par", test_data_single_wire_with_parameters - ) - def test_apply_operation_single_wire_with_parameters_broadcasted( - self, qubit_device_1_wire, tol, operation, input, expected_output, par - ): - """Tests that applying an operation yields the expected output state for single wire - operations that have parameters.""" - - qubit_device_1_wire.target_device._state = np.array( - input, dtype=qubit_device_1_wire.C_DTYPE - ) - - par = tuple(np.array(p) for p in par) - qubit_device_1_wire.apply([operation(*par, wires=[0])]) - - assert np.allclose(qubit_device_1_wire._state, np.array(expected_output), atol=tol, rtol=0) - assert qubit_device_1_wire._state.dtype == qubit_device_1_wire.C_DTYPE - - # Collect test cases for single-scalar-parameter two-wires operations and their inverses - # For each operation, we choose broadcasted state, broadcasted params, or both - state_1 = np.array([0.6, 0.8j, -0.6, -0.8j]) * INVSQ2 - state_5 = np.array( - [ - [0, 1, 0, 0], - [0, 0, 0, 1], - [0, INVSQ2, INVSQ2, 0], - [0.5, 0.5j, -0.5, 0.5], - [0.6, 0, -0.8j, 0], - ] - ) - scalar_par_1 = [np.pi / 2] - scalar_par_5 = [[np.pi / 3, np.pi, 0.5, -1.2, -3 * np.pi / 2]] - two_wires_scalar_par_ops = [ - qml.CRX, - qml.CRY, - qml.CRZ, - qml.MultiRZ, - qml.IsingXX, - qml.IsingYY, - qml.IsingZZ, - ] - two_wires_scalar_par_mats = [CRotx, CRoty, CRotz, MultiRZ2, IsingXX, IsingYY, IsingZZ] - test_data_two_wires_with_parameters = [ - (qml_op, state, mat_vec(mat_op, state, par=par), par) - for (qml_op, mat_op), (state, par) in product( - zip(two_wires_scalar_par_ops, two_wires_scalar_par_mats), - [(state_1, scalar_par_5), (state_5, scalar_par_1), (state_5, scalar_par_5)], - ) - ] - - # Add qml.CRot test cases - multi_par_1 = { - "rz_0": [0.632, 0, 0], - "ry": [0, 0.632, 0], - "rz_1": [0, 0, 0.632], - "mixed": [0.12, -2.468, 0.812], - } - multi_par_5 = { - "rz_0": [[np.pi / 2 * i for i in range(5)], 0, 0], - "ry": [0, [np.pi / 2 * i for i in range(5)], 0], - "rz_1": [0, 0, [np.pi / 2 * i for i in range(5)]], - "mixed": [[np.pi / 2 * i for i in range(5)], [np.pi / 2 * i for i in range(5)], np.pi], - } - for like in ["rz_0", "ry", "rz_1", "mixed"]: - states_and_pars = [ - (state_1, multi_par_5[like]), - (state_5, multi_par_1[like]), - (state_5, multi_par_5[like]), - ] - test_data_two_wires_with_parameters += [ - (qml.CRot, s, mat_vec(CRot3, s, par=par), par) for s, par in states_and_pars - ] - - # Add qml.QubitUnitary test cases - matrix_2_par_1 = [SISWAP] - matrix_2_par_5 = [ - np.array( - [ - np.eye(4), - np.array([[1, -1j, 0, 0], [-1j, 1, 0, 0], [0, 0, 1, -1j], [0, 0, -1j, 1]]) * INVSQ2, - np.array([[1, -1, 0, 0], [1, 1, 0, 0], [0, 0, 1, -1j], [0, 0, 1j, -1]]) * INVSQ2, - np.array([[T_PHASE_C, 0, 0, 0], [0, T_PHASE, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1j]]), - SISWAP, - ] - ) - ] - test_data_two_wires_with_parameters += [ - (qml.QubitUnitary, s, mat_vec(par[0], s), par) - for s, par in [ - (state_1, matrix_2_par_5), - (state_5, matrix_2_par_1), - (state_5, matrix_2_par_5), - ] - ] - - # Add qml.DiagonalQubitUnitary test cases - diag_par_1 = [np.exp(1j * np.array([0.1, np.pi, 0.2, -2.4]))] - diag_par_5 = [ - np.array( - [ - np.ones(4), - [1, -1j, 1, 1j], - [np.exp(1j * 0.4), np.exp(1j * -0.4), 1j, 1], - [np.exp(1j * 0.1), np.exp(1j * np.pi), INVSQ2 * (1 + 1j), INVSQ2 * (1 - 1j)], - [1.0, np.exp(1j * np.pi / 2), 1, 1], - ] - ) - ] - test_data_two_wires_with_parameters += [ - (qml.DiagonalQubitUnitary, s, mat_vec(diag(par[0]), s), par) - for s, par in [(state_1, diag_par_5), (state_5, diag_par_1), (state_5, diag_par_5)] - ] - - # Add qml.SpecialUnitary test cases - theta_2_par_1 = [np.linspace(0.1, 3, 15)] - theta_2_par_5 = [np.array([0.4, -0.2, 1.2, -0.5, 2.2])[:, np.newaxis] * np.eye(15)[2::3]] - test_data_two_wires_with_parameters += [ - (qml.SpecialUnitary, s, mat_vec(qml.SpecialUnitary.compute_matrix(par[0], 2), s), par) - for s, par in [(state_1, theta_2_par_5), (state_5, theta_2_par_1), (state_5, theta_2_par_5)] - ] - - @pytest.mark.parametrize( - "operation,input,expected_output,par", test_data_two_wires_with_parameters - ) - def test_apply_operation_two_wires_with_parameters_broadcasted( - self, qubit_device_2_wires, tol, operation, input, expected_output, par - ): - """Tests that applying an operation yields the expected output state for two wire - operations that have parameters.""" - - shape = (5, 2, 2) if np.array(input).size == 20 else (2, 2) - dtype = qubit_device_2_wires.C_DTYPE - qubit_device_2_wires.target_device._state = np.array(input, dtype=dtype).reshape(shape) - par = tuple(np.array(p) for p in par) - qubit_device_2_wires.apply([operation(*par, wires=[0, 1])]) - - assert np.allclose( - qubit_device_2_wires._state.reshape((5, 4)), expected_output, atol=tol, rtol=0 - ) - assert qubit_device_2_wires._state.dtype == qubit_device_2_wires.C_DTYPE - - def test_apply_errors_qubit_state_vector_broadcasted(self, qubit_device_2_wires): - """Test that apply fails for incorrect state preparation, and > 2 qubit gates""" - with pytest.raises(ValueError, match="The state must be a vector of norm 1.0"): - qubit_device_2_wires.apply([qml.StatePrep(np.array([[1, -1], [0, 2]]), wires=[0])]) - - # Also test that the sum-check is *not* performed along the broadcasting dimension - qubit_device_2_wires.apply([qml.StatePrep(np.array([[0.6, 0.8], [0.6, 0.8]]), wires=[0])]) - - with pytest.raises(ValueError, match=r"State must be of length 4"): - # Second dimension does not match 2**num_wires - p = np.array([[1, 0, 1, 1, 0], [0, 1, 1, 0, 1]]) / np.sqrt(3) - qubit_device_2_wires.apply([qml.StatePrep(p, wires=[0, 1])]) - - with pytest.raises(ValueError, match=r"State must be of length 4"): - # Broadcasting dimension is not first dimension - p = np.array([[1, 1, 0], [0, 1, 1], [1, 0, 1], [0, 1, 1]]) / np.sqrt(2) - qubit_device_2_wires.apply([qml.StatePrep(p, wires=[0, 1])]) - - qubit_device_2_wires.reset() - vec = qml.StatePrep(np.array([[0, 1, 0, 0], [0, 0, 1, 0]]), wires=[0, 1]) - with pytest.raises( - DeviceError, - match="Operation StatePrep cannot be used after other Operations have already been applied " - "on a default.qubit.legacy device.", - ): - qubit_device_2_wires.apply([qml.RZ(0.5, wires=[0]), vec]) - - @pytest.mark.skip("Applying a BasisState does not support broadcasting yet") - def test_apply_errors_basis_state_broadcasted(self, qubit_device_2_wires): - """Test that applying the BasisState operation raises the correct errors.""" - with pytest.raises( - ValueError, match="BasisState parameter must consist of 0 or 1 integers." - ): - op = qml.BasisState(np.array([[-0.2, 4.2], [0.5, 1.2]]), wires=[0, 1]) - qubit_device_2_wires.apply([op]) - - with pytest.raises( - ValueError, match="BasisState parameter and wires must be of equal length." - ): - # Test that the error is raised - qubit_device_2_wires.apply( - [qml.BasisState(np.array([[0, 1], [1, 1], [1, 0]]), wires=[0])] - ) - # Test that the broadcasting dimension is allowed to mismatch the length of the wires - qubit_device_2_wires.apply([qml.BasisState(np.array([[0], [1], [0]]), wires=[0])]) - - qubit_device_2_wires.reset() - qubit_device_2_wires.apply([qml.RZ(0.5, wires=[0])]) - vec = qml.BasisState(np.array([[0, 0], [1, 0], [1, 1]]), wires=[0, 1]) - with pytest.raises( - DeviceError, - match="Operation BasisState cannot be used after other Operations have already been applied " - "on a default.qubit.legacy device.", - ): - qubit_device_2_wires.apply([vec]) - - -zero = [1, 0] -one = [0, 1] -plus = [INVSQ2, INVSQ2] -minus = [INVSQ2, -INVSQ2] -y_plus = [INVSQ2, 1j * INVSQ2] -y_minus = [INVSQ2, -1j * INVSQ2] - - -class TestExpvalBroadcasted: - """Tests that expectation values are properly calculated for broadcasted states - or that the proper errors are raised.""" - - @pytest.mark.parametrize( - "operation,input,expected_output", - [ - (qml.PauliX, np.array([plus, zero, minus]), [1, 0, -1]), - (qml.PauliY, np.array([y_plus, zero, y_minus]), [1, 0, -1]), - (qml.PauliZ, np.array([plus, zero, one]), [0, 1, -1]), - (qml.Hadamard, np.array([plus, zero, one]), [INVSQ2, INVSQ2, -INVSQ2]), - (qml.Identity, np.array([minus, zero, one]), [1, 1, 1]), - ], - ) - def test_expval_single_wire_no_parameters_broadcasted( - self, qubit_device_1_wire, tol, operation, input, expected_output - ): - """Tests that expectation values are properly calculated for single-wire observables without parameters.""" - - obs = operation(wires=[0]) - - qubit_device_1_wire.reset() - qubit_device_1_wire.apply( - [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates() - ) - res = qubit_device_1_wire.expval(obs) - - assert np.allclose(res, expected_output, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "operation,input,expected_output,par", - [(qml.Hermitian, [zero, one, minus, y_plus], [1, 1, 1, 0], I - Y)], - ) - def test_expval_single_wire_with_parameters_broadcasted( - self, qubit_device_1_wire, tol, operation, input, expected_output, par - ): - """Tests that expectation values are properly calculated for single-wire observables with parameters.""" - - obs = operation(np.array(par), wires=[0]) - - qubit_device_1_wire.reset() - qubit_device_1_wire.apply( - [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates() - ) - res = qubit_device_1_wire.expval(obs) - - assert np.allclose(res, expected_output, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "operation,input,expected_output,par", - [ - ( - qml.Hermitian, - [ - [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], - [0, 0, 0, 1], - [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0], - ], - [4 / 3, 0, 1], - [[1, 1j, 0, 1], [-1j, 1, 0, 0], [0, 0, 1, -1j], [1, 0, 1j, 0]], - ), - ( - qml.Hermitian, - [[INVSQ2, 0, 0, INVSQ2], [0, INVSQ2, -INVSQ2, 0]], - [1, -1], - [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], - ), - ], - ) - def test_expval_two_wires_with_parameters_broadcasted( - self, qubit_device_2_wires, tol, operation, input, expected_output, par - ): - """Tests that expectation values are properly calculated for two-wire observables with parameters.""" - - obs = operation(np.array(par), wires=[0, 1]) - - qubit_device_2_wires.reset() - qubit_device_2_wires.apply( - [qml.StatePrep(np.array(input), wires=[0, 1])], obs.diagonalizing_gates() - ) - res = qubit_device_2_wires.expval(obs) - - assert np.allclose(res, expected_output, atol=tol, rtol=0) - - def test_expval_estimate_broadcasted(self): - """Test that the expectation value is not analytically calculated""" - - dev = qml.device("default.qubit.legacy", wires=1, shots=3) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(): - qml.RX(np.zeros(5), wires=0) # Broadcast the tape without applying an op - return qml.expval(qml.PauliX(0)) - - expval = circuit() - - # With 3 samples we are guaranteed to see a difference between - # an estimated variance an an analytically calculated one - assert np.all(expval != 0.0) - - -class TestVarBroadcasted: - """Tests that variances are properly calculated for broadcasted states.""" - - @pytest.mark.parametrize( - "operation,input,expected_output", - [ - (qml.PauliX, [plus, zero, minus], [0, 1, 0]), - (qml.PauliY, [y_plus, zero, y_minus], [0, 1, 0]), - (qml.PauliZ, [plus, zero, one], [1, 0, 0]), - (qml.Hadamard, [plus, zero, one], [0.5, 0.5, 0.5]), - (qml.Identity, [minus, zero, one], [0, 0, 0]), - ], - ) - def test_var_single_wire_no_parameters_broadcasted( - self, qubit_device_1_wire, tol, operation, input, expected_output - ): - """Tests that variances are properly calculated for single-wire observables without parameters.""" - - obs = operation(wires=[0]) - - qubit_device_1_wire.reset() - qubit_device_1_wire.apply( - [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates() - ) - res = qubit_device_1_wire.var(obs) - - assert np.allclose(res, expected_output, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "operation,input,expected_output,par", - [(qml.Hermitian, [zero, one, minus, y_plus], [1, 1, 1, 0], I - Y)], - ) - def test_var_single_wire_with_parameters_broadcasted( - self, qubit_device_1_wire, tol, operation, input, expected_output, par - ): - """Tests that variances are properly calculated for single-wire observables with parameters.""" - - obs = operation(np.array(par), wires=[0]) - - qubit_device_1_wire.reset() - qubit_device_1_wire.apply( - [qml.StatePrep(np.array(input), wires=[0])], obs.diagonalizing_gates() - ) - res = qubit_device_1_wire.var(obs) - - assert np.allclose(res, expected_output, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "operation,input,expected_output,par", - [ - ( - qml.Hermitian, - [ - [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], - [0, 0, 0, 1], - [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0], - ], - [11 / 9, 2, 3 / 2], - [[1, 1j, 0, 1], [-1j, 1, 0, 0], [0, 0, 1, -1j], [1, 0, 1j, 1]], - ), - ( - qml.Hermitian, - [[INVSQ2, 0, 0, INVSQ2], [0, INVSQ2, -INVSQ2, 0]], - [0, 0], - [[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]], - ), - ], - ) - def test_var_two_wires_with_parameters_broadcasted( - self, qubit_device_2_wires, tol, operation, input, expected_output, par - ): - """Tests that variances are properly calculated for two-wire observables with parameters.""" - - obs = operation(np.array(par), wires=[0, 1]) - - qubit_device_2_wires.reset() - qubit_device_2_wires.apply( - [qml.StatePrep(np.array(input), wires=[0, 1])], obs.diagonalizing_gates() - ) - res = qubit_device_2_wires.var(obs) - - assert np.allclose(res, expected_output, atol=tol, rtol=0) - - def test_var_estimate_broadcasted(self): - """Test that the variance is not analytically calculated""" - - dev = qml.device("default.qubit.legacy", wires=1, shots=3) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(): - qml.RX(np.zeros(5), wires=0) # Broadcast the tape without applying an op - return qml.var(qml.PauliX(0)) - - var = circuit() - - # With 3 samples we are guaranteed to see a difference between - # an estimated variance and an analytically calculated one - assert np.all(var != 1.0) - - -class TestSampleBroadcasted: - """Tests that samples are properly calculated for broadcasted states.""" - - def test_sample_dimensions_broadcasted(self): - """Tests if the samples returned by the sample function have - the correct dimensions - """ - - # Explicitly resetting is necessary as the internal - # state is set to None in __init__ and only properly - # initialized during reset - dev = qml.device("default.qubit.legacy", wires=2, shots=1000) - - dev.apply([qml.RX(np.array([np.pi / 2, 0.0]), 0), qml.RX(np.array([np.pi / 2, 0.0]), 1)]) - - dev.target_device.shots = 10 - dev.target_device._wires_measured = {0} - dev.target_device._samples = dev.generate_samples() - s1 = dev.sample(qml.PauliZ(0)) - assert s1.shape == ( - 2, - 10, - ) - - dev.reset() - dev.target_device.shots = 12 - dev.target_device._wires_measured = {1} - dev.target_device._samples = dev.generate_samples() - s2 = dev.sample(qml.PauliZ(wires=[1])) - assert s2.shape == (12,) - - dev.reset() - dev.apply([qml.RX(np.ones(5), 0), qml.RX(np.ones(5), 1)]) - dev.target_device.shots = 17 - dev.target_device._wires_measured = {0, 1} - dev.target_device._samples = dev.generate_samples() - s3 = dev.sample(qml.PauliX(0) @ qml.PauliZ(1)) - assert s3.shape == (5, 17) - - def test_sample_values_broadcasted(self, tol): - """Tests if the samples returned by sample have - the correct values - """ - - # Explicitly resetting is necessary as the internal - # state is set to None in __init__ and only properly - # initialized during reset - dev = qml.device("default.qubit.legacy", wires=2, shots=1000) - - dev.apply([qml.RX(np.ones(3), wires=[0])]) - dev.target_device._wires_measured = {0} - dev.target_device._samples = dev.generate_samples() - - s1 = dev.sample(qml.PauliZ(0)) - - # s1 should only contain 1 and -1, which is guaranteed if - # they square to 1 - assert np.allclose(s1**2, 1, atol=tol, rtol=0) - - -class TestDefaultQubitIntegrationBroadcasted: - """Integration tests for default.qubit.legacy. This test ensures it integrates - properly with the PennyLane interface, in particular QNode.""" - - @pytest.mark.parametrize("r_dtype", [np.float32, np.float64]) - def test_qubit_circuit_broadcasted(self, qubit_device_1_wire, r_dtype, tol): - """Test that the default qubit plugin provides correct result for a simple circuit""" - - p = np.array([0.543, np.pi / 2, 0.0, 1.0]) - - dev = qubit_device_1_wire - dev.target_device.R_DTYPE = r_dtype - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x): - qml.RX(x, wires=0) - return qml.expval(qml.PauliY(0)) - - expected = -np.sin(p) - - res = circuit(p) - assert np.allclose(res, expected, atol=tol, rtol=0) - assert res.dtype == r_dtype # pylint:disable=no-member - - def test_qubit_identity_broadcasted(self, qubit_device_1_wire, tol): - """Test that the default qubit plugin provides correct result for the Identity expectation""" - - p = np.array([0.543, np.pi / 2, 0.0, 1.0]) - - @qml.qnode(qubit_device_1_wire) - def circuit(x): - """Test quantum function""" - qml.RX(x, wires=0) - return qml.expval(qml.Identity(0)) - - assert np.allclose(circuit(p), 1, atol=tol, rtol=0) - - def test_nonzero_shots_broadcasted(self, tol): - """Test that the default qubit plugin provides correct result for high shot number""" - - shots = 10**5 - dev = qml.device("default.qubit.legacy", wires=1, shots=shots) - - p = np.array([0.543, np.pi / 2, 0.0, 1.0]) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x): - """Test quantum function""" - qml.RX(x, wires=0) - return qml.expval(qml.PauliY(0)) - - runs = [] - for _ in range(100): - runs.append(circuit(p)) - - assert np.allclose(np.mean(runs, axis=0), -np.sin(p), atol=tol, rtol=0) - - @pytest.mark.parametrize( - "name,state,expected_output", - [ - ("PauliX", [plus, minus, zero], [1, -1, 0]), - ("PauliY", [y_plus, y_minus, zero], [1, -1, 0]), - ("PauliZ", [plus, one, zero], [0, -1, 1]), - ("Hadamard", [plus, one, zero], [INVSQ2, -INVSQ2, INVSQ2]), - ], - ) - def test_supported_observable_single_wire_no_parameters_broadcasted( - self, qubit_device_1_wire, tol, name, state, expected_output - ): - """Tests supported observables on single wires without parameters.""" - - obs = getattr(qml.ops, name) - - assert qubit_device_1_wire.supports_observable(name) - - @qml.qnode(qubit_device_1_wire) - def circuit(): - qml.StatePrep(np.array(state), wires=[0]) - return qml.expval(obs(wires=[0])) - - assert np.allclose(circuit(), expected_output, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "name,state,expected_output,par", - [ - ("Identity", [zero, one, plus], [1, 1, 1], []), - ("Hermitian", [zero, one, minus], [1, 1, 1], [I - Y]), - ], - ) - def test_supported_observable_single_wire_with_parameters_broadcasted( - self, qubit_device_1_wire, tol, name, state, expected_output, par - ): - """Tests supported observables on single wires with parameters.""" - - obs = getattr(qml.ops, name) - - assert qubit_device_1_wire.supports_observable(name) - - @qml.qnode(qubit_device_1_wire) - def circuit(): - qml.StatePrep(np.array(state), wires=[0]) - return qml.expval(obs(*par, wires=[0])) - - assert np.allclose(circuit(), expected_output, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "name,state,expected_output,par", - [ - ( - "Hermitian", - [ - [1 / math.sqrt(3), 0, 1 / math.sqrt(3), 1 / math.sqrt(3)], - [0, 0, 0, 1], - [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0], - ], - [4 / 3, 0, 1], - ([[1, 1j, 0, 1], [-1j, 1, 0, 0], [0, 0, 1, -1j], [1, 0, 1j, 0]],), - ), - ( - "Hermitian", - [[INVSQ2, 0, 0, INVSQ2], [0, INVSQ2, -INVSQ2, 0]], - [1, -1], - ([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]],), - ), - ], - ) - def test_supported_observable_two_wires_with_parameters_broadcasted( - self, qubit_device_2_wires, tol, name, state, expected_output, par - ): - """Tests supported observables on two wires with parameters.""" - - obs = getattr(qml.ops, name) - - assert qubit_device_2_wires.supports_observable(name) - - @qml.qnode(qubit_device_2_wires) - def circuit(): - qml.StatePrep(np.array(state), wires=[0, 1]) - return qml.expval(obs(*par, wires=[0, 1])) - - assert np.allclose(circuit(), expected_output, atol=tol, rtol=0) - - def test_multi_samples_return_correlated_results_broadcasted(self): - """Tests if the samples returned by the sample function are correlated - correctly for correlated observables. - """ - - dev = qml.device("default.qubit.legacy", wires=2, shots=1000) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(): - qml.Hadamard(0) - qml.RX(np.zeros(5), 0) - qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) - - outcomes = circuit() - - assert np.array_equal(outcomes[0], outcomes[1]) - - @pytest.mark.parametrize("num_wires", [3, 4, 5, 6, 7, 8]) - def test_multi_samples_correlated_results_more_wires_than_observable_broadcasted( - self, num_wires - ): - """Tests if the samples returned by the sample function are correlated - correctly for correlated observables on larger devices than the observables - """ - - dev = qml.device("default.qubit.legacy", wires=num_wires, shots=1000) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(): - qml.Hadamard(0) - qml.RX(np.zeros(5), 0) - qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) - - outcomes = circuit() - - assert np.array_equal(outcomes[0], outcomes[1]) - - -# pylint: disable=unused-argument -@pytest.mark.parametrize( - "theta,phi,varphi", [(THETA, PHI, VARPHI), (THETA, PHI[0], VARPHI), (THETA[0], PHI, VARPHI[1])] -) -class TestTensorExpvalBroadcasted: - """Test tensor expectation values for broadcasted states""" - - def test_paulix_pauliy_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving PauliX and PauliY works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - dev.reset() - - obs = qml.PauliX(0) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) - - expected = np.sin(theta) * np.sin(phi) * np.sin(varphi) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_pauliz_identity_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving PauliZ and Identity works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - dev.reset() - - obs = qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) - - expected = np.cos(varphi) * np.cos(phi) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_pauliz_hadamard_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2) - - dev.reset() - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) - - expected = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_hermitian_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving qml.Hermitian works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - dev.reset() - - A = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) - - obs = qml.PauliZ(0) @ qml.Hermitian(A, wires=[1, 2]) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) - - expected = 0.5 * ( - -6 * np.cos(theta) * (np.cos(varphi) + 1) - - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) - + 3 * np.cos(varphi) * np.sin(phi) - + np.sin(phi) - ) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_hermitian_hermitian_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving two Hermitian matrices works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - - A1 = np.array([[1, 2], [2, 4]]) - - A2 = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) - - obs = qml.Hermitian(A1, wires=[0]) @ qml.Hermitian(A2, wires=[1, 2]) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) - - expected = 0.25 * ( - -30 - + 4 * np.cos(phi) * np.sin(theta) - + 3 * np.cos(varphi) * (-10 + 4 * np.cos(phi) * np.sin(theta) - 3 * np.sin(phi)) - - 3 * np.sin(phi) - - 2 - * (5 + np.cos(phi) * (6 + 4 * np.sin(theta)) + (-3 + 8 * np.sin(theta)) * np.sin(phi)) - * np.sin(varphi) - + np.cos(theta) - * ( - 18 - + 5 * np.sin(phi) - + 3 * np.cos(varphi) * (6 + 5 * np.sin(phi)) - + 2 * (3 + 10 * np.cos(phi) - 5 * np.sin(phi)) * np.sin(varphi) - ) - ) - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_hermitian_identity_expectation_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving an Hermitian matrix and the identity works correctly""" - dev = qml.device("default.qubit.legacy", wires=2) - - A = np.array( - [[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]] - ) - - obs = qml.Hermitian(A, wires=[0]) @ qml.Identity(wires=[1]) - - dev.apply( - [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) - - a = A[0, 0] - re_b = A[0, 1].real - d = A[1, 1] - expected = ((a - d) * np.cos(theta) + 2 * re_b * np.sin(theta) * np.sin(phi) + a + d) / 2 - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_hermitian_two_wires_identity_expectation_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving an Hermitian matrix for two wires and the identity works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - - A = np.array( - [[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]] - ) - Identity = np.array([[1, 0], [0, 1]]) - Ham = np.kron(np.kron(Identity, Identity), A) - obs = qml.Hermitian(Ham, wires=[2, 1, 0]) - - dev.apply( - [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - obs.diagonalizing_gates(), - ) - res = dev.expval(obs) - - a = A[0, 0] - re_b = A[0, 1].real - d = A[1, 1] - - expected = ((a - d) * np.cos(theta) + 2 * re_b * np.sin(theta) * np.sin(phi) + a + d) / 2 - assert np.allclose(res, expected, atol=tol, rtol=0) - - -@pytest.mark.parametrize( - "theta,phi,varphi", [(THETA, PHI, VARPHI), (THETA, PHI[0], VARPHI), (THETA[0], PHI, VARPHI[1])] -) -class TestTensorVarBroadcasted: - """Tests for variance of tensor observables for broadcasted states""" - - def test_paulix_pauliy_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving PauliX and PauliY works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - - obs = qml.PauliX(0) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.var(obs) - - expected = ( - 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 - - np.cos(2 * (theta - phi)) - - np.cos(2 * (theta + phi)) - + 2 * np.cos(2 * theta) - + 2 * np.cos(2 * phi) - + 14 - ) / 16 - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_pauliz_hadamard_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2) - - dev.reset() - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.var(obs) - - expected = ( - 3 - + np.cos(2 * phi) * np.cos(varphi) ** 2 - - np.cos(2 * theta) * np.sin(varphi) ** 2 - - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi) - ) / 4 - - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_hermitian_broadcasted(self, theta, phi, varphi, tol): - """Test that a tensor product involving qml.Hermitian works correctly""" - dev = qml.device("default.qubit.legacy", wires=3) - - A = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) - - obs = qml.PauliZ(0) @ qml.Hermitian(A, wires=[1, 2]) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - res = dev.var(obs) - - expected = ( - 1057 - - np.cos(2 * phi) - + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) - + 16 * np.sin(2 * phi) - - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2 - - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - - 8 - * np.cos(theta) - * ( - 4 - * np.cos(phi) - * ( - 4 - + 8 * np.cos(varphi) - + np.cos(2 * varphi) - - (1 + 6 * np.cos(varphi)) * np.sin(varphi) - ) - + np.sin(phi) - * ( - 15 - + 8 * np.cos(varphi) - - 11 * np.cos(2 * varphi) - + 42 * np.sin(varphi) - + 3 * np.sin(2 * varphi) - ) - ) - ) / 16 - - assert np.allclose(res, expected, atol=tol, rtol=0) - - -@pytest.mark.parametrize( - "theta,phi,varphi", [(THETA, PHI, VARPHI), (THETA, PHI[0], VARPHI), (THETA[0], PHI, VARPHI[1])] -) -class TestTensorSampleBroadcasted: - """Test tensor sampling for broadcated states""" - - def test_paulix_pauliy_broadcasted(self, theta, phi, varphi, tol_stochastic): - """Test that a tensor product involving PauliX and PauliY works correctly""" - dev = qml.device("default.qubit.legacy", wires=3, shots=int(1e6)) - - obs = qml.PauliX(0) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - dev.target_device._wires_measured = {0, 1, 2} - dev.target_device._samples = dev.generate_samples() - dev.sample(obs) - - s1 = obs.eigvals() - p = dev.probability(wires=dev.map_wires(obs.wires)) - - # s1 should only contain 1 and -1 - assert np.allclose(s1**2, 1, atol=tol_stochastic, rtol=0) - - mean = p @ s1 - expected = np.sin(theta) * np.sin(phi) * np.sin(varphi) - assert np.allclose(mean, expected, atol=tol_stochastic, rtol=0) - - var = p @ (s1**2) - (p @ s1).real ** 2 - expected = ( - 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 - - np.cos(2 * (theta - phi)) - - np.cos(2 * (theta + phi)) - + 2 * np.cos(2 * theta) - + 2 * np.cos(2 * phi) - + 14 - ) / 16 - assert np.allclose(var, expected, atol=tol_stochastic, rtol=0) - - def test_pauliz_hadamard_broadcasted(self, theta, phi, varphi, tol_stochastic): - """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" - dev = qml.device("default.qubit.legacy", wires=3, shots=int(1e6)) - obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2) - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - dev.target_device._wires_measured = {0, 1, 2} - dev.target_device._samples = dev.generate_samples() - dev.sample(obs) - - s1 = obs.eigvals() - p = dev.marginal_prob(dev.probability(), wires=obs.wires) - - # s1 should only contain 1 and -1 - assert np.allclose(s1**2, 1, atol=tol_stochastic, rtol=0) - - mean = p @ s1 - expected = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2) - assert np.allclose(mean, expected, atol=tol_stochastic, rtol=0) - - var = p @ (s1**2) - (p @ s1).real ** 2 - expected = ( - 3 - + np.cos(2 * phi) * np.cos(varphi) ** 2 - - np.cos(2 * theta) * np.sin(varphi) ** 2 - - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi) - ) / 4 - assert np.allclose(var, expected, atol=tol_stochastic, rtol=0) - - def test_hermitian_broadcasted(self, theta, phi, varphi, tol_stochastic): - """Test that a tensor product involving qml.Hermitian works correctly""" - dev = qml.device("default.qubit.legacy", wires=3, shots=int(1e6)) - - A = 0.1 * np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) - - obs = qml.PauliZ(0) @ qml.Hermitian(A, wires=[1, 2]) - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - obs.diagonalizing_gates(), - ) - - dev.target_device._wires_measured = {0, 1, 2} - dev.target_device._samples = dev.generate_samples() - dev.sample(obs) - - s1 = obs.eigvals() - p = dev.marginal_prob(dev.probability(), wires=obs.wires) - - # s1 should only contain the eigenvalues of - # the hermitian matrix tensor product Z - z = np.diag([1, -1]) - eigvals = np.linalg.eigvalsh(np.kron(z, A)) - assert set(np.round(s1, 8).tolist()).issubset(set(np.round(eigvals, 8).tolist())) - - mean = p @ s1 - expected = ( - 0.1 - * 0.5 - * ( - -6 * np.cos(theta) * (np.cos(varphi) + 1) - - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) - + 3 * np.cos(varphi) * np.sin(phi) - + np.sin(phi) - ) - ) - assert np.allclose(mean, expected, atol=tol_stochastic, rtol=0) - - var = p @ (s1**2) - (p @ s1).real ** 2 - expected = ( - 0.01 - * ( - 1057 - - np.cos(2 * phi) - + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) - + 16 * np.sin(2 * phi) - - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2 - - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - - 8 - * np.cos(theta) - * ( - 4 - * np.cos(phi) - * ( - 4 - + 8 * np.cos(varphi) - + np.cos(2 * varphi) - - (1 + 6 * np.cos(varphi)) * np.sin(varphi) - ) - + np.sin(phi) - * ( - 15 - + 8 * np.cos(varphi) - - 11 * np.cos(2 * varphi) - + 42 * np.sin(varphi) - + 3 * np.sin(2 * varphi) - ) - ) - ) - / 16 - ) - assert np.allclose(var, expected, atol=tol_stochastic, rtol=0) - - -@pytest.mark.parametrize( - "r_dtype,c_dtype", [(np.float32, np.complex64), (np.float64, np.complex128)] -) -class TestDtypePreservedBroadcasted: - """Test that the user-defined dtype of the device is preserved for QNode - evaluation""" - - @pytest.mark.parametrize( - "op", - [ - qml.SingleExcitation, - qml.SingleExcitationPlus, - qml.SingleExcitationMinus, - qml.DoubleExcitation, - qml.DoubleExcitationPlus, - qml.DoubleExcitationMinus, - qml.OrbitalRotation, - qml.FermionicSWAP, - qml.QubitSum, - qml.QubitCarry, - ], - ) - def test_state_dtype_after_op_broadcasted(self, r_dtype, c_dtype, op): - """Test that the default qubit plugin preserves data types of states when an operation is - applied. As TestApply class check most of operators, we here only check some subtle - examples. - """ - - dev = qml.device("default.qubit.legacy", wires=4, r_dtype=r_dtype, c_dtype=c_dtype) - - n_wires = op.num_wires - n_params = op.num_params - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(): - x = np.array([0.543, 0.622, 1.3]) - if n_params == 0: - op(wires=range(n_wires)) - elif n_params == 1: - op(x, wires=range(n_wires)) - else: - op([x] * n_params, wires=range(n_wires)) - return qml.state() - - res = circuit() - assert res.dtype == c_dtype # pylint:disable=no-member - - @pytest.mark.parametrize( - "measurement", - [ - qml.expval(qml.PauliY(0)), - qml.var(qml.PauliY(0)), - qml.probs(wires=[1]), - qml.probs(wires=[2, 0]), - ], - ) - def test_measurement_real_dtype_broadcasted(self, r_dtype, c_dtype, measurement): - """Test that the default qubit plugin provides correct result for a simple circuit""" - p = np.array([0.543, 0.622, 1.3]) - - dev = qml.device("default.qubit.legacy", wires=3, r_dtype=r_dtype, c_dtype=c_dtype) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x): - qml.RX(x, wires=0) - return qml.apply(measurement) - - res = circuit(p) - assert res.dtype == r_dtype - - def test_measurement_complex_dtype_broadcasted(self, r_dtype, c_dtype): - """Test that the default qubit plugin provides correct result for a simple circuit""" - p = np.array([0.543, 0.622, 1.3]) - m = qml.state() - - dev = qml.device("default.qubit.legacy", wires=3, r_dtype=r_dtype, c_dtype=c_dtype) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x): - qml.RX(x, wires=0) - return qml.apply(m) - - res = circuit(p) - assert res.dtype == c_dtype - - -class TestProbabilityIntegrationBroadcasted: - """Test probability method for when analytic is True/False""" - - # pylint: disable=no-member, unused-argument - def mock_analytic_counter(self, wires=None): - self.analytic_counter += 1 - return np.array([1, 0, 0, 0], dtype=float) - - def test_probability_broadcasted(self, tol): - """Test that the probability function works for finite and infinite shots""" - dev = qml.device("default.qubit.legacy", wires=2, shots=1000) - dev_analytic = qml.device("default.qubit.legacy", wires=2, shots=None) - - x = np.array([[0.2, 0.5, 0.4], [0.9, 0.8, 0.3]]) - - def circuit(x): - qml.RX(x[0], wires=0) - qml.RY(x[1], wires=0) - qml.CNOT(wires=[0, 1]) - return qml.probs(wires=[0, 1]) - - prob = qml.QNode(circuit, dev) - prob_analytic = qml.QNode(circuit, dev_analytic) - - assert np.allclose(prob(x).sum(axis=-1), 1, atol=tol, rtol=0) - assert np.allclose(prob_analytic(x), prob(x), atol=0.1, rtol=0) - assert not np.array_equal(prob_analytic(x), prob(x)) - - -class TestWiresIntegrationBroadcasted: - """Test that the device integrates with PennyLane's wire management.""" - - def make_circuit_probs(self, wires): - """Factory for a qnode returning probabilities using arbitrary wire labels.""" - dev = qml.device("default.qubit.legacy", wires=wires) - n_wires = len(wires) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(): - qml.RX(np.array([0.5, 1.2, -0.6]), wires=wires[0 % n_wires]) - qml.RY(np.array([2.0, 0.4, 1.2]), wires=wires[1 % n_wires]) - if n_wires > 1: - qml.CNOT(wires=[wires[0], wires[1]]) - return qml.probs(wires=wires) - - return circuit - - @pytest.mark.parametrize( - "wires1, wires2", - [ - (["a", "c", "d"], [2, 3, 0]), - ([-1, -2, -3], ["q1", "ancilla", 2]), - (["a", "c"], [3, 0]), - ([-1, -2], ["ancilla", 2]), - (["a"], ["nothing"]), - ], - ) - def test_wires_probs_broadcasted(self, wires1, wires2, tol): - """Test that the probability vector of a circuit is independent from the wire labels used.""" - - circuit1 = self.make_circuit_probs(wires1) - circuit2 = self.make_circuit_probs(wires2) - - assert np.allclose(circuit1(), circuit2(), tol) - - -class TestApplyOpsBroadcasted: - """Tests for special methods listed in _apply_ops that use array manipulation tricks to apply - gates in DefaultQubitLegacy.""" - - broadcasted_state = np.arange(2**4 * 3, dtype=np.complex128).reshape((3, 2, 2, 2, 2)) - with pytest.warns(qml.PennyLaneDeprecationWarning): - dev = qml.device("default.qubit.legacy", wires=4) - - single_qubit_ops = [ - (qml.PauliX, dev._apply_x), - (qml.PauliY, dev._apply_y), - (qml.PauliZ, dev._apply_z), - (qml.Hadamard, dev._apply_hadamard), - (qml.S, dev._apply_s), - (qml.T, dev._apply_t), - (qml.SX, dev._apply_sx), - ] - two_qubit_ops = [ - (qml.CNOT, dev._apply_cnot), - (qml.SWAP, dev._apply_swap), - (qml.CZ, dev._apply_cz), - ] - three_qubit_ops = [ - (qml.Toffoli, dev._apply_toffoli), - ] - - @pytest.mark.parametrize("op, method", single_qubit_ops) - def test_apply_single_qubit_op_broadcasted_state(self, op, method): - """Test if the application of single qubit operations to a - broadcasted state is correct.""" - state_out = method(self.broadcasted_state, axes=[2]) - op = op(wires=[1]) - matrix = op.matrix() - state_out_einsum = np.einsum("ab,mibjk->miajk", matrix, self.broadcasted_state) - assert np.allclose(state_out, state_out_einsum) - - @pytest.mark.parametrize("op, method", two_qubit_ops) - def test_apply_two_qubit_op_broadcasted_state(self, op, method): - """Test if the application of two qubit operations to a - broadcasted state is correct.""" - state_out = method(self.broadcasted_state, axes=[1, 2]) - op = op(wires=[0, 1]) - matrix = op.matrix() - matrix = matrix.reshape((2, 2, 2, 2)) - state_out_einsum = np.einsum("abcd,mcdjk->mabjk", matrix, self.broadcasted_state) - assert np.allclose(state_out, state_out_einsum) - - @pytest.mark.parametrize("op, method", two_qubit_ops) - def test_apply_two_qubit_op_reverse_broadcasted_state(self, op, method): - """Test if the application of two qubit operations to a - broadcasted state is correct when the applied wires are reversed.""" - state_out = method(self.broadcasted_state, axes=[3, 2]) - op = op(wires=[2, 1]) - matrix = op.matrix() - matrix = matrix.reshape((2, 2, 2, 2)) - state_out_einsum = np.einsum("abcd,midck->mibak", matrix, self.broadcasted_state) - assert np.allclose(state_out, state_out_einsum) - - @pytest.mark.parametrize("op, method", three_qubit_ops) - def test_apply_three_qubit_op_controls_smaller_broadcasted_state(self, op, method): - """Test if the application of three qubit operations to a broadcasted - state is correct when both control wires are smaller than the target wire.""" - state_out = method(self.broadcasted_state, axes=[1, 3, 4]) - op = op(wires=[0, 2, 3]) - matrix = op.matrix() - matrix = matrix.reshape((2, 2) * 3) - state_out_einsum = np.einsum("abcdef,mdkef->makbc", matrix, self.broadcasted_state) - assert np.allclose(state_out, state_out_einsum) - - @pytest.mark.parametrize("op, method", three_qubit_ops) - def test_apply_three_qubit_op_controls_greater_broadcasted_state(self, op, method): - """Test if the application of three qubit operations to a broadcasted - state is correct when both control wires are greater than the target wire.""" - state_out = method(self.broadcasted_state, axes=[3, 2, 1]) - op = op(wires=[2, 1, 0]) - matrix = op.matrix() - matrix = matrix.reshape((2, 2) * 3) - state_out_einsum = np.einsum("abcdef,mfedk->mcbak", matrix, self.broadcasted_state) - assert np.allclose(state_out, state_out_einsum) - - @pytest.mark.parametrize("op, method", three_qubit_ops) - def test_apply_three_qubit_op_controls_split_broadcasted_state(self, op, method): - """Test if the application of three qubit operations to a broadcasted state is correct - when one control wire is smaller and one control wire is greater than the target wire.""" - state_out = method(self.broadcasted_state, axes=[4, 2, 3]) - op = op(wires=[3, 1, 2]) - matrix = op.matrix() - matrix = matrix.reshape((2, 2) * 3) - state_out_einsum = np.einsum("abcdef,mkdfe->mkacb", matrix, self.broadcasted_state) - assert np.allclose(state_out, state_out_einsum) - - -class TestStateVectorBroadcasted: - """Unit tests for the _apply_state_vector method with broadcasting""" - - def test_full_subsystem_broadcasted(self, mocker): - """Test applying a state vector to the full subsystem""" - dev = DefaultQubitLegacy(wires=["a", "b", "c"]) - state = np.array([[0, 1, 1, 0, 1, 1, 0, 0], [1, 0, 0, 0, 1, 0, 1, 1]]) / 2.0 - state_wires = qml.wires.Wires(["a", "b", "c"]) - - spy = mocker.spy(dev, "_scatter") - dev._apply_state_vector(state=state, device_wires=state_wires) - - assert np.all(dev._state.reshape((2, 8)) == state) - spy.assert_not_called() - - def test_partial_subsystem_broadcasted(self, mocker): - """Test applying a state vector to a subset of wires of the full subsystem""" - - dev = DefaultQubitLegacy(wires=["a", "b", "c"]) - state = np.array([[0, 1, 1, 0], [1, 0, 1, 0], [1, 1, 0, 0]]) / np.sqrt(2.0) - state_wires = qml.wires.Wires(["a", "c"]) - - spy = mocker.spy(dev, "_scatter") - dev._apply_state_vector(state=state, device_wires=state_wires) - # Axes are (broadcasting, wire "a", wire "b", wire "c"), so we sum over axis=2 - res = np.sum(dev._state, axis=(2,)).reshape((3, 4)) - - assert np.all(res == state) - spy.assert_called() - - -class TestApplyOperationBroadcasted: - """Unit tests for the internal _apply_operation method.""" - - def test_internal_apply_ops_case_broadcasted(self, monkeypatch): - """Tests that if we provide an operation that has an internal - implementation, then we use that specific implementation. - - This test provides a new internal function that `default.qubit.legacy` uses to - apply `PauliX` (rather than redefining the gate itself). - """ - dev = qml.device("default.qubit.legacy", wires=1) - - test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]]) - # Create a dummy operation - expected_test_output = np.ones(1) - supported_gate_application = lambda *args, **kwargs: expected_test_output - - with monkeypatch.context() as m: - # Set the internal ops implementations dict - m.setattr(dev.target_device, "_apply_ops", {"PauliX": supported_gate_application}) - - op = qml.PauliX(0) - - res = dev._apply_operation(test_state, op) - assert np.allclose(res, expected_test_output) - - def test_diagonal_operation_case_broadcasted(self, monkeypatch): - """Tests the case when the operation to be applied is - diagonal in the computational basis and the _apply_diagonal_unitary method is used.""" - dev = qml.device("default.qubit.legacy", wires=1) - par = 0.3 - - test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]]) - wires = 0 - op = qml.PhaseShift(par, wires=wires) - assert op.name not in dev._apply_ops - - # Set the internal _apply_diagonal_unitary - history = [] - mock_apply_diag = lambda state, matrix, wires: history.append((state, matrix, wires)) - with monkeypatch.context() as m: - m.setattr(dev.target_device, "_apply_diagonal_unitary", mock_apply_diag) - assert dev._apply_diagonal_unitary == mock_apply_diag - - dev._apply_operation(test_state, op) - - res_state, res_mat, res_wires = history[0] - - assert np.allclose(res_state, test_state) - assert np.allclose(res_mat, np.diag(op.matrix())) - assert np.allclose(res_wires, wires) - - def test_apply_einsum_case_broadcasted(self, monkeypatch): - """Tests the case when np.einsum is used to apply an operation in - default.qubit.""" - dev = qml.device("default.qubit.legacy", wires=1) - - test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]]) - wires = 0 - - # Redefine the S gate so that it is an example for a one-qubit gate - # that is not registered in the diagonal_in_z_basis attribute - # pylint: disable=too-few-public-methods - class TestSGate(qml.operation.Operation): - num_wires = 1 - - # pylint: disable=unused-argument - @staticmethod - def compute_matrix(*params, **hyperparams): - return np.array([[1, 0], [0, 1j]]) - - dev.operations.add("TestSGate") - op = TestSGate(wires=wires) - - assert op.name in dev.operations - assert op.name not in dev._apply_ops - - # Set the internal _apply_unitary_einsum - history = [] - mock_apply_einsum = lambda state, matrix, wires: history.append((state, matrix, wires)) - with monkeypatch.context() as m: - m.setattr(dev.target_device, "_apply_unitary_einsum", mock_apply_einsum) - - dev._apply_operation(test_state, op) - - res_state, res_mat, res_wires = history[0] - - assert np.allclose(res_state, test_state) - assert np.allclose(res_mat, op.matrix()) - assert np.allclose(res_wires, wires) - - def test_apply_tensordot_case_broadcasted(self, monkeypatch): - """Tests the case when np.tensordot is used to apply an operation in - default.qubit.""" - dev = qml.device("default.qubit.legacy", wires=3) - - test_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]]) - wires = [0, 1, 2] - - # Redefine the Toffoli gate so that it is an example for a gate with - # more than two wires - # pylint: disable=too-few-public-methods - class TestToffoli(qml.operation.Operation): - num_wires = 3 - - # pylint: disable=unused-argument - @staticmethod - def compute_matrix(*params, **hyperparams): - return Toffoli - - dev.operations.add("TestToffoli") - op = TestToffoli(wires=wires) - - assert op.name in dev.operations - assert op.name not in dev._apply_ops - - # Set the internal _apply_unitary_tensordot - history = [] - mock_apply_tensordot = lambda state, matrix, wires: history.append((state, matrix, wires)) - - with monkeypatch.context() as m: - m.setattr(dev.target_device, "_apply_unitary", mock_apply_tensordot) - - dev._apply_operation(test_state, op) - - res_state, res_mat, res_wires = history[0] - - assert np.allclose(res_state, test_state) - assert np.allclose(res_mat, op.matrix()) - assert np.allclose(res_wires, wires) - - def test_identity_skipped_broadcasted(self, mocker): - """Test that applying the identity operation does not perform any additional computations.""" - dev = qml.device("default.qubit.legacy", wires=1) - - starting_state = np.array([[1, 0], [INVSQ2, INVSQ2], [0, 1]]) - op = qml.Identity(0) - - spy_diagonal = mocker.spy(dev.target_device, "_apply_diagonal_unitary") - spy_einsum = mocker.spy(dev.target_device, "_apply_unitary_einsum") - spy_unitary = mocker.spy(dev.target_device, "_apply_unitary") - - res = dev._apply_operation(starting_state, op) - assert res is starting_state - - spy_diagonal.assert_not_called() - spy_einsum.assert_not_called() - spy_unitary.assert_not_called() - - -class TestHamiltonianSupportBroadcasted: - """Tests the devices' native support for Hamiltonian observables.""" - - def test_do_not_split_analytic_broadcasted(self, mocker): - """Tests that the Hamiltonian is not split for shots=None.""" - dev = qml.device("default.qubit.legacy", wires=2) - Ham = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)]) - - @qml.qnode(dev, diff_method="parameter-shift", interface=None) - def circuit(): - qml.RX(np.zeros(5), 0) # Broadcast the state by applying a broadcasted identity - return qml.expval(Ham) - - spy = mocker.spy(dev.target_device, "expval") - - circuit() - # evaluated one expval altogether - assert spy.call_count == 1 - - def test_split_finite_shots_broadcasted(self, mocker): - """Tests that the Hamiltonian is split for finite shots.""" - dev = qml.device("default.qubit.legacy", wires=2, shots=10) - spy = mocker.spy(dev.target_device, "expval") - - ham = qml.Hamiltonian(np.array([0.1, 0.2]), [qml.PauliX(0), qml.PauliZ(1)]) - - @qml.qnode(dev) - def circuit(): - qml.RX(np.zeros(5), 0) # Broadcast the state by applying a broadcasted identity - return qml.expval(ham) - - circuit() - - # evaluated one expval per Pauli observable - assert spy.call_count == 2 - - -original_capabilities = qml.devices.DefaultQubitLegacy.capabilities() - - -@pytest.fixture(scope="function", name="mock_default_qubit") -def mock_default_qubit_fixture(monkeypatch): - """A function to create a mock device that mocks the broadcasting support flag - to be False, so that default support via broadcast_expand transform can be tested""" - - # pylint: disable=unused-argument - def overwrite_support(*cls): - capabilities = original_capabilities.copy() - capabilities.update(supports_broadcasting=False) - return capabilities - - with monkeypatch.context() as m: - m.setattr(qml.devices.DefaultQubitLegacy, "capabilities", overwrite_support) - - def get_default_qubit(wires=1, shots=None): - dev = qml.devices.DefaultQubitLegacy(wires=wires, shots=shots) - return dev - - yield get_default_qubit - - -@pytest.mark.parametrize("shots", [None, 100000]) -class TestBroadcastingSupportViaExpansion: - """Tests that the device correctly makes use of ``broadcast_expand`` to - execute broadcasted tapes if its capability to execute broadcasted tapes - is artificially deactivated.""" - - @pytest.mark.parametrize("x", [0.2, np.array([0.1, 0.6, 0.3]), np.array([0.1])]) - def test_with_single_broadcasted_par(self, x, shots, mock_default_qubit): - """Test that broadcasting on a circuit with a - single parametrized operation works.""" - dev = mock_default_qubit(wires=2, shots=shots) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x): - qml.RX(x, wires=0) - return qml.expval(qml.PauliZ(0)) - - circuit.construct((np.array(x),), {}) - out = circuit(np.array(x)) - - assert circuit.device.num_executions == (1 if isinstance(x, float) else len(x)) - tol = 1e-10 if shots is None else 1e-2 - assert qml.math.allclose(out, qml.math.cos(x), atol=tol, rtol=0) - - @pytest.mark.parametrize( - "x, y", [(0.2, np.array([0.4])), (np.array([0.1, 5.1]), np.array([0.1, -0.3]))] - ) - def test_with_multiple_pars(self, x, y, shots, mock_default_qubit): - """Test that broadcasting on a circuit with a - single parametrized operation works.""" - dev = mock_default_qubit(wires=2, shots=shots) - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x, y): - qml.RX(x, wires=0) - qml.RX(y, wires=1) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) - - out = circuit(x, y) - expected = qml.math.stack([qml.math.cos(x) * qml.math.ones_like(y), -qml.math.sin(y)]) - - assert circuit.device.num_executions == len(y) - tol = 1e-10 if shots is None else 1e-2 - - assert qml.math.allclose(out[0], expected[0], atol=tol, rtol=0) - assert qml.math.allclose(out[1], expected[1], atol=tol, rtol=0) - - @pytest.mark.parametrize( - "x, y", [(0.2, np.array([0.4])), (np.array([0.1, 5.1]), np.array([0.1, -0.3]))] - ) - def test_with_Hamiltonian(self, x, y, shots, mock_default_qubit): - """Test that broadcasting on a circuit with a - single parametrized operation works.""" - dev = mock_default_qubit(wires=2, shots=shots) - - Ham = qml.Hamiltonian([0.3, 0.9], [qml.PauliZ(0), qml.PauliY(1)]) - Ham.compute_grouping() - - @qml.qnode(dev, diff_method="parameter-shift") - def circuit(x, y): - qml.RX(x, wires=0) - qml.RX(y, wires=1) - return qml.expval(Ham) - - out = circuit(x, y) - expected = 0.3 * qml.math.cos(x) * qml.math.ones_like(y) - 0.9 * qml.math.sin(y) - - assert circuit.device.num_executions == len(y) - tol = 1e-10 if shots is None else 1e-2 - assert qml.math.allclose(out, expected, atol=tol, rtol=0) diff --git a/tests/devices/test_legacy_facade.py b/tests/devices/test_legacy_facade.py index dac63c2864e..4af1ae1e77a 100644 --- a/tests/devices/test_legacy_facade.py +++ b/tests/devices/test_legacy_facade.py @@ -21,7 +21,6 @@ import pytest import pennylane as qml -from pennylane.devices.default_qubit_autograd import DefaultQubitAutograd from pennylane.devices.execution_config import ExecutionConfig from pennylane.devices.legacy_facade import ( LegacyDeviceFacade, @@ -401,80 +400,7 @@ class BackpropDevice(DummyDevice): dev = LegacyDeviceFacade(BackpropDevice(wires=2, shots=None)) - x = qml.numpy.array(0.1) - tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))]) - assert dev.supports_derivatives(qml.devices.ExecutionConfig(gradient_method="backprop")) - assert dev._create_temp_device((tape,)) is dev.target_device config = qml.devices.ExecutionConfig(gradient_method="backprop", use_device_gradient=True) assert dev.preprocess(config)[1] is config # unchanged - - def test_backprop_has_passthru_devices(self): - """Test that backprop is supported if the device has passthru devices.""" - - class BackpropDevice(DummyDevice): - - _capabilities = {"passthru_devices": {"autograd": "default.qubit.autograd"}} - - dev = LegacyDeviceFacade(BackpropDevice(shots=None)) - - x = qml.numpy.array(0.1) - tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))]) - assert dev.supports_derivatives() - assert dev.supports_derivatives(ExecutionConfig(gradient_method="backprop")) - assert dev.supports_derivatives(ExecutionConfig(gradient_method="backprop"), tape) - - config = qml.devices.ExecutionConfig(gradient_method="backprop", use_device_gradient=True) - assert dev.preprocess(config)[1] is config # unchanged - - with pytest.warns(qml.PennyLaneDeprecationWarning, match="switching of devices"): - tmp_device = dev._create_temp_device((tape,)) - assert tmp_device.short_name == "default.qubit.autograd" - - def test_backprop_passthru_device_self(self): - """Test that the temporary device is the original device if the passthru device is itself.""" - - class BackpropSelfDevice(DummyDevice): - - short_name = "BackpropSelfDevice" - - _capabilities = {"passthru_devices": {"autograd": "BackpropSelfDevice"}} - - dev = LegacyDeviceFacade(BackpropSelfDevice(wires=2)) - - x = qml.numpy.array(0.1) - tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))]) - tmp_dev = dev._create_temp_device((tape,)) - assert tmp_dev is dev.target_device - - def test_passthru_device_does_not_exist(self): - """Test that if backprop is requested for a device that does not support it, a device error is raised.""" - - x = qml.numpy.array(0.1) - tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))]) - - dev = LegacyDeviceFacade(DummyDevice(wires=2)) - config = qml.devices.ExecutionConfig(gradient_method="backprop") - with pytest.raises(qml.DeviceError, match=r"does not support backpropagation"): - dev.execute(tape, config) - - @pytest.mark.parametrize("dev_class", (qml.devices.DefaultQubitLegacy, DefaultQubitAutograd)) - def test_backprop_device_substitution(self, dev_class): - """Test that default.qubit.legacy is substituted for a backprop device during backprop execution.""" - - with pytest.warns(qml.PennyLaneDeprecationWarning, match="use 'default.qubit'"): - dq_legacy = dev_class(wires=2) - dev = LegacyDeviceFacade(dq_legacy) - - def f(x): - tape = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.expval(qml.Z(0))]) - return dev.execute(tape, qml.devices.ExecutionConfig(gradient_method="backprop")) - - assert qml.math.allclose(dq_legacy.state, np.array([1, 0, 0, 0])) - - with dev.tracker: - g = qml.grad(f)(qml.numpy.array(0.5)) - assert qml.math.allclose(g, -np.sin(0.5)) - assert dev.tracker.totals["executions"] == 1 - assert not qml.math.allclose(dq_legacy.state, np.array([1, 0, 0, 0])) diff --git a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py index d7a0b893ce0..51ffd43753b 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py +++ b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py @@ -864,7 +864,6 @@ def cost_fn(params): assert np.allclose(res[0], expval_expected[0], atol=finite_diff_tol) assert np.allclose(res[1], expval_expected[1], atol=finite_diff_tol) - @pytest.mark.autograd @pytest.mark.parametrize("RX, RY, argnum", [(RX_with_F, qml.RY, 0), (qml.RX, RY_with_F, 1)]) def test_fallback_probs( self, RX, RY, argnum, mocker, broadcast diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py deleted file mode 100644 index 03072f2515d..00000000000 --- a/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py +++ /dev/null @@ -1,1367 +0,0 @@ -# Copyright 2018-2021 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Unit tests for the autograd interface""" -# pylint: disable=protected-access,too-few-public-methods -import sys - -import autograd -import pytest - -import pennylane as qml -from pennylane import numpy as np -from pennylane.devices import DefaultQubitLegacy -from pennylane.gradients import finite_diff, param_shift -from pennylane.operation import AnyWires, Observable - -pytestmark = pytest.mark.autograd - - -class TestAutogradExecuteUnitTests: - """Unit tests for autograd execution""" - - def test_import_error(self, mocker): - """Test that an exception is caught on import error""" - - mock = mocker.patch.object(autograd.extend, "defvjp") - mock.side_effect = ImportError() - - try: - del sys.modules["pennylane.workflow.interfaces.autograd"] - except KeyError: - pass - - dev = qml.device("default.qubit.legacy", wires=2, shots=None) - - with qml.queuing.AnnotatedQueue() as q: - qml.expval(qml.PauliY(1)) - - tape = qml.tape.QuantumScript.from_queue(q) - with pytest.raises( - qml.QuantumFunctionError, - match="autograd not found. Please install the latest version " - "of autograd to enable the 'autograd' interface", - ): - qml.execute([tape], dev, gradient_fn=param_shift, interface="autograd") - - def test_jacobian_options(self, mocker): - """Test setting jacobian options""" - spy = mocker.spy(qml.gradients, "param_shift") - - a = np.array([0.1, 0.2], requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=1) - - def cost(a, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute( - [tape], - device, - gradient_fn=param_shift, - gradient_kwargs={"shifts": [(np.pi / 4,)] * 2}, - )[0] - - qml.jacobian(cost)(a, device=dev) - - for args in spy.call_args_list: - assert args[1]["shifts"] == [(np.pi / 4,)] * 2 - - def test_incorrect_grad_on_execution(self): - """Test that an error is raised if a gradient transform - is used with grad_on_execution=True""" - a = np.array([0.1, 0.2], requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=1) - - def cost(a, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], device, gradient_fn=param_shift, grad_on_execution=True)[0] - - with pytest.raises( - ValueError, match="Gradient transforms cannot be used with grad_on_execution=True" - ): - qml.jacobian(cost)(a, device=dev) - - def test_unknown_interface(self): - """Test that an error is raised if the interface is unknown""" - a = np.array([0.1, 0.2], requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=1) - - def cost(a, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], device, gradient_fn=param_shift, interface="None")[0] - - with pytest.raises(ValueError, match="interface must be in"): - cost(a, device=dev) - - def test_grad_on_execution(self, mocker): - """Test that grad on execution uses the `device.execute_and_gradients` pathway""" - dev = qml.device("default.qubit.legacy", wires=1) - spy = mocker.spy(dev, "execute_and_compute_derivatives") - - def cost(a): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute( - [tape], - dev, - gradient_fn="device", - gradient_kwargs={"method": "adjoint_jacobian", "use_device_state": True}, - )[0] - - a = np.array([0.1, 0.2], requires_grad=True) - cost(a) - - # adjoint method only performs a single device execution, but gets both result and gradient - assert dev.num_executions == 1 - spy.assert_called() - - def test_no_gradients_on_execution(self, mocker): - """Test that no grad on execution uses the `device.batch_execute` and `device.gradients` pathway""" - dev = qml.device("default.qubit.legacy", wires=1) - spy_execute = mocker.spy(qml.devices.DefaultQubitLegacy, "batch_execute") - spy_gradients = mocker.spy(qml.devices.DefaultQubitLegacy, "gradients") - - def cost(a): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute( - [tape], - dev, - gradient_fn="device", - grad_on_execution=False, - gradient_kwargs={"method": "adjoint_jacobian"}, - )[0] - - a = np.array([0.1, 0.2], requires_grad=True) - cost(a) - - assert dev.num_executions == 1 - spy_execute.assert_called() - spy_gradients.assert_not_called() - - qml.jacobian(cost)(a) - spy_gradients.assert_called() - - -class TestBatchTransformExecution: - """Tests to ensure batch transforms can be correctly executed - via qml.execute and batch_transform""" - - def test_batch_transform_dynamic_shots(self): - """Tests that the batch transform considers the number of shots for the execution, not those - statically on the device.""" - dev = qml.device("default.qubit.legacy", wires=1) - H = 2.0 * qml.PauliZ(0) - qscript = qml.tape.QuantumScript(measurements=[qml.expval(H)]) - res = qml.execute([qscript], dev, interface=None) - assert res == (2.0,) - - -class TestCaching: - """Test for caching behaviour""" - - def test_cache_maxsize(self, mocker): - """Test the cachesize property of the cache""" - dev = qml.device("default.qubit.legacy", wires=1) - spy = mocker.spy(qml.workflow.execution._cache_transform, "_transform") - - def cost(a, cachesize): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.probs(wires=0) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], dev, gradient_fn=param_shift, cachesize=cachesize)[0] - - params = np.array([0.1, 0.2]) - qml.jacobian(cost)(params, cachesize=2) - cache = spy.call_args.kwargs["cache"] - - assert cache.maxsize == 2 - assert cache.currsize == 2 - assert len(cache) == 2 - - def test_custom_cache(self, mocker): - """Test the use of a custom cache object""" - dev = qml.device("default.qubit.legacy", wires=1) - spy = mocker.spy(qml.workflow.execution._cache_transform, "_transform") - - def cost(a, cache): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.probs(wires=0) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], dev, gradient_fn=param_shift, cache=cache)[0] - - custom_cache = {} - params = np.array([0.1, 0.2]) - qml.jacobian(cost)(params, cache=custom_cache) - - cache = spy.call_args.kwargs["cache"] - assert cache is custom_cache - - def test_caching_param_shift(self, tol): - """Test that, when using parameter-shift transform, - caching reduces the number of evaluations to their optimum.""" - dev = qml.device("default.qubit.legacy", wires=1) - - def cost(a, cache): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.probs(wires=0) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], dev, gradient_fn=param_shift, cache=cache)[0] - - # Without caching, 5 jacobians should still be performed - params = np.array([0.1, 0.2]) - qml.jacobian(cost)(params, cache=None) - assert dev.num_executions == 5 - - # With caching, 5 evaluations are required to compute - # the Jacobian: 1 (forward pass) + (2 shifts * 2 params) - dev.target_device._num_executions = 0 - jac_fn = qml.jacobian(cost) - grad1 = jac_fn(params, cache=True) - assert dev.num_executions == 5 - - # Check that calling the cost function again - # continues to evaluate the device (that is, the cache - # is emptied between calls) - grad2 = jac_fn(params, cache=True) - assert dev.num_executions == 10 - assert np.allclose(grad1, grad2, atol=tol, rtol=0) - - # Check that calling the cost function again - # with different parameters produces a different Jacobian - grad2 = jac_fn(2 * params, cache=True) - assert dev.num_executions == 15 - assert not np.allclose(grad1, grad2, atol=tol, rtol=0) - - @pytest.mark.parametrize("num_params", [2, 3]) - def test_caching_param_shift_hessian(self, num_params, tol): - """Test that, when using parameter-shift transform, - caching reduces the number of evaluations to their optimum - when computing Hessians.""" - dev = qml.device("default.qubit.legacy", wires=2) - params = np.arange(1, num_params + 1) / 10 - - N = len(params) - - def cost(x, cache): - with qml.queuing.AnnotatedQueue() as q: - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - - for i in range(2, num_params): - qml.RZ(x[i], wires=[i % 2]) - - qml.CNOT(wires=[0, 1]) - qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], dev, gradient_fn=param_shift, cache=cache, max_diff=2)[0] - - # No caching: number of executions is not ideal - hess1 = qml.jacobian(qml.grad(cost))(params, cache=False) - - if num_params == 2: - # compare to theoretical result - x, y, *_ = params - expected = np.array( - [ - [2 * np.cos(2 * x) * np.sin(y) ** 2, np.sin(2 * x) * np.sin(2 * y)], - [np.sin(2 * x) * np.sin(2 * y), -2 * np.cos(x) ** 2 * np.cos(2 * y)], - ] - ) - assert np.allclose(expected, hess1, atol=tol, rtol=0) - - expected_runs = 1 # forward pass - - # Jacobian of an involutory observable: - # ------------------------------------ - # - # 2 * N execs: evaluate the analytic derivative of - # 1 execs: Get , the expectation value of the tape with unshifted parameters. - num_shifted_evals = 2 * N - runs_for_jacobian = num_shifted_evals + 1 - expected_runs += runs_for_jacobian - - # Each tape used to compute the Jacobian is then shifted again - expected_runs += runs_for_jacobian * num_shifted_evals - assert dev.num_executions == expected_runs - - # Use caching: number of executions is ideal - dev.target_device._num_executions = 0 - hess2 = qml.jacobian(qml.grad(cost))(params, cache=True) - assert np.allclose(hess1, hess2, atol=tol, rtol=0) - - expected_runs_ideal = 1 # forward pass - expected_runs_ideal += 2 * N # Jacobian - expected_runs_ideal += N + 1 # Hessian diagonal - expected_runs_ideal += 4 * N * (N - 1) // 2 # Hessian off-diagonal - assert dev.num_executions == expected_runs_ideal - assert expected_runs_ideal < expected_runs - - def test_caching_adjoint_no_grad_on_execution(self): - """Test that caching reduces the number of adjoint evaluations - when the grads is not on execution.""" - dev = qml.device("default.qubit.legacy", wires=2) - params = np.array([0.1, 0.2, 0.3]) - - def cost(a, cache): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.RY(a[2], wires=0) - qml.expval(qml.PauliZ(0)) - qml.expval(qml.PauliZ(1)) - - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack( - qml.execute( - [tape], - dev, - gradient_fn="device", - cache=cache, - grad_on_execution=False, - gradient_kwargs={"method": "adjoint_jacobian"}, - )[0] - ) - - # no caching, but jac for each batch still stored. - qml.jacobian(cost)(params, cache=None) - assert dev.num_executions == 1 - - # With caching, only 1 evaluation required. - dev.target_device._num_executions = 0 - jac_fn = qml.jacobian(cost) - jac_fn(params, cache=True) - assert dev.num_executions == 1 - - def test_single_backward_pass_batch(self): - """Tests that the backward pass is one single batch, not a bunch of batches, when parameter shift - is requested for multiple tapes.""" - - dev = qml.device("default.qubit.legacy", wires=2) - - def f(x): - tape1 = qml.tape.QuantumScript([qml.RX(x, 0)], [qml.probs(wires=0)]) - tape2 = qml.tape.QuantumScript([qml.RY(x, 0)], [qml.probs(wires=0)]) - - results = qml.execute([tape1, tape2], dev, gradient_fn=qml.gradients.param_shift) - return results[0] + results[1] - - x = qml.numpy.array(0.1) - with dev.tracker: - out = qml.jacobian(f)(x) - - assert dev.tracker.totals["batches"] == 2 - assert dev.tracker.history["batch_len"] == [2, 4] - expected = [-2 * np.cos(x / 2) * np.sin(x / 2), 2 * np.sin(x / 2) * np.cos(x / 2)] - assert qml.math.allclose(out, expected) - - def test_single_backward_pass_split_hamiltonian(self): - """Tests that the backward pass is one single batch, not a bunch of batches, when parameter - shift derivatives are requested for a tape that the device split into batches.""" - - dev = qml.device("default.qubit.legacy", wires=2, shots=50000) - - H = qml.Hamiltonian([1, 1], [qml.PauliY(0), qml.PauliZ(0)], grouping_type="qwc") - - def f(x): - tape = qml.tape.QuantumScript([qml.RX(x, wires=0)], [qml.expval(H)]) - return qml.execute([tape], dev, gradient_fn=qml.gradients.param_shift)[0] - - x = qml.numpy.array(0.1) - with dev.tracker: - out = qml.grad(f)(x) - - assert dev.tracker.totals["batches"] == 2 - assert dev.tracker.history["batch_len"] == [1, 2] - - assert qml.math.allclose(out, -np.cos(x) - np.sin(x), atol=0.05) - - -execute_kwargs_integration = [ - {"gradient_fn": param_shift}, - { - "gradient_fn": "device", - "grad_on_execution": True, - "gradient_kwargs": {"method": "adjoint_jacobian", "use_device_state": True}, - }, - { - "gradient_fn": "device", - "grad_on_execution": False, - "gradient_kwargs": {"method": "adjoint_jacobian"}, - }, -] - - -@pytest.mark.parametrize("execute_kwargs", execute_kwargs_integration) -class TestAutogradExecuteIntegration: - """Test the autograd interface execute function - integrates well for both forward and backward execution""" - - def test_execution(self, execute_kwargs): - """Test execution""" - dev = qml.device("default.qubit.legacy", wires=1) - - def cost(a, b): - with qml.queuing.AnnotatedQueue() as q1: - qml.RY(a, wires=0) - qml.RX(b, wires=0) - qml.expval(qml.PauliZ(0)) - - tape1 = qml.tape.QuantumScript.from_queue(q1) - with qml.queuing.AnnotatedQueue() as q2: - qml.RY(a, wires=0) - qml.RX(b, wires=0) - qml.expval(qml.PauliZ(0)) - - tape2 = qml.tape.QuantumScript.from_queue(q2) - return qml.execute([tape1, tape2], dev, **execute_kwargs) - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) - res = cost(a, b) - - assert len(res) == 2 - assert res[0].shape == () - assert res[1].shape == () - - def test_scalar_jacobian(self, execute_kwargs, tol): - """Test scalar jacobian calculation""" - a = np.array(0.1, requires_grad=True) - dev = qml.device("default.qubit.legacy", wires=2) - - def cost(a): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a, wires=0) - qml.expval(qml.PauliZ(0)) - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], dev, **execute_kwargs)[0] - - res = qml.jacobian(cost)(a) - assert res.shape == () - - # compare to standard tape jacobian - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a, wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - tape.trainable_params = [0] - tapes, fn = param_shift(tape) - expected = fn(dev.batch_execute(tapes)) - - assert expected.shape == () - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_jacobian(self, execute_kwargs, tol): - """Test jacobian calculation""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - def cost(a, b, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - qml.expval(qml.PauliY(1)) - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0]) - - dev = qml.device("default.qubit.legacy", wires=2) - - res = cost(a, b, device=dev) - expected = [np.cos(a), -np.cos(a) * np.sin(b)] - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.jacobian(cost)(a, b, device=dev) - assert isinstance(res, tuple) and len(res) == 2 - assert res[0].shape == (2,) - assert res[1].shape == (2,) - - expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)]) - assert all(np.allclose(_r, _e, atol=tol, rtol=0) for _r, _e in zip(res, expected)) - - @pytest.mark.filterwarnings("ignore:Attempted to compute the gradient") - def test_tape_no_parameters(self, execute_kwargs, tol): - """Test that a tape with no parameters is correctly - ignored during the gradient computation""" - - if execute_kwargs["gradient_fn"] == "device": - pytest.skip("Adjoint differentiation does not yet support probabilities") - - dev = qml.device("default.qubit.legacy", wires=2) - - def cost(params): - with qml.queuing.AnnotatedQueue() as q1: - qml.Hadamard(0) - qml.expval(qml.PauliX(0)) - - tape1 = qml.tape.QuantumScript.from_queue(q1) - with qml.queuing.AnnotatedQueue() as q2: - qml.RY(np.array(0.5, requires_grad=False), wires=0) - qml.expval(qml.PauliZ(0)) - - tape2 = qml.tape.QuantumScript.from_queue(q2) - with qml.queuing.AnnotatedQueue() as q3: - qml.RY(params[0], wires=0) - qml.RX(params[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape3 = qml.tape.QuantumScript.from_queue(q3) - with qml.queuing.AnnotatedQueue() as q4: - qml.RY(np.array(0.5, requires_grad=False), wires=0) - qml.probs(wires=[0, 1]) - - tape4 = qml.tape.QuantumScript.from_queue(q4) - return sum( - autograd.numpy.hstack( - qml.execute([tape1, tape2, tape3, tape4], dev, **execute_kwargs) - ) - ) - - params = np.array([0.1, 0.2], requires_grad=True) - x, y = params - - res = cost(params) - expected = 2 + np.cos(0.5) + np.cos(x) * np.cos(y) - assert np.allclose(res, expected, atol=tol, rtol=0) - - grad = qml.grad(cost)(params) - expected = [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)] - assert np.allclose(grad, expected, atol=tol, rtol=0) - - @pytest.mark.filterwarnings("ignore:Attempted to compute the gradient") - def test_tapes_with_different_return_size(self, execute_kwargs): - """Test that tapes wit different can be executed and differentiated.""" - dev = qml.device("default.qubit.legacy", wires=2) - - def cost(params): - with qml.queuing.AnnotatedQueue() as q1: - qml.RY(params[0], wires=0) - qml.RX(params[1], wires=0) - qml.expval(qml.PauliZ(0)) - qml.expval(qml.PauliZ(1)) - - tape1 = qml.tape.QuantumScript.from_queue(q1) - with qml.queuing.AnnotatedQueue() as q2: - qml.RY(np.array(0.5, requires_grad=False), wires=0) - qml.expval(qml.PauliZ(0)) - - tape2 = qml.tape.QuantumScript.from_queue(q2) - with qml.queuing.AnnotatedQueue() as q3: - qml.RY(params[0], wires=0) - qml.RX(params[1], wires=0) - qml.expval(qml.PauliZ(0)) - - tape3 = qml.tape.QuantumScript.from_queue(q3) - return autograd.numpy.hstack(qml.execute([tape1, tape2, tape3], dev, **execute_kwargs)) - - params = np.array([0.1, 0.2], requires_grad=True) - - res = cost(params) - assert isinstance(res, np.ndarray) - assert res.shape == (4,) - - jac = qml.jacobian(cost)(params) - assert isinstance(jac, np.ndarray) - assert jac.shape == (4, 2) - - def test_reusing_quantum_tape(self, execute_kwargs, tol): - """Test re-using a quantum tape by passing new parameters""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=2) - - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - qml.expval(qml.PauliY(1)) - - tape = qml.tape.QuantumScript.from_queue(q) - assert tape.trainable_params == [0, 1] - - def cost(a, b): - new_tape = tape.bind_new_parameters([a, b], [0, 1]) - return autograd.numpy.hstack(qml.execute([new_tape], dev, **execute_kwargs)[0]) - - jac_fn = qml.jacobian(cost) - jac = jac_fn(a, b) - - a = np.array(0.54, requires_grad=True) - b = np.array(0.8, requires_grad=True) - - # check that the cost function continues to depend on the - # values of the parameters for subsequent calls - res2 = cost(2 * a, b) - expected = [np.cos(2 * a), -np.cos(2 * a) * np.sin(b)] - assert np.allclose(res2, expected, atol=tol, rtol=0) - - jac_fn = qml.jacobian(lambda a, b: cost(2 * a, b)) - jac = jac_fn(a, b) - expected = ( - [-2 * np.sin(2 * a), 2 * np.sin(2 * a) * np.sin(b)], - [0, -np.cos(2 * a) * np.cos(b)], - ) - assert isinstance(jac, tuple) and len(jac) == 2 - assert all(np.allclose(_j, _e, atol=tol, rtol=0) for _j, _e in zip(jac, expected)) - - def test_classical_processing(self, execute_kwargs): - """Test classical processing within the quantum tape""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) - c = np.array(0.3, requires_grad=True) - - def cost(a, b, c, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a * c, wires=0) - qml.RZ(b, wires=0) - qml.RX(c + c**2 + np.sin(a), wires=0) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], device, **execute_kwargs)[0] - - dev = qml.device("default.qubit.legacy", wires=2) - res = qml.jacobian(cost)(a, b, c, device=dev) - - # Only two arguments are trainable - assert isinstance(res, tuple) and len(res) == 2 - assert res[0].shape == () - assert res[1].shape == () - - def test_no_trainable_parameters(self, execute_kwargs): - """Test evaluation and Jacobian if there are no trainable parameters""" - a = np.array(0.1, requires_grad=False) - b = np.array(0.2, requires_grad=False) - - def cost(a, b, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RY(a, wires=0) - qml.RX(b, wires=0) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - qml.expval(qml.PauliZ(1)) - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0]) - - dev = qml.device("default.qubit.legacy", wires=2) - res = cost(a, b, device=dev) - assert res.shape == (2,) - - with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"): - res = qml.jacobian(cost)(a, b, device=dev) - assert len(res) == 0 - - def loss(a, b): - return np.sum(cost(a, b, device=dev)) - - with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"): - res = qml.grad(loss)(a, b) - - assert np.allclose(res, 0) - - def test_matrix_parameter(self, execute_kwargs, tol): - """Test that the autograd interface works correctly - with a matrix parameter""" - U = np.array([[0, 1], [1, 0]], requires_grad=False) - a = np.array(0.1, requires_grad=True) - - def cost(a, U, device): - with qml.queuing.AnnotatedQueue() as q: - qml.QubitUnitary(U, wires=0) - qml.RY(a, wires=0) - qml.expval(qml.PauliZ(0)) - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute([tape], device, **execute_kwargs)[0] - - dev = qml.device("default.qubit.legacy", wires=2) - res = cost(a, U, device=dev) - assert isinstance(res, np.ndarray) - assert np.allclose(res, -np.cos(a), atol=tol, rtol=0) - - jac_fn = qml.jacobian(cost) - jac = jac_fn(a, U, device=dev) - assert isinstance(jac, np.ndarray) - assert np.allclose(jac, np.sin(a), atol=tol, rtol=0) - - def test_differentiable_expand(self, execute_kwargs, tol): - """Test that operation and nested tapes expansion - is differentiable""" - - class U3(qml.U3): - def decomposition(self): - theta, phi, lam = self.data - wires = self.wires - return [ - qml.Rot(lam, theta, -lam, wires=wires), - qml.PhaseShift(phi + lam, wires=wires), - ] - - def cost_fn(a, p, device): - with qml.queuing.AnnotatedQueue() as q_tape: - qml.RX(a, wires=0) - U3(*p, wires=0) - qml.expval(qml.PauliX(0)) - - tape = qml.tape.QuantumScript.from_queue(q_tape) - tape = tape.expand(stop_at=lambda obj: device.supports_operation(obj.name)) - return qml.execute([tape], device, **execute_kwargs)[0] - - a = np.array(0.1, requires_grad=False) - p = np.array([0.1, 0.2, 0.3], requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=1) - res = cost_fn(a, p, device=dev) - expected = np.cos(a) * np.cos(p[1]) * np.sin(p[0]) + np.sin(a) * ( - np.cos(p[2]) * np.sin(p[1]) + np.cos(p[0]) * np.cos(p[1]) * np.sin(p[2]) - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - jac_fn = qml.jacobian(cost_fn) - res = jac_fn(a, p, device=dev) - expected = np.array( - [ - np.cos(p[1]) * (np.cos(a) * np.cos(p[0]) - np.sin(a) * np.sin(p[0]) * np.sin(p[2])), - np.cos(p[1]) * np.cos(p[2]) * np.sin(a) - - np.sin(p[1]) - * (np.cos(a) * np.sin(p[0]) + np.cos(p[0]) * np.sin(a) * np.sin(p[2])), - np.sin(a) - * (np.cos(p[0]) * np.cos(p[1]) * np.cos(p[2]) - np.sin(p[1]) * np.sin(p[2])), - ] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_probability_differentiation(self, execute_kwargs, tol): - """Tests correct output shape and evaluation for a tape - with prob outputs""" - - if execute_kwargs["gradient_fn"] == "device": - pytest.skip("Adjoint differentiation does not yet support probabilities") - - def cost(x, y, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.probs(wires=[0]) - qml.probs(wires=[1]) - - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0]) - - dev = qml.device("default.qubit.legacy", wires=2) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - res = cost(x, y, device=dev) - expected = np.array( - [ - [ - np.cos(x / 2) ** 2, - np.sin(x / 2) ** 2, - (1 + np.cos(x) * np.cos(y)) / 2, - (1 - np.cos(x) * np.cos(y)) / 2, - ], - ] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - jac_fn = qml.jacobian(cost) - res = jac_fn(x, y, device=dev) - assert isinstance(res, tuple) and len(res) == 2 - assert res[0].shape == (4,) - assert res[1].shape == (4,) - - expected = ( - np.array( - [ - [ - -np.sin(x) / 2, - np.sin(x) / 2, - -np.sin(x) * np.cos(y) / 2, - np.sin(x) * np.cos(y) / 2, - ], - ] - ), - np.array( - [ - [0, 0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2], - ] - ), - ) - - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - def test_ragged_differentiation(self, execute_kwargs, tol): - """Tests correct output shape and evaluation for a tape - with prob and expval outputs""" - if execute_kwargs["gradient_fn"] == "device": - pytest.skip("Adjoint differentiation does not yet support probabilities") - - def cost(x, y, device): - with qml.queuing.AnnotatedQueue() as q: - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - qml.probs(wires=[1]) - - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack(qml.execute([tape], device, **execute_kwargs)[0]) - - dev = qml.device("default.qubit.legacy", wires=2) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - res = cost(x, y, device=dev) - expected = np.array( - [np.cos(x), (1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - jac_fn = qml.jacobian(cost) - res = jac_fn(x, y, device=dev) - assert isinstance(res, tuple) and len(res) == 2 - assert res[0].shape == (3,) - assert res[1].shape == (3,) - - expected = ( - np.array([-np.sin(x), -np.sin(x) * np.cos(y) / 2, np.sin(x) * np.cos(y) / 2]), - np.array([0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]), - ) - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - def test_sampling(self, execute_kwargs): - """Test sampling works as expected""" - if execute_kwargs["gradient_fn"] == "device" and ( - execute_kwargs["grad_on_execution"] is True - or execute_kwargs["gradient_kwargs"]["method"] == "adjoint_jacobian" - ): - pytest.skip("Adjoint differentiation does not support samples") - - shots = 10 - - def cost(device): - with qml.queuing.AnnotatedQueue() as q: - qml.Hadamard(wires=[0]) - qml.CNOT(wires=[0, 1]) - qml.sample(qml.PauliZ(0)) - qml.sample(qml.PauliX(1)) - - tape = qml.tape.QuantumScript.from_queue(q, shots=10) - return qml.execute([tape], device, **execute_kwargs)[0] - - dev = qml.device("default.qubit.legacy", wires=2, shots=shots) - res = cost(device=dev) - assert isinstance(res, tuple) - assert len(res) == 2 - assert res[0].shape == (shots,) - assert res[1].shape == (shots,) - - -class TestHigherOrderDerivatives: - """Test that the autograd execute function can be differentiated""" - - @pytest.mark.parametrize( - "params", - [ - np.array([0.543, -0.654], requires_grad=True), - np.array([0, -0.654], requires_grad=True), - np.array([-2.0, 0], requires_grad=True), - ], - ) - def test_parameter_shift_hessian(self, params, tol): - """Tests that the output of the parameter-shift transform - can be differentiated using autograd, yielding second derivatives.""" - dev = qml.device("default.qubit.autograd", wires=2) - - def cost_fn(x): - with qml.queuing.AnnotatedQueue() as q1: - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - tape1 = qml.tape.QuantumScript.from_queue(q1) - with qml.queuing.AnnotatedQueue() as q2: - qml.RX(x[0], wires=0) - qml.RY(x[0], wires=1) - qml.CNOT(wires=[0, 1]) - qml.probs(wires=1) - - tape2 = qml.tape.QuantumScript.from_queue(q2) - result = qml.execute([tape1, tape2], dev, gradient_fn=param_shift, max_diff=2) - return result[0] + result[1][0] - - res = cost_fn(params) - x, y = params - expected = 0.5 * (3 + np.cos(x) ** 2 * np.cos(2 * y)) - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.grad(cost_fn)(params) - expected = np.array( - [-np.cos(x) * np.cos(2 * y) * np.sin(x), -np.cos(x) ** 2 * np.sin(2 * y)] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.jacobian(qml.grad(cost_fn))(params) - expected = np.array( - [ - [-np.cos(2 * x) * np.cos(2 * y), np.sin(2 * x) * np.sin(2 * y)], - [np.sin(2 * x) * np.sin(2 * y), -2 * np.cos(x) ** 2 * np.cos(2 * y)], - ] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_adjoint_hessian_one_param(self, tol): - """Since the adjoint hessian is not a differentiable transform, - higher-order derivatives are not supported.""" - dev = qml.device("default.qubit.autograd", wires=2) - params = np.array([0.543, -0.654], requires_grad=True) - - def cost_fn(x): - with qml.queuing.AnnotatedQueue() as q: - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - - tape = qml.tape.QuantumScript.from_queue(q) - return qml.execute( - [tape], - dev, - gradient_fn="device", - gradient_kwargs={"method": "adjoint_jacobian", "use_device_state": True}, - )[0] - - with pytest.warns(UserWarning, match="Output seems independent"): - res = qml.jacobian(qml.grad(cost_fn))(params) - - assert np.allclose(res, np.zeros([2, 2]), atol=tol, rtol=0) - - def test_adjoint_hessian_multiple_params(self, tol): - """Since the adjoint hessian is not a differentiable transform, - higher-order derivatives are not supported.""" - dev = qml.device("default.qubit.autograd", wires=2) - params = np.array([0.543, -0.654], requires_grad=True) - - def cost_fn(x): - with qml.queuing.AnnotatedQueue() as q: - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - qml.expval(qml.PauliZ(1)) - - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack( - qml.execute( - [tape], - dev, - gradient_fn="device", - gradient_kwargs={"method": "adjoint_jacobian", "use_device_state": True}, - )[0] - ) - - with pytest.warns(UserWarning, match="Output seems independent"): - res = qml.jacobian(qml.jacobian(cost_fn))(params) - - assert np.allclose(res, np.zeros([2, 2, 2]), atol=tol, rtol=0) - - def test_max_diff(self, tol): - """Test that setting the max_diff parameter blocks higher-order - derivatives""" - dev = qml.device("default.qubit.legacy", wires=2) - params = np.array([0.543, -0.654], requires_grad=True) - - def cost_fn(x): - with qml.queuing.AnnotatedQueue() as q1: - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - tape1 = qml.tape.QuantumScript.from_queue(q1) - with qml.queuing.AnnotatedQueue() as q2: - qml.RX(x[0], wires=0) - qml.RY(x[0], wires=1) - qml.CNOT(wires=[0, 1]) - qml.probs(wires=1) - - tape2 = qml.tape.QuantumScript.from_queue(q2) - result = qml.execute([tape1, tape2], dev, gradient_fn=param_shift, max_diff=1) - return result[0] + result[1][0] - - res = cost_fn(params) - x, y = params - expected = 0.5 * (3 + np.cos(x) ** 2 * np.cos(2 * y)) - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.grad(cost_fn)(params) - expected = np.array( - [-np.cos(x) * np.cos(2 * y) * np.sin(x), -np.cos(x) ** 2 * np.sin(2 * y)] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - with pytest.warns(UserWarning, match="Output seems independent"): - res = qml.jacobian(qml.grad(cost_fn))(params) - - expected = np.zeros([2, 2]) - assert np.allclose(res, expected, atol=tol, rtol=0) - - -execute_kwargs_hamiltonian = [ - {"gradient_fn": param_shift}, - {"gradient_fn": finite_diff}, -] - - -@pytest.mark.parametrize("execute_kwargs", execute_kwargs_hamiltonian) -class TestHamiltonianWorkflows: - """Test that tapes ending with expectations - of Hamiltonians provide correct results and gradients""" - - @pytest.fixture - def cost_fn(self, execute_kwargs): - """Cost function for gradient tests""" - - def _cost_fn(weights, coeffs1, coeffs2, dev=None): - obs1 = [qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliX(1), qml.PauliY(0)] - H1 = qml.Hamiltonian(coeffs1, obs1) - - obs2 = [qml.PauliZ(0)] - H2 = qml.Hamiltonian(coeffs2, obs2) - - with qml.queuing.AnnotatedQueue() as q: - qml.RX(weights[0], wires=0) - qml.RY(weights[1], wires=1) - qml.CNOT(wires=[0, 1]) - qml.expval(H1) - qml.expval(H2) - - tape = qml.tape.QuantumScript.from_queue(q) - return autograd.numpy.hstack(qml.execute([tape], dev, **execute_kwargs)[0]) - - return _cost_fn - - @staticmethod - def cost_fn_expected(weights, coeffs1, coeffs2): - """Analytic value of cost_fn above""" - a, b, c = coeffs1 - d = coeffs2[0] - x, y = weights - return [-c * np.sin(x) * np.sin(y) + np.cos(x) * (a + b * np.sin(y)), d * np.cos(x)] - - @staticmethod - def cost_fn_jacobian(weights, coeffs1, coeffs2): - """Analytic jacobian of cost_fn above""" - a, b, c = coeffs1 - d = coeffs2[0] - x, y = weights - return np.array( - [ - [ - -c * np.cos(x) * np.sin(y) - np.sin(x) * (a + b * np.sin(y)), - b * np.cos(x) * np.cos(y) - c * np.cos(y) * np.sin(x), - np.cos(x), - np.cos(x) * np.sin(y), - -(np.sin(x) * np.sin(y)), - 0, - ], - [-d * np.sin(x), 0, 0, 0, 0, np.cos(x)], - ] - ) - - def test_multiple_hamiltonians_not_trainable(self, cost_fn, execute_kwargs, tol): - """Test hamiltonian with no trainable parameters.""" - # pylint: disable=unused-argument - coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=False) - coeffs2 = np.array([0.7], requires_grad=False) - weights = np.array([0.4, 0.5], requires_grad=True) - dev = qml.device("default.qubit.legacy", wires=2) - - res = cost_fn(weights, coeffs1, coeffs2, dev=dev) - expected = self.cost_fn_expected(weights, coeffs1, coeffs2) - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.jacobian(cost_fn)(weights, coeffs1, coeffs2, dev=dev) - expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2)[:, :2] - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_multiple_hamiltonians_trainable(self, cost_fn, execute_kwargs, tol): - """Test hamiltonian with trainable parameters.""" - # pylint: disable=unused-argument - coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=True) - coeffs2 = np.array([0.7], requires_grad=True) - weights = np.array([0.4, 0.5], requires_grad=True) - dev = qml.device("default.qubit.legacy", wires=2) - - res = cost_fn(weights, coeffs1, coeffs2, dev=dev) - expected = self.cost_fn_expected(weights, coeffs1, coeffs2) - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = np.hstack(qml.jacobian(cost_fn)(weights, coeffs1, coeffs2, dev=dev)) - expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2) - assert np.allclose(res, expected, atol=tol, rtol=0) - - -class TestCustomJacobian: - """Test for custom Jacobian.""" - - def test_custom_jacobians(self): - """Test custom Jacobian device methood""" - - class CustomJacobianDevice(DefaultQubitLegacy): - @classmethod - def capabilities(cls): - capabilities = super().capabilities() - capabilities["provides_jacobian"] = True - return capabilities - - def jacobian(self, tape): - # pylint: disable=unused-argument - return np.array([1.0, 2.0, 3.0, 4.0]) - - dev = CustomJacobianDevice(wires=2) - - @qml.qnode(dev, diff_method="device") - def circuit(v): - qml.RX(v, wires=0) - return qml.probs(wires=[0, 1]) - - d_circuit = qml.jacobian(circuit, argnum=0) - - params = np.array(1.0, requires_grad=True) - - d_out = d_circuit(params) - assert np.allclose(d_out, np.array([1.0, 2.0, 3.0, 4.0])) - - def test_custom_jacobians_param_shift(self): - """Test computing the gradient using the parameter-shift - rule with a device that provides a jacobian""" - - class MyQubit(DefaultQubitLegacy): - @classmethod - def capabilities(cls): - capabilities = super().capabilities().copy() - capabilities.update( - provides_jacobian=True, - ) - return capabilities - - def jacobian(self, *args, **kwargs): - raise NotImplementedError() - - dev = MyQubit(wires=2) - - @qml.qnode(dev, diff_method="parameter-shift", grad_on_execution=False) - def qnode(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - def cost(a, b): - return autograd.numpy.hstack(qnode(a, b)) - - res = qml.jacobian(cost)(a, b) - expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)]) - - assert np.allclose(res[0], expected[0]) - assert np.allclose(res[1], expected[1]) - - -class SpecialObject: - """SpecialObject - A special object that conveniently encapsulates the return value of - a special observable supported by a special device and which supports - multiplication with scalars and addition. - """ - - def __init__(self, val): - self.val = val - - def __mul__(self, other): - new = SpecialObject(self.val) - new *= other - return new - - def __imul__(self, other): - self.val *= other - return self - - def __rmul__(self, other): - return self * other - - def __iadd__(self, other): - self.val += other.val if isinstance(other, self.__class__) else other - return self - - def __add__(self, other): - new = SpecialObject(self.val) - new += other.val if isinstance(other, self.__class__) else other - return new - - def __radd__(self, other): - return self + other - - -class SpecialObservable(Observable): - """SpecialObservable""" - - num_wires = AnyWires - num_params = 0 - par_domain = None - - def diagonalizing_gates(self): - """Diagonalizing gates""" - return [] - - -class DeviceSupportingSpecialObservable(DefaultQubitLegacy): - name = "Device supporting SpecialObservable" - short_name = "default.qubit.specialobservable" - observables = DefaultQubitLegacy.observables.union({"SpecialObservable"}) - - @staticmethod - def _asarray(arr, dtype=None): - # pylint: disable=unused-argument - return arr - - @classmethod - def capabilities(cls): - capabilities = super().capabilities().copy() - capabilities.update( - provides_jacobian=True, - ) - return capabilities - - def expval(self, observable, **kwargs): - if self.analytic and isinstance(observable, SpecialObservable): - val = super().expval(qml.PauliZ(wires=0), **kwargs) - return np.array(SpecialObject(val)) - - return super().expval(observable, **kwargs) - - def jacobian(self, tape): - # we actually let pennylane do the work of computing the - # jacobian for us but return it as a device jacobian - gradient_tapes, fn = qml.gradients.param_shift(tape) - tape_jacobian = fn(qml.execute(gradient_tapes, self, None)) - return tape_jacobian - - -@pytest.mark.autograd -class TestObservableWithObjectReturnType: - """Unit tests for qnode returning a custom object""" - - def test_custom_return_type(self): - """Test custom return values for a qnode""" - - dev = DeviceSupportingSpecialObservable(wires=1, shots=None) - - # force diff_method='parameter-shift' because otherwise - # PennyLane swaps out dev for default.qubit.autograd - @qml.qnode(dev, diff_method="parameter-shift") - def qnode(x): - qml.RY(x, wires=0) - return qml.expval(SpecialObservable(wires=0)) - - @qml.qnode(dev, diff_method="parameter-shift") - def reference_qnode(x): - qml.RY(x, wires=0) - return qml.expval(qml.PauliZ(wires=0)) - - out = qnode(0.2) - assert isinstance(out, np.ndarray) - assert isinstance(out.item(), SpecialObject) - assert np.isclose(out.item().val, reference_qnode(0.2)) - - def test_jacobian_with_custom_return_type(self): - """Test differentiation of a QNode on a device supporting a - special observable that returns an object rather than a number.""" - - dev = DeviceSupportingSpecialObservable(wires=1, shots=None) - - # force diff_method='parameter-shift' because otherwise - # PennyLane swaps out dev for default.qubit.autograd - @qml.qnode(dev, diff_method="parameter-shift") - def qnode(x): - qml.RY(x, wires=0) - return qml.expval(SpecialObservable(wires=0)) - - @qml.qnode(dev, diff_method="parameter-shift") - def reference_qnode(x): - qml.RY(x, wires=0) - return qml.expval(qml.PauliZ(wires=0)) - - reference_jac = (qml.jacobian(reference_qnode)(np.array(0.2, requires_grad=True)),) - - assert np.isclose( - reference_jac, - qml.jacobian(qnode)(np.array(0.2, requires_grad=True)).item().val, - ) - - # now check that also the device jacobian works with a custom return type - @qml.qnode(dev, diff_method="device") - def device_gradient_qnode(x): - qml.RY(x, wires=0) - return qml.expval(SpecialObservable(wires=0)) - - assert np.isclose( - reference_jac, - qml.jacobian(device_gradient_qnode)(np.array(0.2, requires_grad=True)).item().val, - ) diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py deleted file mode 100644 index 97f9ff5a58f..00000000000 --- a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_legacy.py +++ /dev/null @@ -1,2365 +0,0 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Integration tests for using the autograd interface with a QNode""" -# pylint: disable=too-many-arguments,too-few-public-methods, use-dict-literal, use-implicit-booleaness-not-comparison, -# pylint: disable=unnecessary-lambda-assignment -import autograd -import autograd.numpy as anp -import pytest - -import pennylane as qml -from pennylane import numpy as np -from pennylane import qnode - -qubit_device_and_diff_method = [ - ["default.qubit.legacy", "finite-diff", False], - ["default.qubit.legacy", "parameter-shift", False], - ["default.qubit.legacy", "backprop", True], - ["default.qubit.legacy", "adjoint", True], - ["default.qubit.legacy", "adjoint", False], - ["default.qubit.legacy", "spsa", False], - ["default.qubit.legacy", "hadamard", False], -] - -interface_qubit_device_and_diff_method = [ - ["autograd", "default.qubit.legacy", "finite-diff", False], - ["autograd", "default.qubit.legacy", "parameter-shift", False], - ["autograd", "default.qubit.legacy", "backprop", True], - ["autograd", "default.qubit.legacy", "adjoint", True], - ["autograd", "default.qubit.legacy", "adjoint", False], - ["autograd", "default.qubit.legacy", "spsa", False], - ["autograd", "default.qubit.legacy", "hadamard", False], - ["auto", "default.qubit.legacy", "finite-diff", False], - ["auto", "default.qubit.legacy", "parameter-shift", False], - ["auto", "default.qubit.legacy", "backprop", True], - ["auto", "default.qubit.legacy", "adjoint", True], - ["auto", "default.qubit.legacy", "adjoint", False], - ["auto", "default.qubit.legacy", "spsa", False], - ["auto", "default.qubit.legacy", "hadamard", False], -] - -pytestmark = pytest.mark.autograd - -TOL_FOR_SPSA = 1.0 -SEED_FOR_SPSA = 32651 -H_FOR_SPSA = 0.01 - - -@pytest.mark.parametrize( - "interface,dev_name,diff_method,grad_on_execution", interface_qubit_device_and_diff_method -) -class TestQNode: - """Test that using the QNode with Autograd integrates with the PennyLane stack""" - - # pylint: disable=unused-argument - - def test_nondiff_param_unwrapping( - self, interface, dev_name, diff_method, grad_on_execution, mocker - ): - """Test that non-differentiable parameters are correctly unwrapped - to NumPy ndarrays or floats (if 0-dimensional)""" - if diff_method != "parameter-shift": - pytest.skip("Test only supports parameter-shift") - - dev = qml.device("default.qubit.legacy", wires=1) - - @qnode(dev, interface=interface, diff_method=diff_method) - def circuit(x, y): - qml.RX(x[0], wires=0) - qml.Rot(*x[1:], wires=0) - qml.RY(y[0], wires=0) - return qml.expval(qml.PauliZ(0)) - - x = np.array([0.1, 0.2, 0.3, 0.4], requires_grad=False) - y = np.array([0.5], requires_grad=True) - - param_data = [] - - def mock_apply(*args, **kwargs): - for op in args[0]: - param_data.extend(op.data) - - mocker.patch.object(dev.target_device, "apply", side_effect=mock_apply) - circuit(x, y) - assert param_data == [0.1, 0.2, 0.3, 0.4, 0.5] - assert not any(isinstance(p, np.tensor) for p in param_data) - - # test the jacobian works correctly - param_data = [] - qml.grad(circuit)(x, y) - assert param_data == [ - 0.1, - 0.2, - 0.3, - 0.4, - 0.5, - 0.1, - 0.2, - 0.3, - 0.4, - 0.5 + np.pi / 2, - 0.1, - 0.2, - 0.3, - 0.4, - 0.5 - np.pi / 2, - ] - assert not any(isinstance(p, np.tensor) for p in param_data) - - def test_execution_no_interface(self, interface, dev_name, diff_method, grad_on_execution): - """Test execution works without an interface""" - if diff_method == "backprop": - pytest.skip("Test does not support backprop") - - num_wires = 1 - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface=None, diff_method=diff_method) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array(0.1, requires_grad=True) - - res = circuit(a) - - # without the interface, the QNode simply returns a scalar array - assert isinstance(res, np.ndarray) - assert res.shape == tuple() - - # gradients should cause an error - with pytest.raises(TypeError, match="must be real number, not ArrayBox"): - qml.grad(circuit)(a) - - def test_execution_with_interface(self, interface, dev_name, diff_method, grad_on_execution): - """Test execution works with the interface""" - if diff_method == "backprop": - pytest.skip("Test does not support backprop") - - num_wires = 1 - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface=interface, diff_method=diff_method) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array(0.1, requires_grad=True) - assert circuit.interface == interface - - # gradients should work - grad = qml.grad(circuit)(a) - - assert isinstance(grad, float) - assert grad.shape == tuple() - - def test_jacobian(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test jacobian calculation""" - num_wires = 2 - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - if diff_method == "spsa": - spsa_kwargs = dict(sampler_rng=np.random.default_rng(SEED_FOR_SPSA), num_directions=10) - kwargs = {**kwargs, **spsa_kwargs} - tol = TOL_FOR_SPSA - elif diff_method == "hadamard": - num_wires = 3 - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, **kwargs) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) - - res = circuit(a, b) - - def cost(x, y): - return autograd.numpy.hstack(circuit(x, y)) - - assert circuit.qtape.trainable_params == [0, 1] - assert isinstance(res, tuple) - assert len(res) == 2 - - expected = [np.cos(a), -np.cos(a) * np.sin(b)] - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.jacobian(cost)(a, b) - assert isinstance(res, tuple) and len(res) == 2 - expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)]) - assert isinstance(res[0], np.ndarray) - assert res[0].shape == (2,) - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - - assert isinstance(res[1], np.ndarray) - assert res[1].shape == (2,) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - def test_jacobian_no_evaluate(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test jacobian calculation when no prior circuit evaluation has been performed""" - num_wires = 2 - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - - if diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - elif diff_method == "hadamard": - num_wires = 3 - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, **kwargs) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) - - def cost(x, y): - return autograd.numpy.hstack(circuit(x, y)) - - jac_fn = qml.jacobian(cost) - res = jac_fn(a, b) - expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)]) - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - # call the Jacobian with new parameters - a = np.array(0.6, requires_grad=True) - b = np.array(0.832, requires_grad=True) - - res = jac_fn(a, b) - expected = ([-np.sin(a), np.sin(a) * np.sin(b)], [0, -np.cos(a) * np.cos(b)]) - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - def test_jacobian_options(self, interface, dev_name, diff_method, grad_on_execution): - """Test setting jacobian options""" - wires = [0] - if diff_method in ["backprop", "adjoint"]: - pytest.skip("Test does not support backprop or adjoint method") - elif diff_method == "finite-diff": - kwargs = {"h": 1e-8, "approx_order": 2} - elif diff_method == "parameter-shift": - kwargs = {"shifts": [(0.1,), (0.2,)]} - elif diff_method == "hadamard": - wires = [0, "aux"] - kwargs = {"aux_wire": qml.wires.Wires("aux"), "device_wires": wires} - else: - kwargs = {} - - a = np.array([0.1, 0.2], requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=wires) - - @qnode(dev, interface=interface, diff_method=diff_method, **kwargs) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.expval(qml.PauliZ(0)) - - circuit(a) - - qml.jacobian(circuit)(a) - - def test_changing_trainability(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test changing the trainability of parameters changes the - number of differentiation requests made""" - if diff_method != "parameter-shift": - pytest.skip("Test only supports parameter-shift") - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - dev = qml.device("default.qubit.legacy", wires=2) - - @qnode(dev, interface=interface, diff_method=diff_method) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1)) - - def loss(a, b): - return np.sum(autograd.numpy.hstack(circuit(a, b))) - - grad_fn = qml.grad(loss) - res = grad_fn(a, b) - - # the tape has reported both arguments as trainable - assert circuit.qtape.trainable_params == [0, 1] - - expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)] - assert np.allclose(res, expected, atol=tol, rtol=0) - - # make the second QNode argument a constant - a = np.array(0.54, requires_grad=True) - b = np.array(0.8, requires_grad=False) - - res = grad_fn(a, b) - - # the tape has reported only the first argument as trainable - assert circuit.qtape.trainable_params == [0] - - expected = [-np.sin(a) + np.sin(a) * np.sin(b)] - assert np.allclose(res, expected, atol=tol, rtol=0) - - # trainability also updates on evaluation - a = np.array(0.54, requires_grad=False) - b = np.array(0.8, requires_grad=True) - circuit(a, b) - assert circuit.qtape.trainable_params == [1] - - def test_classical_processing(self, interface, dev_name, diff_method, grad_on_execution): - """Test classical processing within the quantum tape""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) - c = np.array(0.3, requires_grad=True) - - num_wires = 1 - - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode( - dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - def circuit(a, b, c): - qml.RY(a * c, wires=0) - qml.RZ(b, wires=0) - qml.RX(c + c**2 + np.sin(a), wires=0) - return qml.expval(qml.PauliZ(0)) - - res = qml.jacobian(circuit)(a, b, c) - - assert circuit.qtape.trainable_params == [0, 2] - tape_params = np.array(circuit.qtape.get_parameters()) - assert np.all(tape_params == [a * c, c + c**2 + np.sin(a)]) - - assert isinstance(res, tuple) and len(res) == 2 - assert res[0].shape == () - assert res[1].shape == () - - def test_no_trainable_parameters(self, interface, dev_name, diff_method, grad_on_execution): - """Test evaluation and Jacobian if there are no trainable parameters""" - dev = qml.device(dev_name, wires=2) - - @qnode( - dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - - a = np.array(0.1, requires_grad=False) - b = np.array(0.2, requires_grad=False) - - res = circuit(a, b) - - if diff_method == "finite-diff": - assert circuit.qtape.trainable_params == [] - - assert len(res) == 2 - assert isinstance(res, tuple) - - def cost0(x, y): - return autograd.numpy.hstack(circuit(x, y)) - - with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"): - assert not qml.jacobian(cost0)(a, b) - - def cost1(a, b): - return np.sum(circuit(a, b)) - - with pytest.warns(UserWarning, match="Attempted to differentiate a function with no"): - grad = qml.grad(cost1)(a, b) - - assert grad == tuple() - - def test_matrix_parameter(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test that the autograd interface works correctly - with a matrix parameter""" - U = np.array([[0, 1], [1, 0]], requires_grad=False) - a = np.array(0.1, requires_grad=True) - - num_wires = 1 - - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode( - dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - def circuit(U, a): - qml.QubitUnitary(U, wires=0) - qml.RY(a, wires=0) - return qml.expval(qml.PauliZ(0)) - - res = circuit(U, a) - - if diff_method == "finite-diff": - assert circuit.qtape.trainable_params == [1] - - res = qml.grad(circuit)(U, a) - assert np.allclose(res, np.sin(a), atol=tol, rtol=0) - - def test_gradient_non_differentiable_exception( - self, interface, dev_name, diff_method, grad_on_execution - ): - """Test that an exception is raised if non-differentiable data is - differentiated""" - dev = qml.device(dev_name, wires=2) - - @qnode(dev, interface=interface, diff_method=diff_method) - def circuit(data1): - qml.templates.AmplitudeEmbedding(data1, wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - grad_fn = qml.grad(circuit, argnum=0) - data1 = np.array([0, 1, 1, 0], requires_grad=False) / np.sqrt(2) - - with pytest.raises(qml.numpy.NonDifferentiableError, match="is non-differentiable"): - grad_fn(data1) - - def test_differentiable_expand(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test that operation and nested tape expansion - is differentiable""" - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - if diff_method == "spsa": - spsa_kwargs = dict(sampler_rng=np.random.default_rng(SEED_FOR_SPSA), num_directions=20) - kwargs = {**kwargs, **spsa_kwargs} - tol = TOL_FOR_SPSA - - class U3(qml.U3): - def decomposition(self): - theta, phi, lam = self.data - wires = self.wires - return [ - qml.Rot(lam, theta, -lam, wires=wires), - qml.PhaseShift(phi + lam, wires=wires), - ] - - dev = qml.device(dev_name, wires=2) - a = np.array(0.1, requires_grad=False) - p = np.array([0.1, 0.2, 0.3], requires_grad=True) - - @qnode(dev, **kwargs) - def circuit(a, p): - qml.RX(a, wires=0) - U3(p[0], p[1], p[2], wires=0) - return qml.expval(qml.PauliX(0)) - - res = circuit(a, p) - expected = np.cos(a) * np.cos(p[1]) * np.sin(p[0]) + np.sin(a) * ( - np.cos(p[2]) * np.sin(p[1]) + np.cos(p[0]) * np.cos(p[1]) * np.sin(p[2]) - ) - assert isinstance(res, np.ndarray) - assert res.shape == () - assert np.allclose(res, expected, atol=tol, rtol=0) - - res = qml.grad(circuit)(a, p) - - assert isinstance(res, np.ndarray) - assert len(res) == 3 - - expected = np.array( - [ - np.cos(p[1]) * (np.cos(a) * np.cos(p[0]) - np.sin(a) * np.sin(p[0]) * np.sin(p[2])), - np.cos(p[1]) * np.cos(p[2]) * np.sin(a) - - np.sin(p[1]) - * (np.cos(a) * np.sin(p[0]) + np.cos(p[0]) * np.sin(a) * np.sin(p[2])), - np.sin(a) - * (np.cos(p[0]) * np.cos(p[1]) * np.cos(p[2]) - np.sin(p[1]) * np.sin(p[2])), - ] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - -class TestShotsIntegration: - """Test that the QNode correctly changes shot value, and - remains differentiable.""" - - def test_changing_shots(self, mocker, tol): - """Test that changing shots works on execution""" - dev = qml.device("default.qubit.legacy", wires=2, shots=None) - a, b = np.array([0.543, -0.654], requires_grad=True) - - @qnode(dev, diff_method=qml.gradients.param_shift) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliY(1)) - - spy = mocker.spy(dev.target_device, "sample") - - # execute with device default shots (None) - res = circuit(a, b) - assert np.allclose(res, -np.cos(a) * np.sin(b), atol=tol, rtol=0) - spy.assert_not_called() - - # execute with shots=100 - res = circuit(a, b, shots=100) # pylint: disable=unexpected-keyword-arg - spy.assert_called_once() - assert spy.spy_return.shape == (100,) - - # device state has been unaffected - assert not dev.shots - res = circuit(a, b) - assert np.allclose(res, -np.cos(a) * np.sin(b), atol=tol, rtol=0) - spy.assert_called_once() # same single call performed above - - @pytest.mark.xfail(reason="Param shift and shot vectors.") - def test_gradient_integration(self): - """Test that temporarily setting the shots works - for gradient computations""" - dev = qml.device("default.qubit.legacy", wires=2, shots=None) - a, b = np.array([0.543, -0.654], requires_grad=True) - - @qnode(dev, diff_method=qml.gradients.param_shift) - def cost_fn(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliY(1)) - - # TODO: fix the shot vectors issue - res = qml.jacobian(cost_fn)(a, b, shots=[10000, 10000, 10000]) - assert dev.shots is None - assert isinstance(res, tuple) and len(res) == 2 - assert all(r.shape == (3,) for r in res) - - expected = [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)] - assert all( - np.allclose(np.mean(r, axis=0), e, atol=0.1, rtol=0) for r, e in zip(res, expected) - ) - - def test_update_diff_method(self, mocker): - """Test that temporarily setting the shots updates the diff method""" - dev = qml.device("default.qubit.legacy", wires=2, shots=100) - a, b = np.array([0.543, -0.654], requires_grad=True) - - spy = mocker.spy(qml, "execute") - - @qnode(dev) - def cost_fn(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliY(1)) - - cost_fn(a, b) - # since we are using finite shots, parameter-shift will - # be chosen - with pytest.warns( - qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated" - ): - assert cost_fn.gradient_fn is qml.gradients.param_shift - assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift - - # if we set the shots to None, backprop can now be used - cost_fn(a, b, shots=None) # pylint: disable=unexpected-keyword-arg - assert spy.call_args[1]["gradient_fn"] == "backprop" - with pytest.warns( - qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated" - ): - assert cost_fn.gradient_fn == "backprop" - - cost_fn(a, b) - with pytest.warns( - qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated" - ): - assert cost_fn.gradient_fn is qml.gradients.param_shift - assert spy.call_args[1]["gradient_fn"] is qml.gradients.param_shift - - -@pytest.mark.parametrize( - "interface,dev_name,diff_method,grad_on_execution", interface_qubit_device_and_diff_method -) -class TestQubitIntegration: - """Tests that ensure various qubit circuits integrate correctly""" - - # pylint: disable=unused-argument - - def test_probability_differentiation( - self, interface, dev_name, diff_method, grad_on_execution, tol - ): - """Tests correct output shape and evaluation for a tape - with a single prob output""" - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - elif diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - @qnode(dev, **kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.probs(wires=[1]) - - res = qml.jacobian(circuit)(x, y) - assert isinstance(res, tuple) and len(res) == 2 - - expected = ( - np.array([-np.sin(x) * np.cos(y) / 2, np.cos(y) * np.sin(x) / 2]), - np.array([-np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]), - ) - assert all(np.allclose(r, e, atol=tol, rtol=0) for r, e in zip(res, expected)) - - def test_multiple_probability_differentiation( - self, interface, dev_name, diff_method, grad_on_execution, tol - ): - """Tests correct output shape and evaluation for a tape - with multiple prob outputs""" - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - elif diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - @qnode(dev, **kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.probs(wires=[0]), qml.probs(wires=[1]) - - res = circuit(x, y) - - expected = np.array( - [ - [np.cos(x / 2) ** 2, np.sin(x / 2) ** 2], - [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2], - ] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - def cost(x, y): - return autograd.numpy.hstack(circuit(x, y)) - - res = qml.jacobian(cost)(x, y) - - assert isinstance(res, tuple) and len(res) == 2 - assert res[0].shape == (4,) - assert res[1].shape == (4,) - - expected = ( - np.array( - [ - [ - -np.sin(x) / 2, - np.sin(x) / 2, - -np.sin(x) * np.cos(y) / 2, - np.sin(x) * np.cos(y) / 2, - ], - ] - ), - np.array( - [ - [0, 0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2], - ] - ), - ) - assert all(np.allclose(r, e, atol=tol, rtol=0) for r, e in zip(res, expected)) - - def test_ragged_differentiation(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Tests correct output shape and evaluation for a tape - with prob and expval outputs""" - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - num_wires = 2 - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - elif diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - elif diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - @qnode(dev, **kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[1]) - - res = circuit(x, y) - assert isinstance(res, tuple) - expected = [np.cos(x), [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2]] - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - def cost(x, y): - return autograd.numpy.hstack(circuit(x, y)) - - res = qml.jacobian(cost)(x, y) - assert isinstance(res, tuple) - assert len(res) == 2 - - assert res[0].shape == (3,) - assert res[1].shape == (3,) - - expected = ( - np.array([-np.sin(x), -np.sin(x) * np.cos(y) / 2, np.sin(x) * np.cos(y) / 2]), - np.array([0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]), - ) - assert np.allclose(res[0], expected[0], atol=tol, rtol=0) - assert np.allclose(res[1], expected[1], atol=tol, rtol=0) - - def test_ragged_differentiation_variance( - self, interface, dev_name, diff_method, grad_on_execution, tol - ): - """Tests correct output shape and evaluation for a tape - with prob and variance outputs""" - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - elif diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - dev = qml.device(dev_name, wires=2) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - @qnode(dev, **kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0)), qml.probs(wires=[1]) - - res = circuit(x, y) - - expected_var = np.array(np.sin(x) ** 2) - expected_probs = np.array( - [(1 + np.cos(x) * np.cos(y)) / 2, (1 - np.cos(x) * np.cos(y)) / 2] - ) - - assert isinstance(res, tuple) - assert len(res) == 2 - - assert isinstance(res[0], np.ndarray) - assert res[0].shape == () - assert np.allclose(res[0], expected_var, atol=tol, rtol=0) - - assert isinstance(res[1], np.ndarray) - assert res[1].shape == (2,) - assert np.allclose(res[1], expected_probs, atol=tol, rtol=0) - - def cost(x, y): - return autograd.numpy.hstack(circuit(x, y)) - - jac = qml.jacobian(cost)(x, y) - assert isinstance(res, tuple) and len(res) == 2 - - expected = ( - np.array([np.sin(2 * x), -np.sin(x) * np.cos(y) / 2, np.sin(x) * np.cos(y) / 2]), - np.array([0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2]), - ) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - - assert isinstance(jac[0], np.ndarray) - assert jac[0].shape == (3,) - assert np.allclose(jac[0], expected[0], atol=tol, rtol=0) - - assert isinstance(jac[1], np.ndarray) - assert jac[1].shape == (3,) - assert np.allclose(jac[1], expected[1], atol=tol, rtol=0) - - def test_chained_qnodes(self, interface, dev_name, diff_method, grad_on_execution): - """Test that the gradient of chained QNodes works without error""" - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - class Template(qml.templates.StronglyEntanglingLayers): - def decomposition(self): - return [qml.templates.StronglyEntanglingLayers(*self.parameters, self.wires)] - - @qnode(dev, interface=interface, diff_method=diff_method) - def circuit1(weights): - Template(weights, wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) - - @qnode(dev, interface=interface, diff_method=diff_method) - def circuit2(data, weights): - qml.templates.AngleEmbedding(data, wires=[0, 1]) - Template(weights, wires=[0, 1]) - return qml.expval(qml.PauliX(0)) - - def cost(w1, w2): - c1 = circuit1(w1) - c2 = circuit2(c1, w2) - return np.sum(c2) ** 2 - - w1 = qml.templates.StronglyEntanglingLayers.shape(n_wires=2, n_layers=3) - w2 = qml.templates.StronglyEntanglingLayers.shape(n_wires=2, n_layers=4) - - weights = [ - np.random.random(w1, requires_grad=True), - np.random.random(w2, requires_grad=True), - ] - - grad_fn = qml.grad(cost) - res = grad_fn(*weights) - - assert len(res) == 2 - - def test_chained_gradient_value(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test that the returned gradient value for two chained qubit QNodes - is correct.""" - kwargs = dict(interface=interface, diff_method=diff_method) - if diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - num_wires = 3 - - if diff_method == "hadamard": - num_wires = 4 - - dev1 = qml.device(dev_name, wires=num_wires) - - @qnode(dev1, **kwargs) - def circuit1(a, b, c): - qml.RX(a, wires=0) - qml.RX(b, wires=1) - qml.RX(c, wires=2) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(2)) - - dev2 = qml.device("default.qubit.legacy", wires=num_wires) - - @qnode(dev2, interface=interface, diff_method=diff_method) - def circuit2(data, weights): - qml.RX(data[0], wires=0) - qml.RX(data[1], wires=1) - qml.CNOT(wires=[0, 1]) - qml.RZ(weights[0], wires=0) - qml.RZ(weights[1], wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliX(0) @ qml.PauliY(1)) - - def cost(a, b, c, weights): - return circuit2(circuit1(a, b, c), weights) - - grad_fn = qml.grad(cost) - - # Set the first parameter of circuit1 as non-differentiable. - a = np.array(0.4, requires_grad=False) - - # The remaining free parameters are all differentiable. - b = np.array(0.5, requires_grad=True) - c = np.array(0.1, requires_grad=True) - weights = np.array([0.2, 0.3], requires_grad=True) - - res = grad_fn(a, b, c, weights) - - # Output should have shape [dcost/db, dcost/dc, dcost/dw], - # where b,c are scalars, and w is a vector of length 2. - assert len(res) == 3 - assert res[0].shape == tuple() # scalar - assert res[1].shape == tuple() # scalar - assert res[2].shape == (2,) # vector - - cacbsc = np.cos(a) * np.cos(b) * np.sin(c) - - expected = np.array( - [ - # analytic expression for dcost/db - -np.cos(a) - * np.sin(b) - * np.sin(c) - * np.cos(cacbsc) - * np.sin(weights[0]) - * np.sin(np.cos(a)), - # analytic expression for dcost/dc - np.cos(a) - * np.cos(b) - * np.cos(c) - * np.cos(cacbsc) - * np.sin(weights[0]) - * np.sin(np.cos(a)), - # analytic expression for dcost/dw[0] - np.sin(cacbsc) * np.cos(weights[0]) * np.sin(np.cos(a)), - # analytic expression for dcost/dw[1] - 0, - ] - ) - - # np.hstack 'flattens' the ragged gradient array allowing it - # to be compared with the expected result - assert np.allclose(np.hstack(res), expected, atol=tol, rtol=0) - - if diff_method != "backprop": - # Check that the gradient was computed - # for all parameters in circuit2 - assert circuit2.qtape.trainable_params == [0, 1, 2, 3] - - # Check that the parameter-shift rule was not applied - # to the first parameter of circuit1. - assert circuit1.qtape.trainable_params == [1, 2] - - def test_second_derivative(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test second derivative calculation of a scalar valued QNode""" - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=1) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(x): - qml.RY(x[0], wires=0) - qml.RX(x[1], wires=0) - return qml.expval(qml.PauliZ(0)) - - x = np.array([1.0, 2.0], requires_grad=True) - res = circuit(x) - g = qml.grad(circuit)(x) - g2 = qml.grad(lambda x: np.sum(qml.grad(circuit)(x)))(x) - - a, b = x - - expected_res = np.cos(a) * np.cos(b) - assert np.allclose(res, expected_res, atol=tol, rtol=0) - - expected_g = [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)] - assert np.allclose(g, expected_g, atol=tol, rtol=0) - - expected_g2 = [ - -np.cos(a) * np.cos(b) + np.sin(a) * np.sin(b), - np.sin(a) * np.sin(b) - np.cos(a) * np.cos(b), - ] - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - assert np.allclose(g2, expected_g2, atol=tol, rtol=0) - - def test_hessian(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test hessian calculation of a scalar valued QNode""" - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=1) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(x): - qml.RY(x[0], wires=0) - qml.RX(x[1], wires=0) - return qml.expval(qml.PauliZ(0)) - - x = np.array([1.0, 2.0], requires_grad=True) - res = circuit(x) - - a, b = x - - expected_res = np.cos(a) * np.cos(b) - - assert isinstance(res, np.ndarray) - assert res.shape == () - assert np.allclose(res, expected_res, atol=tol, rtol=0) - - grad_fn = qml.grad(circuit) - g = grad_fn(x) - - expected_g = [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)] - - assert isinstance(g, np.ndarray) - assert g.shape == (2,) - assert np.allclose(g, expected_g, atol=tol, rtol=0) - - hess = qml.jacobian(grad_fn)(x) - - expected_hess = [ - [-np.cos(a) * np.cos(b), np.sin(a) * np.sin(b)], - [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)], - ] - - assert isinstance(hess, np.ndarray) - assert hess.shape == (2, 2) - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - assert np.allclose(hess, expected_hess, atol=tol, rtol=0) - - def test_hessian_unused_parameter( - self, interface, dev_name, diff_method, grad_on_execution, tol - ): - """Test hessian calculation of a scalar valued QNode""" - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=1) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(x): - qml.RY(x[0], wires=0) - return qml.expval(qml.PauliZ(0)) - - x = np.array([1.0, 2.0], requires_grad=True) - res = circuit(x) - - a, _ = x - - expected_res = np.cos(a) - assert np.allclose(res, expected_res, atol=tol, rtol=0) - - grad_fn = qml.grad(circuit) - - hess = qml.jacobian(grad_fn)(x) - - expected_hess = [ - [-np.cos(a), 0], - [0, 0], - ] - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - assert np.allclose(hess, expected_hess, atol=tol, rtol=0) - - def test_hessian_vector_valued(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test hessian calculation of a vector valued QNode""" - - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=1) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(x): - qml.RY(x[0], wires=0) - qml.RX(x[1], wires=0) - return qml.probs(wires=0) - - x = np.array([1.0, 2.0], requires_grad=True) - res = circuit(x) - - a, b = x - - expected_res = [0.5 + 0.5 * np.cos(a) * np.cos(b), 0.5 - 0.5 * np.cos(a) * np.cos(b)] - - assert isinstance(res, np.ndarray) - assert res.shape == (2,) - assert np.allclose(res, expected_res, atol=tol, rtol=0) - - jac_fn = qml.jacobian(circuit) - jac = jac_fn(x) - - expected_res = [ - [-0.5 * np.sin(a) * np.cos(b), -0.5 * np.cos(a) * np.sin(b)], - [0.5 * np.sin(a) * np.cos(b), 0.5 * np.cos(a) * np.sin(b)], - ] - - assert isinstance(jac, np.ndarray) - assert jac.shape == (2, 2) - assert np.allclose(jac, expected_res, atol=tol, rtol=0) - - hess = qml.jacobian(jac_fn)(x) - - expected_hess = [ - [ - [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.sin(a) * np.sin(b)], - [0.5 * np.sin(a) * np.sin(b), -0.5 * np.cos(a) * np.cos(b)], - ], - [ - [0.5 * np.cos(a) * np.cos(b), -0.5 * np.sin(a) * np.sin(b)], - [-0.5 * np.sin(a) * np.sin(b), 0.5 * np.cos(a) * np.cos(b)], - ], - ] - - assert isinstance(hess, np.ndarray) - assert hess.shape == (2, 2, 2) - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - assert np.allclose(hess, expected_hess, atol=tol, rtol=0) - - def test_hessian_vector_valued_postprocessing( - self, interface, dev_name, diff_method, grad_on_execution, tol - ): - """Test hessian calculation of a vector valued QNode with post-processing""" - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=1) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(x): - qml.RX(x[0], wires=0) - qml.RY(x[1], wires=0) - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(0)) - - def cost_fn(x): - return x @ autograd.numpy.hstack(circuit(x)) - - x = np.array([0.76, -0.87], requires_grad=True) - res = cost_fn(x) - - a, b = x - - expected_res = x @ [np.cos(a) * np.cos(b), np.cos(a) * np.cos(b)] - - assert isinstance(res, np.ndarray) - assert res.shape == () - assert np.allclose(res, expected_res, atol=tol, rtol=0) - - hess = qml.jacobian(qml.grad(cost_fn))(x) - - expected_hess = [ - [ - -(np.cos(b) * ((a + b) * np.cos(a) + 2 * np.sin(a))), - -(np.cos(b) * np.sin(a)) + (-np.cos(a) + (a + b) * np.sin(a)) * np.sin(b), - ], - [ - -(np.cos(b) * np.sin(a)) + (-np.cos(a) + (a + b) * np.sin(a)) * np.sin(b), - -(np.cos(a) * ((a + b) * np.cos(b) + 2 * np.sin(b))), - ], - ] - - assert hess.shape == (2, 2) - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - assert np.allclose(hess, expected_hess, atol=tol, rtol=0) - - def test_hessian_vector_valued_separate_args( - self, interface, dev_name, diff_method, grad_on_execution, tol - ): - """Test hessian calculation of a vector valued QNode that has separate input arguments""" - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=1) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.probs(wires=0) - - a = np.array(1.0, requires_grad=True) - b = np.array(2.0, requires_grad=True) - res = circuit(a, b) - - expected_res = [0.5 + 0.5 * np.cos(a) * np.cos(b), 0.5 - 0.5 * np.cos(a) * np.cos(b)] - assert isinstance(res, np.ndarray) - assert res.shape == (2,) - assert np.allclose(res, expected_res, atol=tol, rtol=0) - - jac_fn = qml.jacobian(circuit) - g = jac_fn(a, b) - assert isinstance(g, tuple) and len(g) == 2 - - expected_g = ( - [-0.5 * np.sin(a) * np.cos(b), 0.5 * np.sin(a) * np.cos(b)], - [-0.5 * np.cos(a) * np.sin(b), 0.5 * np.cos(a) * np.sin(b)], - ) - assert g[0].shape == (2,) - assert np.allclose(g[0], expected_g[0], atol=tol, rtol=0) - - assert g[1].shape == (2,) - assert np.allclose(g[1], expected_g[1], atol=tol, rtol=0) - - jac_fn_a = lambda *args: jac_fn(*args)[0] - jac_fn_b = lambda *args: jac_fn(*args)[1] - hess_a = qml.jacobian(jac_fn_a)(a, b) - hess_b = qml.jacobian(jac_fn_b)(a, b) - assert isinstance(hess_a, tuple) and len(hess_a) == 2 - assert isinstance(hess_b, tuple) and len(hess_b) == 2 - - exp_hess_a = ( - [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.cos(a) * np.cos(b)], - [0.5 * np.sin(a) * np.sin(b), -0.5 * np.sin(a) * np.sin(b)], - ) - exp_hess_b = ( - [0.5 * np.sin(a) * np.sin(b), -0.5 * np.sin(a) * np.sin(b)], - [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.cos(a) * np.cos(b)], - ) - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - for hess, exp_hess in zip([hess_a, hess_b], [exp_hess_a, exp_hess_b]): - assert np.allclose(hess[0], exp_hess[0], atol=tol, rtol=0) - assert np.allclose(hess[1], exp_hess[1], atol=tol, rtol=0) - - def test_hessian_ragged(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test hessian calculation of a ragged QNode""" - if diff_method not in {"parameter-shift", "backprop"}: - pytest.skip("Test only supports parameter-shift or backprop") - - dev = qml.device(dev_name, wires=2) - - @qnode( - dev, - diff_method=diff_method, - interface=interface, - grad_on_execution=grad_on_execution, - max_diff=2, - ) - def circuit(x): - qml.RY(x[0], wires=0) - qml.RX(x[1], wires=0) - qml.RY(x[0], wires=1) - qml.RX(x[1], wires=1) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=1) - - x = np.array([1.0, 2.0], requires_grad=True) - - a, b = x - - cos_prod = np.cos(a) * np.cos(b) - expected_res = (cos_prod, [0.5 + 0.5 * cos_prod, 0.5 - 0.5 * cos_prod]) - res = circuit(x) - assert all(qml.math.allclose(r, e) for r, e in zip(res, expected_res)) - - def cost_fn(x): - return autograd.numpy.hstack(circuit(x)) - - jac_fn = qml.jacobian(cost_fn) - - hess = qml.jacobian(jac_fn)(x) - expected_hess = [ - [ - [-np.cos(a) * np.cos(b), np.sin(a) * np.sin(b)], - [np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)], - ], - [ - [-0.5 * np.cos(a) * np.cos(b), 0.5 * np.sin(a) * np.sin(b)], - [0.5 * np.sin(a) * np.sin(b), -0.5 * np.cos(a) * np.cos(b)], - ], - [ - [0.5 * np.cos(a) * np.cos(b), -0.5 * np.sin(a) * np.sin(b)], - [-0.5 * np.sin(a) * np.sin(b), 0.5 * np.cos(a) * np.cos(b)], - ], - ] - - if diff_method in {"finite-diff"}: - tol = 10e-2 - - assert np.allclose(hess, expected_hess, atol=tol, rtol=0) - - def test_state(self, interface, dev_name, diff_method, grad_on_execution, tol): - """Test that the state can be returned and differentiated""" - if diff_method == "adjoint": - pytest.skip("Adjoint does not support states") - - dev = qml.device(dev_name, wires=2) - - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) - - @qnode( - dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.state() - - def cost_fn(x, y): - res = circuit(x, y) - assert res.dtype is np.dtype("complex128") - probs = np.abs(res) ** 2 - return probs[0] + probs[2] - - res = cost_fn(x, y) - - if diff_method not in {"backprop"}: - pytest.skip("Test only supports backprop") - - res = qml.jacobian(cost_fn)(x, y) - expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2]) - assert isinstance(res, tuple) - assert len(res) == 2 - - assert isinstance(res[0], np.ndarray) - assert res[0].shape == () - assert isinstance(res[1], np.ndarray) - assert res[1].shape == () - assert np.allclose(res, expected, atol=tol, rtol=0) - - @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector - def test_projector(self, state, interface, dev_name, diff_method, grad_on_execution, tol): - """Test that the variance of a projector is correctly returned""" - kwargs = dict( - diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution - ) - if diff_method == "adjoint": - pytest.skip("Adjoint does not support projectors") - elif diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - dev = qml.device(dev_name, wires=2) - P = np.array(state, requires_grad=False) - x, y = np.array([0.765, -0.654], requires_grad=True) - - @qnode(dev, **kwargs) - def circuit(x, y): - qml.RX(x, wires=0) - qml.RY(y, wires=1) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.Projector(P, wires=0) @ qml.PauliX(1)) - - res = circuit(x, y) - expected = 0.25 * np.sin(x / 2) ** 2 * (3 + np.cos(2 * y) + 2 * np.cos(x) * np.sin(y) ** 2) - assert isinstance(res, np.ndarray) - assert res.shape == () - assert np.allclose(res, expected, atol=tol, rtol=0) - - jac = qml.jacobian(circuit)(x, y) - expected = np.array( - [ - [ - 0.5 * np.sin(x) * (np.cos(x / 2) ** 2 + np.cos(2 * y) * np.sin(x / 2) ** 2), - -2 * np.cos(y) * np.sin(x / 2) ** 4 * np.sin(y), - ] - ] - ) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - - assert isinstance(jac[0], np.ndarray) - assert jac[0].shape == () - - assert isinstance(jac[1], np.ndarray) - assert jac[1].shape == () - - assert np.allclose(jac, expected, atol=tol, rtol=0) - - -@pytest.mark.parametrize( - "diff_method,kwargs", - [ - ["finite-diff", {}], - ["spsa", {"num_directions": 100, "h": 0.05}], - ("parameter-shift", {}), - ("parameter-shift", {"force_order2": True}), - ], -) -class TestCV: - """Tests for CV integration""" - - def test_first_order_observable(self, diff_method, kwargs, tol): - """Test variance of a first order CV observable""" - dev = qml.device("default.gaussian", wires=1) - if diff_method == "spsa": - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - tol = TOL_FOR_SPSA - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - r = np.array(0.543, requires_grad=True) - phi = np.array(-0.654, requires_grad=True) - - @qnode(dev, diff_method=diff_method, **kwargs) - def circuit(r, phi): - qml.Squeezing(r, 0, wires=0) - qml.Rotation(phi, wires=0) - return qml.var(qml.QuadX(0)) - - res = circuit(r, phi) - expected = np.exp(2 * r) * np.sin(phi) ** 2 + np.exp(-2 * r) * np.cos(phi) ** 2 - assert np.allclose(res, expected, atol=tol, rtol=0) - - # circuit jacobians - res = qml.jacobian(circuit)(r, phi) - expected = np.array( - [ - [ - 2 * np.exp(2 * r) * np.sin(phi) ** 2 - 2 * np.exp(-2 * r) * np.cos(phi) ** 2, - 2 * np.sinh(2 * r) * np.sin(2 * phi), - ] - ] - ) - assert np.allclose(res, expected, atol=tol, rtol=0) - - def test_second_order_observable(self, diff_method, kwargs, tol): - """Test variance of a second order CV expectation value""" - dev = qml.device("default.gaussian", wires=1) - if diff_method == "spsa": - tol = TOL_FOR_SPSA - kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - n = np.array(0.12, requires_grad=True) - a = np.array(0.765, requires_grad=True) - - @qnode(dev, diff_method=diff_method, **kwargs) - def circuit(n, a): - qml.ThermalState(n, wires=0) - qml.Displacement(a, 0, wires=0) - return qml.var(qml.NumberOperator(0)) - - res = circuit(n, a) - expected = n**2 + n + np.abs(a) ** 2 * (1 + 2 * n) - assert np.allclose(res, expected, atol=tol, rtol=0) - - # circuit jacobians - res = qml.jacobian(circuit)(n, a) - expected = np.array([[2 * a**2 + 2 * n + 1, 2 * a * (2 * n + 1)]]) - assert np.allclose(res, expected, atol=tol, rtol=0) - - -def test_adjoint_reuse_device_state(mocker): - """Tests that the autograd interface reuses the device state for adjoint differentiation""" - dev = qml.device("default.qubit.legacy", wires=1) - - @qnode(dev, diff_method="adjoint") - def circ(x): - qml.RX(x, wires=0) - return qml.expval(qml.PauliZ(0)) - - spy = mocker.spy(dev.target_device, "adjoint_jacobian") - - qml.grad(circ, argnum=0)(1.0) - assert circ.device.num_executions == 1 - - spy.assert_called_with(mocker.ANY, use_device_state=True) - - -@pytest.mark.parametrize("dev_name,diff_method,grad_on_execution", qubit_device_and_diff_method) -class TestTapeExpansion: - """Test that tape expansion within the QNode integrates correctly - with the Autograd interface""" - - @pytest.mark.parametrize("max_diff", [1, 2]) - def test_gradient_expansion_trainable_only( - self, dev_name, diff_method, grad_on_execution, max_diff - ): - """Test that a *supported* operation with no gradient recipe is only - expanded for parameter-shift and finite-differences when it is trainable.""" - if diff_method not in ("parameter-shift", "finite-diff", "spsa", "hadamard"): - pytest.skip("Only supports gradient transforms") - if max_diff == 2 and diff_method == "hadamard": - pytest.skip("Max diff > 1 not supported for Hadamard gradient.") - - num_wires = 1 - - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - class PhaseShift(qml.PhaseShift): - grad_method = None - - def decomposition(self): - return [qml.RY(3 * self.data[0], wires=self.wires)] - - @qnode(dev, diff_method=diff_method, grad_on_execution=grad_on_execution, max_diff=max_diff) - def circuit(x, y): - qml.Hadamard(wires=0) - PhaseShift(x, wires=0) - PhaseShift(2 * y, wires=0) - return qml.expval(qml.PauliX(0)) - - x = np.array(0.5, requires_grad=True) - y = np.array(0.7, requires_grad=False) - circuit(x, y) - - qml.grad(circuit)(x, y) - - @pytest.mark.parametrize("max_diff", [1, 2]) - def test_hamiltonian_expansion_analytic( - self, dev_name, diff_method, grad_on_execution, max_diff, tol - ): - """Test that if there are non-commuting groups and the number of shots is None - the first and second order gradients are correctly evaluated""" - kwargs = dict( - diff_method=diff_method, - grad_on_execution=grad_on_execution, - max_diff=max_diff, - ) - if diff_method in ["adjoint", "hadamard"]: - pytest.skip("The diff method requested does not yet support Hamiltonians") - elif diff_method == "spsa": - tol = TOL_FOR_SPSA - spsa_kwargs = dict(sampler_rng=np.random.default_rng(SEED_FOR_SPSA), num_directions=10) - kwargs = {**kwargs, **spsa_kwargs} - - dev = qml.device(dev_name, wires=3, shots=None) - obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)] - - @qnode(dev, **kwargs) - def circuit(data, weights, coeffs): - weights = weights.reshape(1, -1) - qml.templates.AngleEmbedding(data, wires=[0, 1]) - qml.templates.BasicEntanglerLayers(weights, wires=[0, 1]) - return qml.expval(qml.Hamiltonian(coeffs, obs)) - - d = np.array([0.1, 0.2], requires_grad=False) - w = np.array([0.654, -0.734], requires_grad=True) - c = np.array([-0.6543, 0.24, 0.54], requires_grad=True) - - # test output - res = circuit(d, w, c) - expected = c[2] * np.cos(d[1] + w[1]) - c[1] * np.sin(d[0] + w[0]) * np.sin(d[1] + w[1]) - assert np.allclose(res, expected, atol=tol) - - # test gradients - grad = qml.grad(circuit)(d, w, c) - expected_w = [ - -c[1] * np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]), - -c[1] * np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]) - c[2] * np.sin(d[1] + w[1]), - ] - expected_c = [0, -np.sin(d[0] + w[0]) * np.sin(d[1] + w[1]), np.cos(d[1] + w[1])] - assert np.allclose(grad[0], expected_w, atol=tol) - assert np.allclose(grad[1], expected_c, atol=tol) - - # test second-order derivatives - if diff_method in ("parameter-shift", "backprop") and max_diff == 2: - if diff_method == "backprop": - with pytest.warns(UserWarning, match=r"Output seems independent of input."): - grad2_c = qml.jacobian(qml.grad(circuit, argnum=2), argnum=2)(d, w, c) - else: - grad2_c = qml.jacobian(qml.grad(circuit, argnum=2), argnum=2)(d, w, c) - assert np.allclose(grad2_c, 0, atol=tol) - - grad2_w_c = qml.jacobian(qml.grad(circuit, argnum=1), argnum=2)(d, w, c) - expected = [0, -np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]), 0], [ - 0, - -np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]), - -np.sin(d[1] + w[1]), - ] - assert np.allclose(grad2_w_c, expected, atol=tol) - - @pytest.mark.slow - @pytest.mark.parametrize("max_diff", [1, 2]) - def test_hamiltonian_expansion_finite_shots( - self, dev_name, diff_method, grad_on_execution, max_diff, mocker - ): - """Test that the Hamiltonian is expanded if there - are non-commuting groups and the number of shots is finite - and the first and second order gradients are correctly evaluated""" - gradient_kwargs = {} - tol = 0.3 - if diff_method in ("adjoint", "backprop", "hadamard"): - pytest.skip("The adjoint and backprop methods do not yet support sampling") - elif diff_method == "spsa": - gradient_kwargs = dict( - h=H_FOR_SPSA, - sampler_rng=np.random.default_rng(SEED_FOR_SPSA), - num_directions=20, - ) - tol = TOL_FOR_SPSA - elif diff_method == "finite-diff": - gradient_kwargs = {"h": 0.05} - - dev = qml.device(dev_name, wires=3, shots=50000) - spy = mocker.spy(qml.transforms, "split_non_commuting") - obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)] - - @qnode( - dev, - diff_method=diff_method, - grad_on_execution=grad_on_execution, - max_diff=max_diff, - **gradient_kwargs, - ) - def circuit(data, weights, coeffs): - weights = weights.reshape(1, -1) - qml.templates.AngleEmbedding(data, wires=[0, 1]) - qml.templates.BasicEntanglerLayers(weights, wires=[0, 1]) - H = qml.Hamiltonian(coeffs, obs) - H.compute_grouping() - return qml.expval(H) - - d = np.array([0.1, 0.2], requires_grad=False) - w = np.array([0.654, -0.734], requires_grad=True) - c = np.array([-0.6543, 0.24, 0.54], requires_grad=True) - - # test output - res = circuit(d, w, c) - expected = c[2] * np.cos(d[1] + w[1]) - c[1] * np.sin(d[0] + w[0]) * np.sin(d[1] + w[1]) - assert np.allclose(res, expected, atol=tol) - spy.assert_called() - - # test gradients - grad = qml.grad(circuit)(d, w, c) - expected_w = [ - -c[1] * np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]), - -c[1] * np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]) - c[2] * np.sin(d[1] + w[1]), - ] - expected_c = [0, -np.sin(d[0] + w[0]) * np.sin(d[1] + w[1]), np.cos(d[1] + w[1])] - assert np.allclose(grad[0], expected_w, atol=tol) - assert np.allclose(grad[1], expected_c, atol=tol) - - # test second-order derivatives - if diff_method == "parameter-shift" and max_diff == 2: - grad2_c = qml.jacobian(qml.grad(circuit, argnum=2), argnum=2)(d, w, c) - - assert np.allclose(grad2_c, 0, atol=tol) - - grad2_w_c = qml.jacobian(qml.grad(circuit, argnum=1), argnum=2)(d, w, c) - expected = [0, -np.cos(d[0] + w[0]) * np.sin(d[1] + w[1]), 0], [ - 0, - -np.cos(d[1] + w[1]) * np.sin(d[0] + w[0]), - -np.sin(d[1] + w[1]), - ] - assert np.allclose(grad2_w_c, expected, atol=tol) - - -class TestSample: - """Tests for the sample integration""" - - def test_backprop_error(self): - """Test that sampling in backpropagation grad_on_execution raises an error""" - dev = qml.device("default.qubit.legacy", wires=2) - - @qnode(dev, diff_method="backprop") - def circuit(): - qml.RX(0.54, wires=0) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliX(1)) - - with pytest.raises( - qml.QuantumFunctionError, match="does not support backprop with requested circuit" - ): - circuit(shots=10) # pylint: disable=unexpected-keyword-arg - - def test_sample_dimension(self): - """Test that the sample function outputs samples of the right size""" - n_sample = 10 - - dev = qml.device("default.qubit.legacy", wires=2, shots=n_sample) - - @qnode(dev) - def circuit(): - qml.RX(0.54, wires=0) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliX(1)) - - res = circuit() - - assert isinstance(res, tuple) - assert len(res) == 2 - - assert res[0].shape == (10,) - assert isinstance(res[0], np.ndarray) - - assert res[1].shape == (10,) - assert isinstance(res[1], np.ndarray) - - def test_sample_combination(self): - """Test the output of combining expval, var and sample""" - - n_sample = 10 - - dev = qml.device("default.qubit.legacy", wires=3, shots=n_sample) - - @qnode(dev, diff_method="parameter-shift") - def circuit(): - qml.RX(0.54, wires=0) - - return qml.sample(qml.PauliZ(0)), qml.expval(qml.PauliX(1)), qml.var(qml.PauliY(2)) - - result = circuit() - - assert isinstance(result, tuple) - assert len(result) == 3 - - assert np.array_equal(result[0].shape, (n_sample,)) - assert isinstance(result[1], np.ndarray) - assert isinstance(result[2], np.ndarray) - assert result[0].dtype == np.dtype("float") - - def test_single_wire_sample(self): - """Test the return type and shape of sampling a single wire""" - n_sample = 10 - - dev = qml.device("default.qubit.legacy", wires=1, shots=n_sample) - - @qnode(dev) - def circuit(): - qml.RX(0.54, wires=0) - - return qml.sample(qml.PauliZ(0)) - - result = circuit() - - assert isinstance(result, np.ndarray) - assert np.array_equal(result.shape, (n_sample,)) - - def test_multi_wire_sample_regular_shape(self): - """Test the return type and shape of sampling multiple wires - where a rectangular array is expected""" - n_sample = 10 - - dev = qml.device("default.qubit.legacy", wires=3, shots=n_sample) - - @qnode(dev) - def circuit(): - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)), qml.sample(qml.PauliZ(2)) - - result = circuit() - - # If all the dimensions are equal the result will end up to be a proper rectangular array - assert isinstance(result, tuple) - assert len(result) == 3 - - assert result[0].shape == (10,) - assert isinstance(result[0], np.ndarray) - - assert result[1].shape == (10,) - assert isinstance(result[1], np.ndarray) - - assert result[2].shape == (10,) - assert isinstance(result[2], np.ndarray) - - -@pytest.mark.parametrize("dev_name,diff_method,grad_on_execution", qubit_device_and_diff_method) -class TestReturn: - """Class to test the shape of the Grad/Jacobian/Hessian with different return types.""" - - # pylint: disable=unused-argument - - def test_grad_single_measurement_param(self, dev_name, diff_method, grad_on_execution): - """For one measurement and one param, the gradient is a float.""" - num_wires = 1 - - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array(0.1, requires_grad=True) - - grad = qml.grad(circuit)(a) - - import sys - - python_version = sys.version_info.minor - if diff_method == "backprop" and python_version > 7: - # Since numpy 1.23.0 - assert isinstance(grad, np.ndarray) - else: - assert isinstance(grad, float) - - def test_grad_single_measurement_multiple_param(self, dev_name, diff_method, grad_on_execution): - """For one measurement and multiple param, the gradient is a tuple of arrays.""" - num_wires = 1 - - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - grad = qml.grad(circuit)(a, b) - - assert isinstance(grad, tuple) - assert len(grad) == 2 - assert grad[0].shape == () - assert grad[1].shape == () - - def test_grad_single_measurement_multiple_param_array( - self, dev_name, diff_method, grad_on_execution - ): - """For one measurement and multiple param as a single array params, the gradient is an array.""" - num_wires = 1 - - if diff_method == "hadamard": - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array([0.1, 0.2], requires_grad=True) - - grad = qml.grad(circuit)(a) - - assert isinstance(grad, np.ndarray) - assert len(grad) == 2 - assert grad.shape == (2,) - - def test_jacobian_single_measurement_param_probs( - self, dev_name, diff_method, grad_on_execution - ): - """For a multi dimensional measurement (probs), check that a single array is returned with the correct - dimension""" - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.probs(wires=[0, 1]) - - a = np.array(0.1, requires_grad=True) - - jac = qml.jacobian(circuit)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (4,) - - def test_jacobian_single_measurement_probs_multiple_param( - self, dev_name, diff_method, grad_on_execution - ): - """For a multi dimensional measurement (probs), check that a single tuple is returned containing arrays with - the correct dimension""" - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.probs(wires=[0, 1]) - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - jac = qml.jacobian(circuit)(a, b) - - assert isinstance(jac, tuple) - - assert isinstance(jac[0], np.ndarray) - assert jac[0].shape == (4,) - - assert isinstance(jac[1], np.ndarray) - assert jac[1].shape == (4,) - - def test_jacobian_single_measurement_probs_multiple_param_single_array( - self, dev_name, diff_method, grad_on_execution - ): - """For a multi dimensional measurement (probs), check that a single array is returned.""" - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.probs(wires=[0, 1]) - - a = np.array([0.1, 0.2], requires_grad=True) - jac = qml.jacobian(circuit)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (4, 2) - - def test_jacobian_multiple_measurement_single_param( - self, dev_name, diff_method, grad_on_execution - ): - """The jacobian of multiple measurements with a single params return an array.""" - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - a = np.array(0.1, requires_grad=True) - - def cost(x): - return anp.hstack(circuit(x)) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (5,) - - def test_jacobian_multiple_measurement_multiple_param( - self, dev_name, diff_method, grad_on_execution - ): - """The jacobian of multiple measurements with a multiple params return a tuple of arrays.""" - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - def cost(x, y): - return anp.hstack(circuit(x, y)) - - jac = qml.jacobian(cost)(a, b) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - - assert isinstance(jac[0], np.ndarray) - assert jac[0].shape == (5,) - - assert isinstance(jac[1], np.ndarray) - assert jac[1].shape == (5,) - - def test_jacobian_multiple_measurement_multiple_param_array( - self, dev_name, diff_method, grad_on_execution - ): - """The jacobian of multiple measurements with a multiple params array return a single array.""" - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support returning probabilities") - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 3 - - dev = qml.device(dev_name, wires=num_wires) - - @qnode(dev, interface="autograd", diff_method=diff_method) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - a = np.array([0.1, 0.2], requires_grad=True) - - def cost(x): - return anp.hstack(circuit(x)) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (5, 2) - - def test_hessian_expval_multiple_params(self, dev_name, diff_method, grad_on_execution): - """The hessian of single a measurement with multiple params return a tuple of arrays.""" - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 4 - - dev = qml.device(dev_name, wires=num_wires) - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support second-order diff.") - - par_0 = qml.numpy.array(0.1, requires_grad=True) - par_1 = qml.numpy.array(0.2, requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x, y): - return anp.hstack(qml.grad(circuit)(x, y)) - - hess = qml.jacobian(cost)(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - - assert isinstance(hess[0], np.ndarray) - assert hess[0].shape == (2,) - - assert isinstance(hess[1], np.ndarray) - assert hess[1].shape == (2,) - - def test_hessian_expval_multiple_param_array(self, dev_name, diff_method, grad_on_execution): - """The hessian of single measurement with a multiple params array return a single array.""" - - num_wires = 2 - - if diff_method == "hadamard": - num_wires = 4 - - dev = qml.device(dev_name, wires=num_wires) - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support second-order diff.") - - params = qml.numpy.array([0.1, 0.2], requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) - - hess = qml.jacobian(qml.grad(circuit))(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (2, 2) - - def test_hessian_var_multiple_params(self, dev_name, diff_method, grad_on_execution): - """The hessian of single a measurement with multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2) - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support second-order diff.") - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - par_0 = qml.numpy.array(0.1, requires_grad=True) - par_1 = qml.numpy.array(0.2, requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x, y): - return anp.hstack(qml.grad(circuit)(x, y)) - - hess = qml.jacobian(cost)(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - - assert isinstance(hess[0], np.ndarray) - assert hess[0].shape == (2,) - - assert isinstance(hess[1], np.ndarray) - assert hess[1].shape == (2,) - - def test_hessian_var_multiple_param_array(self, dev_name, diff_method, grad_on_execution): - """The hessian of single measurement with a multiple params array return a single array.""" - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support second-order diff.") - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - dev = qml.device(dev_name, wires=2) - - params = qml.numpy.array([0.1, 0.2], requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - hess = qml.jacobian(qml.grad(circuit))(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (2, 2) - - def test_hessian_probs_expval_multiple_params(self, dev_name, diff_method, grad_on_execution): - """The hessian of multiple measurements with multiple params return a tuple of arrays.""" - num_wires = 2 - - dev = qml.device(dev_name, wires=num_wires) - - if diff_method in ["adjoint", "hadamard"]: - pytest.skip("The adjoint method does not currently support second-order diff.") - - par_0 = qml.numpy.array(0.1, requires_grad=True) - par_1 = qml.numpy.array(0.2, requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0]) - - def circuit_stack(x, y): - return anp.hstack(circuit(x, y)) - - def cost(x, y): - return anp.hstack(qml.jacobian(circuit_stack)(x, y)) - - hess = qml.jacobian(cost)(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - - assert isinstance(hess[0], np.ndarray) - assert hess[0].shape == (6,) - - assert isinstance(hess[1], np.ndarray) - assert hess[1].shape == (6,) - - def test_hessian_expval_probs_multiple_param_array( - self, dev_name, diff_method, grad_on_execution - ): - """The hessian of multiple measurements with a multiple param array return a single array.""" - - if diff_method in ["adjoint", "hadamard"]: - pytest.skip("The adjoint method does not currently support second-order diff.") - - dev = qml.device(dev_name, wires=2) - - params = qml.numpy.array([0.1, 0.2], requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0]) - - def cost(x): - return anp.hstack(circuit(x)) - - hess = qml.jacobian(qml.jacobian(cost))(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (3, 2, 2) - - def test_hessian_probs_var_multiple_params(self, dev_name, diff_method, grad_on_execution): - """The hessian of multiple measurements with multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2) - - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support second-order diff.") - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - par_0 = qml.numpy.array(0.1, requires_grad=True) - par_1 = qml.numpy.array(0.2, requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0]) - - def circuit_stack(x, y): - return anp.hstack(circuit(x, y)) - - def cost(x, y): - return anp.hstack(qml.jacobian(circuit_stack)(x, y)) - - hess = qml.jacobian(cost)(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - - assert isinstance(hess[0], np.ndarray) - assert hess[0].shape == (6,) - - assert isinstance(hess[1], np.ndarray) - assert hess[1].shape == (6,) - - def test_hessian_var_probs_multiple_param_array(self, dev_name, diff_method, grad_on_execution): - """The hessian of multiple measurements with a multiple param array return a single array.""" - if diff_method == "adjoint": - pytest.skip("The adjoint method does not currently support second-order diff.") - elif diff_method == "hadamard": - pytest.skip("Hadamard gradient does not support variances.") - - dev = qml.device(dev_name, wires=2) - - params = qml.numpy.array([0.1, 0.2], requires_grad=True) - - @qnode(dev, interface="autograd", diff_method=diff_method, max_diff=2) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0]) - - def cost(x): - return anp.hstack(circuit(x)) - - hess = qml.jacobian(qml.jacobian(cost))(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (3, 2, 2) - - -@pytest.mark.parametrize("dev_name", ["default.qubit.legacy", "default.mixed"]) -def test_no_ops(dev_name): - """Test that the return value of the QNode matches in the interface - even if there are no ops""" - - dev = qml.device(dev_name, wires=1) - - @qml.qnode(dev, interface="autograd") - def circuit(): - qml.Hadamard(wires=0) - return qml.state() - - res = circuit() - assert isinstance(res, np.tensor) diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py deleted file mode 100644 index 24d1d824ced..00000000000 --- a/tests/interfaces/legacy_devices_integration/test_autograd_qnode_shot_vector_legacy.py +++ /dev/null @@ -1,658 +0,0 @@ -# Copyright 2022 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Integration tests for using the Autograd interface with shot vectors and with a QNode""" -# pylint: disable=too-many-arguments,redefined-outer-name - -import pytest - -import pennylane as qml -from pennylane import numpy as np -from pennylane import qnode - -pytestmark = pytest.mark.autograd - -shots_and_num_copies = [(((5, 2), 1, 10), 4), ((1, 10, (5, 2)), 4)] -shots_and_num_copies_hess = [(((5, 1), 10), 2), ((10, (5, 1)), 2)] - - -kwargs = { - "finite-diff": {"h": 0.05}, - "parameter-shift": {}, - "spsa": {"h": 0.05, "num_directions": 20}, -} - -qubit_device_and_diff_method = [ - ["default.qubit.legacy", "finite-diff"], - ["default.qubit.legacy", "parameter-shift"], - ["default.qubit.legacy", "spsa"], -] - -TOLS = { - "finite-diff": 0.3, - "parameter-shift": 1e-2, - "spsa": 0.3, -} - - -@pytest.fixture -def gradient_kwargs(request): - diff_method = request.node.funcargs["diff_method"] - return kwargs[diff_method] | ( - {"sampler_rng": np.random.default_rng(42)} if diff_method == "spsa" else {} - ) - - -@pytest.mark.parametrize("shots,num_copies", shots_and_num_copies) -@pytest.mark.parametrize("dev_name,diff_method", qubit_device_and_diff_method) -class TestReturnWithShotVectors: - """Class to test the shape of the Jacobian/Hessian with different return types and shot vectors.""" - - def test_jac_single_measurement_param( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """For one measurement and one param, the gradient is a float.""" - dev = qml.device(dev_name, wires=1, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array(0.1) - - def cost(a): - return qml.math.stack(circuit(a)) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies,) - - def test_jac_single_measurement_multiple_param( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """For one measurement and multiple param, the gradient is a tuple of arrays.""" - dev = qml.device(dev_name, wires=1, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array(0.1) - b = np.array(0.2) - - def cost(a, b): - return qml.math.stack(circuit(a, b)) - - jac = qml.jacobian(cost, argnum=[0, 1])(a, b) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - for j in jac: - assert isinstance(j, np.ndarray) - assert j.shape == (num_copies,) - - def test_jacobian_single_measurement_multiple_param_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """For one measurement and multiple param as a single array params, the gradient is an array.""" - dev = qml.device(dev_name, wires=1, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.expval(qml.PauliZ(0)) - - a = np.array([0.1, 0.2]) - - def cost(a): - return qml.math.stack(circuit(a)) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 2) - - def test_jacobian_single_measurement_param_probs( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """For a multi dimensional measurement (probs), check that a single array is returned with the correct - dimension""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.probs(wires=[0, 1]) - - a = np.array(0.1) - - def cost(a): - return qml.math.stack(circuit(a)) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 4) - - def test_jacobian_single_measurement_probs_multiple_param( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """For a multi dimensional measurement (probs), check that a single tuple is returned containing arrays with - the correct dimension""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.probs(wires=[0, 1]) - - a = np.array(0.1) - b = np.array(0.2) - - def cost(a, b): - return qml.math.stack(circuit(a, b)) - - jac = qml.jacobian(cost, argnum=[0, 1])(a, b) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - for j in jac: - assert isinstance(j, np.ndarray) - assert j.shape == (num_copies, 4) - - def test_jacobian_single_measurement_probs_multiple_param_single_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """For a multi dimensional measurement (probs), check that a single tuple is returned containing arrays with - the correct dimension""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.probs(wires=[0, 1]) - - a = np.array([0.1, 0.2]) - - def cost(a): - return qml.math.stack(circuit(a)) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 4, 2) - - def test_jacobian_expval_expval_multiple_params( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The gradient of multiple measurements with multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - par_0 = np.array(0.1) - par_1 = np.array(0.2) - - @qnode(dev, diff_method=diff_method, max_diff=1, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0)) - - def cost(x, y): - res = circuit(x, y) - return qml.math.stack([qml.math.stack(r) for r in res]) - - jac = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - for j in jac: - assert isinstance(j, np.ndarray) - assert j.shape == (num_copies, 2) - - def test_jacobian_expval_expval_multiple_params_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The jacobian of multiple measurements with a multiple params array return a single array.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.RY(a[2], wires=0) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0)) - - a = np.array([0.1, 0.2, 0.3]) - - def cost(a): - res = circuit(a) - return qml.math.stack([qml.math.stack(r) for r in res]) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 2, 3) - - def test_jacobian_var_var_multiple_params( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The jacobian of multiple measurements with multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - par_0 = np.array(0.1) - par_1 = np.array(0.2) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.var(qml.PauliZ(0)) - - def cost(x, y): - res = circuit(x, y) - return qml.math.stack([qml.math.stack(r) for r in res]) - - jac = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - for j in jac: - assert isinstance(j, np.ndarray) - assert j.shape == (num_copies, 2) - - def test_jacobian_var_var_multiple_params_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The jacobian of multiple measurements with a multiple params array return a single array.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - qml.RY(a[2], wires=0) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.var(qml.PauliZ(0)) - - a = np.array([0.1, 0.2, 0.3]) - - def cost(a): - res = circuit(a) - return qml.math.stack([qml.math.stack(r) for r in res]) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 2, 3) - - def test_jacobian_multiple_measurement_single_param( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The jacobian of multiple measurements with a single params return an array.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a, wires=0) - qml.RX(0.2, wires=0) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - a = np.array(0.1) - - def cost(a): - res = circuit(a) - return qml.math.stack([qml.math.hstack(r) for r in res]) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 5) - - def test_jacobian_multiple_measurement_multiple_param( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The jacobian of multiple measurements with a multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a, b): - qml.RY(a, wires=0) - qml.RX(b, wires=0) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) - - def cost(a, b): - res = circuit(a, b) - return qml.math.stack([qml.math.hstack(r) for r in res]) - - jac = qml.jacobian(cost, argnum=[0, 1])(a, b) - - assert isinstance(jac, tuple) - assert len(jac) == 2 - for j in jac: - assert isinstance(j, np.ndarray) - assert j.shape == (num_copies, 5) - - def test_jacobian_multiple_measurement_multiple_param_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The jacobian of multiple measurements with a multiple params array return a single array.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(a): - qml.RY(a[0], wires=0) - qml.RX(a[1], wires=0) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - a = np.array([0.1, 0.2]) - - def cost(a): - res = circuit(a) - return qml.math.stack([qml.math.hstack(r) for r in res]) - - jac = qml.jacobian(cost)(a) - - assert isinstance(jac, np.ndarray) - assert jac.shape == (num_copies, 5, 2) - - -@pytest.mark.slow -@pytest.mark.parametrize("shots,num_copies", shots_and_num_copies_hess) -@pytest.mark.parametrize("dev_name,diff_method", qubit_device_and_diff_method) -class TestReturnShotVectorHessian: - """Class to test the shape of the Hessian with different return types and shot vectors.""" - - def test_hessian_expval_multiple_params( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The hessian of a single measurement with multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - par_0 = np.array(0.1) - par_1 = np.array(0.2) - - @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x, y): - def cost2(x, y): - res = circuit(x, y) - return qml.math.stack(res) - - return qml.math.stack(qml.jacobian(cost2, argnum=[0, 1])(x, y)) - - hess = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - for h in hess: - assert isinstance(h, np.ndarray) - assert h.shape == (2, num_copies) - - def test_hessian_expval_multiple_param_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The hessian of single measurement with a multiple params array return a single array.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - params = np.array([0.1, 0.2]) - - @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x): - def cost2(x): - res = circuit(x) - return qml.math.stack(res) - - return qml.jacobian(cost2)(x) - - hess = qml.jacobian(cost)(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (num_copies, 2, 2) - - def test_hessian_var_multiple_params( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The hessian of a single measurement with multiple params return a tuple of arrays.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - par_0 = np.array(0.1) - par_1 = np.array(0.2) - - @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x, y): - def cost2(x, y): - res = circuit(x, y) - return qml.math.stack(res) - - return qml.math.stack(qml.jacobian(cost2, argnum=[0, 1])(x, y)) - - hess = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - for h in hess: - assert isinstance(h, np.ndarray) - assert h.shape == (2, num_copies) - - def test_hessian_var_multiple_param_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The hessian of single measurement with a multiple params array return a single array.""" - dev = qml.device(dev_name, wires=2, shots=shots) - - params = np.array([0.1, 0.2]) - - @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.var(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x): - def cost2(x): - res = circuit(x) - return qml.math.stack(res) - - return qml.jacobian(cost2)(x) - - hess = qml.jacobian(cost)(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (num_copies, 2, 2) - - def test_hessian_probs_expval_multiple_params( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The hessian of multiple measurements with multiple params return a tuple of arrays.""" - if diff_method == "spsa": - pytest.skip("SPSA does not support iterated differentiation in Autograd.") - dev = qml.device(dev_name, wires=2, shots=shots) - - par_0 = np.array(0.1) - par_1 = np.array(0.2) - - @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0]) - - def cost(x, y): - def cost2(x, y): - res = circuit(x, y) - return qml.math.stack([qml.math.hstack(r) for r in res]) - - return qml.math.stack(qml.jacobian(cost2, argnum=[0, 1])(x, y)) - - hess = qml.jacobian(cost, argnum=[0, 1])(par_0, par_1) - - assert isinstance(hess, tuple) - assert len(hess) == 2 - for h in hess: - assert isinstance(h, np.ndarray) - assert h.shape == (2, num_copies, 3) - - def test_hessian_expval_probs_multiple_param_array( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """The hessian of multiple measurements with a multiple param array return a single array.""" - if diff_method == "spsa": - pytest.skip("SPSA does not support iterated differentiation in Autograd.") - - dev = qml.device(dev_name, wires=2, shots=shots) - - params = np.array([0.1, 0.2]) - - @qnode(dev, diff_method=diff_method, max_diff=2, **gradient_kwargs) - def circuit(x): - qml.RX(x[0], wires=[0]) - qml.RY(x[1], wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0]) - - def cost(x): - def cost2(x): - res = circuit(x) - return qml.math.stack([qml.math.hstack(r) for r in res]) - - return qml.jacobian(cost2)(x) - - hess = qml.jacobian(cost)(params) - - assert isinstance(hess, np.ndarray) - assert hess.shape == (num_copies, 3, 2, 2) - - -shots_and_num_copies = [((30000, 28000, 26000), 3), ((30000, (28000, 2)), 3)] - - -@pytest.mark.parametrize("shots,num_copies", shots_and_num_copies) -@pytest.mark.parametrize("dev_name,diff_method", qubit_device_and_diff_method) -class TestReturnShotVectorIntegration: - """Tests for the integration of shots with the autograd interface.""" - - def test_single_expectation_value( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """Tests correct output shape and evaluation for a tape - with a single expval output""" - dev = qml.device(dev_name, wires=2, shots=shots) - x = np.array(0.543) - y = np.array(-0.654) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) - - def cost(x, y): - res = circuit(x, y) - return qml.math.stack(res) - - all_res = qml.jacobian(cost, argnum=[0, 1])(x, y) - - assert isinstance(all_res, tuple) - assert len(all_res) == 2 - - expected = np.array([-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]) - tol = TOLS[diff_method] - - for res, exp in zip(all_res, expected): - assert isinstance(res, np.ndarray) - assert res.shape == (num_copies,) - assert np.allclose(res, exp, atol=tol, rtol=0) - - def test_prob_expectation_values( - self, dev_name, diff_method, gradient_kwargs, shots, num_copies - ): - """Tests correct output shape and evaluation for a tape - with prob and expval outputs""" - dev = qml.device(dev_name, wires=2, shots=shots) - x = np.array(0.543) - y = np.array(-0.654) - - @qnode(dev, diff_method=diff_method, **gradient_kwargs) - def circuit(x, y): - qml.RX(x, wires=[0]) - qml.RY(y, wires=[1]) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)), qml.probs(wires=[0, 1]) - - def cost(x, y): - res = circuit(x, y) - return qml.math.stack([qml.math.hstack(r) for r in res]) - - all_res = qml.jacobian(cost, argnum=[0, 1])(x, y) - - assert isinstance(all_res, tuple) - assert len(all_res) == 2 - - expected = np.array( - [ - [ - -np.sin(x), - -(np.cos(y / 2) ** 2 * np.sin(x)) / 2, - -(np.sin(x) * np.sin(y / 2) ** 2) / 2, - (np.sin(x) * np.sin(y / 2) ** 2) / 2, - (np.cos(y / 2) ** 2 * np.sin(x)) / 2, - ], - [ - 0, - -(np.cos(x / 2) ** 2 * np.sin(y)) / 2, - (np.cos(x / 2) ** 2 * np.sin(y)) / 2, - (np.sin(x / 2) ** 2 * np.sin(y)) / 2, - -(np.sin(x / 2) ** 2 * np.sin(y)) / 2, - ], - ] - ) - - tol = TOLS[diff_method] - - for res, exp in zip(all_res, expected): - assert isinstance(res, np.ndarray) - assert res.shape == (num_copies, 5) - assert np.allclose(res, exp, atol=tol, rtol=0) diff --git a/tests/interfaces/test_execute.py b/tests/interfaces/test_execute.py index 18003f95586..1732f741d8a 100644 --- a/tests/interfaces/test_execute.py +++ b/tests/interfaces/test_execute.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for exeuction with default qubit 2 independent of any interface.""" +import numpy as np import pytest import pennylane as qml @@ -42,3 +43,15 @@ def test_caching(gradient_fn): assert tracker.totals["batches"] == 1 assert tracker.totals["executions"] == 1 assert cache[qs.hash] == -1.0 + + +def test_execute_legacy_device(): + """Test that qml.execute works when passed a legacy device class.""" + + dev = qml.devices.DefaultMixed(wires=2) + + tape = qml.tape.QuantumScript([qml.RX(0.1, 0)], [qml.expval(qml.Z(0))]) + + res = qml.execute((tape,), dev) + + assert qml.math.allclose(res[0], np.cos(0.1)) diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index 8d433da51fe..f8e56cc6fb4 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -76,21 +76,6 @@ def capabilities(self): return capabilities -def test_backprop_switching_deprecation(): - """Test that a PennyLaneDeprecationWarning is raised when a device is subtituted - for a different backprop device. - """ - - with pytest.warns(qml.PennyLaneDeprecationWarning): - - @qml.qnode(DummyDevice(shots=None), interface="autograd") - def circ(x): - qml.RX(x, 0) - return qml.expval(qml.Z(0)) - - circ(pnp.array(3)) - - # pylint: disable=too-many-public-methods class TestValidation: """Tests for QNode creation and validation""" diff --git a/tests/test_qubit_device.py b/tests/test_qubit_device.py index 58fc05370ec..c42ca5a6d28 100644 --- a/tests/test_qubit_device.py +++ b/tests/test_qubit_device.py @@ -1508,10 +1508,7 @@ class TestResourcesTracker: Resources(2, 6, {"Hadamard": 3, "RX": 2, "CNOT": 1}, {1: 5, 2: 1}, 4, Shots((10, 10, 50))), ) # Resources(wires, gates, gate_types, gate_sizes, depth, shots) - devices = ( - "default.qubit.legacy", - "default.qubit.autograd", - ) + devices = ("default.qubit.legacy",) @pytest.mark.all_interfaces @pytest.mark.parametrize("dev_name", devices) From 43cac7225d8b1c5f29391c5c4e542c006eb6ff12 Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Fri, 13 Sep 2024 17:37:12 -0400 Subject: [PATCH 03/14] Deprecate top level access to legacy device base classes (#6238) **Context:** `qml.Device`, `qml.QubitDevice`, and `qml.QutritDevice` all reflect the legacy device interface, which is no longer the recommended way of creating devices. **Description of the Change:** Deprecate top level access to the `Device`, `QubitDevice`, and `QutritDevice`. **Benefits:** Further isolation of the legacy device interface. **Possible Drawbacks:** All deprecations propagate through the ecosystem and can cause issues. **Related GitHub Issues:** [sc-71519] --------- Co-authored-by: Mudit Pandey --- .github/workflows/interface-unit-tests.yml | 3 +- doc/development/deprecations.rst | 7 ++ doc/releases/changelog-dev.md | 5 + pennylane/__init__.py | 19 +++- pennylane/devices/tests/conftest.py | 12 -- .../tests/test_compare_default_qubit.py | 5 +- pennylane/devices/tests/test_gates.py | 22 ++-- .../devices/tests/test_gates_with_expval.py | 2 +- pennylane/devices/tests/test_measurements.py | 56 +++++----- pennylane/devices/tests/test_templates.py | 2 +- pennylane/devices/tests/test_tracker.py | 8 +- pennylane/qcut/cutstrategy.py | 2 +- pennylane/workflow/interfaces/jax_jit.py | 15 +-- pennylane/workflow/jacobian_products.py | 6 +- tests/conftest.py | 32 +++--- tests/devices/test_default_qubit_legacy.py | 4 +- .../{test_device.py => test_legacy_device.py} | 10 +- tests/{ => devices}/test_qubit_device.py | 12 +- tests/{ => devices}/test_qutrit_device.py | 8 +- .../parameter_shift/test_parameter_shift.py | 3 +- .../test_parameter_shift_shot_vec.py | 3 +- tests/interfaces/test_jacobian_products.py | 15 --- tests/test_debugging.py | 10 +- tests/test_qnode_legacy.py | 4 +- tests/test_return_types_qnode.py | 104 +++++++++++++----- tests/test_vqe.py | 27 ----- tests/transforms/test_qcut.py | 2 +- 27 files changed, 218 insertions(+), 180 deletions(-) rename tests/devices/{test_device.py => test_legacy_device.py} (99%) rename tests/{ => devices}/test_qubit_device.py (99%) rename tests/{ => devices}/test_qutrit_device.py (99%) diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml index 601e762fc92..70104146499 100644 --- a/.github/workflows/interface-unit-tests.yml +++ b/.github/workflows/interface-unit-tests.yml @@ -379,8 +379,7 @@ jobs: # catalyst requires the latest version of pennylane that is about to be released. # Installing catalyst after pennylane to make sure that the latest catalyst is used. install_catalyst_nightly: true - # using lightning master does not work for the tests with external libraries - install_pennylane_lightning_master: false + install_pennylane_lightning_master: true pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }} pytest_markers: external additional_pip_packages: pyzx matplotlib stim quimb mitiq pennylane-qiskit ply diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index 967417ad71b..c91979963a9 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -9,6 +9,13 @@ deprecations are listed below. Pending deprecations -------------------- +* ``Device``, ``QubitDevice``, and ``QutritDevice`` will no longer be imported top level in v0.40. They instead + we be available as ``qml.devices.LegacyDevice``, ``qml.devices.QubitDevice``, and ``qml.devices.QutritDevice`` + respectively. + + - Deprecated top level access in v0.39 + - Top level access removed in v0.40 + * `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` instead. `QNode.get_gradient_fn` can also be used to process the diff method. diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 7b9416d62c3..488fd8d37e8 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -86,6 +86,11 @@

Deprecations πŸ‘‹

+* `Device`, `QubitDevice`, and `QutritDevice` will no longer be accessible via top-level import in v0.40. + They will still be accessible as `qml.devices.LegacyDevice`, `qml.devices.QubitDevice`, and `qml.devices.QutritDevice` + respectively. + [(#6238)](https://github.com/PennyLaneAI/pennylane/pull/6238/) + * `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` and `QNode.get_gradient_fn` instead. [(#6244)](https://github.com/PennyLaneAI/pennylane/pull/6244) diff --git a/pennylane/__init__.py b/pennylane/__init__.py index 7f029461dea..3db66c953d2 100644 --- a/pennylane/__init__.py +++ b/pennylane/__init__.py @@ -16,8 +16,6 @@ PennyLane can be directly imported. """ -import numpy as _np - from pennylane.boolean_fn import BooleanFn import pennylane.numpy @@ -180,13 +178,30 @@ def __getattr__(name): if name == "plugin_devices": return pennylane.devices.device_constructor.plugin_devices + from warnings import warn # pylint: disable=import-outside-toplevel + if name == "QubitDevice": + warn( + "QubitDevice will no longer be accessible top level. Please access " + " the class as pennylane.devices.QubitDevice", + PennyLaneDeprecationWarning, + ) return pennylane.devices._qubit_device.QubitDevice # pylint:disable=protected-access if name == "QutritDevice": + warn( + "QutritDevice will no longer be accessible top level. Please access " + " the class as pennylane.devices.QutritDevice", + PennyLaneDeprecationWarning, + ) return pennylane.devices._qutrit_device.QutritDevice # pylint:disable=protected-access if name == "Device": + warn( + "Device will no longer be accessible top level. Please access " + " the class as pennylane.devices.LegacyDevice", + PennyLaneDeprecationWarning, + ) return pennylane.devices._legacy_device.Device # pylint:disable=protected-access raise AttributeError(f"module 'pennylane' has no attribute '{name}'") diff --git a/pennylane/devices/tests/conftest.py b/pennylane/devices/tests/conftest.py index 5ea86da0aae..f462138c4d5 100755 --- a/pennylane/devices/tests/conftest.py +++ b/pennylane/devices/tests/conftest.py @@ -110,12 +110,6 @@ def validate_diff_method(device, diff_method, device_kwargs): if diff_method == "backprop" and device_kwargs.get("shots") is not None: pytest.skip(reason="test should only be run in analytic mode") dev = device(1) - if isinstance(dev, qml.Device): - passthru_devices = dev.capabilities().get("passthru_devices") - if diff_method == "backprop" and passthru_devices is None: - pytest.skip(reason="device does not support backprop") - return - config = qml.devices.ExecutionConfig(gradient_method=diff_method) if not dev.supports_derivatives(execution_config=config): pytest.skip(reason="device does not support diff_method") @@ -141,12 +135,6 @@ def _device(wires): f"plugin and all of its dependencies must be installed." ) - if isinstance(dev, qml.Device): - capabilities = dev.capabilities() - if capabilities.get("model", None) != "qubit": - # exit the tests if device based on cv model (currently not supported) - pytest.exit("The device test suite currently only runs on qubit-based devices.") - return dev return _device diff --git a/pennylane/devices/tests/test_compare_default_qubit.py b/pennylane/devices/tests/test_compare_default_qubit.py index 05b2b6509e8..75e91809e2a 100755 --- a/pennylane/devices/tests/test_compare_default_qubit.py +++ b/pennylane/devices/tests/test_compare_default_qubit.py @@ -38,9 +38,6 @@ def test_hermitian_expectation(self, device, tol, benchmark): if dev.shots: pytest.skip("Device is in non-analytical mode.") - if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables: - pytest.skip("Device does not support the Hermitian observable.") - if dev.name == "default.qubit": pytest.skip("Device is default.qubit.") @@ -107,7 +104,7 @@ def test_projector_expectation(self, device, state, tol, benchmark): if dev.shots: pytest.skip("Device is in non-analytical mode.") - if isinstance(dev, qml.Device) and "Projector" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables: pytest.skip("Device does not support the Projector observable.") if dev.name == "default.qubit": diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py index b948f6962c4..7226e021994 100644 --- a/pennylane/devices/tests/test_gates.py +++ b/pennylane/devices/tests/test_gates.py @@ -358,7 +358,7 @@ def test_supported_gates_can_be_implemented(self, device_kwargs, operation): device_kwargs["wires"] = 4 # maximum size of current gates dev = qml.device(**device_kwargs) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if operation not in dev.operations: pytest.skip("operation not supported.") else: @@ -395,7 +395,7 @@ def test_basis_state(self, device, basis_state, tol, skip_if): """Test basis state initialization.""" n_wires = 4 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) @qml.qnode(dev) @@ -413,7 +413,7 @@ def test_state_prep(self, device, init_state, tol, skip_if): """Test StatePrep initialisation.""" n_wires = 1 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) rnd_state = init_state(n_wires) @@ -433,7 +433,7 @@ def test_single_qubit_no_parameters(self, device, init_state, op, mat, tol, skip """Test PauliX application.""" n_wires = 1 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) rnd_state = init_state(n_wires) @@ -457,7 +457,7 @@ def test_single_qubit_parameters( """Test single qubit gates taking a single scalar argument.""" n_wires = 1 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) rnd_state = init_state(n_wires) @@ -477,7 +477,7 @@ def test_rotation(self, device, init_state, tol, skip_if, benchmark): """Test three axis rotation gate.""" n_wires = 1 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) rnd_state = init_state(n_wires) @@ -501,7 +501,7 @@ def test_two_qubit_no_parameters(self, device, init_state, op, mat, tol, skip_if """Test two qubit gates.""" n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) if not dev.supports_operation(op(wires=range(n_wires)).name): pytest.skip("op not supported") @@ -527,7 +527,7 @@ def test_two_qubit_parameters( """Test parametrized two qubit gates taking a single scalar argument.""" n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) rnd_state = init_state(n_wires) @@ -549,7 +549,7 @@ def test_qubit_unitary(self, device, init_state, mat, tol, skip_if, benchmark): n_wires = int(np.log2(len(mat))) dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "QubitUnitary" not in dev.operations: pytest.skip("Skipped because device does not support QubitUnitary.") @@ -574,7 +574,7 @@ def test_special_unitary(self, device, init_state, theta_, tol, skip_if, benchma n_wires = int(np.log(len(theta_) + 1) / np.log(4)) dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "SpecialUnitary" not in dev.operations: pytest.skip("Skipped because device does not support SpecialUnitary.") @@ -603,7 +603,7 @@ def test_three_qubit_no_parameters(self, device, init_state, op, mat, tol, skip_ n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"returns_probs": False}) rnd_state = init_state(n_wires) diff --git a/pennylane/devices/tests/test_gates_with_expval.py b/pennylane/devices/tests/test_gates_with_expval.py index 7239ab2bcd3..f2e7e8951d6 100755 --- a/pennylane/devices/tests/test_gates_with_expval.py +++ b/pennylane/devices/tests/test_gates_with_expval.py @@ -256,7 +256,7 @@ def test_supported_gate_two_wires_no_parameters(self, device, tol, name, expecte dev = device(n_wires) op = getattr(qml.ops, name) - if isinstance(dev, qml.Device) and not dev.supports_operation(op): + if isinstance(dev, qml.devices.LegacyDevice) and not dev.supports_operation(op): pytest.skip("operation not supported") @qml.qnode(dev) diff --git a/pennylane/devices/tests/test_measurements.py b/pennylane/devices/tests/test_measurements.py index 2a81366ba36..cde18014781 100644 --- a/pennylane/devices/tests/test_measurements.py +++ b/pennylane/devices/tests/test_measurements.py @@ -116,7 +116,7 @@ def test_supported_observables_can_be_implemented(self, device_kwargs, observabl if dev.shots and observable == "SparseHamiltonian": pytest.skip("SparseHamiltonian only supported in analytic mode") - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): assert hasattr(dev, "observables") if observable not in dev.observables: pytest.skip("observable not supported") @@ -313,7 +313,7 @@ def test_hermitian_expectation(self, device, tol): n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") theta = 0.432 @@ -342,7 +342,7 @@ def test_projector_expectation(self, device, tol): n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device) and "Projector" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") theta = 0.732 @@ -380,7 +380,7 @@ def test_multi_mode_hermitian_expectation(self, device, tol): n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") theta = 0.432 @@ -426,7 +426,7 @@ def circuit(): def test_op_arithmetic_matches_default_qubit(self, o, device, tol): """Test that devices (which support the observable) match default.qubit results.""" dev = device(2) - if isinstance(dev, qml.Device) and o.name not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and o.name not in dev.observables: pytest.skip(f"Skipped because device does not support the {o.name} observable.") def circuit(): @@ -448,7 +448,7 @@ def test_paulix_pauliy(self, device, tol, skip_if): """Test that a tensor product involving PauliX and PauliY works correctly""" n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 @@ -473,7 +473,7 @@ def test_pauliz_hadamard(self, device, tol, skip_if): """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 @@ -517,7 +517,7 @@ def circ(obs): """ n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) @qml.qnode(dev) @@ -545,7 +545,7 @@ def circ(wire_labels): """ dev = device(wires=3) dev_custom_labels = device(wires=label_map) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) def circ(wire_labels): @@ -567,7 +567,7 @@ def test_hermitian(self, device, tol, skip_if): n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") @@ -609,7 +609,7 @@ def test_projector(self, device, tol, skip_if): n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") @@ -661,7 +661,7 @@ def test_sparse_hamiltonian_expval(self, device, tol): n_wires = 4 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "SparseHamiltonian" not in dev.observables: pytest.skip( "Skipped because device does not support the SparseHamiltonian observable." @@ -724,7 +724,7 @@ def test_sample_values_hermitian(self, device, tol): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") A_ = np.array([[1, 2j], [-2j, 0]]) @@ -760,7 +760,7 @@ def test_sample_values_projector(self, device, tol): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device) and "Projector" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") theta = 0.543 @@ -808,7 +808,7 @@ def test_sample_values_hermitian_multi_qubit(self, device, tol): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") theta = 0.543 @@ -857,7 +857,7 @@ def test_sample_values_projector_multi_qubit(self, device, tol): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device) and "Projector" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") theta = 0.543 @@ -915,7 +915,7 @@ def test_paulix_pauliy(self, device, tol, skip_if): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 @@ -959,7 +959,7 @@ def test_pauliz_hadamard(self, device, tol, skip_if): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 @@ -1001,7 +1001,7 @@ def test_hermitian(self, device, tol, skip_if): if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") @@ -1095,7 +1095,7 @@ def test_projector(self, device, tol, skip_if): # pylint: disable=too-many-stat if not dev.shots: pytest.skip("Device is in analytic mode, cannot test sampling.") - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") @@ -1270,7 +1270,7 @@ def test_var_hermitian(self, device, tol): n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") phi = 0.543 @@ -1306,7 +1306,7 @@ def test_var_projector(self, device, tol): n_wires = 2 dev = device(n_wires) - if isinstance(dev, qml.Device) and "Projector" not in dev.observables: + if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") phi = 0.543 @@ -1367,7 +1367,7 @@ def test_paulix_pauliy(self, device, tol, skip_if): """Test that a tensor product involving PauliX and PauliY works correctly""" n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 @@ -1399,7 +1399,7 @@ def test_pauliz_hadamard(self, device, tol, skip_if): """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) theta = 0.432 @@ -1449,7 +1449,7 @@ def circ(obs): """ n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) @qml.qnode(dev) @@ -1476,7 +1476,7 @@ def circ(wire_labels): """ dev = device(wires=3) dev_custom_labels = device(wires=label_map) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): skip_if(dev, {"supports_tensor_observables": False}) def circ(wire_labels): @@ -1498,7 +1498,7 @@ def test_hermitian(self, device, tol, skip_if): n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "Hermitian" not in dev.observables: pytest.skip("Skipped because device does not support the Hermitian observable.") @@ -1570,7 +1570,7 @@ def test_projector(self, device, tol, skip_if): n_wires = 3 dev = device(n_wires) - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if "Projector" not in dev.observables: pytest.skip("Skipped because device does not support the Projector observable.") diff --git a/pennylane/devices/tests/test_templates.py b/pennylane/devices/tests/test_templates.py index 07b0b56b39c..86e1101340a 100644 --- a/pennylane/devices/tests/test_templates.py +++ b/pennylane/devices/tests/test_templates.py @@ -33,7 +33,7 @@ def check_op_supported(op, dev): """Skip test if device does not support an operation. Works with both device APIs""" - if isinstance(dev, qml.Device): + if isinstance(dev, qml.devices.LegacyDevice): if op.name not in dev.operations: pytest.skip("operation not supported.") else: diff --git a/pennylane/devices/tests/test_tracker.py b/pennylane/devices/tests/test_tracker.py index 75fc6145ac8..c46a5ad952e 100644 --- a/pennylane/devices/tests/test_tracker.py +++ b/pennylane/devices/tests/test_tracker.py @@ -26,7 +26,9 @@ def test_tracker_initialization(self, device): dev = device(1) - if isinstance(dev, qml.Device) and not dev.capabilities().get("supports_tracker", False): + if isinstance(dev, qml.devices.LegacyDevice) and not dev.capabilities().get( + "supports_tracker", False + ): pytest.skip("Device does not support a tracker") assert isinstance(dev.tracker, qml.Tracker) @@ -36,7 +38,9 @@ def test_tracker_updated_in_execution_mode(self, device): dev = device(1) - if isinstance(dev, qml.Device) and not dev.capabilities().get("supports_tracker", False): + if isinstance(dev, qml.devices.LegacyDevice) and not dev.capabilities().get( + "supports_tracker", False + ): pytest.skip("Device does not support a tracker") @qml.qnode(dev, diff_method="parameter-shift") diff --git a/pennylane/qcut/cutstrategy.py b/pennylane/qcut/cutstrategy.py index a26a528b338..2e92a8cdb87 100644 --- a/pennylane/qcut/cutstrategy.py +++ b/pennylane/qcut/cutstrategy.py @@ -41,7 +41,7 @@ class CutStrategy: check out the :func:`qml.cut_circuit() ` transform for more details. Args: - devices (Union[qml.Device, Sequence[qml.Device]]): Single, or Sequence of, device(s). + devices (Union[qml.devices.Device, Sequence[qml.devices.Device]]): Single, or Sequence of, device(s). Optional only when ``max_free_wires`` is provided. max_free_wires (int): Number of wires for the largest available device. Optional only when ``devices`` is provided where it defaults to the maximum number of wires among diff --git a/pennylane/workflow/interfaces/jax_jit.py b/pennylane/workflow/interfaces/jax_jit.py index d52f686148e..cdfade8a979 100644 --- a/pennylane/workflow/interfaces/jax_jit.py +++ b/pennylane/workflow/interfaces/jax_jit.py @@ -96,7 +96,7 @@ def _get_counts_shape(mp: "qml.measurements.CountsMP", num_device_wires=0): return outcome_counts -def _result_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.Device"): +def _result_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.devices.Device"): """Auxiliary function for creating the shape and dtype object structure given a tape.""" @@ -125,7 +125,7 @@ def struct(mp, shots): return tuple(shape) if tape.shots.has_partitioned_shots else shape[0] -def _jac_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.Device"): +def _jac_shape_dtype_struct(tape: "qml.tape.QuantumScript", device: "qml.devices.Device"): """The shape of a jacobian for a single tape given a device. Args: @@ -167,14 +167,9 @@ def pure_callback_wrapper(p): new_tapes = _set_fn(tapes.vals, p) return _to_jax(execute_fn(new_tapes)) - if isinstance(device, qml.Device): - device_supports_vectorization = device.capabilities().get("supports_broadcasting") - else: - # first order way of determining native parameter broadcasting support - # will be inaccurate when inclusion of broadcast_expand depends on ExecutionConfig values (like adjoint) - device_supports_vectorization = ( - qml.transforms.broadcast_expand not in device.preprocess()[0] - ) + # first order way of determining native parameter broadcasting support + # will be inaccurate when inclusion of broadcast_expand depends on ExecutionConfig values (like adjoint) + device_supports_vectorization = qml.transforms.broadcast_expand not in device.preprocess()[0] out = jax.pure_callback( pure_callback_wrapper, shape_dtype_structs, params, vectorized=device_supports_vectorization ) diff --git a/pennylane/workflow/jacobian_products.py b/pennylane/workflow/jacobian_products.py index d5ede1227a5..d3ae50dca65 100644 --- a/pennylane/workflow/jacobian_products.py +++ b/pennylane/workflow/jacobian_products.py @@ -18,7 +18,7 @@ import inspect import logging from collections.abc import Callable, Sequence -from typing import Optional, Union +from typing import Optional import numpy as np from cachetools import LRUCache @@ -334,7 +334,7 @@ def compute_jacobian(self, tapes: QuantumScriptBatch): class DeviceDerivatives(JacobianProductCalculator): - """Calculate jacobian products via a device provided jacobian. This class relies on either ``qml.Device.gradients`` or + """Calculate jacobian products via a device provided jacobian. This class relies on ``qml.devices.Device.compute_derivatives``. Args: @@ -399,7 +399,7 @@ def __repr__(self): def __init__( self, - device: Union["qml.devices.Device", "qml.Device"], + device: "qml.devices.Device", execution_config: Optional["qml.devices.ExecutionConfig"] = None, ): if execution_config is None: diff --git a/tests/conftest.py b/tests/conftest.py index 3094a2fd784..d478b6f0998 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,20 +36,6 @@ TOL_STOCHASTIC = 0.05 -# pylint: disable=too-few-public-methods -class DummyDevice(DefaultGaussian): - """Dummy device to allow Kerr operations""" - - _operation_map = DefaultGaussian._operation_map.copy() - _operation_map["Kerr"] = lambda *x, **y: np.identity(2) - - -@pytest.fixture(autouse=True) -def set_numpy_seed(): - np.random.seed(9872653) - yield - - @pytest.fixture(scope="function", autouse=True) def capture_legacy_device_deprecation_warnings(): with warnings.catch_warnings(record=True) as recwarn: @@ -67,6 +53,20 @@ def capture_legacy_device_deprecation_warnings(): warnings.warn(message=w.message, category=w.category) +# pylint: disable=too-few-public-methods +class DummyDevice(DefaultGaussian): + """Dummy device to allow Kerr operations""" + + _operation_map = DefaultGaussian._operation_map.copy() + _operation_map["Kerr"] = lambda *x, **y: np.identity(2) + + +@pytest.fixture(autouse=True) +def set_numpy_seed(): + np.random.seed(9872653) + yield + + @pytest.fixture(scope="session") def tol(): """Numerical tolerance for equality tests.""" @@ -181,12 +181,12 @@ def mock_device(monkeypatch): """A mock instance of the abstract Device class""" with monkeypatch.context() as m: - dev = qml.Device + dev = qml.devices.LegacyDevice m.setattr(dev, "__abstractmethods__", frozenset()) m.setattr(dev, "short_name", "mock_device") m.setattr(dev, "capabilities", lambda cls: {"model": "qubit"}) m.setattr(dev, "operations", {"RX", "RY", "RZ", "CNOT", "SWAP"}) - yield qml.Device(wires=2) # pylint:disable=abstract-class-instantiated + yield qml.devices.LegacyDevice(wires=2) # pylint:disable=abstract-class-instantiated # pylint: disable=protected-access diff --git a/tests/devices/test_default_qubit_legacy.py b/tests/devices/test_default_qubit_legacy.py index f3c47f90e23..11ca082441c 100644 --- a/tests/devices/test_default_qubit_legacy.py +++ b/tests/devices/test_default_qubit_legacy.py @@ -2344,7 +2344,7 @@ def test_Hamiltonian_filtered_from_rotations(self, mocker): dev = qml.device("default.qubit.legacy", wires=2, shots=10) H = qml.Hamiltonian([0.1, 0.2], [qml.PauliX(0), qml.PauliZ(1)]) - spy = mocker.spy(qml.QubitDevice, "_get_diagonalizing_gates") + spy = mocker.spy(qml.devices.QubitDevice, "_get_diagonalizing_gates") qs = qml.tape.QuantumScript([qml.RX(1, 0)], [qml.expval(qml.PauliX(0)), qml.expval(H)]) rotations = dev._get_diagonalizing_gates(qs) @@ -2382,7 +2382,7 @@ def circuit(y, z, is_state_batched): def test_super_expval_not_called(self, is_state_batched, mocker): """Tests basic expval result, and ensures QubitDevice.expval is not called.""" dev = qml.device("default.qubit.legacy", wires=1) - spy = mocker.spy(qml.QubitDevice, "expval") + spy = mocker.spy(qml.devices.QubitDevice, "expval") obs = qml.sum(qml.s_prod(0.1, qml.PauliX(0)), qml.s_prod(0.2, qml.PauliZ(0))) assert np.isclose(dev.expval(obs), 0.2) spy.assert_not_called() diff --git a/tests/devices/test_device.py b/tests/devices/test_legacy_device.py similarity index 99% rename from tests/devices/test_device.py rename to tests/devices/test_legacy_device.py index c05a5e76309..c08d9411dca 100644 --- a/tests/devices/test_device.py +++ b/tests/devices/test_legacy_device.py @@ -22,7 +22,7 @@ import pytest import pennylane as qml -from pennylane import Device +from pennylane.devices import LegacyDevice as Device from pennylane.wires import Wires mock_device_paulis = ["PauliX", "PauliY", "PauliZ"] @@ -188,6 +188,12 @@ def get_device(wires=1): yield get_device +def test_deprecated_access(): + """Test that accessing via top-level is deprecated.""" + with pytest.warns(qml.PennyLaneDeprecationWarning, match="Device will no longer be accessible"): + qml.Device # pylint: disable=pointless-statement + + # pylint: disable=pointless-statement def test_invalid_attribute_in_devices_raises_error(): with pytest.raises(AttributeError, match="'pennylane.devices' has no attribute 'blabla'"): @@ -1151,7 +1157,7 @@ class TestGrouping: """Tests for the use_grouping option for devices.""" # pylint: disable=too-few-public-methods, unused-argument, missing-function-docstring, missing-class-docstring - class SomeDevice(qml.Device): + class SomeDevice(qml.devices.LegacyDevice): name = "" short_name = "" pennylane_requires = "" diff --git a/tests/test_qubit_device.py b/tests/devices/test_qubit_device.py similarity index 99% rename from tests/test_qubit_device.py rename to tests/devices/test_qubit_device.py index c42ca5a6d28..9edc522a408 100644 --- a/tests/test_qubit_device.py +++ b/tests/devices/test_qubit_device.py @@ -21,8 +21,8 @@ import pytest import pennylane as qml -from pennylane import QubitDevice from pennylane import numpy as pnp +from pennylane.devices import QubitDevice from pennylane.measurements import ( Expectation, ExpectationMP, @@ -165,6 +165,12 @@ def _working_get_batch_size(tensor, expected_shape, expected_size): return None +def test_deprecated_access(): + """Test that accessing via top-level is deprecated.""" + with pytest.warns(qml.PennyLaneDeprecationWarning, match="Device will no longer be accessible"): + qml.QubitDevice # pylint: disable=pointless-statement + + def test_notimplemented_circuit_hash(mock_qubit_device): """Test that the circuit hash property is not implemented""" dev = mock_qubit_device() @@ -1655,7 +1661,7 @@ def test_generate_basis_states(): def test_samples_to_counts_all_outomces(): """Test that _samples_to_counts can handle counts with all outcomes.""" - class DummyQubitDevice(qml.QubitDevice): + class DummyQubitDevice(qml.devices.QubitDevice): author = None name = "bla" @@ -1676,7 +1682,7 @@ def apply(self, operations, **kwargs): def test_no_adjoint_jacobian_errors(): """Test that adjoint_jacobian errors with batching and shot vectors""" - class DummyQubitDevice(qml.QubitDevice): + class DummyQubitDevice(qml.devices.QubitDevice): author = None name = "bla" diff --git a/tests/test_qutrit_device.py b/tests/devices/test_qutrit_device.py similarity index 99% rename from tests/test_qutrit_device.py rename to tests/devices/test_qutrit_device.py index 6291a3e9d71..8799d75234b 100644 --- a/tests/test_qutrit_device.py +++ b/tests/devices/test_qutrit_device.py @@ -22,8 +22,8 @@ from scipy.stats import unitary_group import pennylane as qml -from pennylane import QubitDevice, QutritDevice from pennylane import numpy as pnp +from pennylane.devices import QubitDevice, QutritDevice from pennylane.measurements import ( Counts, CountsMP, @@ -142,6 +142,12 @@ def get_qutrit_device(wires=1): # TODO: Add tests for expval, var after observables are added +def test_deprecated_access(): + """Test that accessing via top-level is deprecated.""" + with pytest.warns(qml.PennyLaneDeprecationWarning, match="Device will no longer be accessible"): + qml.QutritDevice # pylint: disable=pointless-statement + + class TestOperations: """Tests the logic related to operations""" diff --git a/tests/gradients/parameter_shift/test_parameter_shift.py b/tests/gradients/parameter_shift/test_parameter_shift.py index 237178b538d..e5a00b2cdf0 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift.py +++ b/tests/gradients/parameter_shift/test_parameter_shift.py @@ -3563,8 +3563,7 @@ def cost_fn(weights, coeffs1, coeffs2, dev=None, broadcast=False): tape = qml.tape.QuantumScript.from_queue(q) tape.trainable_params = {0, 1, 2, 3, 4, 5} tapes, fn = qml.gradients.param_shift(tape, broadcast=broadcast) - execute_fn = dev.batch_execute if isinstance(dev, qml.Device) else dev.execute - jac = fn(execute_fn(tapes)) + jac = fn(dev.execute(tapes)) return jac @staticmethod diff --git a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py index 51ffd43753b..6d9212dceb9 100644 --- a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py +++ b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py @@ -2228,8 +2228,7 @@ def cost_fn(weights, coeffs1, coeffs2, dev=None, broadcast=False): tape = qml.tape.QuantumScript.from_queue(q, shots=dev.shots) tape.trainable_params = {0, 1, 2, 3, 4, 5} tapes, fn = qml.gradients.param_shift(tape, broadcast=broadcast) - execute_fn = dev.batch_execute if isinstance(dev, qml.Device) else dev.execute - return fn(execute_fn(tapes)) + return fn(dev.execute(tapes)) @staticmethod def cost_fn_expected(weights, coeffs1, coeffs2): diff --git a/tests/interfaces/test_jacobian_products.py b/tests/interfaces/test_jacobian_products.py index 2fc275558e7..3d758c38642 100644 --- a/tests/interfaces/test_jacobian_products.py +++ b/tests/interfaces/test_jacobian_products.py @@ -129,21 +129,6 @@ def test_device_jacobians_initialization_new_dev(self): assert isinstance(jpc._jacs_cache, LRUCache) assert len(jpc._jacs_cache) == 0 - def test_device_jacobians_initialization_old_dev(self): - """Test the private attributes are set during initialization of a DeviceDerivatives class with the - old device interface.""" - - device = qml.devices.DefaultQubitLegacy(wires=5) - - jpc = DeviceDerivatives(device, aj_config) - - assert jpc._device is device - assert jpc._execution_config == aj_config - assert isinstance(jpc._results_cache, LRUCache) - assert len(jpc._results_cache) == 0 - assert isinstance(jpc._jacs_cache, LRUCache) - assert len(jpc._jacs_cache) == 0 - def test_device_jacobians_repr(self): """Test the repr method for device jacobians.""" device = qml.device("default.qubit") diff --git a/tests/test_debugging.py b/tests/test_debugging.py index bdd10a4cfd5..4e049fd3e72 100644 --- a/tests/test_debugging.py +++ b/tests/test_debugging.py @@ -176,7 +176,7 @@ def circuit(): if "mixed" in dev.name: qml.Snapshot(measurement=qml.density_matrix(wires=[0, 1])) - if isinstance(dev, qml.QutritDevice): + if isinstance(dev, qml.devices.QutritDevice): return qml.expval(qml.GellMann(0, 1)) return qml.expval(qml.PauliZ(0)) @@ -191,7 +191,7 @@ def circuit(): @pytest.mark.parametrize("diff_method", [None, "parameter-shift"]) def test_all_state_measurement_snapshot_pure_qubit_dev(self, dev, diff_method): """Test that the correct measurement snapshots are returned for different measurement types.""" - if isinstance(dev, (qml.devices.default_mixed.DefaultMixed, qml.QutritDevice)): + if isinstance(dev, (qml.devices.default_mixed.DefaultMixed, qml.devices.QutritDevice)): pytest.skip() @qml.qnode(dev, diff_method=diff_method) @@ -230,7 +230,7 @@ def test_empty_snapshots(self, dev): @qml.qnode(dev) def circuit(): - if isinstance(dev, qml.QutritDevice): + if isinstance(dev, qml.devices.QutritDevice): qml.THadamard(wires=0) return qml.expval(qml.GellMann(0, index=6)) @@ -238,7 +238,7 @@ def circuit(): return qml.expval(qml.PauliX(0)) result = qml.snapshots(circuit)() - if isinstance(dev, qml.QutritDevice): + if isinstance(dev, qml.devices.QutritDevice): expected = {"execution_results": np.array(0.66666667)} else: expected = {"execution_results": np.array(1.0)} @@ -700,7 +700,7 @@ def circuit(): assert np.allclose(result["execution_results"], expected["execution_results"]) - del result["execution_results"] + del result["execution_results"] # pylint: disable=unsupported-delete-operation del expected["execution_results"] _compare_numpy_dicts(result, expected) diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index f8e56cc6fb4..3ee36d99bdb 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -813,7 +813,9 @@ def test_no_defer_measurements_if_supported(self, mocker): """Test that the defer_measurements transform is not used during QNode construction if the device supports mid-circuit measurements.""" dev = qml.device("default.qubit.legacy", wires=3) - mocker.patch.object(qml.Device, "_capabilities", {"supports_mid_measure": True}) + mocker.patch.object( + qml.devices.LegacyDevice, "_capabilities", {"supports_mid_measure": True} + ) spy = mocker.spy(qml.defer_measurements, "_transform") @qml.qnode(dev) diff --git a/tests/test_return_types_qnode.py b/tests/test_return_types_qnode.py index 6f74f84bb14..364eb468922 100644 --- a/tests/test_return_types_qnode.py +++ b/tests/test_return_types_qnode.py @@ -2204,7 +2204,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2232,7 +2234,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2268,7 +2272,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2295,7 +2301,9 @@ def circuit(x): all_shot_copies = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2326,7 +2334,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2358,7 +2368,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2395,7 +2407,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2427,7 +2441,9 @@ def circuit(x): all_shot_copies = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2455,7 +2471,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2552,7 +2570,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2584,7 +2604,9 @@ def test_scalar_sample_with_obs(self, shot_vector, meas1, meas2, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2601,7 +2623,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2642,7 +2666,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2656,7 +2682,9 @@ def circuit(x): for m in measurement_res ) - for shot_tuple in dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector: + for shot_tuple in ( + dev.shot_vector if isinstance(dev, qml.devices.LegacyDevice) else dev.shots.shot_vector + ): for idx in range(shot_tuple.copies): for i, r in enumerate(res[idx]): if i % 2 == 0 or shot_tuple.shots == 1: @@ -2674,7 +2702,9 @@ def test_scalar_counts_with_obs(self, shot_vector, meas1, meas2, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2691,7 +2721,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2726,7 +2758,9 @@ def test_scalar_counts_no_obs(self, shot_vector, meas1, meas2, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2743,7 +2777,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2774,7 +2810,9 @@ def test_probs_sample(self, shot_vector, sample_obs, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2799,7 +2837,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2835,7 +2875,9 @@ def test_probs_counts(self, shot_vector, sample_obs, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2860,7 +2902,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2896,7 +2940,9 @@ def test_sample_counts(self, shot_vector, sample_wires, counts_wires, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2927,7 +2973,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) @@ -2960,7 +3008,9 @@ def test_scalar_probs_sample_counts(self, shot_vector, meas1, meas2, device): raw_shot_vector = [ shot_tuple.shots for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) for _ in range(shot_tuple.copies) ] @@ -2983,7 +3033,9 @@ def circuit(x): [ shot_tuple.copies for shot_tuple in ( - dev.shot_vector if isinstance(dev, qml.Device) else dev.shots.shot_vector + dev.shot_vector + if isinstance(dev, qml.devices.LegacyDevice) + else dev.shots.shot_vector ) ] ) diff --git a/tests/test_vqe.py b/tests/test_vqe.py index e8edc6c84cf..ca7ea9a9e38 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -193,33 +193,6 @@ def amp_embed_and_strong_ent_layer(params, wires=None): (amp_embed_and_strong_ent_layer, (EMBED_PARAMS, LAYER_PARAMS)), ] -##################################################### -# Device - - -@pytest.fixture(scope="function", name="mock_device") -def mock_device_fixture(monkeypatch): - with monkeypatch.context() as m: - m.setattr(qml.Device, "__abstractmethods__", frozenset()) - m.setattr( - qml.Device, "_capabilities", {"supports_tensor_observables": True, "model": "qubit"} - ) - m.setattr(qml.Device, "operations", ["RX", "RY", "Rot", "CNOT", "Hadamard", "StatePrep"]) - m.setattr( - qml.Device, "observables", ["PauliX", "PauliY", "PauliZ", "Hadamard", "Hermitian"] - ) - m.setattr(qml.Device, "short_name", "MockDevice") - m.setattr(qml.Device, "expval", lambda self, x, y, z: 1) - m.setattr(qml.Device, "var", lambda self, x, y, z: 2) - m.setattr(qml.Device, "sample", lambda self, x, y, z: 3) - m.setattr(qml.Device, "apply", lambda self, x, y, z: None) - - def get_device(wires=1): - return qml.Device(wires=wires) # pylint:disable=abstract-class-instantiated - - yield get_device - - ##################################################### # Queues diff --git a/tests/transforms/test_qcut.py b/tests/transforms/test_qcut.py index 1a78c1ab4e2..96b0d1e3af8 100644 --- a/tests/transforms/test_qcut.py +++ b/tests/transforms/test_qcut.py @@ -4681,7 +4681,7 @@ def test_init_raises(self, devices, imbalance_tolerance, num_fragments_probed): """Test if ill-initialized instances throw errors.""" if ( - isinstance(devices, (qml.Device, qml.devices.Device)) + isinstance(devices, qml.devices.Device) and imbalance_tolerance is None and num_fragments_probed is None ): From d0344b02309fee6b2d01c7a9a433e89af4ad5a78 Mon Sep 17 00:00:00 2001 From: David Wierichs Date: Sun, 15 Sep 2024 21:47:02 +0200 Subject: [PATCH 04/14] [Program Capture] Add pytree support to captured `qml.grad` and `qml.jacobian` (#6134) **Context:** #6120 and #6127 add support to capture `qml.grad` and `qml.jacobian` in plxpr. Once captured, they dispatch to `jax.grad` and `jax.jacobian`. **Description of the Change:** This PR adds support for pytree inputs and outputs of the differentiated functions, similar to #6081. For this, it extends the internal class `FlatFn` by the extra functionality to turn the wrapper into a `*flat_args -> *flat_outputs` function, instead of a `*pytree_args -> *flat_outputs` function. **Benefits:** Pytree support :deciduous_tree: **Possible Drawbacks:** **Related GitHub Issues:** [sc-70930] [sc-71862] --------- Co-authored-by: Christina Lee Co-authored-by: Mudit Pandey --- doc/releases/changelog-dev.md | 3 +- pennylane/_grad.py | 50 ++++++-- pennylane/capture/__init__.py | 3 + pennylane/capture/capture_diff.py | 2 +- pennylane/capture/capture_qnode.py | 10 +- pennylane/capture/explanations.md | 7 +- pennylane/capture/flatfn.py | 31 ++++- tests/capture/test_capture_diff.py | 188 ++++++++++++++++++++++++++--- 8 files changed, 260 insertions(+), 34 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 488fd8d37e8..0a93727855d 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -25,9 +25,10 @@ * Differentiation of hybrid programs via `qml.grad` and `qml.jacobian` can now be captured into plxpr. When evaluating a captured `qml.grad` (`qml.jacobian`) instruction, it will dispatch to `jax.grad` (`jax.jacobian`), which differs from the Autograd implementation - without capture. + without capture. Pytree inputs and outputs are supported. [(#6120)](https://github.com/PennyLaneAI/pennylane/pull/6120) [(#6127)](https://github.com/PennyLaneAI/pennylane/pull/6127) + [(#6134)](https://github.com/PennyLaneAI/pennylane/pull/6134) * Improve unit testing for capturing of nested control flows. [(#6111)](https://github.com/PennyLaneAI/pennylane/pull/6111) diff --git a/pennylane/_grad.py b/pennylane/_grad.py index 859ae5d9fbb..d7cb52e0d52 100644 --- a/pennylane/_grad.py +++ b/pennylane/_grad.py @@ -25,6 +25,7 @@ from pennylane.capture import enabled from pennylane.capture.capture_diff import _get_grad_prim, _get_jacobian_prim +from pennylane.capture.flatfn import FlatFn from pennylane.compiler import compiler from pennylane.compiler.compiler import CompileError @@ -33,18 +34,53 @@ def _capture_diff(func, argnum=None, diff_prim=None, method=None, h=None): """Capture-compatible gradient computation.""" - import jax # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel + import jax + from jax.tree_util import tree_flatten, tree_leaves, tree_unflatten, treedef_tuple - if isinstance(argnum, int): - argnum = [argnum] if argnum is None: - argnum = [0] + argnum = 0 + if argnum_is_int := isinstance(argnum, int): + argnum = [argnum] @wraps(func) def new_func(*args, **kwargs): - jaxpr = jax.make_jaxpr(partial(func, **kwargs))(*args) - prim_kwargs = {"argnum": argnum, "jaxpr": jaxpr.jaxpr, "n_consts": len(jaxpr.consts)} - return diff_prim.bind(*jaxpr.consts, *args, **prim_kwargs, method=method, h=h) + flat_args, in_trees = zip(*(tree_flatten(arg) for arg in args)) + full_in_tree = treedef_tuple(in_trees) + + # Create a new input tree that only takes inputs marked by argnum into account + trainable_in_trees = (in_tree for i, in_tree in enumerate(in_trees) if i in argnum) + # If an integer was provided as argnum, unpack the arguments axis of the derivatives + if argnum_is_int: + trainable_in_tree = list(trainable_in_trees)[0] + else: + trainable_in_tree = treedef_tuple(trainable_in_trees) + + # Create argnum for the flat list of input arrays. For each flattened argument, + # add a list of flat argnums if the argument is trainable and an empty list otherwise. + start = 0 + flat_argnum_gen = ( + ( + list(range(start, (start := start + len(flat_arg)))) + if i in argnum + else list(range((start := start + len(flat_arg)), start)) + ) + for i, flat_arg in enumerate(flat_args) + ) + flat_argnum = sum(flat_argnum_gen, start=[]) + + # Create fully flattened function (flat inputs & outputs) + flat_fn = FlatFn(partial(func, **kwargs) if kwargs else func, full_in_tree) + flat_args = sum(flat_args, start=[]) + jaxpr = jax.make_jaxpr(flat_fn)(*flat_args) + prim_kwargs = {"argnum": flat_argnum, "jaxpr": jaxpr.jaxpr, "n_consts": len(jaxpr.consts)} + out_flat = diff_prim.bind(*jaxpr.consts, *flat_args, **prim_kwargs, method=method, h=h) + # flatten once more to go from 2D derivative structure (outputs, args) to flat structure + out_flat = tree_leaves(out_flat) + assert flat_fn.out_tree is not None, "out_tree should be set after executing flat_fn" + # The derivative output tree is the composition of output tree and trainable input trees + combined_tree = flat_fn.out_tree.compose(trainable_in_tree) + return tree_unflatten(combined_tree, out_flat) return new_func diff --git a/pennylane/capture/__init__.py b/pennylane/capture/__init__.py index 6deeef29682..2e43a246e2c 100644 --- a/pennylane/capture/__init__.py +++ b/pennylane/capture/__init__.py @@ -34,6 +34,7 @@ ~create_measurement_wires_primitive ~create_measurement_mcm_primitive ~qnode_call + ~FlatFn The ``primitives`` submodule offers easy access to objects with jax dependencies such as @@ -154,6 +155,7 @@ def _(*args, **kwargs): create_measurement_mcm_primitive, ) from .capture_qnode import qnode_call +from .flatfn import FlatFn # by defining this here, we avoid # E0611: No name 'AbstractOperator' in module 'pennylane.capture' (no-name-in-module) @@ -196,4 +198,5 @@ def __getattr__(key): "AbstractOperator", "AbstractMeasurement", "qnode_prim", + "FlatFn", ) diff --git a/pennylane/capture/capture_diff.py b/pennylane/capture/capture_diff.py index 92dde5a2956..829a9516af1 100644 --- a/pennylane/capture/capture_diff.py +++ b/pennylane/capture/capture_diff.py @@ -103,7 +103,7 @@ def _(*args, argnum, jaxpr, n_consts, method, h): def func(*inner_args): return jax.core.eval_jaxpr(jaxpr, consts, *inner_args) - return jax.jacobian(func, argnums=argnum)(*args) + return jax.tree_util.tree_leaves(jax.jacobian(func, argnums=argnum)(*args)) # pylint: disable=unused-argument @jacobian_prim.def_abstract_eval diff --git a/pennylane/capture/capture_qnode.py b/pennylane/capture/capture_qnode.py index f7f06451230..491b9f3f6a4 100644 --- a/pennylane/capture/capture_qnode.py +++ b/pennylane/capture/capture_qnode.py @@ -82,8 +82,12 @@ def _(*args, qnode, shots, device, qnode_kwargs, qfunc_jaxpr, n_consts): mps = qfunc_jaxpr.outvars return _get_shapes_for(*mps, shots=shots, num_device_wires=len(device.wires)) - def _qnode_jvp(*args_and_tangents, **impl_kwargs): - return jax.jvp(partial(qnode_prim.impl, **impl_kwargs), *args_and_tangents) + def make_zero(tan, arg): + return jax.lax.zeros_like_array(arg) if isinstance(tan, ad.Zero) else tan + + def _qnode_jvp(args, tangents, **impl_kwargs): + tangents = tuple(map(make_zero, tangents, args)) + return jax.jvp(partial(qnode_prim.impl, **impl_kwargs), args, tangents) ad.primitive_jvps[qnode_prim] = _qnode_jvp @@ -174,7 +178,7 @@ def f(x): qnode_kwargs = {"diff_method": qnode.diff_method, **execute_kwargs, **mcm_config} qnode_prim = _get_qnode_prim() - flat_args, _ = jax.tree_util.tree_flatten(args) + flat_args = jax.tree_util.tree_leaves(args) res = qnode_prim.bind( *qfunc_jaxpr.consts, *flat_args, diff --git a/pennylane/capture/explanations.md b/pennylane/capture/explanations.md index 61b88b94030..84feef9786f 100644 --- a/pennylane/capture/explanations.md +++ b/pennylane/capture/explanations.md @@ -159,12 +159,11 @@ You can also see the const variable `a` as argument `e:i32[]` to the inner neste ### Pytree handling Evaluating a jaxpr requires accepting and returning a flat list of tensor-like inputs and outputs. -list of tensor-like outputs. These long lists can be hard to manage and are very -restrictive on the allowed functions, but we can take advantage of pytrees to allow handling -arbitrary functions. +These long lists can be hard to manage and are very restrictive on the allowed functions, but we +can take advantage of pytrees to allow handling arbitrary functions. To start, we import the `FlatFn` helper. This class converts a function to one that caches -the resulting result pytree into `flat_fn.out_tree` when executed. This can be used to repack the +the result pytree into `flat_fn.out_tree` when executed. This can be used to repack the results into the correct shape. It also returns flattened results. This does not particularly matter for program capture, as we will only be producing jaxpr from the function, not calling it directly. diff --git a/pennylane/capture/flatfn.py b/pennylane/capture/flatfn.py index 4ba6005f41e..59e1c52b948 100644 --- a/pennylane/capture/flatfn.py +++ b/pennylane/capture/flatfn.py @@ -29,23 +29,48 @@ class FlatFn: property, so that the results can be repacked later. It also returns flattened results instead of the original result object. + If an ``in_tree`` is provided, the function accepts flattened inputs instead of the + original inputs with tree structure given by ``in_tree``. + + **Example** + + >>> import jax + >>> from pennylane.capture.flatfn import FlatFn >>> def f(x): ... return {"y": 2+x["x"]} >>> flat_f = FlatFn(f) - >>> res = flat_f({"x": 0}) + >>> arg = {"x": 0.5} + >>> res = flat_f(arg) + >>> res + [2.5] + >>> jax.tree_util.tree_unflatten(flat_f.out_tree, res) + {'y': 2.5} + + If we want to use a fully flattened function that also takes flat inputs instead of + the original inputs with tree structure, we can provide the treedef for this input + structure: + + >>> flat_args, in_tree = jax.tree_util.tree_flatten((arg,)) + >>> flat_f = FlatFn(f, in_tree) + >>> res = flat_f(*flat_args) >>> res - [2] + [2.5] >>> jax.tree_util.tree_unflatten(flat_f.out_tree, res) {'y': 2.5} + Note that the ``in_tree`` has to be created by flattening a tuple of all input + arguments, even if there is only a single argument. """ - def __init__(self, f): + def __init__(self, f, in_tree=None): self.f = f + self.in_tree = in_tree self.out_tree = None update_wrapper(self, f) def __call__(self, *args): + if self.in_tree is not None: + args = jax.tree_util.tree_unflatten(self.in_tree, args) out = self.f(*args) out_flat, out_tree = jax.tree_util.tree_flatten(out) self.out_tree = out_tree diff --git a/tests/capture/test_capture_diff.py b/tests/capture/test_capture_diff.py index cf7834aafb8..07596e01b47 100644 --- a/tests/capture/test_capture_diff.py +++ b/tests/capture/test_capture_diff.py @@ -99,11 +99,11 @@ def func_jax(x): assert qml.math.allclose(func_qml(x), jax_out) # Check overall jaxpr properties - if isinstance(argnum, int): - argnum = [argnum] jaxpr = jax.make_jaxpr(func_qml)(x) assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] assert len(jaxpr.eqns) == 3 + if isinstance(argnum, int): + argnum = [argnum] assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * len(argnum) grad_eqn = jaxpr.eqns[2] @@ -260,6 +260,106 @@ def circuit(x): jax.config.update("jax_enable_x64", initial_mode) + @pytest.mark.parametrize("argnum", ([0, 1], [0], [1])) + def test_grad_pytree_input(self, x64_mode, argnum): + """Test that the qml.grad primitive can be captured with pytree inputs.""" + + initial_mode = jax.config.jax_enable_x64 + jax.config.update("jax_enable_x64", x64_mode) + fdtype = jax.numpy.float64 if x64_mode else jax.numpy.float32 + + def inner_func(x, y): + return jnp.prod(jnp.sin(x["a"]) * jnp.cos(y[0]["b"][1]) ** 2) + + def func_qml(x): + return qml.grad(inner_func, argnum=argnum)( + {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]},) + ) + + def func_jax(x): + return jax.grad(inner_func, argnums=argnum)( + {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]},) + ) + + x = 0.7 + jax_out = func_jax(x) + jax_out_flat, jax_out_tree = jax.tree_util.tree_flatten(jax_out) + qml_out_flat, qml_out_tree = jax.tree_util.tree_flatten(func_qml(x)) + assert jax_out_tree == qml_out_tree + assert qml.math.allclose(jax_out_flat, qml_out_flat) + + # Check overall jaxpr properties + jaxpr = jax.make_jaxpr(func_qml)(x) + assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] + assert len(jaxpr.eqns) == 3 + argnum = [argnum] if isinstance(argnum, int) else argnum + assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * len(argnum) + + grad_eqn = jaxpr.eqns[2] + diff_eqn_assertions(grad_eqn, grad_prim, argnum=argnum) + assert [var.aval for var in grad_eqn.outvars] == jaxpr.out_avals + assert len(grad_eqn.params["jaxpr"].eqns) == 6 # 5 numeric eqns, 1 conversion eqn + + manual_out = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x) + manual_out_flat, manual_out_tree = jax.tree_util.tree_flatten(manual_out) + # Assert that the output from the manual evaluation is flat + assert manual_out_tree == jax.tree_util.tree_flatten(manual_out_flat)[1] + assert qml.math.allclose(jax_out_flat, manual_out_flat) + + jax.config.update("jax_enable_x64", initial_mode) + + @pytest.mark.parametrize("argnum", ([0, 1, 2], [0, 2], [1], 0)) + def test_grad_qnode_with_pytrees(self, argnum, x64_mode): + """Test capturing the gradient of a qnode that uses Pytrees.""" + # pylint: disable=protected-access + initial_mode = jax.config.jax_enable_x64 + jax.config.update("jax_enable_x64", x64_mode) + fdtype = jax.numpy.float64 if x64_mode else jax.numpy.float32 + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(x, y, z): + qml.RX(x["a"], wires=0) + qml.RY(y, wires=0) + qml.RZ(z[1][0], wires=0) + return qml.expval(qml.X(0)) + + dcircuit = qml.grad(circuit, argnum=argnum) + x = {"a": 0.6, "b": 0.9} + y = 0.6 + z = ({"c": 0.5}, [0.2, 0.3]) + qml_out = dcircuit(x, y, z) + qml_out_flat, qml_out_tree = jax.tree_util.tree_flatten(qml_out) + jax_out = jax.grad(circuit, argnums=argnum)(x, y, z) + jax_out_flat, jax_out_tree = jax.tree_util.tree_flatten(jax_out) + assert jax_out_tree == qml_out_tree + assert qml.math.allclose(jax_out_flat, qml_out_flat) + + jaxpr = jax.make_jaxpr(dcircuit)(x, y, z) + + assert len(jaxpr.eqns) == 1 # grad equation + assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * 6 + argnum = [argnum] if isinstance(argnum, int) else argnum + num_out_avals = 2 * (0 in argnum) + (1 in argnum) + 3 * (2 in argnum) + assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] * num_out_avals + + grad_eqn = jaxpr.eqns[0] + assert all(invar.aval == in_aval for invar, in_aval in zip(grad_eqn.invars, jaxpr.in_avals)) + flat_argnum = [0, 1] * (0 in argnum) + [2] * (1 in argnum) + [3, 4, 5] * (2 in argnum) + diff_eqn_assertions(grad_eqn, grad_prim, argnum=flat_argnum) + grad_jaxpr = grad_eqn.params["jaxpr"] + assert len(grad_jaxpr.eqns) == 1 # qnode equation + + flat_args = jax.tree_util.tree_leaves((x, y, z)) + manual_out = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, *flat_args) + manual_out_flat, manual_out_tree = jax.tree_util.tree_flatten(manual_out) + # Assert that the output from the manual evaluation is flat + assert manual_out_tree == jax.tree_util.tree_flatten(manual_out_flat)[1] + assert qml.math.allclose(jax_out_flat, manual_out_flat) + + jax.config.update("jax_enable_x64", initial_mode) + def _jac_allclose(jac1, jac2, num_axes, atol=1e-8): """Test that two Jacobians, given as nested sequences of arrays, are equal.""" @@ -279,10 +379,6 @@ class TestJacobian: @pytest.mark.parametrize("argnum", ([0, 1], [0], [1], 0, 1)) def test_classical_jacobian(self, x64_mode, argnum): """Test that the qml.jacobian primitive can be captured with classical nodes.""" - if isinstance(argnum, list) and len(argnum) > 1: - # These cases will only be unlocked with Pytree support - pytest.xfail() - initial_mode = jax.config.jax_enable_x64 jax.config.update("jax_enable_x64", x64_mode) fdtype = jnp.float64 if x64_mode else jnp.float32 @@ -307,14 +403,14 @@ def inner_func(x, y): func_jax = jax.jacobian(inner_func, argnums=argnum) jax_out = func_jax(x, y) - num_axes = 1 if isinstance(argnum, int) else 2 - assert _jac_allclose(func_qml(x, y), jax_out, num_axes) + qml_out = func_qml(x, y) + num_axes = 1 if (int_argnum := isinstance(argnum, int)) else 2 + assert _jac_allclose(qml_out, jax_out, num_axes) # Check overall jaxpr properties - jaxpr = jax.make_jaxpr(func_jax)(x, y) jaxpr = jax.make_jaxpr(func_qml)(x, y) - if isinstance(argnum, int): + if int_argnum: argnum = [argnum] exp_in_avals = [shaped_array(shape) for shape in [(4,), (2, 3)]] @@ -331,6 +427,9 @@ def inner_func(x, y): diff_eqn_assertions(jac_eqn, jacobian_prim, argnum=argnum) manual_eval = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x, y) + # Evaluating jaxpr gives flat list results. Need to adapt the JAX output to that + if not int_argnum: + jax_out = sum(jax_out, start=()) assert _jac_allclose(manual_eval, jax_out, num_axes) jax.config.update("jax_enable_x64", initial_mode) @@ -354,7 +453,6 @@ def func(x): return jnp.prod(x) * jnp.sin(x), jnp.sum(x**2) x = jnp.array([0.7, -0.9, 0.6, 0.3]) - x = x[:1] dim = len(x) eye = jnp.eye(dim) @@ -382,15 +480,20 @@ def func(x): # 2nd order qml_func_2 = qml.jacobian(qml_func_1) + hyperdiag = qml.numpy.zeros((4, 4, 4)) + for i in range(4): + hyperdiag[i, i, i] = 1 expected_2 = ( prod_sin[:, None, None] / x[None, :, None] / x[None, None, :] + - jnp.tensordot(prod_sin, eye / x**2, axes=0) # Correct diagonal entries + prod_cos_e_i[:, :, None] / x[None, None, :] + prod_cos_e_i[:, None, :] / x[None, :, None] - - jnp.tensordot(prod_sin, eye + eye / x**2, axes=0), - jnp.tensordot(jnp.ones(dim), eye * 2, axes=0), + - prod_sin * hyperdiag, + eye * 2, ) # Output only has one tuple axis - assert _jac_allclose(qml_func_2(x), expected_2, 1) + atol = 1e-8 if x64_mode else 2e-7 + assert _jac_allclose(qml_func_2(x), expected_2, 1, atol=atol) jaxpr_2 = jax.make_jaxpr(qml_func_2)(x) assert jaxpr_2.in_avals == [jax.core.ShapedArray((dim,), fdtype)] @@ -406,7 +509,7 @@ def func(x): assert jac_eqn.params["jaxpr"].eqns[0].primitive == jacobian_prim manual_eval_2 = jax.core.eval_jaxpr(jaxpr_2.jaxpr, jaxpr_2.consts, x) - assert _jac_allclose(manual_eval_2, expected_2, 1) + assert _jac_allclose(manual_eval_2, expected_2, 1, atol=atol) jax.config.update("jax_enable_x64", initial_mode) @@ -473,3 +576,58 @@ def circuit(x): assert _jac_allclose(manual_res, expected_res, 1) jax.config.update("jax_enable_x64", initial_mode) + + @pytest.mark.parametrize("argnum", ([0, 1], [0], [1])) + def test_jacobian_pytrees(self, x64_mode, argnum): + """Test that the qml.jacobian primitive can be captured with + pytree inputs and outputs.""" + + initial_mode = jax.config.jax_enable_x64 + jax.config.update("jax_enable_x64", x64_mode) + fdtype = jax.numpy.float64 if x64_mode else jax.numpy.float32 + + def inner_func(x, y): + return { + "prod_cos": jnp.prod(jnp.sin(x["a"]) * jnp.cos(y[0]["b"][1]) ** 2), + "sum_sin": jnp.sum(jnp.sin(x["a"]) * jnp.sin(y[1]["c"]) ** 2), + } + + def func_qml(x): + return qml.jacobian(inner_func, argnum=argnum)( + {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]}, {"c": 0.5}) + ) + + def func_jax(x): + return jax.jacobian(inner_func, argnums=argnum)( + {"a": x}, ({"b": [None, 0.4 * jnp.sqrt(x)]}, {"c": 0.5}) + ) + + x = 0.7 + jax_out = func_jax(x) + jax_out_flat, jax_out_tree = jax.tree_util.tree_flatten(jax_out) + qml_out_flat, qml_out_tree = jax.tree_util.tree_flatten(func_qml(x)) + assert jax_out_tree == qml_out_tree + assert qml.math.allclose(jax_out_flat, qml_out_flat) + + # Check overall jaxpr properties + jaxpr = jax.make_jaxpr(func_qml)(x) + assert jaxpr.in_avals == [jax.core.ShapedArray((), fdtype, weak_type=True)] + assert len(jaxpr.eqns) == 3 + + argnum = [argnum] if isinstance(argnum, int) else argnum + # Compute the flat argnum in order to determine the expected number of out tracers + flat_argnum = [0] * (0 in argnum) + [1, 2] * (1 in argnum) + assert jaxpr.out_avals == [jax.core.ShapedArray((), fdtype)] * (2 * len(flat_argnum)) + + jac_eqn = jaxpr.eqns[2] + + diff_eqn_assertions(jac_eqn, jacobian_prim, argnum=flat_argnum) + assert [var.aval for var in jac_eqn.outvars] == jaxpr.out_avals + + manual_out = jax.core.eval_jaxpr(jaxpr.jaxpr, jaxpr.consts, x) + manual_out_flat, manual_out_tree = jax.tree_util.tree_flatten(manual_out) + # Assert that the output from the manual evaluation is flat + assert manual_out_tree == jax.tree_util.tree_flatten(manual_out_flat)[1] + assert qml.math.allclose(jax_out_flat, manual_out_flat) + + jax.config.update("jax_enable_x64", initial_mode) From 79fc6d3dadb4a1dcd435f28e0d6e26aceb72ae3f Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Mon, 16 Sep 2024 09:51:46 +0000 Subject: [PATCH 05/14] [no ci] bump nightly version --- pennylane/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/_version.py b/pennylane/_version.py index a6ead820881..77639685bc6 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.39.0-dev14" +__version__ = "0.39.0-dev15" From 94f067a257d1e2a96812d30b576bc90da5381318 Mon Sep 17 00:00:00 2001 From: Cristian Emiliano Godinez Ramirez <57567043+EmilianoG-byte@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:08:41 +0200 Subject: [PATCH 06/14] Pennylane is compatible with numpy 2.0 (#6061) **Context:** We want to make Pennylane compatible with Numpy 2.0. After several discussions, we decided to test NumPy 2.0 on the CI by default in every PR (testing both Python versions would have been to slow). Some jobs still downgrade automatically to Numpy 1.x, since some interfaces (such as Tensorflow) still do not support NumPy 2.0. **Description of the Change:** We can distinguish the changes into 3 main categories: *Changes to workflows* - None in the final version *Changes to requirements and setup files* - Unpin the Numpy version in `setup.py` (now we also allow Numpy 2.0). - Update `requirements-ci.txt` to include Scipy 1.13 (this adds support for Numpy 2.0). - Pin Numpy in `requirements-ci.txt` to 2.0. *Changes to the source code* - Change `np.NaN` to `np.nan`. - Use legacy printing representation in tests, contrary to the new numpy representation of scalars, e.g. np.float64(3.0) rather than just 3.0. - Update probabilities warning to be case insensitive and check for a partial match, since this warning was changed in Numpy 2.0. - Check the datatype of np.exp from the Global phase only for Numpy 1.x, since this gets promoted to complex128 in Numpy 2.x. https://numpy.org/neps/nep-0050-scalar-promotion.html#schema-of-the-new-proposed-promotion-rules. **Benefits:** Make Pennylane compatible with Numpy 2.0. **Possible Drawbacks:** - We need to create a separate workflow to keep testing PennyLane with NumPy 1.x, since we still want to maintain compatibility with previous NumPy versions. This will be done in a separate PR. - We are not testing Numpy 2.x for the interfaces that implicitly require Numpy 1.x. These currently seem to be `tensorflow` and `openfermionpyscf` (notice that `tensorflow` is required in some code sections like qcut). In particular, `openfermionpyscf` causes an error: ``` AttributeError: np.string_ was removed in the NumPy 2.0 release. Use np.bytes_ instead. ``` in the qchem tests. The attribute `np.string_` is not used in the PL source code, so it is a problem with the package itself. [sc-61399] [sc-66548] --------- Co-authored-by: PietropaoloFrisoni Co-authored-by: Pietropaolo Frisoni --- .github/workflows/install_deps/action.yml | 6 +-- .gitignore | 2 + doc/releases/changelog-dev.md | 5 +++ pennylane/numpy/random.py | 5 ++- requirements-ci.txt | 2 +- setup.py | 2 +- .../data/attributes/operator/test_operator.py | 11 +++-- tests/data/attributes/test_dict.py | 10 +++-- tests/data/attributes/test_list.py | 12 +++-- tests/data/base/test_attribute.py | 4 +- tests/devices/qubit/test_measure.py | 8 ++-- tests/devices/qubit/test_sampling.py | 14 +++--- .../test_qutrit_mixed_sampling.py | 2 +- tests/devices/test_default_qubit_legacy.py | 44 ++++++++++--------- tests/devices/test_qubit_device.py | 6 +-- tests/measurements/test_counts.py | 6 +-- .../test_subroutines/test_prepselprep.py | 9 ++-- 17 files changed, 88 insertions(+), 60 deletions(-) diff --git a/.github/workflows/install_deps/action.yml b/.github/workflows/install_deps/action.yml index 809358ce745..99b77dc8157 100644 --- a/.github/workflows/install_deps/action.yml +++ b/.github/workflows/install_deps/action.yml @@ -15,7 +15,7 @@ inputs: jax_version: description: The version of JAX to install for any job that requires JAX required: false - default: 0.4.23 + default: '0.4.23' install_tensorflow: description: Indicate if TensorFlow should be installed or not required: false @@ -23,7 +23,7 @@ inputs: tensorflow_version: description: The version of TensorFlow to install for any job that requires TensorFlow required: false - default: 2.16.0 + default: '2.16.0' install_pytorch: description: Indicate if PyTorch should be installed or not required: false @@ -31,7 +31,7 @@ inputs: pytorch_version: description: The version of PyTorch to install for any job that requires PyTorch required: false - default: 2.3.0 + default: '2.3.0' install_pennylane_lightning_master: description: Indicate if PennyLane-Lightning should be installed from the master branch required: false diff --git a/.gitignore b/.gitignore index fc69a5281fd..d9da3038c69 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ config.toml qml_debug.log datasets/* .benchmarks/* +*.h5 +*.hdf5 diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 0a93727855d..506504f631b 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -6,6 +6,9 @@

Improvements πŸ› 

+* PennyLane is now compatible with NumPy 2.0. + [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061) + * `qml.qchem.excitations` now optionally returns fermionic operators. [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) @@ -124,6 +127,8 @@ This release contains contributions from (in alphabetical order): Guillermo Alonso, Utkarsh Azad, Lillian M. A. Frederiksen, +Pietropaolo Frisoni, +Emiliano Godinez, Christina Lee, William Maxwell, Lee J. O'Riordan, diff --git a/pennylane/numpy/random.py b/pennylane/numpy/random.py index eae1511cf4f..12a00e798a5 100644 --- a/pennylane/numpy/random.py +++ b/pennylane/numpy/random.py @@ -16,9 +16,10 @@ it works with the PennyLane :class:`~.tensor` class. """ -from autograd.numpy import random as _random +# isort: skip_file from numpy import __version__ as np_version from numpy.random import MT19937, PCG64, SFC64, Philox # pylint: disable=unused-import +from autograd.numpy import random as _random from packaging.specifiers import SpecifierSet from packaging.version import Version @@ -26,8 +27,8 @@ wrap_arrays(_random.__dict__, globals()) - if Version(np_version) in SpecifierSet(">=0.17.0"): + # pylint: disable=too-few-public-methods # pylint: disable=missing-class-docstring class Generator(_random.Generator): diff --git a/requirements-ci.txt b/requirements-ci.txt index 083beaae25f..d552b95e904 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -1,5 +1,5 @@ numpy -scipy<1.13.0 +scipy<=1.13.0 cvxpy cvxopt networkx diff --git a/setup.py b/setup.py index e13673fb1fa..41ae9775027 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ version = f.readlines()[-1].split()[-1].strip("\"'") requirements = [ - "numpy<2.0", + "numpy<=2.0", "scipy", "networkx", "rustworkx>=0.14.0", diff --git a/tests/data/attributes/operator/test_operator.py b/tests/data/attributes/operator/test_operator.py index 83e0c658ab6..8079b720f89 100644 --- a/tests/data/attributes/operator/test_operator.py +++ b/tests/data/attributes/operator/test_operator.py @@ -174,6 +174,7 @@ def test_value_init(self, attribute_cls, op_in): """Test that a DatasetOperator can be value-initialized from an operator, and that the deserialized operator is equivalent.""" + if not qml.operation.active_new_opmath() and isinstance(op_in, qml.ops.LinearCombination): op_in = qml.operation.convert_to_legacy_H(op_in) @@ -183,7 +184,8 @@ def test_value_init(self, attribute_cls, op_in): assert dset_op.info["py_type"] == get_type_str(type(op_in)) op_out = dset_op.get_value() - assert repr(op_out) == repr(op_in) + with np.printoptions(legacy="1.21"): + assert repr(op_out) == repr(op_in) assert op_in.data == op_out.data @pytest.mark.parametrize( @@ -199,6 +201,7 @@ def test_bind_init(self, attribute_cls, op_in): """Test that a DatasetOperator can be bind-initialized from an operator, and that the deserialized operator is equivalent.""" + if not qml.operation.active_new_opmath() and isinstance(op_in, qml.ops.LinearCombination): op_in = qml.operation.convert_to_legacy_H(op_in) @@ -210,10 +213,12 @@ def test_bind_init(self, attribute_cls, op_in): assert dset_op.info["py_type"] == get_type_str(type(op_in)) op_out = dset_op.get_value() - assert repr(op_out) == repr(op_in) + with np.printoptions(legacy="1.21"): + assert repr(op_out) == repr(op_in) assert op_in.data == op_out.data assert op_in.wires == op_out.wires - assert repr(op_in) == repr(op_out) + with np.printoptions(legacy="1.21"): + assert repr(op_in) == repr(op_out) @pytest.mark.parametrize("attribute_cls", [DatasetOperator, DatasetPyTree]) diff --git a/tests/data/attributes/test_dict.py b/tests/data/attributes/test_dict.py index 6bf6e202fd6..3e3a2a9d4b2 100644 --- a/tests/data/attributes/test_dict.py +++ b/tests/data/attributes/test_dict.py @@ -15,6 +15,7 @@ Tests for the ``DatasetDict`` attribute type. """ +import numpy as np import pytest from pennylane.data.attributes import DatasetDict @@ -45,7 +46,8 @@ def test_value_init(self, value): assert dset_dict.info.py_type == "dict" assert dset_dict.bind.keys() == value.keys() assert len(dset_dict) == len(value) - assert repr(value) == repr(dset_dict) + with np.printoptions(legacy="1.21"): + assert repr(value) == repr(dset_dict) @pytest.mark.parametrize( "value", [{"a": 1, "b": 2}, {}, {"a": 1, "b": {"x": "y", "z": [1, 2]}}] @@ -93,7 +95,8 @@ def test_copy(self, value): assert builtin_dict.keys() == value.keys() assert len(builtin_dict) == len(value) - assert repr(builtin_dict) == repr(value) + with np.printoptions(legacy="1.21"): + assert repr(builtin_dict) == repr(value) @pytest.mark.parametrize( "value", [{"a": 1, "b": 2}, {}, {"a": 1, "b": {"x": "y", "z": [1, 2]}}] @@ -121,4 +124,5 @@ def test_equality_same_length(self): ) def test_string_conversion(self, value): dset_dict = DatasetDict(value) - assert str(dset_dict) == str(value) + with np.printoptions(legacy="1.21"): + assert str(dset_dict) == str(value) diff --git a/tests/data/attributes/test_list.py b/tests/data/attributes/test_list.py index eef27057616..2f4c937d178 100644 --- a/tests/data/attributes/test_list.py +++ b/tests/data/attributes/test_list.py @@ -18,6 +18,7 @@ from itertools import combinations +import numpy as np import pytest from pennylane.data import DatasetList @@ -56,8 +57,9 @@ def test_value_init(self, input_type, value): lst = DatasetList(input_type(value)) assert lst == value - assert repr(lst) == repr(value) assert len(lst) == len(value) + with np.printoptions(legacy="1.21"): + assert repr(lst) == repr(value) @pytest.mark.parametrize("input_type", (list, tuple)) @pytest.mark.parametrize("value", [[], [1], [1, 2, 3], ["a", "b", "c"], [{"a": 1}]]) @@ -148,12 +150,14 @@ def test_setitem_out_of_range(self, index): @pytest.mark.parametrize("value", [[], [1], [1, 2, 3], ["a", "b", "c"], [{"a": 1}]]) def test_copy(self, input_type, value): """Test that a `DatasetList` can be copied.""" + ds = DatasetList(input_type(value)) ds_copy = ds.copy() assert ds_copy == value - assert repr(ds_copy) == repr(value) assert len(ds_copy) == len(value) + with np.printoptions(legacy="1.21"): + assert repr(ds_copy) == repr(value) @pytest.mark.parametrize("input_type", (list, tuple)) @pytest.mark.parametrize("value", [[], [1], [1, 2, 3], ["a", "b", "c"], [{"a": 1}]]) @@ -169,8 +173,10 @@ def test_equality(self, input_type, value): @pytest.mark.parametrize("value", [[], [1], [1, 2, 3], ["a", "b", "c"], [{"a": 1}]]) def test_string_conversion(self, value): """Test that a `DatasetList` is converted to a string correctly.""" + dset_dict = DatasetList(value) - assert str(dset_dict) == str(value) + with np.printoptions(legacy="1.21"): + assert str(dset_dict) == str(value) @pytest.mark.parametrize("value", [[1], [1, 2, 3], ["a", "b", "c"], [{"a": 1}]]) def test_deleting_elements(self, value): diff --git a/tests/data/base/test_attribute.py b/tests/data/base/test_attribute.py index d38249c1672..da500db48e5 100644 --- a/tests/data/base/test_attribute.py +++ b/tests/data/base/test_attribute.py @@ -285,8 +285,8 @@ def test_bind_init_from_other_bind(self): ) def test_repr(self, val, attribute_type): """Test that __repr__ has the expected format.""" - - assert repr(attribute(val)) == f"{attribute_type.__name__}({repr(val)})" + with np.printoptions(legacy="1.21"): + assert repr(attribute(val)) == f"{attribute_type.__name__}({repr(val)})" @pytest.mark.parametrize( "val", diff --git a/tests/devices/qubit/test_measure.py b/tests/devices/qubit/test_measure.py index d0c618311cf..47e4d8c2a31 100644 --- a/tests/devices/qubit/test_measure.py +++ b/tests/devices/qubit/test_measure.py @@ -302,7 +302,7 @@ class TestNaNMeasurements: def test_nan_float_result(self, mp, interface): """Test that the result of circuits with 0 probability postselections is NaN with the expected shape.""" - state = qml.math.full((2, 2), np.NaN, like=interface) + state = qml.math.full((2, 2), np.nan, like=interface) res = measure(mp, state, is_state_batched=False) assert qml.math.ndim(res) == 0 @@ -339,7 +339,7 @@ def test_nan_float_result(self, mp, interface): def test_nan_float_result_jax(self, mp, use_jit): """Test that the result of circuits with 0 probability postselections is NaN with the expected shape.""" - state = qml.math.full((2, 2), np.NaN, like="jax") + state = qml.math.full((2, 2), np.nan, like="jax") if use_jit: import jax @@ -360,7 +360,7 @@ def test_nan_float_result_jax(self, mp, use_jit): def test_nan_probs(self, mp, interface): """Test that the result of circuits with 0 probability postselections is NaN with the expected shape.""" - state = qml.math.full((2, 2), np.NaN, like=interface) + state = qml.math.full((2, 2), np.nan, like=interface) res = measure(mp, state, is_state_batched=False) assert qml.math.shape(res) == (2 ** len(mp.wires),) @@ -375,7 +375,7 @@ def test_nan_probs(self, mp, interface): def test_nan_probs_jax(self, mp, use_jit): """Test that the result of circuits with 0 probability postselections is NaN with the expected shape.""" - state = qml.math.full((2, 2), np.NaN, like="jax") + state = qml.math.full((2, 2), np.nan, like="jax") if use_jit: import jax diff --git a/tests/devices/qubit/test_sampling.py b/tests/devices/qubit/test_sampling.py index 4174ed63aae..e36c69c26a3 100644 --- a/tests/devices/qubit/test_sampling.py +++ b/tests/devices/qubit/test_sampling.py @@ -591,7 +591,7 @@ def test_only_catch_nan_errors(self, shots): mp = qml.expval(qml.PauliZ(0)) _shots = Shots(shots) - with pytest.raises(ValueError, match="probabilities do not sum to 1"): + with pytest.raises(ValueError, match=r"(?i)probabilities do not sum to 1"): _ = measure_with_samples([mp], state, _shots) @pytest.mark.all_interfaces @@ -619,7 +619,7 @@ def test_only_catch_nan_errors(self, shots): def test_nan_float_result(self, mp, interface, shots): """Test that the result of circuits with 0 probability postselections is NaN with the expected shape.""" - state = qml.math.full((2, 2), np.NaN, like=interface) + state = qml.math.full((2, 2), np.nan, like=interface) res = measure_with_samples((mp,), state, _FlexShots(shots), is_state_batched=False) if not isinstance(shots, list): @@ -646,7 +646,7 @@ def test_nan_float_result(self, mp, interface, shots): def test_nan_samples(self, mp, interface, shots): """Test that the result of circuits with 0 probability postselections is NaN with the expected shape.""" - state = qml.math.full((2, 2), np.NaN, like=interface) + state = qml.math.full((2, 2), np.nan, like=interface) res = measure_with_samples((mp,), state, _FlexShots(shots), is_state_batched=False) if not isinstance(shots, list): @@ -672,7 +672,7 @@ def test_nan_samples(self, mp, interface, shots): def test_nan_classical_shadows(self, interface, shots): """Test that classical_shadows returns an empty array when the state has NaN values""" - state = qml.math.full((2, 2), np.NaN, like=interface) + state = qml.math.full((2, 2), np.nan, like=interface) res = measure_with_samples( (qml.classical_shadow([0]),), state, _FlexShots(shots), is_state_batched=False ) @@ -699,7 +699,7 @@ def test_nan_classical_shadows(self, interface, shots): def test_nan_shadow_expval(self, H, interface, shots): """Test that shadow_expval returns an empty array when the state has NaN values""" - state = qml.math.full((2, 2), np.NaN, like=interface) + state = qml.math.full((2, 2), np.nan, like=interface) res = measure_with_samples( (qml.shadow_expval(H),), state, _FlexShots(shots), is_state_batched=False ) @@ -757,7 +757,7 @@ def test_sample_state_renorm_error(self, interface): """Test that renormalization does not occur if the error is too large.""" state = qml.math.array(two_qubit_state_not_normalized, like=interface) - with pytest.raises(ValueError, match="probabilities do not sum to 1"): + with pytest.raises(ValueError, match=r"(?i)probabilities do not sum to 1"): _ = sample_state(state, 10) @pytest.mark.all_interfaces @@ -775,7 +775,7 @@ def test_sample_batched_state_renorm_error(self, interface): """Test that renormalization does not occur if the error is too large.""" state = qml.math.array(batched_state_not_normalized, like=interface) - with pytest.raises(ValueError, match="probabilities do not sum to 1"): + with pytest.raises(ValueError, match=r"(?i)probabilities do not sum to 1"): _ = sample_state(state, 10, is_state_batched=True) diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py index eb3383ed5a6..ecd2fbbcca8 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_sampling.py @@ -402,7 +402,7 @@ def test_only_catch_nan_errors(self, shots): mp = qml.sample(wires=range(2)) _shots = Shots(shots) - with pytest.raises(ValueError, match="probabilities do not sum to 1"): + with pytest.raises(ValueError, match=r"(?i)probabilities do not sum to 1"): _ = measure_with_samples(mp, state, _shots) @pytest.mark.parametrize("mp", [qml.probs(0), qml.probs(op=qml.GellMann(0, 1))]) diff --git a/tests/devices/test_default_qubit_legacy.py b/tests/devices/test_default_qubit_legacy.py index 11ca082441c..9b67d18b5c6 100644 --- a/tests/devices/test_default_qubit_legacy.py +++ b/tests/devices/test_default_qubit_legacy.py @@ -18,6 +18,7 @@ # pylint: disable=protected-access,cell-var-from-loop import cmath import math +from importlib.metadata import version import pytest @@ -628,7 +629,8 @@ def test_apply_global_phase(self, qubit_device_3_wires, tol, wire, input_state): expected_output = np.array(input_state) * np.exp(-1j * phase) assert np.allclose(qubit_device_3_wires._state, np.array(expected_output), atol=tol, rtol=0) - assert qubit_device_3_wires._state.dtype == qubit_device_3_wires.C_DTYPE + if version("numpy") < "2.0.0": + assert qubit_device_3_wires._state.dtype == qubit_device_3_wires.C_DTYPE def test_apply_errors_qubit_state_vector(self, qubit_device_2_wires): """Test that apply fails for incorrect state preparation, and > 2 qubit gates""" @@ -650,26 +652,26 @@ def test_apply_errors_qubit_state_vector(self, qubit_device_2_wires): ) def test_apply_errors_basis_state(self, qubit_device_2_wires): - - with pytest.raises( - ValueError, match=r"Basis state must only consist of 0s and 1s; got \[-0\.2, 4\.2\]" - ): - qubit_device_2_wires.apply([qml.BasisState(np.array([-0.2, 4.2]), wires=[0, 1])]) - - with pytest.raises( - ValueError, match=r"State must be of length 1; got length 2 \(state=\[0 1\]\)\." - ): - qubit_device_2_wires.apply([qml.BasisState(np.array([0, 1]), wires=[0])]) - - with pytest.raises( - qml.DeviceError, - match="Operation BasisState cannot be used after other Operations have already been applied " - "on a default.qubit.legacy device.", - ): - qubit_device_2_wires.reset() - qubit_device_2_wires.apply( - [qml.RZ(0.5, wires=[0]), qml.BasisState(np.array([1, 1]), wires=[0, 1])] - ) + with np.printoptions(legacy="1.21"): + with pytest.raises( + ValueError, match=r"Basis state must only consist of 0s and 1s; got \[-0\.2, 4\.2\]" + ): + qubit_device_2_wires.apply([qml.BasisState(np.array([-0.2, 4.2]), wires=[0, 1])]) + + with pytest.raises( + ValueError, match=r"State must be of length 1; got length 2 \(state=\[0 1\]\)\." + ): + qubit_device_2_wires.apply([qml.BasisState(np.array([0, 1]), wires=[0])]) + + with pytest.raises( + qml.DeviceError, + match="Operation BasisState cannot be used after other Operations have already been applied " + "on a default.qubit.legacy device.", + ): + qubit_device_2_wires.reset() + qubit_device_2_wires.apply( + [qml.RZ(0.5, wires=[0]), qml.BasisState(np.array([1, 1]), wires=[0, 1])] + ) class TestExpval: diff --git a/tests/devices/test_qubit_device.py b/tests/devices/test_qubit_device.py index 9edc522a408..8f50bb65329 100644 --- a/tests/devices/test_qubit_device.py +++ b/tests/devices/test_qubit_device.py @@ -1605,9 +1605,9 @@ def test_samples_to_counts_with_nan(self): # imitate hardware return with NaNs (requires dtype float) samples = qml.math.cast_like(samples, np.array([1.2])) - samples[0][0] = np.NaN - samples[17][1] = np.NaN - samples[850][0] = np.NaN + samples[0][0] = np.nan + samples[17][1] = np.nan + samples[850][0] = np.nan result = device._samples_to_counts(samples, mp=qml.measurements.CountsMP(), num_wires=2) diff --git a/tests/measurements/test_counts.py b/tests/measurements/test_counts.py index 3f3badb5c0e..08da35015c9 100644 --- a/tests/measurements/test_counts.py +++ b/tests/measurements/test_counts.py @@ -135,9 +135,9 @@ def test_counts_with_nan_samples(self): rng = np.random.default_rng(123) samples = rng.choice([0, 1], size=(shots, 2)).astype(np.float64) - samples[0][0] = np.NaN - samples[17][1] = np.NaN - samples[850][0] = np.NaN + samples[0][0] = np.nan + samples[17][1] = np.nan + samples[850][0] = np.nan result = qml.counts(wires=[0, 1]).process_samples(samples, wire_order=[0, 1]) diff --git a/tests/templates/test_subroutines/test_prepselprep.py b/tests/templates/test_subroutines/test_prepselprep.py index 95e7f771ef7..7e3b7966c28 100644 --- a/tests/templates/test_subroutines/test_prepselprep.py +++ b/tests/templates/test_subroutines/test_prepselprep.py @@ -47,13 +47,16 @@ def test_standard_checks(lcu, control): def test_repr(): """Test the repr method.""" + lcu = qml.dot([0.25, 0.75], [qml.Z(2), qml.X(1) @ qml.X(2)]) control = [0] op = qml.PrepSelPrep(lcu, control) - assert ( - repr(op) == "PrepSelPrep(coeffs=(0.25, 0.75), ops=(Z(2), X(1) @ X(2)), control=Wires([0]))" - ) + with np.printoptions(legacy="1.21"): + assert ( + repr(op) + == "PrepSelPrep(coeffs=(0.25, 0.75), ops=(Z(2), X(1) @ X(2)), control=Wires([0]))" + ) def _get_new_terms(lcu): From 228fdaf815df25ade861636d294d8e5ef51d1aa2 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 16 Sep 2024 09:57:22 -0400 Subject: [PATCH 07/14] Clean up how `interface` is handled in `QNode` and `qml.execute` (#6225) Regarding `numpy` and `autograd`: - When the parameters are of the `numpy` interface, internally treat it as `interface=None`. - Does not change the behaviour of treating user specified `interface="numpy"` as using autograd. Regarding interfaces in general: - The set of canonical interface names in `INTERFACE_MAP` is expanded to include more specific names such as `jax-jit`, and `tf-autograph`. `_convert_to_interfaces` in `qnode.py` uses a separate `interface_conversion_map` to further map the specific interfaces to their corresponding general interface names that can be passed to the `like` argument of `qml.math.asarray` (e.g. "tf" to "tensorflow", "jax-jit" to "jax"). - In `QNode` and `qml.execute`, every time we get an interface from user input or `qml.math.get_interface`, we map it to a canonical interface name using `INTERFACE_MAP`. Aside from these two scenarios, we assume that the interface name is one of the canonical interface names everywhere else. `QNode.interface` is now assumed to be one of the canonical interface names. - User input of `interface=None` gets mapped to `numpy` immediately. Internally, `QNode.interface` will never be `None`. It'll be `numpy` for having no interface. - If `qml.math.get_interface` returns `numpy`, we do not map it to anything. We keep `numpy`. Collateral bug fix included as well: - Fixes a bug where a circuit of the `autograd` interfaces sometimes returns results that are not `autograd`. - Adds `compute_sparse_matrix` to `Hermitian` [sc-73144] --------- Co-authored-by: Christina Lee --- doc/releases/changelog-dev.md | 19 +++-- pennylane/devices/execution_config.py | 6 +- pennylane/devices/legacy_facade.py | 10 +-- pennylane/devices/qubit/simulate.py | 4 +- pennylane/ops/qubit/observables.py | 4 + pennylane/workflow/__init__.py | 2 +- pennylane/workflow/execution.py | 75 ++++++++++-------- pennylane/workflow/interfaces/autograd.py | 17 ++++- pennylane/workflow/qnode.py | 40 +++++++--- .../default_qubit/test_default_qubit.py | 2 +- tests/devices/qubit/test_simulate.py | 2 +- .../finite_diff/test_spsa_gradient.py | 76 ++++++++++--------- .../test_spsa_gradient_shot_vec.py | 70 ++++++++--------- tests/interfaces/test_jax_jit.py | 2 +- tests/measurements/test_sample.py | 4 +- tests/qnn/test_keras.py | 6 +- tests/qnn/test_qnn_torch.py | 6 +- tests/test_qnode.py | 18 ++++- tests/test_qnode_legacy.py | 2 +- 19 files changed, 221 insertions(+), 144 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 506504f631b..559f1f20b6e 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -10,18 +10,14 @@ [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061) * `qml.qchem.excitations` now optionally returns fermionic operators. - [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) + [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) * The `diagonalize_measurements` transform now uses a more efficient method of diagonalization when possible, based on the `pauli_rep` of the relevant observables. [#6113](https://github.com/PennyLaneAI/pennylane/pull/6113/) -

Capturing and representing hybrid programs

- -* Differentiation of hybrid programs via `qml.grad` can now be captured into plxpr. - When evaluating a captured `qml.grad` instruction, it will dispatch to `jax.grad`, - which differs from the Autograd implementation of `qml.grad` itself. - [(#6120)](https://github.com/PennyLaneAI/pennylane/pull/6120) +* The `Hermitian` operator now has a `compute_sparse_matrix` implementation. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225)

Capturing and representing hybrid programs

@@ -120,12 +116,19 @@ * The ``qml.FABLE`` template now returns the correct value when JIT is enabled. [(#6263)](https://github.com/PennyLaneAI/pennylane/pull/6263) -*

Contributors ✍️

+* Fixes a bug where a circuit using the `autograd` interface sometimes returns nested values that are not of the `autograd` interface. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225) + +* Fixes a bug where a simple circuit with no parameters or only builtin/numpy arrays as parameters returns autograd tensors. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225) + +

Contributors ✍️

This release contains contributions from (in alphabetical order): Guillermo Alonso, Utkarsh Azad, +Astral Cai, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, diff --git a/pennylane/devices/execution_config.py b/pennylane/devices/execution_config.py index 5b7af096d81..7f3866d9e86 100644 --- a/pennylane/devices/execution_config.py +++ b/pennylane/devices/execution_config.py @@ -17,7 +17,7 @@ from dataclasses import dataclass, field from typing import Optional, Union -from pennylane.workflow import SUPPORTED_INTERFACES +from pennylane.workflow import SUPPORTED_INTERFACE_NAMES @dataclass @@ -110,9 +110,9 @@ def __post_init__(self): Note that this hook is automatically called after init via the dataclass integration. """ - if self.interface not in SUPPORTED_INTERFACES: + if self.interface not in SUPPORTED_INTERFACE_NAMES: raise ValueError( - f"Unknown interface. interface must be in {SUPPORTED_INTERFACES}, got {self.interface} instead." + f"Unknown interface. interface must be in {SUPPORTED_INTERFACE_NAMES}, got {self.interface} instead." ) if self.grad_on_execution not in {True, False, None}: diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index 41c1e0dea2c..bd2190f0fe1 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -24,6 +24,7 @@ import pennylane as qml from pennylane.measurements import MidMeasureMP, Shots from pennylane.transforms.core.transform_program import TransformProgram +from pennylane.workflow.execution import INTERFACE_MAP from .device_api import Device from .execution_config import DefaultExecutionConfig @@ -322,25 +323,24 @@ def _validate_backprop_method(self, tape): return False params = tape.get_parameters(trainable_only=False) interface = qml.math.get_interface(*params) + if interface != "numpy": + interface = INTERFACE_MAP.get(interface, interface) if tape and any(isinstance(m.obs, qml.SparseHamiltonian) for m in tape.measurements): return False - if interface == "numpy": - interface = None - mapped_interface = qml.workflow.execution.INTERFACE_MAP.get(interface, interface) # determine if the device supports backpropagation backprop_interface = self._device.capabilities().get("passthru_interface", None) if backprop_interface is not None: # device supports backpropagation natively - return mapped_interface in [backprop_interface, "Numpy"] + return interface in [backprop_interface, "numpy"] # determine if the device has any child devices that support backpropagation backprop_devices = self._device.capabilities().get("passthru_devices", None) if backprop_devices is None: return False - return mapped_interface in backprop_devices or mapped_interface == "Numpy" + return interface in backprop_devices or interface == "numpy" def _validate_adjoint_method(self, tape): # The conditions below provide a minimal set of requirements that we can likely improve upon in diff --git a/pennylane/devices/qubit/simulate.py b/pennylane/devices/qubit/simulate.py index 56e4a8f1a48..89c041b8f3e 100644 --- a/pennylane/devices/qubit/simulate.py +++ b/pennylane/devices/qubit/simulate.py @@ -922,7 +922,7 @@ def _(original_measurement: ExpectationMP, measures): # pylint: disable=unused- for v in measures.values(): if not v[0] or v[1] is tuple(): continue - cum_value += v[0] * v[1] + cum_value += qml.math.multiply(v[0], v[1]) total_counts += v[0] return cum_value / total_counts @@ -935,7 +935,7 @@ def _(original_measurement: ProbabilityMP, measures): # pylint: disable=unused- for v in measures.values(): if not v[0] or v[1] is tuple(): continue - cum_value += v[0] * v[1] + cum_value += qml.math.multiply(v[0], v[1]) total_counts += v[0] return cum_value / total_counts diff --git a/pennylane/ops/qubit/observables.py b/pennylane/ops/qubit/observables.py index 8f992c81bc2..4fc4a98c092 100644 --- a/pennylane/ops/qubit/observables.py +++ b/pennylane/ops/qubit/observables.py @@ -137,6 +137,10 @@ def compute_matrix(A: TensorLike) -> TensorLike: # pylint: disable=arguments-di Hermitian._validate_input(A) return A + @staticmethod + def compute_sparse_matrix(A) -> csr_matrix: # pylint: disable=arguments-differ + return csr_matrix(Hermitian.compute_matrix(A)) + @property def eigendecomposition(self) -> dict[str, TensorLike]: """Return the eigendecomposition of the matrix specified by the Hermitian observable. diff --git a/pennylane/workflow/__init__.py b/pennylane/workflow/__init__.py index 55068804b68..b41c031e8a4 100644 --- a/pennylane/workflow/__init__.py +++ b/pennylane/workflow/__init__.py @@ -56,6 +56,6 @@ """ from .construct_batch import construct_batch, get_transform_program -from .execution import INTERFACE_MAP, SUPPORTED_INTERFACES, execute +from .execution import INTERFACE_MAP, SUPPORTED_INTERFACE_NAMES, execute from .qnode import QNode, qnode from .set_shots import set_shots diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index 7445bcea2b7..8d8f0adb9ef 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -51,12 +51,9 @@ "autograd", "numpy", "torch", - "pytorch", "jax", - "jax-python", "jax-jit", "tf", - "tensorflow", } SupportedInterfaceUserInput = Literal[ @@ -78,30 +75,29 @@ ] _mapping_output = ( - "Numpy", + "numpy", "auto", "autograd", "autograd", "numpy", "jax", - "jax", + "jax-jit", "jax", "jax", "torch", "torch", "tf", "tf", - "tf", - "tf", + "tf-autograph", + "tf-autograph", ) + INTERFACE_MAP = dict(zip(get_args(SupportedInterfaceUserInput), _mapping_output)) """dict[str, str]: maps an allowed interface specification to its canonical name.""" -#: list[str]: allowed interface strings -SUPPORTED_INTERFACES = list(INTERFACE_MAP) +SUPPORTED_INTERFACE_NAMES = list(INTERFACE_MAP) """list[str]: allowed interface strings""" - _CACHED_EXECUTION_WITH_FINITE_SHOTS_WARNINGS = ( "Cached execution with finite shots detected!\n" "Note that samples as well as all noisy quantities computed via sampling " @@ -135,23 +131,21 @@ def _get_ml_boundary_execute( pennylane.QuantumFunctionError if the required package is not installed. """ - mapped_interface = INTERFACE_MAP[interface] try: - if mapped_interface == "autograd": + if interface == "autograd": from .interfaces.autograd import autograd_execute as ml_boundary - elif mapped_interface == "tf": - if "autograph" in interface: - from .interfaces.tensorflow_autograph import execute as ml_boundary + elif interface == "tf-autograph": + from .interfaces.tensorflow_autograph import execute as ml_boundary - ml_boundary = partial(ml_boundary, grad_on_execution=grad_on_execution) + ml_boundary = partial(ml_boundary, grad_on_execution=grad_on_execution) - else: - from .interfaces.tensorflow import tf_execute as full_ml_boundary + elif interface == "tf": + from .interfaces.tensorflow import tf_execute as full_ml_boundary - ml_boundary = partial(full_ml_boundary, differentiable=differentiable) + ml_boundary = partial(full_ml_boundary, differentiable=differentiable) - elif mapped_interface == "torch": + elif interface == "torch": from .interfaces.torch import execute as ml_boundary elif interface == "jax-jit": @@ -159,7 +153,8 @@ def _get_ml_boundary_execute( from .interfaces.jax_jit import jax_jit_vjp_execute as ml_boundary else: from .interfaces.jax_jit import jax_jit_jvp_execute as ml_boundary - else: # interface in {"jax", "jax-python", "JAX"}: + + else: # interface is jax if device_vjp: from .interfaces.jax_jit import jax_jit_vjp_execute as ml_boundary else: @@ -167,9 +162,10 @@ def _get_ml_boundary_execute( except ImportError as e: # pragma: no cover raise qml.QuantumFunctionError( - f"{mapped_interface} not found. Please install the latest " - f"version of {mapped_interface} to enable the '{mapped_interface}' interface." + f"{interface} not found. Please install the latest " + f"version of {interface} to enable the '{interface}' interface." ) from e + return ml_boundary @@ -263,12 +259,22 @@ def _get_interface_name(tapes, interface): Returns: str: Interface name""" + + if interface not in SUPPORTED_INTERFACE_NAMES: + raise qml.QuantumFunctionError( + f"Unknown interface {interface}. Interface must be one of {SUPPORTED_INTERFACE_NAMES}." + ) + + interface = INTERFACE_MAP[interface] + if interface == "auto": params = [] for tape in tapes: params.extend(tape.get_parameters(trainable_only=False)) interface = qml.math.get_interface(*params) - if INTERFACE_MAP.get(interface, "") == "tf" and _use_tensorflow_autograph(): + if interface != "numpy": + interface = INTERFACE_MAP[interface] + if interface == "tf" and _use_tensorflow_autograph(): interface = "tf-autograph" if interface == "jax": try: # pragma: no cover @@ -439,6 +445,7 @@ def cost_fn(params, x): ### Specifying and preprocessing variables #### + _interface_user_input = interface interface = _get_interface_name(tapes, interface) # Only need to calculate derivatives with jax when we know it will be executed later. if interface in {"jax", "jax-jit"}: @@ -460,7 +467,11 @@ def cost_fn(params, x): ) # Mid-circuit measurement configuration validation - mcm_interface = interface or _get_interface_name(tapes, "auto") + # If the user specifies `interface=None`, regular execution considers it numpy, but the mcm + # workflow still needs to know if jax-jit is used + mcm_interface = ( + _get_interface_name(tapes, "auto") if _interface_user_input is None else interface + ) finite_shots = any(tape.shots for tape in tapes) _update_mcm_config(config.mcm_config, mcm_interface, finite_shots) @@ -479,12 +490,12 @@ def cost_fn(params, x): cache = None # changing this set of conditions causes a bunch of tests to break. - no_interface_boundary_required = interface is None or config.gradient_method in { + no_interface_boundary_required = interface == "numpy" or config.gradient_method in { None, "backprop", } device_supports_interface_data = no_interface_boundary_required and ( - interface is None + interface == "numpy" or config.gradient_method == "backprop" or getattr(device, "short_name", "") == "default.mixed" ) @@ -497,9 +508,9 @@ def cost_fn(params, x): numpy_only=not device_supports_interface_data, ) - # moved to its own explicit step so it will be easier to remove + # moved to its own explicit step so that it will be easier to remove def inner_execute_with_empty_jac(tapes, **_): - return (inner_execute(tapes), []) + return inner_execute(tapes), [] if interface in jpc_interfaces: execute_fn = inner_execute @@ -522,7 +533,7 @@ def inner_execute_with_empty_jac(tapes, **_): and getattr(device, "short_name", "") in ("lightning.gpu", "lightning.kokkos") and interface in jpc_interfaces ): # pragma: no cover - if INTERFACE_MAP[interface] == "jax" and "use_device_state" in gradient_kwargs: + if "jax" in interface and "use_device_state" in gradient_kwargs: gradient_kwargs["use_device_state"] = False jpc = LightningVJPs(device, gradient_kwargs=gradient_kwargs) @@ -563,7 +574,7 @@ def execute_fn(internal_tapes) -> tuple[ResultBatch, tuple]: config: the ExecutionConfig that specifies how to perform the simulations. """ numpy_tapes, _ = qml.transforms.convert_to_numpy_parameters(internal_tapes) - return (device.execute(numpy_tapes, config), tuple()) + return device.execute(numpy_tapes, config), tuple() def gradient_fn(internal_tapes): """A partial function that wraps compute_derivatives method of the device. @@ -612,7 +623,7 @@ def gradient_fn(internal_tapes): # trainable parameters can only be set on the first pass for jax # not higher order passes for higher order derivatives - if interface in {"jax", "jax-python", "jax-jit"}: + if "jax" in interface: for tape in tapes: params = tape.get_parameters(trainable_only=False) tape.trainable_params = qml.math.get_trainable_indices(params) diff --git a/pennylane/workflow/interfaces/autograd.py b/pennylane/workflow/interfaces/autograd.py index 9452af31854..cb5731ddc8b 100644 --- a/pennylane/workflow/interfaces/autograd.py +++ b/pennylane/workflow/interfaces/autograd.py @@ -147,6 +147,21 @@ def autograd_execute( return _execute(parameters, tuple(tapes), execute_fn, jpc) +def _to_autograd(result: qml.typing.ResultBatch) -> qml.typing.ResultBatch: + """Converts an arbitrary result batch to one with autograd arrays. + Args: + result (ResultBatch): a nested structure of lists, tuples, dicts, and numpy arrays + Returns: + ResultBatch: a nested structure of tuples, dicts, and jax arrays + """ + if isinstance(result, dict): + return result + # pylint: disable=no-member + if isinstance(result, (list, tuple, autograd.builtins.tuple, autograd.builtins.list)): + return tuple(_to_autograd(r) for r in result) + return autograd.numpy.array(result) + + @autograd.extend.primitive def _execute( parameters, @@ -165,7 +180,7 @@ def _execute( for the input tapes. """ - return execute_fn(tapes) + return _to_autograd(execute_fn(tapes)) # pylint: disable=unused-argument diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 408a0794674..ab68a9ad147 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -32,7 +32,7 @@ from pennylane.tape import QuantumScript, QuantumTape from pennylane.transforms.core import TransformContainer, TransformDispatcher, TransformProgram -from .execution import INTERFACE_MAP, SUPPORTED_INTERFACES, SupportedInterfaceUserInput +from .execution import INTERFACE_MAP, SUPPORTED_INTERFACE_NAMES, SupportedInterfaceUserInput logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) @@ -56,9 +56,8 @@ def _convert_to_interface(res, interface): """ Recursively convert res to the given interface. """ - interface = INTERFACE_MAP[interface] - if interface in ["Numpy"]: + if interface == "numpy": return res if isinstance(res, (list, tuple)): @@ -67,7 +66,18 @@ def _convert_to_interface(res, interface): if isinstance(res, dict): return {k: _convert_to_interface(v, interface) for k, v in res.items()} - return qml.math.asarray(res, like=interface if interface != "tf" else "tensorflow") + interface_conversion_map = { + "autograd": "autograd", + "jax": "jax", + "jax-jit": "jax", + "torch": "torch", + "tf": "tensorflow", + "tf-autograph": "tensorflow", + } + + interface_name = interface_conversion_map[interface] + + return qml.math.asarray(res, like=interface_name) def _make_execution_config( @@ -495,10 +505,10 @@ def __init__( gradient_kwargs, ) - if interface not in SUPPORTED_INTERFACES: + if interface not in SUPPORTED_INTERFACE_NAMES: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " - f"one of {SUPPORTED_INTERFACES}." + f"one of {SUPPORTED_INTERFACE_NAMES}." ) if not isinstance(device, (qml.devices.LegacyDevice, qml.devices.Device)): @@ -524,7 +534,7 @@ def __init__( # input arguments self.func = func self.device = device - self._interface = None if diff_method is None else interface + self._interface = "numpy" if diff_method is None else INTERFACE_MAP[interface] self.diff_method = diff_method mcm_config = qml.devices.MCMConfig(mcm_method=mcm_method, postselect_mode=postselect_mode) cache = (max_diff > 1) if cache == "auto" else cache @@ -617,10 +627,10 @@ def interface(self) -> str: @interface.setter def interface(self, value: SupportedInterfaceUserInput): - if value not in SUPPORTED_INTERFACES: + if value not in SUPPORTED_INTERFACE_NAMES: raise qml.QuantumFunctionError( - f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACES}." + f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACE_NAMES}." ) self._interface = INTERFACE_MAP[value] @@ -923,12 +933,18 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result: execute_kwargs["mcm_config"] = mcm_config + # Mapping numpy to None here because `qml.execute` will map None back into + # numpy. If we do not do this, numpy will become autograd in `qml.execute`. + # If the user specified interface="numpy", it would've already been converted to + # "autograd", and it wouldn't be affected. + interface = None if self.interface == "numpy" else self.interface + # pylint: disable=unexpected-keyword-arg res = qml.execute( (self._tape,), device=self.device, gradient_fn=gradient_fn, - interface=self.interface, + interface=interface, transform_program=full_transform_program, inner_transform=inner_transform_program, config=config, @@ -961,7 +977,9 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if qml.capture.enabled() else qml.math.get_interface(*args, *list(kwargs.values())) ) - self._interface = INTERFACE_MAP[interface] + if interface != "numpy": + interface = INTERFACE_MAP[interface] + self._interface = interface try: res = self._execution_component(args, kwargs) diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py index 8b3a1e257dd..d3049d90eae 100644 --- a/tests/devices/default_qubit/test_default_qubit.py +++ b/tests/devices/default_qubit/test_default_qubit.py @@ -1960,7 +1960,7 @@ def test_postselection_invalid_analytic( dev = qml.device("default.qubit") @qml.defer_measurements - @qml.qnode(dev, interface=interface) + @qml.qnode(dev, interface=None if interface == "numpy" else interface) def circ(): qml.RX(np.pi, 0) qml.CNOT([0, 1]) diff --git a/tests/devices/qubit/test_simulate.py b/tests/devices/qubit/test_simulate.py index dbe9573b8df..4dce5afd4c5 100644 --- a/tests/devices/qubit/test_simulate.py +++ b/tests/devices/qubit/test_simulate.py @@ -205,7 +205,7 @@ def test_result_has_correct_interface(self, op): def test_expand_state_keeps_autograd_interface(self): """Test that expand_state doesn't convert autograd to numpy.""" - @qml.qnode(qml.device("default.qubit", wires=2)) + @qml.qnode(qml.device("default.qubit", wires=2), interface="autograd") def circuit(x): qml.RX(x, 0) return qml.probs(wires=[0, 1]) diff --git a/tests/gradients/finite_diff/test_spsa_gradient.py b/tests/gradients/finite_diff/test_spsa_gradient.py index d8f19dcf826..2730cd53d00 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient.py +++ b/tests/gradients/finite_diff/test_spsa_gradient.py @@ -14,11 +14,11 @@ """ Tests for the gradients.spsa_gradient module. """ -import numpy +import numpy as np import pytest import pennylane as qml -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubitLegacy from pennylane.gradients import spsa_grad from pennylane.gradients.spsa_gradient import _rademacher_sampler @@ -168,7 +168,7 @@ def circuit(param): expected_message = "The argument sampler_rng is expected to be a NumPy PRNG" with pytest.raises(ValueError, match=expected_message): - qml.grad(circuit)(np.array(1.0)) + qml.grad(circuit)(pnp.array(1.0)) def test_trainable_batched_tape_raises(self): """Test that an error is raised for a broadcasted/batched tape if the broadcasted @@ -202,7 +202,7 @@ def test_nontrainable_batched_tape(self): def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" - psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) + psi = pnp.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) with qml.queuing.AnnotatedQueue() as q: qml.StatePrep(psi, wires=[0, 1]) @@ -227,10 +227,10 @@ def test_non_differentiable_error(self): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == (4,) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == (4,) @pytest.mark.parametrize("num_directions", [1, 10]) @@ -252,8 +252,8 @@ def test_independent_parameter(self, num_directions, mocker): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[0], np.ndarray) + assert isinstance(res[1], np.ndarray) # 2 tapes per direction because the default strategy for SPSA is "center" assert len(spy.call_args_list) == num_directions @@ -282,7 +282,7 @@ def test_no_trainable_params_tape(self): res = post_processing(qml.execute(g_tapes, dev, None)) assert g_tapes == [] - assert isinstance(res, numpy.ndarray) + assert isinstance(res, np.ndarray) assert res.shape == (0,) def test_no_trainable_params_multiple_return_tape(self): @@ -383,7 +383,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) @@ -402,7 +402,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.expval(qml.PauliZ(wires=2)), qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) @@ -514,7 +514,7 @@ def cost6(x): qml.Rot(*x, wires=0) return qml.probs([0, 1]), qml.probs([2, 3]) - x = np.random.rand(3) + x = pnp.random.rand(3) circuits = [qml.QNode(cost, dev) for cost in (cost1, cost2, cost3, cost4, cost5, cost6)] transform = [qml.math.shape(spsa_grad(c)(x)) for c in circuits] @@ -576,7 +576,7 @@ class DeviceSupportingSpecialObservable(DefaultQubitLegacy): @staticmethod def _asarray(arr, dtype=None): - return np.array(arr, dtype=dtype) + return pnp.array(arr, dtype=dtype) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -603,9 +603,11 @@ def reference_qnode(x): qml.RY(x, wires=0) return qml.expval(qml.PauliZ(wires=0)) - par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par)) - assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) + par = pnp.array(0.2, requires_grad=True) + assert np.isclose(qnode(par).item().val, reference_qnode(par).item()) + assert np.isclose( + qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() + ) @pytest.mark.parametrize("approx_order", [2, 4]) @@ -684,10 +686,10 @@ def test_single_expectation_value(self, approx_order, strategy, validate, tol): # 1 / num_params here. res = tuple(qml.math.convert_like(r * 2, r) for r in res) - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) @@ -728,10 +730,10 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, # 1 / num_params here. res = tuple(qml.math.convert_like(r * 2, r) for r in res) - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) @@ -772,10 +774,10 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () expected = [0, np.cos(y) * np.cos(x)] @@ -856,14 +858,14 @@ def test_multiple_expectation_values(self, approx_order, strategy, validate, tol assert isinstance(res[0], tuple) assert len(res[0]) == 2 assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 assert np.allclose(res[1], [0, np.cos(y)], atol=tol, rtol=0) - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) def test_var_expectation_values(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -901,14 +903,14 @@ def test_var_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 assert np.allclose(res[1], [0, -2 * np.cos(y) * np.sin(y)], atol=tol, rtol=0) - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) def test_prob_expectation_values(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -946,9 +948,9 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 assert np.allclose(res[0][0], -np.sin(x), atol=tol, rtol=0) - assert isinstance(res[0][0], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) assert np.allclose(res[0][1], 0, atol=tol, rtol=0) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 @@ -963,7 +965,7 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): atol=tol, rtol=0, ) - assert isinstance(res[1][0], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) assert np.allclose( res[1][1], [ @@ -975,7 +977,7 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): atol=tol, rtol=0, ) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][1], np.ndarray) @pytest.mark.parametrize( @@ -989,7 +991,7 @@ def test_autograd(self, sampler, num_directions, atol): """Tests that the output of the SPSA gradient transform can be differentiated using autograd, yielding second derivatives.""" dev = qml.device("default.qubit", wires=2) - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): @@ -1004,7 +1006,7 @@ def cost_fn(x): tapes, fn = spsa_grad( tape, n=1, num_directions=num_directions, sampler=sampler, sampler_rng=rng ) - jac = np.array(fn(dev.execute(tapes))) + jac = pnp.array(fn(dev.execute(tapes))) if sampler is coordinate_sampler: jac *= 2 return jac @@ -1025,7 +1027,7 @@ def test_autograd_ragged(self, sampler, num_directions, atol): """Tests that the output of the SPSA gradient transform of a ragged tape can be differentiated using autograd, yielding second derivatives.""" dev = qml.device("default.qubit", wires=2) - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): diff --git a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py index 46f8aa1288e..2c771dc2832 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py +++ b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py @@ -14,11 +14,11 @@ """ Tests for the gradients.spsa_gradient module using shot vectors. """ -import numpy +import numpy as np import pytest import pennylane as qml -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubitLegacy from pennylane.gradients import spsa_grad from pennylane.measurements import Shots @@ -49,7 +49,7 @@ class TestSpsaGradient: def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" - psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) + psi = pnp.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) with qml.queuing.AnnotatedQueue() as q: qml.StatePrep(psi, wires=[0, 1]) @@ -78,10 +78,10 @@ def test_non_differentiable_error(self): for res in all_res: assert isinstance(res, tuple) - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == (4,) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == (4,) @pytest.mark.parametrize("num_directions", [1, 6]) @@ -107,8 +107,8 @@ def test_independent_parameter(self, num_directions, mocker): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[0], np.ndarray) + assert isinstance(res[1], np.ndarray) # 2 tapes per direction because the default strategy for SPSA is "center" assert len(spy.call_args_list) == num_directions @@ -139,7 +139,7 @@ def test_no_trainable_params_tape(self): for res in all_res: assert g_tapes == [] - assert isinstance(res, numpy.ndarray) + assert isinstance(res, np.ndarray) assert res.shape == (0,) def test_no_trainable_params_multiple_return_tape(self): @@ -244,7 +244,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) grad_fn = spsa_grad(circuit, h=h_val, sampler_rng=rng) all_result = grad_fn(params) @@ -269,7 +269,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.expval(qml.PauliZ(wires=2)), qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) grad_fn = spsa_grad(circuit, h=h_val, sampler_rng=rng) all_result = grad_fn(params) @@ -416,7 +416,7 @@ def cost6(x): qml.Rot(*x, wires=0) return qml.probs([0, 1]), qml.probs([2, 3]) - x = np.random.rand(3) + x = pnp.random.rand(3) circuits = [qml.QNode(cost, dev) for cost in (cost1, cost2, cost3, cost4, cost5, cost6)] transform = [qml.math.shape(spsa_grad(c, h=h_val)(x)) for c in circuits] @@ -498,9 +498,11 @@ def reference_qnode(x): qml.RY(x, wires=0) return qml.expval(qml.PauliZ(wires=0)) - par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par)) - assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) + par = pnp.array(0.2, requires_grad=True) + assert np.isclose(qnode(par).item().val, reference_qnode(par).item()) + assert np.isclose( + qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() + ) @pytest.mark.parametrize("approx_order", [2, 4]) @@ -586,10 +588,10 @@ def test_single_expectation_value(self, approx_order, strategy, validate): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () # The coordinate_sampler produces the right evaluation points, but the tape execution @@ -635,10 +637,10 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () # The coordinate_sampler produces the right evaluation points, but the tape execution @@ -689,10 +691,10 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () # The coordinate_sampler produces the right evaluation points and there is just one @@ -783,13 +785,13 @@ def test_multiple_expectation_values(self, approx_order, strategy, validate): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) # The coordinate_sampler produces the right evaluation points, but the tape execution # results are averaged instead of added, so that we need to revert the prefactor @@ -837,13 +839,13 @@ def test_var_expectation_values(self, approx_order, strategy, validate): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) # The coordinate_sampler produces the right evaluation points, but the tape execution # results are averaged instead of added, so that we need to revert the prefactor @@ -892,13 +894,13 @@ def test_prob_expectation_values(self, approx_order, strategy, validate): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) # The coordinate_sampler produces the right evaluation points, but the tape execution # results are averaged instead of added, so that we need to revert the prefactor @@ -943,7 +945,7 @@ def test_autograd(self, approx_order, strategy): """Tests that the output of the SPSA gradient transform can be differentiated using autograd, yielding second derivatives.""" dev = qml.device("default.qubit", wires=2, shots=many_shots_shot_vector) - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): @@ -986,7 +988,7 @@ def test_autograd_ragged(self, approx_order, strategy): """Tests that the output of the SPSA gradient transform of a ragged tape can be differentiated using autograd, yielding second derivatives.""" dev = qml.device("default.qubit", wires=2, shots=many_shots_shot_vector) - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): diff --git a/tests/interfaces/test_jax_jit.py b/tests/interfaces/test_jax_jit.py index a9927dad7fb..eea7b6be52a 100644 --- a/tests/interfaces/test_jax_jit.py +++ b/tests/interfaces/test_jax_jit.py @@ -107,7 +107,7 @@ def cost(a, device): interface="None", )[0] - with pytest.raises(ValueError, match="Unknown interface"): + with pytest.raises(qml.QuantumFunctionError, match="Unknown interface"): cost(a, device=dev) def test_grad_on_execution(self, mocker): diff --git a/tests/measurements/test_sample.py b/tests/measurements/test_sample.py index e0d4ec25724..d31ce97d4a5 100644 --- a/tests/measurements/test_sample.py +++ b/tests/measurements/test_sample.py @@ -121,8 +121,8 @@ def circuit(): # If all the dimensions are equal the result will end up to be a proper rectangular array assert len(result) == 3 - assert isinstance(result[0], np.ndarray) - assert isinstance(result[1], np.ndarray) + assert isinstance(result[0], float) + assert isinstance(result[1], float) assert result[2].dtype == np.dtype("float") assert np.array_equal(result[2].shape, (n_sample,)) diff --git a/tests/qnn/test_keras.py b/tests/qnn/test_keras.py index f4f9769edc2..1115460922d 100644 --- a/tests/qnn/test_keras.py +++ b/tests/qnn/test_keras.py @@ -588,7 +588,11 @@ def circuit(inputs, w1): return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) qlayer = KerasLayer(circuit, weight_shapes, output_dim=2) - assert qlayer.qnode.interface == circuit.interface == interface + assert ( + qlayer.qnode.interface + == circuit.interface + == qml.workflow.execution.INTERFACE_MAP[interface] + ) @pytest.mark.tf diff --git a/tests/qnn/test_qnn_torch.py b/tests/qnn/test_qnn_torch.py index 64aeb9b1a9c..e2642df0e4b 100644 --- a/tests/qnn/test_qnn_torch.py +++ b/tests/qnn/test_qnn_torch.py @@ -632,7 +632,11 @@ def circuit(inputs, w1): return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) qlayer = TorchLayer(circuit, weight_shapes) - assert qlayer.qnode.interface == circuit.interface == interface + assert ( + qlayer.qnode.interface + == circuit.interface + == qml.workflow.execution.INTERFACE_MAP[interface] + ) @pytest.mark.torch diff --git a/tests/test_qnode.py b/tests/test_qnode.py index 38b8847106d..1322ca62c16 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -434,7 +434,7 @@ def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) - assert circuit.interface is None + assert circuit.interface == "numpy" with pytest.warns( qml.PennyLaneDeprecationWarning, match=r"QNode.gradient_fn is deprecated" ): @@ -1139,6 +1139,20 @@ def circuit(): assert q.queue == [] # pylint: disable=use-implicit-booleaness-not-comparison assert len(circuit.tape.operations) == 1 + def test_qnode_preserves_inferred_numpy_interface(self): + """Tests that the QNode respects the inferred numpy interface.""" + + dev = qml.device("default.qubit", wires=1) + + @qml.qnode(dev) + def circuit(x): + qml.RX(x, wires=0) + return qml.expval(qml.PauliZ(0)) + + x = np.array(0.8) + res = circuit(x) + assert qml.math.get_interface(res) == "numpy" + class TestShots: """Unit tests for specifying shots per call.""" @@ -1899,7 +1913,7 @@ def circuit(x): else: spy = mocker.spy(circuit.device, "execute") - x = np.array(0.5) + x = pnp.array(0.5) circuit(x) tape = spy.call_args[0][0][0] diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index 3ee36d99bdb..73eaf29b302 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -1488,7 +1488,7 @@ def circuit(x): else: spy = mocker.spy(circuit.device, "execute") - x = np.array(0.5) + x = pnp.array(0.5) circuit(x) tape = spy.call_args[0][0][0] From b78565cb119e926388375898093aa2387a4e6480 Mon Sep 17 00:00:00 2001 From: Pietropaolo Frisoni Date: Mon, 16 Sep 2024 10:47:02 -0400 Subject: [PATCH 08/14] Updating torch to 2.3.0 (#6258) **Context:** We want to make PL compatible with torch 2.3.0 (including GPU tests) after updating Numpy from 1.x to 2.x. **Description of the Change:** As above. **Benefits:** We are sure that PL is compatible with torch 2.3.0. **Possible Drawbacks:** None that I can think of right now. **Related GitHub Issues:** None. **Related Shortcut Stories:** [sc-61391] --- .github/workflows/tests-gpu.yml | 2 +- doc/releases/changelog-dev.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests-gpu.yml b/.github/workflows/tests-gpu.yml index 846261ff898..bd8cd5dae67 100644 --- a/.github/workflows/tests-gpu.yml +++ b/.github/workflows/tests-gpu.yml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - TORCH_VERSION: 2.2.0 + TORCH_VERSION: 2.3.0 jobs: gpu-tests: diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 559f1f20b6e..045f0b4528c 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -7,7 +7,8 @@

Improvements πŸ› 

* PennyLane is now compatible with NumPy 2.0. - [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061) + [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061) + [(#6258)](https://github.com/PennyLaneAI/pennylane/pull/6258) * `qml.qchem.excitations` now optionally returns fermionic operators. [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) From 7a4a44bdda8105e33074c41b8284b81cbd2005ed Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Tue, 17 Sep 2024 09:51:42 +0000 Subject: [PATCH 09/14] [no ci] bump nightly version --- pennylane/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/_version.py b/pennylane/_version.py index 77639685bc6..0c39c922ce2 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.39.0-dev15" +__version__ = "0.39.0-dev16" From 805d4cf0abe85a5b79445411f6cb8b29195ba14f Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 17 Sep 2024 09:58:23 -0400 Subject: [PATCH 10/14] Add `reference.qubit` for testing and reference (#6181) Created from https://github.com/PennyLaneAI/pennylane/pull/5445 [sc-65558] --------- Co-authored-by: dwierichs Co-authored-by: Christina Lee Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- doc/releases/changelog-dev.md | 3 + pennylane/devices/__init__.py | 3 + pennylane/devices/reference_qubit.py | 154 ++++++++++++++++++++++ setup.py | 1 + tests/interfaces/test_autograd.py | 110 +++++++++------- tests/interfaces/test_autograd_qnode.py | 6 + tests/interfaces/test_jax.py | 20 ++- tests/interfaces/test_jax_jit_qnode.py | 13 ++ tests/interfaces/test_jax_qnode.py | 19 ++- tests/interfaces/test_tensorflow.py | 10 ++ tests/interfaces/test_tensorflow_qnode.py | 6 + tests/interfaces/test_torch.py | 15 +++ tests/interfaces/test_torch_qnode.py | 6 + 13 files changed, 311 insertions(+), 55 deletions(-) create mode 100644 pennylane/devices/reference_qubit.py diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 045f0b4528c..91194ac1e19 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -51,6 +51,9 @@ unique representation of the object. [(#6167)](https://github.com/PennyLaneAI/pennylane/pull/6167) +* A `ReferenceQubit` is introduced for testing purposes and as a reference for future plugin development. + [(#6181)](https://github.com/PennyLaneAI/pennylane/pull/6181) + * The `to_mat` methods for `FermiWord` and `FermiSentence` now optionally return a sparse matrix. [(#6173)](https://github.com/PennyLaneAI/pennylane/pull/6173) diff --git a/pennylane/devices/__init__.py b/pennylane/devices/__init__.py index a542ba7df1d..ac9581ede40 100644 --- a/pennylane/devices/__init__.py +++ b/pennylane/devices/__init__.py @@ -37,6 +37,7 @@ _qubit_device _qutrit_device null_qubit + reference_qubit tests Next generation devices @@ -58,6 +59,7 @@ DefaultQubit DefaultTensor NullQubit + ReferenceQubit DefaultQutritMixed LegacyDeviceFacade @@ -160,6 +162,7 @@ def execute(self, circuits, execution_config = qml.devices.DefaultExecutionConfi from .default_clifford import DefaultClifford from .default_tensor import DefaultTensor from .null_qubit import NullQubit +from .reference_qubit import ReferenceQubit from .default_qutrit import DefaultQutrit from .default_qutrit_mixed import DefaultQutritMixed from ._legacy_device import Device as LegacyDevice diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py new file mode 100644 index 00000000000..49537d71a6e --- /dev/null +++ b/pennylane/devices/reference_qubit.py @@ -0,0 +1,154 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Contains the ReferenceQubit device, a minimal device that can be used for testing +and plugin development purposes. +""" + +import numpy as np + +import pennylane as qml + +from .device_api import Device +from .execution_config import DefaultExecutionConfig +from .modifiers import simulator_tracking, single_tape_support +from .preprocess import decompose, validate_device_wires, validate_measurements + + +def sample_state(state: np.ndarray, shots: int, seed=None): + """Generate samples from the provided state and number of shots.""" + + probs = np.imag(state) ** 2 + np.real(state) ** 2 + basis_states = np.arange(len(probs)) + + num_wires = int(np.log2(len(probs))) + + rng = np.random.default_rng(seed) + basis_samples = rng.choice(basis_states, shots, p=probs) + + # convert basis state integers to array of booleans + bin_strings = (format(s, f"0{num_wires}b") for s in basis_samples) + return np.array([[int(val) for val in s] for s in bin_strings]) + + +def simulate(tape: qml.tape.QuantumTape, seed=None) -> qml.typing.Result: + """Simulate a tape and turn it into results. + + Args: + tape (.QuantumTape): a representation of a circuit + seed (Any): A seed to use to control the generation of samples. + + """ + # 1) create the initial state + state = np.zeros(2 ** len(tape.wires)) + state[0] = 1.0 + + # 2) apply all the operations + for op in tape.operations: + op_mat = op.matrix(wire_order=tape.wires) + state = qml.math.matmul(op_mat, state) + + # 3) perform measurements + # note that shots are pulled from the tape, not from the device + if tape.shots: + samples = sample_state(state, shots=tape.shots.total_shots, seed=seed) + # Shot vector support + results = [] + for lower, upper in tape.shots.bins(): + sub_samples = samples[lower:upper] + results.append( + tuple(mp.process_samples(sub_samples, tape.wires) for mp in tape.measurements) + ) + if len(tape.measurements) == 1: + results = [res[0] for res in results] + if not tape.shots.has_partitioned_shots: + results = results[0] + else: + results = tuple(results) + else: + results = tuple(mp.process_state(state, tape.wires) for mp in tape.measurements) + if len(tape.measurements) == 1: + results = results[0] + + return results + + +operations = frozenset({"PauliX", "PauliY", "PauliZ", "Hadamard", "CNOT", "CZ", "RX", "RY", "RZ"}) + + +def supports_operation(op: qml.operation.Operator) -> bool: + """This function used by preprocessing determines what operations + are natively supported by the device. + + While in theory ``simulate`` can support any operation with a matrix, we limit the target + gate set for improved testing and reference purposes. + + """ + return getattr(op, "name", None) in operations + + +@simulator_tracking # update device.tracker with some relevant information +@single_tape_support # add support for device.execute(tape) in addition to device.execute((tape,)) +class ReferenceQubit(Device): + """A slimmed down numpy-based simulator for reference and testing purposes. + + Args: + wires (int, Iterable[Number, str]): Number of wires present on the device, or iterable that + contains unique labels for the wires as numbers (i.e., ``[-1, 0, 2]``) or strings + (``['aux', 'q1', 'q2']``). Default ``None`` if not specified. While this device allows + for ``wires`` to be unspecified at construction time, other devices may make this argument + mandatory. Devices can also implement additional restrictions on the possible wires. + shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots + to use in executions involving this device. Note that during execution, shots + are pulled from the circuit, not from the device. + seed (Union[str, None, int, array_like[int], SeedSequence, BitGenerator, Generator, jax.random.PRNGKey]): A + seed-like parameter matching that of ``seed`` for ``numpy.random.default_rng``. This is an optional + keyword argument added to follow recommend NumPy best practices. Other devices do not need + this parameter if it does not make sense for them. + + """ + + name = "reference.qubit" + + def __init__(self, wires=None, shots=None, seed=None): + super().__init__(wires=wires, shots=shots) + + # seed and rng not necessary for a device, but part of recommended + # numpy practices to use a local random number generator + self._rng = np.random.default_rng(seed) + + def preprocess(self, execution_config=DefaultExecutionConfig): + + # Here we convert an arbitrary tape into one natively supported by the device + program = qml.transforms.core.TransformProgram() + program.add_transform(validate_device_wires, wires=self.wires, name="reference.qubit") + program.add_transform(qml.defer_measurements) + program.add_transform(qml.transforms.split_non_commuting) + program.add_transform(qml.transforms.diagonalize_measurements) + program.add_transform( + decompose, + stopping_condition=supports_operation, + skip_initial_state_prep=False, + name="reference.qubit", + ) + program.add_transform(validate_measurements, name="reference.qubit") + program.add_transform(qml.transforms.broadcast_expand) + + # no need to preprocess the config as the device does not support derivatives + return program, execution_config + + def execute(self, circuits, execution_config=DefaultExecutionConfig): + for tape in circuits: + assert all(supports_operation(op) for op in tape.operations) + return tuple(simulate(tape, seed=self._rng) for tape in circuits) diff --git a/setup.py b/setup.py index 41ae9775027..4db98cdca25 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ "default.qubit.legacy = pennylane.devices:DefaultQubitLegacy", "default.gaussian = pennylane.devices:DefaultGaussian", "default.mixed = pennylane.devices.default_mixed:DefaultMixed", + "reference.qubit = pennylane.devices.reference_qubit:ReferenceQubit", "null.qubit = pennylane.devices.null_qubit:NullQubit", "default.qutrit = pennylane.devices.default_qutrit:DefaultQutrit", "default.clifford = pennylane.devices.default_clifford:DefaultClifford", diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 2a6ee306508..d206f1758d3 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -13,12 +13,13 @@ # limitations under the License. """Autograd specific tests for execute and default qubit 2.""" import autograd +import numpy as np import pytest from param_shift_dev import ParamShiftDerivativesDevice import pennylane as qml from pennylane import execute -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubit from pennylane.gradients import param_shift from pennylane.measurements import Shots @@ -36,7 +37,7 @@ def test_caching_param_shift_hessian(self, num_params): caching reduces the number of evaluations to their optimum when computing Hessians.""" dev = DefaultQubit() - params = np.arange(1, num_params + 1) / 10 + params = pnp.arange(1, num_params + 1) / 10 N = len(params) @@ -125,8 +126,8 @@ def f(x): # add tests for lightning 2 when possible # set rng for device when possible test_matrix = [ - ({"gradient_fn": param_shift}, Shots(100000), DefaultQubit(seed=42)), - ({"gradient_fn": param_shift}, Shots((100000, 100000)), DefaultQubit(seed=42)), + ({"gradient_fn": param_shift}, Shots(50000), DefaultQubit(seed=42)), + ({"gradient_fn": param_shift}, Shots((50000, 50000)), DefaultQubit(seed=42)), ({"gradient_fn": param_shift}, Shots(None), DefaultQubit()), ({"gradient_fn": "backprop"}, Shots(None), DefaultQubit()), ( @@ -146,7 +147,7 @@ def f(x): ({"gradient_fn": "adjoint", "device_vjp": True}, Shots(None), DefaultQubit()), ( {"gradient_fn": "device", "device_vjp": False}, - Shots((100000, 100000)), + Shots((50000, 50000)), ParamShiftDerivativesDevice(seed=904747894), ), ( @@ -154,12 +155,27 @@ def f(x): Shots((100000, 100000)), ParamShiftDerivativesDevice(seed=10490244), ), + ( + {"gradient_fn": param_shift}, + Shots(None), + qml.device("reference.qubit"), + ), + ( + {"gradient_fn": param_shift}, + Shots(50000), + qml.device("reference.qubit", seed=8743274), + ), + ( + {"gradient_fn": param_shift}, + Shots((50000, 50000)), + qml.device("reference.qubit", seed=8743274), + ), ] def atol_for_shots(shots): """Return higher tolerance if finite shots.""" - return 1e-2 if shots else 1e-6 + return 5e-2 if shots else 1e-6 @pytest.mark.parametrize("execute_kwargs, shots, device", test_matrix) @@ -179,8 +195,8 @@ def cost(a, b): return execute([tape1, tape2], device, **execute_kwargs) - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=False) with device.tracker: res = cost(a, b) @@ -200,7 +216,7 @@ def cost(a, b): def test_scalar_jacobian(self, execute_kwargs, shots, device): """Test scalar jacobian calculation""" - a = np.array(0.1, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) def cost(a): tape = qml.tape.QuantumScript([qml.RY(a, 0)], [qml.expval(qml.PauliZ(0))], shots=shots) @@ -224,8 +240,8 @@ def cost(a): def test_jacobian(self, execute_kwargs, shots, device): """Test jacobian calculation""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=True) def cost(a, b): ops = [qml.RY(a, wires=0), qml.RX(b, wires=1), qml.CNOT(wires=[0, 1])] @@ -270,7 +286,7 @@ def cost(params): ) tape2 = qml.tape.QuantumScript( - [qml.RY(np.array(0.5, requires_grad=False), wires=0)], + [qml.RY(pnp.array(0.5, requires_grad=False), wires=0)], [qml.expval(qml.PauliZ(0))], shots=shots, ) @@ -282,7 +298,7 @@ def cost(params): ) tape4 = qml.tape.QuantumScript( - [qml.RY(np.array(0.5, requires_grad=False), 0)], + [qml.RY(pnp.array(0.5, requires_grad=False), 0)], [qml.probs(wires=[0, 1])], shots=shots, ) @@ -291,7 +307,7 @@ def cost(params): res = tuple(i for r in res for i in r) return sum(autograd.numpy.hstack(res)) - params = np.array([0.1, 0.2], requires_grad=True) + params = pnp.array([0.1, 0.2], requires_grad=True) x, y = params res = cost(params) @@ -321,7 +337,7 @@ def cost(params): ) tape2 = qml.tape.QuantumScript( - [qml.RY(np.array(0.5, requires_grad=False), 0)], + [qml.RY(pnp.array(0.5, requires_grad=False), 0)], [qml.expval(qml.PauliZ(0))], shots=shots, ) @@ -336,7 +352,7 @@ def cost(params): res = tuple(i for r in res for i in r) return autograd.numpy.hstack(res) - params = np.array([0.1, 0.2], requires_grad=True) + params = pnp.array([0.1, 0.2], requires_grad=True) x, y = params res = cost(params) @@ -392,8 +408,8 @@ def cost(params): def test_reusing_quantum_tape(self, execute_kwargs, shots, device): """Test re-using a quantum tape by passing new parameters""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=True) tape = qml.tape.QuantumScript( [qml.RY(a, 0), qml.RX(b, 1), qml.CNOT((0, 1))], @@ -408,8 +424,8 @@ def cost(a, b): jac_fn = qml.jacobian(cost) jac = jac_fn(a, b) - a = np.array(0.54, requires_grad=True) - b = np.array(0.8, requires_grad=True) + a = pnp.array(0.54, requires_grad=True) + b = pnp.array(0.8, requires_grad=True) # check that the cost function continues to depend on the # values of the parameters for subsequent calls @@ -429,15 +445,15 @@ def cost(a, b): def test_classical_processing(self, execute_kwargs, device, shots): """Test classical processing within the quantum tape""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) - c = np.array(0.3, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=False) + c = pnp.array(0.3, requires_grad=True) def cost(a, b, c): ops = [ qml.RY(a * c, wires=0), qml.RZ(b, wires=0), - qml.RX(c + c**2 + np.sin(a), wires=0), + qml.RX(c + c**2 + pnp.sin(a), wires=0), ] tape = qml.tape.QuantumScript(ops, [qml.expval(qml.PauliZ(0))], shots=shots) @@ -457,8 +473,8 @@ def cost(a, b, c): def test_no_trainable_parameters(self, execute_kwargs, shots, device): """Test evaluation and Jacobian if there are no trainable parameters""" - a = np.array(0.1, requires_grad=False) - b = np.array(0.2, requires_grad=False) + a = pnp.array(0.1, requires_grad=False) + b = pnp.array(0.2, requires_grad=False) def cost(a, b): ops = [qml.RY(a, 0), qml.RX(b, 0), qml.CNOT((0, 1))] @@ -484,8 +500,8 @@ def loss(a, b): def test_matrix_parameter(self, execute_kwargs, device, shots): """Test that the autograd interface works correctly with a matrix parameter""" - U = np.array([[0, 1], [1, 0]], requires_grad=False) - a = np.array(0.1, requires_grad=True) + U = pnp.array([[0, 1], [1, 0]], requires_grad=False) + a = pnp.array(0.1, requires_grad=True) def cost(a, U): ops = [qml.QubitUnitary(U, wires=0), qml.RY(a, wires=0)] @@ -535,8 +551,8 @@ def cost_fn(a, p): program, _ = device.preprocess(execution_config=config) return execute([tape], device, **execute_kwargs, transform_program=program)[0] - a = np.array(0.1, requires_grad=False) - p = np.array([0.1, 0.2, 0.3], requires_grad=True) + a = pnp.array(0.1, requires_grad=False) + p = pnp.array([0.1, 0.2, 0.3], requires_grad=True) res = cost_fn(a, p) expected = np.cos(a) * np.cos(p[1]) * np.sin(p[0]) + np.sin(a) * ( @@ -568,8 +584,8 @@ def cost(x, y): tape = qml.tape.QuantumScript(ops, m) return autograd.numpy.hstack(execute([tape], device, **execute_kwargs)[0]) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) + x = pnp.array(0.543, requires_grad=True) + y = pnp.array(-0.654, requires_grad=True) res = cost(x, y) expected = np.array( @@ -621,8 +637,8 @@ def cost(x, y): tape = qml.tape.QuantumScript(ops, m) return autograd.numpy.hstack(execute([tape], device, **execute_kwargs)[0]) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) + x = pnp.array(0.543, requires_grad=True) + y = pnp.array(-0.654, requires_grad=True) res = cost(x, y) expected = np.array( @@ -650,9 +666,9 @@ class TestHigherOrderDerivatives: @pytest.mark.parametrize( "params", [ - np.array([0.543, -0.654], requires_grad=True), - np.array([0, -0.654], requires_grad=True), - np.array([-2.0, 0], requires_grad=True), + pnp.array([0.543, -0.654], requires_grad=True), + pnp.array([0, -0.654], requires_grad=True), + pnp.array([-2.0, 0], requires_grad=True), ], ) def test_parameter_shift_hessian(self, params, tol): @@ -693,7 +709,7 @@ def test_max_diff(self, tol): """Test that setting the max_diff parameter blocks higher-order derivatives""" dev = DefaultQubit() - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) def cost_fn(x): ops = [qml.RX(x[0], 0), qml.RY(x[1], 1), qml.CNOT((0, 1))] @@ -788,11 +804,11 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot """Test hamiltonian with no trainable parameters.""" if execute_kwargs["gradient_fn"] == "adjoint" and not qml.operation.active_new_opmath(): - pytest.skip("adjoint differentiation does not suppport hamiltonians.") + pytest.skip("adjoint differentiation does not support hamiltonians.") - coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=False) - coeffs2 = np.array([0.7], requires_grad=False) - weights = np.array([0.4, 0.5], requires_grad=True) + coeffs1 = pnp.array([0.1, 0.2, 0.3], requires_grad=False) + coeffs2 = pnp.array([0.7], requires_grad=False) + weights = pnp.array([0.4, 0.5], requires_grad=True) res = cost_fn(weights, coeffs1, coeffs2) expected = self.cost_fn_expected(weights, coeffs1, coeffs2) @@ -817,9 +833,9 @@ def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): if qml.operation.active_new_opmath(): pytest.skip("parameter shift derivatives do not yet support sums.") - coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=True) - coeffs2 = np.array([0.7], requires_grad=True) - weights = np.array([0.4, 0.5], requires_grad=True) + coeffs1 = pnp.array([0.1, 0.2, 0.3], requires_grad=True) + coeffs2 = pnp.array([0.7], requires_grad=True) + weights = pnp.array([0.4, 0.5], requires_grad=True) res = cost_fn(weights, coeffs1, coeffs2) expected = self.cost_fn_expected(weights, coeffs1, coeffs2) @@ -829,11 +845,11 @@ def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): else: assert np.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) - res = np.hstack(qml.jacobian(cost_fn)(weights, coeffs1, coeffs2)) + res = pnp.hstack(qml.jacobian(cost_fn)(weights, coeffs1, coeffs2)) expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2) if shots.has_partitioned_shots: pytest.xfail( "multiple hamiltonians with shot vectors does not seem to be differentiable." ) else: - assert np.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) + assert qml.math.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) diff --git a/tests/interfaces/test_autograd_qnode.py b/tests/interfaces/test_autograd_qnode.py index 129ab56dfe8..1d6dcfe397b 100644 --- a/tests/interfaces/test_autograd_qnode.py +++ b/tests/interfaces/test_autograd_qnode.py @@ -37,6 +37,7 @@ [ParamShiftDerivativesDevice(), "best", False, False], [ParamShiftDerivativesDevice(), "parameter-shift", True, False], [ParamShiftDerivativesDevice(), "parameter-shift", False, True], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_qubit_device_and_diff_method = [ @@ -62,6 +63,7 @@ ["auto", DefaultQubit(), "hadamard", False, False], ["auto", qml.device("lightning.qubit", wires=5), "adjoint", False, False], ["auto", qml.device("lightning.qubit", wires=5), "adjoint", True, False], + ["auto", qml.device("reference.qubit"), "parameter-shift", False, False], ] pytestmark = pytest.mark.autograd @@ -1378,6 +1380,8 @@ def test_projector( """Test that the variance of a projector is correctly returned""" if diff_method == "adjoint": pytest.skip("adjoint supports either expvals or diagonal measurements.") + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") kwargs = dict( diff_method=diff_method, interface=interface, @@ -1435,6 +1439,8 @@ def test_postselection_differentiation( if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") @qml.qnode( dev, diff --git a/tests/interfaces/test_jax.py b/tests/interfaces/test_jax.py index 519c0daa028..1c12ba0b524 100644 --- a/tests/interfaces/test_jax.py +++ b/tests/interfaces/test_jax.py @@ -122,16 +122,22 @@ def cost(x, cache): # add tests for lightning 2 when possible # set rng for device when possible no_shots = Shots(None) +shots_10k = Shots(10000) shots_2_10k = Shots((10000, 10000)) -dev_def = DefaultQubit() +dev_def = DefaultQubit(seed=42) dev_ps = ParamShiftDerivativesDevice(seed=54353453) +dev_ref = qml.device("reference.qubit") test_matrix = [ - ({"gradient_fn": param_shift}, Shots(100000), DefaultQubit(seed=42)), # 0 - ({"gradient_fn": param_shift}, no_shots, dev_def), # 1 - ({"gradient_fn": "backprop"}, no_shots, dev_def), # 2 - ({"gradient_fn": "adjoint"}, no_shots, dev_def), # 3 - ({"gradient_fn": "adjoint", "device_vjp": True}, no_shots, dev_def), # 4 - ({"gradient_fn": "device"}, shots_2_10k, dev_ps), # 5 + ({"gradient_fn": param_shift}, shots_10k, dev_def), # 0 + ({"gradient_fn": param_shift}, shots_2_10k, dev_def), # 1 + ({"gradient_fn": param_shift}, no_shots, dev_def), # 2 + ({"gradient_fn": "backprop"}, no_shots, dev_def), # 3 + ({"gradient_fn": "adjoint"}, no_shots, dev_def), # 4 + ({"gradient_fn": "adjoint", "device_vjp": True}, no_shots, dev_def), # 5 + ({"gradient_fn": "device"}, shots_2_10k, dev_ps), # 6 + ({"gradient_fn": param_shift}, no_shots, dev_ref), # 7 + ({"gradient_fn": param_shift}, shots_10k, dev_ref), # 8 + ({"gradient_fn": param_shift}, shots_2_10k, dev_ref), # 9 ] diff --git a/tests/interfaces/test_jax_jit_qnode.py b/tests/interfaces/test_jax_jit_qnode.py index cce76a83b9e..cafa9c47fa1 100644 --- a/tests/interfaces/test_jax_jit_qnode.py +++ b/tests/interfaces/test_jax_jit_qnode.py @@ -41,6 +41,7 @@ [qml.device("lightning.qubit", wires=5), "adjoint", False, False], [qml.device("lightning.qubit", wires=5), "adjoint", True, True], [qml.device("lightning.qubit", wires=5), "parameter-shift", False, False], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_and_qubit_device_and_diff_method = [ ["auto"] + inner_list for inner_list in qubit_device_and_diff_method @@ -1040,6 +1041,8 @@ def test_postselection_differentiation( pytest.xfail("gradient transforms have a different vjp shape convention") elif dev.name == "lightning.qubit": pytest.xfail("lightning qubit does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") @qml.qnode( dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution @@ -1431,6 +1434,8 @@ def test_projector( elif diff_method == "spsa": gradient_kwargs = {"h": H_FOR_SPSA, "sampler_rng": np.random.default_rng(SEED_FOR_SPSA)} tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = jax.numpy.array(state) x, y = 0.765, -0.654 @@ -1514,6 +1519,11 @@ def test_hamiltonian_expansion_analytic( are non-commuting groups and the number of shots is None and the first and second order gradients are correctly evaluated""" gradient_kwargs = {} + if dev.name == "reference.qubit": + pytest.skip( + "Cannot add transform to the transform program in preprocessing" + "when using mocker.spy on it." + ) if dev.name == "param_shift.qubit": pytest.xfail("gradients transforms have a different vjp shape convention.") if diff_method == "adjoint": @@ -1840,6 +1850,9 @@ def test_hermitian( to different reasons, hence the parametrization in the test. """ # pylint: disable=unused-argument + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support Hermitians (sc-72911)") + if diff_method == "backprop": pytest.skip("Backpropagation is unsupported if shots > 0.") diff --git a/tests/interfaces/test_jax_qnode.py b/tests/interfaces/test_jax_qnode.py index d24dec3383d..4b612da8e25 100644 --- a/tests/interfaces/test_jax_qnode.py +++ b/tests/interfaces/test_jax_qnode.py @@ -40,6 +40,7 @@ [qml.device("lightning.qubit", wires=5), "adjoint", True, True], [qml.device("lightning.qubit", wires=5), "adjoint", False, False], [qml.device("lightning.qubit", wires=5), "adjoint", True, False], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_and_device_and_diff_method = [ @@ -911,6 +912,8 @@ def test_postselection_differentiation(self, dev, diff_method, grad_on_execution if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.xfail("reference.qubit does not support postselection.") @qml.qnode( dev, @@ -1298,6 +1301,8 @@ def test_projector( elif diff_method == "spsa": gradient_kwargs = {"h": H_FOR_SPSA, "sampler_rng": np.random.default_rng(SEED_FOR_SPSA)} tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = jax.numpy.array(state) x, y = 0.765, -0.654 @@ -1373,7 +1378,7 @@ def circuit(x, y): jax.grad(circuit, argnums=[0])(x, y) @pytest.mark.parametrize("max_diff", [1, 2]) - def test_hamiltonian_expansion_analytic( + def test_split_non_commuting_analytic( self, dev, diff_method, grad_on_execution, max_diff, interface, device_vjp, mocker, tol ): """Test that the Hamiltonian is not expanded if there @@ -1391,6 +1396,11 @@ def test_hamiltonian_expansion_analytic( "sampler_rng": np.random.default_rng(SEED_FOR_SPSA), } tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.skip( + "Cannot add transform to the transform program in preprocessing" + "when using mocker.spy on it." + ) spy = mocker.spy(qml.transforms, "split_non_commuting") obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)] @@ -1451,6 +1461,13 @@ def test_hamiltonian_finite_shots( """Test that the Hamiltonian is correctly measured (and not expanded) if there are non-commuting groups and the number of shots is finite and the first and second order gradients are correctly evaluated""" + + if dev.name == "reference.qubit": + pytest.skip( + "Cannot added to a transform to the transform program in " + "preprocessing when using mocker.spy on it." + ) + gradient_kwargs = {} tol = 0.3 if diff_method in ("adjoint", "backprop", "finite-diff"): diff --git a/tests/interfaces/test_tensorflow.py b/tests/interfaces/test_tensorflow.py index b2329cd27c1..0abe82c1942 100644 --- a/tests/interfaces/test_tensorflow.py +++ b/tests/interfaces/test_tensorflow.py @@ -118,6 +118,16 @@ def cost(x, cache): ({"gradient_fn": "backprop", "interface": "tf-autograph"}, None, DefaultQubit()), # 6 ({"gradient_fn": "adjoint", "interface": "tf-autograph"}, None, DefaultQubit()), # 7 ({"gradient_fn": "adjoint", "interface": "tf", "device_vjp": True}, None, DefaultQubit()), # 8 + ( + {"gradient_fn": param_shift, "interface": "tensorflow"}, + None, + qml.device("reference.qubit"), + ), # 9 + ( + {"gradient_fn": param_shift, "interface": "tensorflow"}, + 100000, + qml.device("reference.qubit"), + ), # 10 ] diff --git a/tests/interfaces/test_tensorflow_qnode.py b/tests/interfaces/test_tensorflow_qnode.py index c09f1632202..c01d32091c6 100644 --- a/tests/interfaces/test_tensorflow_qnode.py +++ b/tests/interfaces/test_tensorflow_qnode.py @@ -38,6 +38,7 @@ [qml.device("lightning.qubit", wires=4), "adjoint", False, False], [qml.device("lightning.qubit", wires=4), "adjoint", True, True], [qml.device("lightning.qubit", wires=4), "adjoint", True, False], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] TOL_FOR_SPSA = 1.0 @@ -980,6 +981,8 @@ def test_projector( kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) kwargs["num_directions"] = 20 tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = tf.constant(state, dtype=dtype) @@ -1014,6 +1017,9 @@ def test_postselection_differentiation( if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") + @qml.qnode( dev, diff_method=diff_method, diff --git a/tests/interfaces/test_torch.py b/tests/interfaces/test_torch.py index 3cdcf5eae30..3640d31de9c 100644 --- a/tests/interfaces/test_torch.py +++ b/tests/interfaces/test_torch.py @@ -159,6 +159,21 @@ def cost_cache(x): Shots((100000, 100000)), ParamShiftDerivativesDevice(), ), + ( + {"gradient_fn": param_shift}, + Shots(None), + qml.device("reference.qubit"), + ), + ( + {"gradient_fn": param_shift}, + Shots(100000), + qml.device("reference.qubit"), + ), + ( + {"gradient_fn": param_shift}, + Shots((100000, 100000)), + qml.device("reference.qubit"), + ), ] diff --git a/tests/interfaces/test_torch_qnode.py b/tests/interfaces/test_torch_qnode.py index 82dbda669d4..5ecf181d343 100644 --- a/tests/interfaces/test_torch_qnode.py +++ b/tests/interfaces/test_torch_qnode.py @@ -47,6 +47,7 @@ [ParamShiftDerivativesDevice(), "best", False, False], [ParamShiftDerivativesDevice(), "parameter-shift", True, False], [ParamShiftDerivativesDevice(), "parameter-shift", False, True], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_and_qubit_device_and_diff_method = [ @@ -1131,6 +1132,8 @@ def test_projector( tol = TOL_FOR_SPSA elif diff_method == "hadamard": pytest.skip("Hadamard does not support variances.") + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = torch.tensor(state, requires_grad=False) @@ -1167,6 +1170,9 @@ def test_postselection_differentiation( if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") + @qml.qnode( dev, diff_method=diff_method, From fa97f16ecce36bdf2cba3ede228f6ecb49eb449f Mon Sep 17 00:00:00 2001 From: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:52:31 -0400 Subject: [PATCH 11/14] Deprecation QubitStateVector (#6172) [sc-72046] --- doc/development/deprecations.rst | 6 ++++++ doc/releases/changelog-dev.md | 4 ++++ pennylane/ops/qubit/state_preparation.py | 16 ++++++++++++++-- tests/drawer/test_drawable_layers.py | 2 +- tests/ops/functions/conftest.py | 2 +- tests/ops/qubit/test_state_prep.py | 6 ++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index c91979963a9..22b478ab3a5 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -39,6 +39,12 @@ Pending deprecations - Deprecated in v0.37 - Will be removed in v0.39 +* The ``QubitStateVector`` template is deprecated. + Instead, use ``StatePrep``. + + - Deprecated in v0.39 + - Will be removed in v0.40 + New operator arithmetic deprecations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 91194ac1e19..3f3a3c04326 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -90,6 +90,10 @@

Deprecations πŸ‘‹

+* The ``QubitStateVector`` template is deprecated. + Instead, use ``StatePrep``. + [(#6172)](https://github.com/PennyLaneAI/pennylane/pull/6172) + * `Device`, `QubitDevice`, and `QutritDevice` will no longer be accessible via top-level import in v0.40. They will still be accessible as `qml.devices.LegacyDevice`, `qml.devices.QubitDevice`, and `qml.devices.QutritDevice` respectively. diff --git a/pennylane/ops/qubit/state_preparation.py b/pennylane/ops/qubit/state_preparation.py index 7ef84be70fd..6bfb4179e00 100644 --- a/pennylane/ops/qubit/state_preparation.py +++ b/pennylane/ops/qubit/state_preparation.py @@ -15,6 +15,8 @@ This submodule contains the discrete-variable quantum operations concerned with preparing a certain state on the device. """ +import warnings + # pylint:disable=too-many-branches,abstract-method,arguments-differ,protected-access,no-member from typing import Optional @@ -442,9 +444,19 @@ def _preprocess(state, wires, pad_with, normalize, validate_norm): return state -# pylint: disable=missing-class-docstring class QubitStateVector(StatePrep): - pass # QSV is still available + r""" + ``QubitStateVector`` is deprecated and will be removed in version 0.40. Instead, please use ``StatePrep``. + """ + + # pylint: disable=too-many-arguments + def __init__(self, state, wires, pad_with=None, normalize=False, validate_norm=True): + warnings.warn( + "QubitStateVector is deprecated and will be removed in version 0.40. " + "Instead, please use StatePrep.", + qml.PennyLaneDeprecationWarning, + ) + super().__init__(state, wires, pad_with, normalize, validate_norm) class QubitDensityMatrix(Operation): diff --git a/tests/drawer/test_drawable_layers.py b/tests/drawer/test_drawable_layers.py index 7e1bdb1d0bd..729e0f74609 100644 --- a/tests/drawer/test_drawable_layers.py +++ b/tests/drawer/test_drawable_layers.py @@ -196,7 +196,7 @@ def test_mid_measure_custom_wires(self): m1 = qml.measurements.MeasurementValue([mp1], lambda v: v) def teleport(state): - qml.QubitStateVector(state, wires=["A"]) + qml.StatePrep(state, wires=["A"]) qml.Hadamard(wires="a") qml.CNOT(wires=["a", "B"]) qml.CNOT(wires=["A", "a"]) diff --git a/tests/ops/functions/conftest.py b/tests/ops/functions/conftest.py index 92863eb7ab1..692745b48b6 100644 --- a/tests/ops/functions/conftest.py +++ b/tests/ops/functions/conftest.py @@ -38,7 +38,6 @@ qml.sum(qml.X(0), qml.X(0), qml.Z(0), qml.Z(0)), qml.BasisState([1], wires=[0]), qml.ControlledQubitUnitary(np.eye(2), control_wires=1, wires=0), - qml.QubitStateVector([0, 1], wires=0), qml.QubitChannel([np.array([[1, 0], [0, 0.8]]), np.array([[0, 0.6], [0, 0]])], wires=0), qml.MultiControlledX(wires=[0, 1]), qml.Projector([1], 0), # the state-vector version is already tested @@ -137,6 +136,7 @@ PowOpObs, PowOperation, PowObs, + qml.QubitStateVector, } """Types that should not have actual instances created.""" diff --git a/tests/ops/qubit/test_state_prep.py b/tests/ops/qubit/test_state_prep.py index 342aaff5df0..e6da832a8eb 100644 --- a/tests/ops/qubit/test_state_prep.py +++ b/tests/ops/qubit/test_state_prep.py @@ -36,6 +36,12 @@ def test_adjoint_error_exception(op): op.adjoint() +def test_QubitStateVector_is_deprecated(): + """Test that QubitStateVector is deprecated.""" + with pytest.warns(qml.PennyLaneDeprecationWarning, match="QubitStateVector is deprecated"): + _ = qml.QubitStateVector([1, 0, 0, 0], wires=[0, 1]) + + @pytest.mark.parametrize( "op, mat, base", [ From 7e21a762b65ed4f21f7e048ed199deac6a057646 Mon Sep 17 00:00:00 2001 From: Pietropaolo Frisoni Date: Tue, 17 Sep 2024 11:43:21 -0400 Subject: [PATCH 12/14] PennyLane is compatible with JAX 0.4.28 (#6255) **Context:** As part of the effort to make PL compatible with Numpy 2.0 (see #6061), we need to upgrade JAX to 0.4.26+ since such a version introduced the support for Numpy 2.0. We opted for JAX 0.4.28 since it is the same version used by Catalyst. **Description of the Change:** As above. **Benefits:** PL is compatible with Numpy 2.0 and Jax 0.4.28. **Possible Drawbacks:** - From JAX 0.4.27, in `jax.jit`, passing invalid static_argnums or static_argnames now leads to an error rather than a warning. In PL, this breaks every test where we set `shots` in the `QNode` call with `static_argnames=["shots"]`. At this stage, we decided to mark such tests with `pytest.xfail` to allow the upgrade. **Related GitHub Issues:** None. **Related Shortcut Stories**: [sc-61389] --------- Co-authored-by: dwierichs --- .github/workflows/install_deps/action.yml | 4 ++-- doc/releases/changelog-dev.md | 3 +++ .../devices/default_qubit/test_default_qubit.py | 2 ++ .../qutrit_mixed/test_qutrit_mixed_measure.py | 2 +- tests/devices/test_default_qutrit_mixed.py | 2 +- tests/interfaces/test_jax_jit_qnode.py | 17 +++++++++++++++++ .../test_optimization_utils.py | 13 +++++++++++-- 7 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/workflows/install_deps/action.yml b/.github/workflows/install_deps/action.yml index 99b77dc8157..e1dc636bb84 100644 --- a/.github/workflows/install_deps/action.yml +++ b/.github/workflows/install_deps/action.yml @@ -15,7 +15,7 @@ inputs: jax_version: description: The version of JAX to install for any job that requires JAX required: false - default: '0.4.23' + default: '0.4.28' install_tensorflow: description: Indicate if TensorFlow should be installed or not required: false @@ -86,7 +86,7 @@ runs: if: inputs.install_jax == 'true' env: JAX_VERSION: ${{ inputs.jax_version != '' && format('=={0}', inputs.jax_version) || '' }} - run: pip install "jax${{ env.JAX_VERSION}}" "jaxlib${{ env.JAX_VERSION }}" scipy~=1.12.0 + run: pip install "jax${{ env.JAX_VERSION}}" "jaxlib${{ env.JAX_VERSION }}" - name: Install additional PIP packages shell: bash diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 3f3a3c04326..b03ff7b4f72 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -10,6 +10,9 @@ [(#6061)](https://github.com/PennyLaneAI/pennylane/pull/6061) [(#6258)](https://github.com/PennyLaneAI/pennylane/pull/6258) +* PennyLane is now compatible with Jax 0.4.28. + [(#6255)](https://github.com/PennyLaneAI/pennylane/pull/6255) + * `qml.qchem.excitations` now optionally returns fermionic operators. [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py index d3049d90eae..6820b0afdcb 100644 --- a/tests/devices/default_qubit/test_default_qubit.py +++ b/tests/devices/default_qubit/test_default_qubit.py @@ -1864,6 +1864,7 @@ def circ_expected(): if use_jit: import jax + pytest.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") circ_postselect = jax.jit(circ_postselect, static_argnames=["shots"]) res = circ_postselect(param, shots=shots) @@ -2051,6 +2052,7 @@ def circ(): if use_jit: import jax + pytest.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") circ = jax.jit(circ, static_argnames=["shots"]) res = circ(shots=shots) diff --git a/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py b/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py index 8a8de382f57..56d61fa339b 100644 --- a/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py +++ b/tests/devices/qutrit_mixed/test_qutrit_mixed_measure.py @@ -499,7 +499,7 @@ def test_jax_backprop(self, use_jit): x = jax.numpy.array(self.x, dtype=jax.numpy.float64) coeffs = (5.2, 6.7) - f = jax.jit(self.f, static_argnums=(1, 2, 3, 4)) if use_jit else self.f + f = jax.jit(self.f, static_argnums=(1, 2, 3)) if use_jit else self.f out = f(x, coeffs) expected_out = self.expected(x, coeffs) diff --git a/tests/devices/test_default_qutrit_mixed.py b/tests/devices/test_default_qutrit_mixed.py index 5178e1c800a..13f3d744bb1 100644 --- a/tests/devices/test_default_qutrit_mixed.py +++ b/tests/devices/test_default_qutrit_mixed.py @@ -823,7 +823,7 @@ def test_jax_backprop(self, use_jit): x = jax.numpy.array(self.x, dtype=jax.numpy.float64) coeffs = (5.2, 6.7) - f = jax.jit(self.f, static_argnums=(1, 2, 3, 4)) if use_jit else self.f + f = jax.jit(self.f, static_argnums=(1, 2, 3)) if use_jit else self.f out = f(x, coeffs) expected_out = self.expected(x, coeffs) diff --git a/tests/interfaces/test_jax_jit_qnode.py b/tests/interfaces/test_jax_jit_qnode.py index cafa9c47fa1..99ac281ef8f 100644 --- a/tests/interfaces/test_jax_jit_qnode.py +++ b/tests/interfaces/test_jax_jit_qnode.py @@ -813,6 +813,7 @@ def circuit(a, b): res = circuit(a, b, shots=100) # pylint: disable=unexpected-keyword-arg assert res.shape == (100, 2) # pylint:disable=comparison-with-callable + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_gradient_integration(self, interface): """Test that temporarily setting the shots works for gradient computations""" @@ -912,6 +913,7 @@ def circuit(x): class TestQubitIntegration: """Tests that ensure various qubit circuits integrate correctly""" + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_sampling(self, dev, diff_method, grad_on_execution, device_vjp, interface): """Test sampling works as expected""" if grad_on_execution: @@ -941,6 +943,7 @@ def circuit(): assert isinstance(res[1], jax.Array) assert res[1].shape == (10,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_counts(self, dev, diff_method, grad_on_execution, device_vjp, interface): """Test counts works as expected""" if grad_on_execution: @@ -2041,6 +2044,7 @@ def circ(p, U): class TestReturn: """Class to test the shape of the Grad/Jacobian with different return types.""" + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_grad_single_measurement_param( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2073,6 +2077,7 @@ def circuit(a): assert isinstance(grad, jax.numpy.ndarray) assert grad.shape == () + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_grad_single_measurement_multiple_param( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2110,6 +2115,7 @@ def circuit(a, b): assert grad[0].shape == () assert grad[1].shape == () + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_grad_single_measurement_multiple_param_array( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2142,6 +2148,7 @@ def circuit(a): assert isinstance(grad, jax.numpy.ndarray) assert grad.shape == (2,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_single_measurement_param_probs( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2175,6 +2182,7 @@ def circuit(a): assert isinstance(jac, jax.numpy.ndarray) assert jac.shape == (4,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_single_measurement_probs_multiple_param( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2214,6 +2222,7 @@ def circuit(a, b): assert isinstance(jac[1], jax.numpy.ndarray) assert jac[1].shape == (4,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_single_measurement_probs_multiple_param_single_array( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2246,6 +2255,7 @@ def circuit(a): assert isinstance(jac, jax.numpy.ndarray) assert jac.shape == (4, 2) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_expval_expval_multiple_params( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2295,6 +2305,7 @@ def circuit(x, y): assert isinstance(jac[1][1], jax.numpy.ndarray) assert jac[1][1].shape == () + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_expval_expval_multiple_params_array( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2333,6 +2344,7 @@ def circuit(a): assert isinstance(jac[1], jax.numpy.ndarray) assert jac[1].shape == (2,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_var_var_multiple_params( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2385,6 +2397,7 @@ def circuit(x, y): assert isinstance(jac[1][1], jax.numpy.ndarray) assert jac[1][1].shape == () + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_var_var_multiple_params_array( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2425,6 +2438,7 @@ def circuit(a): assert isinstance(jac[1], jax.numpy.ndarray) assert jac[1].shape == (2,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_multiple_measurement_single_param( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2463,6 +2477,7 @@ def circuit(a): assert isinstance(jac[1], jax.numpy.ndarray) assert jac[1].shape == (4,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_multiple_measurement_multiple_param( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2510,6 +2525,7 @@ def circuit(a, b): assert isinstance(jac[1][1], jax.numpy.ndarray) assert jac[1][1].shape == (4,) + @pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") def test_jacobian_multiple_measurement_multiple_param_array( self, dev, diff_method, grad_on_execution, device_vjp, jacobian, shots, interface ): @@ -2871,6 +2887,7 @@ def circuit(x): assert hess[1].shape == (2, 2, 2) +@pytest.mark.xfail(reason="'shots' cannot be a static_argname for 'jit' in JAX 0.4.28") @pytest.mark.parametrize("hessian", hessian_fn) @pytest.mark.parametrize("diff_method", ["parameter-shift", "hadamard"]) def test_jax_device_hessian_shots(hessian, diff_method): diff --git a/tests/transforms/test_optimization/test_optimization_utils.py b/tests/transforms/test_optimization/test_optimization_utils.py index ff31cd999c6..19145b7ce53 100644 --- a/tests/transforms/test_optimization/test_optimization_utils.py +++ b/tests/transforms/test_optimization/test_optimization_utils.py @@ -238,8 +238,17 @@ def test_jacobian_jax(self, use_jit): special_angles = np.array(list(product(special_points, repeat=6))).reshape((-1, 2, 3)) random_angles = np.random.random((1000, 2, 3)) # Need holomorphic derivatives and complex inputs because the output matrices are complex - all_angles = jax.numpy.concatenate([special_angles, random_angles], dtype=complex) - jac_fn = lambda fn: jax.vmap(jax.jacobian(fn, holomorphic=True)) + all_angles = jax.numpy.concatenate([special_angles, random_angles]) + + # We need to define the Jacobian function manually because fuse_rot_angles is not guaranteed to be holomorphic, + # and jax.jacobian requires real-valued outputs for non-holomorphic functions. + def jac_fn(fn): + real_fn = lambda arg: qml.math.real(fn(arg)) + imag_fn = lambda arg: qml.math.imag(fn(arg)) + real_jac_fn = jax.vmap(jax.jacobian(real_fn)) + imag_jac_fn = jax.vmap(jax.jacobian(imag_fn)) + return lambda arg: real_jac_fn(arg) + 1j * imag_jac_fn(arg) + jit_fn = jax.jit if use_jit else None self.run_jacobian_test(all_angles, jac_fn, is_batched=True, jit_fn=jit_fn) From 2c0fdf0ffd87ea05633f24c0e09714b6e681a54c Mon Sep 17 00:00:00 2001 From: Isaac De Vlugt <34751083+isaacdevlugt@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:44:40 -0400 Subject: [PATCH 13/14] `qinfo` module deprecations and removals (#5911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Context:** As part of our v0.39 deprecation cycle, `qinfo` will be deprecated. All PRs for deprecating each function can be merged to here, **Description of the Change:** Deprecates `qinfo`. Specifically: - [x] deprecate `qinfo.mutual_info`: https://github.com/PennyLaneAI/pennylane/pull/5917 - [x] deprecate `qinfo.reduced_dm`: https://github.com/PennyLaneAI/pennylane/pull/5915 - [x] deprecate `qinfo.purity`: https://github.com/PennyLaneAI/pennylane/pull/5916 - [x] deprecate `qinfo.vn_entropy`: https://github.com/PennyLaneAI/pennylane/pull/5912 - [x] deprecate `qinfo.vn_entanglement_entropy` https://github.com/PennyLaneAI/pennylane/pull/5914 - [x] deprecate `qinfo.fidelity`: https://github.com/PennyLaneAI/pennylane/pull/5915 - [x] deprecate `qinfo.relative_entropy`: https://github.com/PennyLaneAI/pennylane/pull/5915 - [x] deprecate `qinfo.trace_distance` - [x] remove `qinfo.classical_fisher` - [x] remove `qinfo.quantum_fisher` **Benefits:** Desired UI and less redundancies. **Possible Drawbacks:** None **Related GitHub Issues:** [sc-67217] [sc-67216] [sc-66716] [sc-66715] [sc-66714] [sc-66713] [sc-67664] [sc-67665] [sc-67663] [sc-67446] --------- Co-authored-by: Mudit Pandey Co-authored-by: Astral Cai Co-authored-by: Ahmed Darwish Co-authored-by: Cristian Emiliano Godinez Ramirez <57567043+EmilianoG-byte@users.noreply.github.com> Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> Co-authored-by: albi3ro Co-authored-by: Christina Lee Co-authored-by: ringo-but-quantum Co-authored-by: David Wierichs Co-authored-by: Thomas R. Bromley <49409390+trbromley@users.noreply.github.com> Co-authored-by: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Co-authored-by: Tonmoy Bhattacharya Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Vincent Michaud-Rioux Co-authored-by: Matthew Silverman Co-authored-by: Guillermo Alonso-Linaje <65235481+KetpuntoG@users.noreply.github.com> Co-authored-by: Utkarsh Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> Co-authored-by: Romain Moyard Co-authored-by: Diksha Dhawan <40900030+ddhawan11@users.noreply.github.com> Co-authored-by: soranjh Co-authored-by: Jorge J. MartΓ­nez de Lejarza <61199780+gmlejarza@users.noreply.github.com> Co-authored-by: anthayes92 <34694788+anthayes92@users.noreply.github.com> Co-authored-by: Alex Preciado Co-authored-by: Mikhail Andrenkov Co-authored-by: Jack Brown Co-authored-by: Austin Huang <65315367+austingmhuang@users.noreply.github.com> Co-authored-by: Pietropaolo Frisoni Co-authored-by: Justin Pickering <79890410+justinpickering@users.noreply.github.com> Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> Co-authored-by: Will Co-authored-by: Josh Izaac Co-authored-by: Lee James O'Riordan Co-authored-by: Pietropaolo Frisoni --- doc/code/qml_qinfo.rst | 11 +- doc/development/deprecations.rst | 20 +- doc/releases/changelog-dev.md | 10 + pennylane/devices/default_mixed.py | 5 +- pennylane/measurements/mutual_info.py | 2 +- pennylane/measurements/purity.py | 2 +- pennylane/measurements/vn_entropy.py | 6 +- pennylane/qinfo/__init__.py | 2 - pennylane/qinfo/transforms.py | 399 +++++------------- tests/devices/test_default_mixed.py | 7 +- tests/measurements/test_mutual_info.py | 47 ++- tests/qinfo/test_entropies.py | 67 ++- tests/qinfo/test_fidelity.py | 39 +- tests/qinfo/test_fisher_deprecation.py | 47 --- tests/qinfo/test_purity.py | 24 +- tests/qinfo/test_reduced_dm.py | 23 +- tests/qinfo/test_trace_distance.py | 22 +- ... => test_vn_entanglement_entropy_qinfo.py} | 20 + tests/shadow/test_shadow_entropies.py | 10 +- 19 files changed, 367 insertions(+), 396 deletions(-) delete mode 100644 tests/qinfo/test_fisher_deprecation.py rename tests/qinfo/{test_vn_entanglement_entropy.py => test_vn_entanglement_entropy_qinfo.py} (92%) diff --git a/doc/code/qml_qinfo.rst b/doc/code/qml_qinfo.rst index 04563167e2c..02674bbce41 100644 --- a/doc/code/qml_qinfo.rst +++ b/doc/code/qml_qinfo.rst @@ -4,6 +4,13 @@ qml.qinfo Overview -------- +.. warning:: + + The ``qinfo`` module is deprecated and scheduled to be removed in v0.40. Most quantum information transforms + are available as measurement processes (see the :mod:`~pennylane.measurements` module for more details). + Additionally, the transforms are also available as standalone functions in the :mod:`~pennylane.math` and + :mod:`~pennylane.gradients` modules. + This module provides a collection of methods to return quantum information quantities from :class:`~.QNode` returning :func:`~pennylane.state`. @@ -15,8 +22,4 @@ Transforms .. automodapi:: pennylane.qinfo.transforms :no-heading: :no-inherited-members: - :skip: metric_tensor - :skip: adjoint_metric_tensor :skip: transform - :skip: classical_fisher - :skip: quantum_fisher diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index 22b478ab3a5..f44598fc765 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -9,12 +9,18 @@ deprecations are listed below. Pending deprecations -------------------- +* The ``qml.qinfo`` module has been deprecated. Please see the respective functions in the ``qml.math`` and ``qml.measurements`` + modules instead. + + - Deprecated in v0.39 + - Will be removed in v0.40 + * ``Device``, ``QubitDevice``, and ``QutritDevice`` will no longer be imported top level in v0.40. They instead we be available as ``qml.devices.LegacyDevice``, ``qml.devices.QubitDevice``, and ``qml.devices.QutritDevice`` respectively. - Deprecated top level access in v0.39 - - Top level access removed in v0.40 + - Top level access will be removed in v0.40 * `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` instead. `QNode.get_gradient_fn` can also be used to process the diff method. @@ -27,12 +33,6 @@ Pending deprecations - Deprecated in v0.38 - Will be removed in v0.39 -* The functions ``qml.qinfo.classical_fisher`` and ``qml.qinfo.quantum_fisher`` are deprecated since they are being migrated - to the ``qml.gradients`` module. Therefore, ``qml.gradients.classical_fisher`` and ``qml.gradients.quantum_fisher`` should be used instead. - - - Deprecated and Duplicated in v0.38 - - Will be removed in v0.39 - * The ``simplify`` argument in ``qml.Hamiltonian`` and ``qml.ops.LinearCombination`` is deprecated. Instead, ``qml.simplify()`` can be called on the constructed operator. @@ -89,6 +89,12 @@ Other deprecations Completed deprecation cycles ---------------------------- +* The functions ``qml.qinfo.classical_fisher`` and ``qml.qinfo.quantum_fisher`` have been removed and migrated to the ``qml.gradients`` + module. Therefore, ``qml.gradients.classical_fisher`` and ``qml.gradients.quantum_fisher`` should be used instead. + + - Deprecated in v0.38 + - Removed in v0.39 + * All of the legacy devices (any with the name ``default.qubit.{autograd,torch,tf,jax,legacy}``) are removed. Use ``default.qubit`` instead, as it supports backpropagation for the many backends the legacy devices support. diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index b03ff7b4f72..c75f86c9e55 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -63,6 +63,10 @@

Breaking changes πŸ’”

+* The functions `qml.qinfo.classical_fisher` and `qml.qinfo.quantum_fisher` have been removed and migrated to the `qml.gradients` + module. Therefore, `qml.gradients.classical_fisher` and `qml.gradients.quantum_fisher` should be used instead. + [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) + * Remove support for Python 3.9. [(#6223)](https://github.com/PennyLaneAI/pennylane/pull/6223) @@ -93,6 +97,10 @@

Deprecations πŸ‘‹

+* The `qml.qinfo` module has been deprecated. Please see the respective functions in the `qml.math` and + `qml.measurements` modules instead. + [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) + * The ``QubitStateVector`` template is deprecated. Instead, use ``StatePrep``. [(#6172)](https://github.com/PennyLaneAI/pennylane/pull/6172) @@ -140,10 +148,12 @@ This release contains contributions from (in alphabetical order): Guillermo Alonso, Utkarsh Azad, Astral Cai, +Isaac De Vlugt, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, Christina Lee, William Maxwell, Lee J. O'Riordan, +Mudit Pandey, David Wierichs, diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py index d7a3a55e0b5..d0284e4b3e8 100644 --- a/pennylane/devices/default_mixed.py +++ b/pennylane/devices/default_mixed.py @@ -763,8 +763,9 @@ def execute(self, circuit, **kwargs): self.measured_wires = self.wires return super().execute(circuit, **kwargs) if isinstance(m, (VnEntropyMP, MutualInfoMP)): - # VnEntropy, MutualInfo: Computed for the state prior to measurement. So, readout - # error need not be applied on the corresponding device wires. + # VnEntropy, MutualInfo: Computed for the state + # prior to measurement. So, readout error need not be applied to the + # corresponding device wires. continue wires_list.append(m.wires) self.measured_wires = qml.wires.Wires.all_wires(wires_list) diff --git a/pennylane/measurements/mutual_info.py b/pennylane/measurements/mutual_info.py index 18335fe4306..81e39864896 100644 --- a/pennylane/measurements/mutual_info.py +++ b/pennylane/measurements/mutual_info.py @@ -74,7 +74,7 @@ def circuit_mutual(x): using the classical backpropagation differentiation method (``diff_method="backprop"``) with a compatible device and finite differences (``diff_method="finite-diff"``). - .. seealso:: :func:`~.vn_entropy`, :func:`pennylane.qinfo.transforms.mutual_info` and :func:`pennylane.math.mutual_info` + .. seealso:: :func:`~pennylane.vn_entropy`, :func:`pennylane.math.mutual_info` """ wires0 = qml.wires.Wires(wires0) wires1 = qml.wires.Wires(wires1) diff --git a/pennylane/measurements/purity.py b/pennylane/measurements/purity.py index 83e4166cbc9..4427f16231d 100644 --- a/pennylane/measurements/purity.py +++ b/pennylane/measurements/purity.py @@ -57,7 +57,7 @@ def circuit_purity(p): >>> circuit_purity(0.1) array(0.7048) - .. seealso:: :func:`pennylane.qinfo.transforms.purity` and :func:`pennylane.math.purity` + .. seealso:: :func:`pennylane.math.purity` """ wires = Wires(wires) return PurityMP(wires=wires) diff --git a/pennylane/measurements/vn_entropy.py b/pennylane/measurements/vn_entropy.py index 79c1a875eae..e07789add66 100644 --- a/pennylane/measurements/vn_entropy.py +++ b/pennylane/measurements/vn_entropy.py @@ -61,11 +61,11 @@ def circuit_entropy(x): .. note:: - Calculating the derivative of :func:`~.vn_entropy` is currently supported when + Calculating the derivative of :func:`~pennylane.vn_entropy` is currently supported when using the classical backpropagation differentiation method (``diff_method="backprop"``) with a compatible device and finite differences (``diff_method="finite-diff"``). - .. seealso:: :func:`pennylane.qinfo.transforms.vn_entropy` and :func:`pennylane.math.vn_entropy` + .. seealso:: :func:`pennylane.math.vn_entropy` """ wires = Wires(wires) return VnEntropyMP(wires=wires, log_base=log_base) @@ -74,7 +74,7 @@ def circuit_entropy(x): class VnEntropyMP(StateMeasurement): """Measurement process that computes the Von Neumann entropy of the system prior to measurement. - Please refer to :func:`vn_entropy` for detailed documentation. + Please refer to :func:`~pennylane.vn_entropy` for detailed documentation. Args: wires (.Wires): The wires the measurement process applies to. diff --git a/pennylane/qinfo/__init__.py b/pennylane/qinfo/__init__.py index 819c52d989f..691e357c0d0 100644 --- a/pennylane/qinfo/__init__.py +++ b/pennylane/qinfo/__init__.py @@ -18,8 +18,6 @@ vn_entropy, purity, mutual_info, - classical_fisher, - quantum_fisher, fidelity, relative_entropy, trace_distance, diff --git a/pennylane/qinfo/transforms.py b/pennylane/qinfo/transforms.py index 07e58cfe33f..d9fceccda04 100644 --- a/pennylane/qinfo/transforms.py +++ b/pennylane/qinfo/transforms.py @@ -19,8 +19,7 @@ import pennylane as qml from pennylane import transform -from pennylane.devices import DefaultMixed, DefaultQubit, DefaultQubitLegacy -from pennylane.gradients import adjoint_metric_tensor, metric_tensor +from pennylane.devices import DefaultMixed from pennylane.measurements import DensityMatrixMP, StateMP from pennylane.tape import QuantumScript, QuantumScriptBatch from pennylane.typing import PostprocessingFn @@ -31,6 +30,11 @@ def reduced_dm(tape: QuantumScript, wires, **kwargs) -> tuple[QuantumScriptBatch """Compute the reduced density matrix from a :class:`~.QNode` returning :func:`~pennylane.state`. + .. warning:: + + The ``qml.qinfo.reduced_dm`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.density_matrix` measurement process in the return line of your QNode. + Args: tape (QuantumTape or QNode or Callable)): A quantum circuit returning :func:`~pennylane.state`. wires (Sequence(int)): List of wires in the considered subsystem. @@ -78,6 +82,14 @@ def measured_circuit(x): .. seealso:: :func:`pennylane.density_matrix` and :func:`pennylane.math.reduce_dm` """ + + warnings.warn( + "The qml.qinfo.reduced_dm transform is deprecated and will be removed " + "in v0.40. Instead include the qml.density_matrix measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + # device_wires is provided by the custom QNode transform all_wires = kwargs.get("device_wires", tape.wires) wire_map = {w: i for i, w in enumerate(all_wires)} @@ -137,6 +149,11 @@ def purity(tape: QuantumScript, wires, **kwargs) -> tuple[QuantumScriptBatch, Po It is possible to compute the purity of a sub-system from a given state. To find the purity of the overall state, include all wires in the ``wires`` argument. + .. warning:: + + The ``qml.qinfo.purity transform`` is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.purity` measurement process in the return line of your QNode. + Args: tape (QNode or QuantumTape or Callable): A quantum circuit object returning a :func:`~pennylane.state`. wires (Sequence(int)): List of wires in the considered subsystem. @@ -175,6 +192,14 @@ def circuit(x): .. seealso:: :func:`pennylane.math.purity` """ + + warnings.warn( + "The qml.qinfo.purity transform is deprecated and will be removed " + "in v0.40. Instead include the qml.purity measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + # device_wires is provided by the custom QNode transform all_wires = kwargs.get("device_wires", tape.wires) wire_map = {w: i for i, w in enumerate(all_wires)} @@ -229,6 +254,11 @@ def vn_entropy( .. math:: S( \rho ) = -\text{Tr}( \rho \log ( \rho )) + .. warning:: + + The ``qml.qinfo.vn_entropy`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.vn_entropy` measurement process in the return line of your QNode. + Args: tape (QNode or QuantumTape or Callable): A quantum circuit returning a :func:`~pennylane.state`. wires (Sequence(int)): List of wires in the considered subsystem. @@ -263,6 +293,14 @@ def circuit(x): .. seealso:: :func:`pennylane.math.vn_entropy` and :func:`pennylane.vn_entropy` """ + + warnings.warn( + "The qml.qinfo.vn_entropy transform is deprecated and will be removed " + "in v0.40. Instead include the qml.vn_entropy measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + # device_wires is provided by the custom QNode transform all_wires = kwargs.get("device_wires", tape.wires) wire_map = {w: i for i, w in enumerate(all_wires)} @@ -366,6 +404,11 @@ def mutual_info( More specifically, it quantifies the amount of information obtained about one system by measuring the other system. + .. warning:: + + The ``qml.qinfo.mutual_info`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.mutual_info` measurement process in the return line of your QNode. + Args: qnode (QNode or QuantumTape or Callable): A quantum circuit returning a :func:`~pennylane.state`. wires0 (Sequence(int)): List of wires in the first subsystem. @@ -403,6 +446,14 @@ def circuit(x): .. seealso:: :func:`~.qinfo.vn_entropy`, :func:`pennylane.math.mutual_info` and :func:`pennylane.mutual_info` """ + + warnings.warn( + "The qml.qinfo.mutual_info transform is deprecated and will be removed " + "in v0.40. Instead include the qml.mutual_info measurement process in the " + "return line of your QNode.", + qml.PennyLaneDeprecationWarning, + ) + return _bipartite_qinfo_transform(qml.math.mutual_info, tape, wires0, wires1, base, **kwargs) @@ -437,6 +488,10 @@ def vn_entanglement_entropy( where :math:`S` is the von Neumann entropy; :math:`\rho_A = \text{Tr}_B [\rho_{AB}]` and :math:`\rho_B = \text{Tr}_A [\rho_{AB}]` are the reduced density matrices for each partition. + .. warning:: + The ``qml.qinfo.vn_entanglement_entropy`` transform is deprecated and will be removed in v0.40. Instead include + the :func:`pennylane.vn_entanglement_entropy` measurement process in the return line of your QNode. + The Von Neumann entanglement entropy is a measure of the degree of quantum entanglement between two subsystems constituting a pure bipartite quantum state. The entropy of entanglement is the Von Neumann entropy of the reduced density matrix for any of the subsystems. If it is non-zero, @@ -455,297 +510,18 @@ def vn_entanglement_entropy( will provide the entanglement entropy in the form of a tensor. """ - return _bipartite_qinfo_transform( - qml.math.vn_entanglement_entropy, tape, wires0, wires1, base, **kwargs - ) - - -def classical_fisher(qnode, argnums=0): - r"""Returns a function that computes the classical fisher information matrix (CFIM) of a given :class:`.QNode` or - quantum tape. - - Given a parametrized (classical) probability distribution :math:`p(\bm{\theta})`, the classical fisher information - matrix quantifies how changes to the parameters :math:`\bm{\theta}` are reflected in the probability distribution. - For a parametrized quantum state, we apply the concept of classical fisher information to the computational - basis measurement. - More explicitly, this function implements eq. (15) in `arxiv:2103.15191 `_: - - .. math:: - - \text{CFIM}_{i, j} = \sum_{\ell=0}^{2^N-1} \frac{1}{p_\ell(\bm{\theta})} \frac{\partial p_\ell(\bm{\theta})}{ - \partial \theta_i} \frac{\partial p_\ell(\bm{\theta})}{\partial \theta_j} - - for :math:`N` qubits. - - Args: - tape (:class:`.QNode` or qml.QuantumTape): A :class:`.QNode` or quantum tape that may have arbitrary return types. - argnums (Optional[int or List[int]]): Arguments to be differentiated in case interface ``jax`` is used. - - Returns: - func: The function that computes the classical fisher information matrix. This function accepts the same - signature as the :class:`.QNode`. If the signature contains one differentiable variable ``params``, the function - returns a matrix of size ``(len(params), len(params))``. For multiple differentiable arguments ``x, y, z``, - it returns a list of sizes ``[(len(x), len(x)), (len(y), len(y)), (len(z), len(z))]``. - - .. warning:: - ``pennylane.qinfo.classical_fisher`` is being migrated to a different module and will - removed in version 0.39. Instead, use :func:`pennylane.gradients.classical_fisher`. - - .. seealso:: :func:`~.pennylane.metric_tensor`, :func:`~.pennylane.qinfo.transforms.quantum_fisher` - - **Example** - - First, let us define a parametrized quantum state and return its (classical) probability distribution for all - computational basis elements: - - .. code-block:: python - - import pennylane.numpy as pnp - - dev = qml.device("default.qubit") - - @qml.qnode(dev) - def circ(params): - qml.RX(params[0], wires=0) - qml.CNOT([0, 1]) - qml.CRY(params[1], wires=[1, 0]) - qml.Hadamard(1) - return qml.probs(wires=[0, 1]) - - Executing this circuit yields the ``2**2=4`` elements of :math:`p_\ell(\bm{\theta})` - - >>> pnp.random.seed(25) - >>> params = pnp.random.random(2) - >>> circ(params) - [0.41850088 0.41850088 0.08149912 0.08149912] - - We can obtain its ``(2, 2)`` classical fisher information matrix (CFIM) by simply calling the function returned - by ``classical_fisher()``: - - >>> cfim_func = qml.qinfo.classical_fisher(circ) - >>> cfim_func(params) - [[ 0.901561 -0.125558] - [-0.125558 0.017486]] - - This function has the same signature as the :class:`.QNode`. Here is a small example with multiple arguments: - - .. code-block:: python - - @qml.qnode(dev) - def circ(x, y): - qml.RX(x, wires=0) - qml.RY(y, wires=0) - return qml.probs(wires=range(n_wires)) - - >>> x, y = pnp.array([0.5, 0.6], requires_grad=True) - >>> circ(x, y) - [0.86215007 0. 0.13784993 0. ] - >>> qml.qinfo.classical_fisher(circ)(x, y) - [array([[0.32934729]]), array([[0.51650396]])] - - Note how in the case of multiple variables we get a list of matrices with sizes - ``[(n_params0, n_params0), (n_params1, n_params1)]``, which in this case is simply two ``(1, 1)`` matrices. - - - A typical setting where the classical fisher information matrix is used is in variational quantum algorithms. - Closely related to the `quantum natural gradient `_, which employs the - `quantum` fisher information matrix, we can compute a rescaled gradient using the CFIM. In this scenario, - typically a Hamiltonian objective function :math:`\langle H \rangle` is minimized: - - .. code-block:: python - - H = qml.Hamiltonian(coeffs=[0.5, 0.5], observables=[qml.Z(0), qml.Z(1)]) - - @qml.qnode(dev) - def circ(params): - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=0) - qml.RX(params[2], wires=1) - qml.RY(params[3], wires=1) - qml.CNOT(wires=(0,1)) - return qml.expval(H) - - params = pnp.random.random(4) - - We can compute both the gradient of :math:`\langle H \rangle` and the CFIM with the same :class:`.QNode` ``circ`` - in this example since ``classical_fisher()`` ignores the return types and assumes ``qml.probs()`` for all wires. - >>> grad = qml.grad(circ)(params) - >>> cfim = qml.qinfo.classical_fisher(circ)(params) - >>> print(grad.shape, cfim.shape) - (4,) (4, 4) - - Combined together, we can get a rescaled gradient to be employed for optimization schemes like natural gradient - descent. - - >>> rescaled_grad = cfim @ grad - >>> print(rescaled_grad) - [-0.66772533 -0.16618756 -0.05865127 -0.06696078] - - The ``classical_fisher`` matrix itself is again differentiable: - - .. code-block:: python - - @qml.qnode(dev) - def circ(params): - qml.RX(qml.math.cos(params[0]), wires=0) - qml.RX(qml.math.cos(params[0]), wires=1) - qml.RX(qml.math.cos(params[1]), wires=0) - qml.RX(qml.math.cos(params[1]), wires=1) - return qml.probs(wires=range(2)) - - params = pnp.random.random(2) - - >>> qml.qinfo.classical_fisher(circ)(params) - [[4.18575068e-06 2.34443943e-03] - [2.34443943e-03 1.31312079e+00]] - >>> qml.jacobian(qml.qinfo.classical_fisher(circ))(params) - array([[[9.98030491e-01, 3.46944695e-18], - [1.36541817e-01, 5.15248592e-01]], - [[1.36541817e-01, 5.15248592e-01], - [2.16840434e-18, 2.81967252e-01]]])) - - """ warnings.warn( - "pennylane.qinfo.classical_fisher is being migrated to a different module and will " - "removed in version 0.39. Instead, use pennylane.gradients.classical_fisher.", + "The qml.qinfo.vn_entanglement_entropy transform is deprecated and will be removed " + "in v0.40. Instead include the qml.vn_entanglement_entropy measurement process in the " + "return line of your QNode.", qml.PennyLaneDeprecationWarning, ) - return qml.gradients.classical_fisher(qnode, argnums=argnums) - - -@partial(transform, is_informative=True) -def quantum_fisher( - tape: QuantumScript, device, *args, **kwargs -) -> tuple[QuantumScriptBatch, PostprocessingFn]: - r"""Returns a function that computes the quantum fisher information matrix (QFIM) of a given :class:`.QNode`. - - Given a parametrized quantum state :math:`|\psi(\bm{\theta})\rangle`, the quantum fisher information matrix (QFIM) quantifies how changes to the parameters :math:`\bm{\theta}` - are reflected in the quantum state. The metric used to induce the QFIM is the fidelity :math:`f = |\langle \psi | \psi' \rangle|^2` between two (pure) quantum states. - This leads to the following definition of the QFIM (see eq. (27) in `arxiv:2103.15191 `_): - - .. math:: - - \text{QFIM}_{i, j} = 4 \text{Re}\left[ \langle \partial_i \psi(\bm{\theta}) | \partial_j \psi(\bm{\theta}) \rangle - - \langle \partial_i \psi(\bm{\theta}) | \psi(\bm{\theta}) \rangle \langle \psi(\bm{\theta}) | \partial_j \psi(\bm{\theta}) \rangle \right] - - with short notation :math:`| \partial_j \psi(\bm{\theta}) \rangle := \frac{\partial}{\partial \theta_j}| \psi(\bm{\theta}) \rangle`. - - .. seealso:: - :func:`~.pennylane.metric_tensor`, :func:`~.pennylane.adjoint_metric_tensor`, :func:`~.pennylane.qinfo.transforms.classical_fisher` - - Args: - tape (QNode or QuantumTape or Callable): A quantum circuit that may have arbitrary return types. - *args: In case finite shots are used, further arguments according to :func:`~.pennylane.metric_tensor` may be passed. - - Returns: - qnode (QNode) or quantum function (Callable) or tuple[List[QuantumTape], function]: - - The transformed circuit as described in :func:`qml.transform `. Executing this circuit - will provide the quantum Fisher information in the form of a tensor. - - .. warning:: - ``pennylane.qinfo.quantum_fisher`` is being migrated to a different module and will - removed in version 0.39. Instead, use :func:`pennylane.gradients.quantum_fisher`. - - .. note:: - - ``quantum_fisher`` coincides with the ``metric_tensor`` with a prefactor of :math:`4`. - Internally, :func:`~.pennylane.adjoint_metric_tensor` is used when executing on ``"default.qubit"`` - with exact expectations (``shots=None``). In all other cases, e.g. if a device with finite shots - is used, the hardware-compatible transform :func:`~.pennylane.metric_tensor` is used, which - may require an additional wire on the device. - Please refer to the respective documentations for details. - - **Example** - - The quantum Fisher information matrix (QIFM) can be used to compute the `natural` gradient for `Quantum Natural Gradient Descent `_. - A typical scenario is optimizing the expectation value of a Hamiltonian: - - .. code-block:: python - - n_wires = 2 - - dev = qml.device("default.qubit", wires=n_wires) - - H = 1.*qml.X(0) @ qml.X(1) - 0.5 * qml.Z(1) - - @qml.qnode(dev) - def circ(params): - qml.RY(params[0], wires=1) - qml.CNOT(wires=(1,0)) - qml.RY(params[1], wires=1) - qml.RZ(params[2], wires=1) - return qml.expval(H) - - params = pnp.array([0.5, 1., 0.2], requires_grad=True) - - The natural gradient is then simply the QFIM multiplied by the gradient: - - >>> grad = qml.grad(circ)(params) - >>> grad - [ 0.59422561 -0.02615095 -0.05146226] - >>> qfim = qml.qinfo.quantum_fisher(circ)(params) - >>> qfim - [[1. 0. 0. ] - [0. 1. 0. ] - [0. 0. 0.77517241]] - >>> qfim @ grad - tensor([ 0.59422561, -0.02615095, -0.03989212], requires_grad=True) - - When using real hardware or finite shots, ``quantum_fisher`` is internally calling :func:`~.pennylane.metric_tensor`. - To obtain the full QFIM, we need an auxilary wire to perform the Hadamard test. - - >>> dev = qml.device("default.qubit", wires=n_wires+1, shots=1000) - >>> @qml.qnode(dev) - ... def circ(params): - ... qml.RY(params[0], wires=1) - ... qml.CNOT(wires=(1,0)) - ... qml.RY(params[1], wires=1) - ... qml.RZ(params[2], wires=1) - ... return qml.expval(H) - >>> qfim = qml.qinfo.quantum_fisher(circ)(params) - - Alternatively, we can fall back on the block-diagonal QFIM without the additional wire. - - >>> qfim = qml.qinfo.quantum_fisher(circ, approx="block-diag")(params) - - """ - warnings.warn( - "pennylane.qinfo.quantum_fisher is being migrated to a different module and will " - "removed in version 0.39. Instead, use pennylane.gradients.quantum_fisher.", - qml.PennyLaneDeprecationWarning, + return _bipartite_qinfo_transform( + qml.math.vn_entanglement_entropy, tape, wires0, wires1, base, **kwargs ) - if device.shots or not isinstance(device, (DefaultQubitLegacy, DefaultQubit)): - tapes, processing_fn = metric_tensor(tape, *args, **kwargs) - - def processing_fn_multiply(res): - res = qml.execute(res, device=device) - return 4 * processing_fn(res) - - return tapes, processing_fn_multiply - - res = adjoint_metric_tensor(tape, *args, **kwargs) - - def processing_fn_multiply(r): # pylint: disable=function-redefined - r = qml.math.stack(r) - return 4 * r - - return res, processing_fn_multiply - - -@quantum_fisher.custom_qnode_transform -def qnode_execution_wrapper(self, qnode, targs, tkwargs): - """Here, we overwrite the QNode execution wrapper in order - to take into account that classical processing may be present - inside the QNode.""" - - tkwargs["device"] = qnode.device - - return self.default_qnode_transform(qnode, targs, tkwargs) - def fidelity(qnode0, qnode1, wires0, wires1): r"""Compute the fidelity for two :class:`.QNode` returning a :func:`~pennylane.state` (a state can be a state vector @@ -773,6 +549,11 @@ def fidelity(qnode0, qnode1, wires0, wires1): The second state is coerced to the type and dtype of the first state. The fidelity is returned in the type of the interface of the first state. + .. warning:: + + ``qml.qinfo.fidelity`` is deprecated and will be removed in v0.40. Instead, use + :func:`pennylane.math.fidelity`. + Args: state0 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. state1 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. @@ -877,9 +658,21 @@ def circuit_ry(y, use_ry): .. seealso:: :func:`pennylane.math.fidelity` """ + warnings.warn( + "qml.qinfo.fidelity is deprecated and will be removed in v0.40. Instead, use " + "qml.math.fidelity.", + qml.PennyLaneDeprecationWarning, + ) + if len(wires0) != len(wires1): raise qml.QuantumFunctionError("The two states must have the same number of wires.") + # with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="The qml.qinfo.reduced_dm transform", + category=qml.PennyLaneDeprecationWarning, + ) state_qnode0 = qml.qinfo.reduced_dm(qnode0, wires=wires0) state_qnode1 = qml.qinfo.reduced_dm(qnode1, wires=wires1) @@ -947,6 +740,11 @@ def relative_entropy(qnode0, qnode1, wires0, wires1): Roughly speaking, quantum relative entropy is a measure of distinguishability between two quantum states. It is the quantum mechanical analog of relative entropy. + .. warning:: + + ``qml.qinfo.relative_entropy`` is deprecated and will be removed in v0.40. Instead, use + :func:`~pennylane.math.relative_entropy`. + Args: qnode0 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. qnode1 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. @@ -997,9 +795,21 @@ def wrapper(x, y): tensor(0.16953273, requires_grad=True)) """ + warnings.warn( + "qml.qinfo.relative_entropy is deprecated and will be removed in v0.40. Instead, use " + "qml.math.relative_entropy.", + qml.PennyLaneDeprecationWarning, + ) + if len(wires0) != len(wires1): raise qml.QuantumFunctionError("The two states must have the same number of wires.") + # with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="The qml.qinfo.reduced_dm transform", + category=qml.PennyLaneDeprecationWarning, + ) state_qnode0 = qml.qinfo.reduced_dm(qnode0, wires=wires0) state_qnode1 = qml.qinfo.reduced_dm(qnode1, wires=wires1) @@ -1068,6 +878,11 @@ def trace_distance(qnode0, qnode1, wires0, wires1): The trace distance measures how close two quantum states are. In particular, it upper-bounds the probability of distinguishing two quantum states. + .. warning:: + + ``qml.qinfo.trace_distance`` is deprecated and will be removed in v0.40. Instead, use + :func:`~pennylane.math.trace_distance`. + Args: qnode0 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. qnode1 (QNode): A :class:`.QNode` returning a :func:`~pennylane.state`. @@ -1118,9 +933,21 @@ def wrapper(x, y): tensor(0.28232124, requires_grad=True)) """ + warnings.warn( + "qml.qinfo.trace_distance is deprecated and will be removed in v0.40. Instead, use " + "qml.math.trace_distance.", + qml.PennyLaneDeprecationWarning, + ) + if len(wires0) != len(wires1): raise qml.QuantumFunctionError("The two states must have the same number of wires.") + # with warnings.catch_warnings(): + warnings.filterwarnings( + action="ignore", + message="The qml.qinfo.reduced_dm transform", + category=qml.PennyLaneDeprecationWarning, + ) state_qnode0 = qml.qinfo.reduced_dm(qnode0, wires=wires0) state_qnode1 = qml.qinfo.reduced_dm(qnode1, wires=wires1) diff --git a/tests/devices/test_default_mixed.py b/tests/devices/test_default_mixed.py index a6bf45665a4..86cdbf02430 100644 --- a/tests/devices/test_default_mixed.py +++ b/tests/devices/test_default_mixed.py @@ -1203,13 +1203,14 @@ def circuit(): @pytest.mark.parametrize("prob", [0, 0.5, 1]) @pytest.mark.parametrize("nr_wires", [2, 3]) def test_readout_vnentropy_and_mutualinfo(self, nr_wires, prob): - """Tests the output of qml.vn_entropy and qml.mutual_info is not affected by readout error""" + """Tests the output of qml.vn_entropy and qml.mutual_info are not affected by readout error""" dev = qml.device("default.mixed", wires=nr_wires, readout_prob=prob) @qml.qnode(dev) def circuit(): - return qml.vn_entropy(wires=0, log_base=2), qml.mutual_info( - wires0=[0], wires1=[1], log_base=2 + return ( + qml.vn_entropy(wires=0, log_base=2), + qml.mutual_info(wires0=[0], wires1=[1], log_base=2), ) res = circuit() diff --git a/tests/measurements/test_mutual_info.py b/tests/measurements/test_mutual_info.py index a47284f744b..fa0ab30b83d 100644 --- a/tests/measurements/test_mutual_info.py +++ b/tests/measurements/test_mutual_info.py @@ -22,6 +22,12 @@ from pennylane.measurements.mutual_info import MutualInfoMP from pennylane.wires import Wires +DEP_WARNING_MESSAGE_MUTUAL_INFO = ( + "The qml.qinfo.mutual_info transform is deprecated and will be removed " + "in v0.40. Instead include the qml.mutual_info measurement process in the " + "return line of your QNode." +) + class TestMutualInfoUnitTests: """Tests for the mutual_info function""" @@ -143,7 +149,11 @@ def circuit(params): qml.CNOT(wires=[0, 1]) return qml.state() - actual = qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1])(params) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + actual = qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1])(params) # compare transform results with analytic values expected = -2 * np.cos(params / 2) ** 2 * np.log( @@ -175,12 +185,13 @@ def circuit_state(params): qml.RY(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) - return qml.state() + return qml.density_matrix(wires=[0, 1]) actual = circuit_mutual_info(params) # compare measurement results with transform results - expected = qml.qinfo.mutual_info(circuit_state, wires0=[0], wires1=[1])(params) + dm = circuit_state(params) + expected = qml.math.mutual_info(dm, indices0=[0], indices1=[1]) assert np.allclose(actual, expected) @@ -197,7 +208,11 @@ def circuit(param): qml.CNOT(wires=wires) return qml.state() - actual = qml.qinfo.mutual_info(circuit, wires0=[wires[0]], wires1=[wires[1]])(param) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + actual = qml.qinfo.mutual_info(circuit, wires0=[wires[0]], wires1=[wires[1]])(param) # compare transform results with analytic values expected = -2 * np.cos(param / 2) ** 2 * np.log(np.cos(param / 2) ** 2) - 2 * np.sin( @@ -236,7 +251,11 @@ def circuit(params): transformed_circuit = qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1]) with pytest.raises(ValueError, match="The qfunc return type needs to be a state"): - _ = transformed_circuit(0.1) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + _ = transformed_circuit(0.1) @pytest.mark.jax @pytest.mark.parametrize("params", np.linspace(0, 2 * np.pi, 8)) @@ -256,7 +275,11 @@ def circuit(params): qml.CNOT(wires=[0, 1]) return qml.state() - actual = jax.jit(qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1]))(params) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=DEP_WARNING_MESSAGE_MUTUAL_INFO, + ): + actual = jax.jit(qml.qinfo.mutual_info(circuit, wires0=[0], wires1=[1]))(params) # compare transform results with analytic values expected = -2 * jnp.cos(params / 2) ** 2 * jnp.log( @@ -290,12 +313,13 @@ def circuit_state(params): qml.RY(params[0], wires=0) qml.RY(params[1], wires=1) qml.CNOT(wires=[0, 1]) - return qml.state() + return qml.density_matrix(wires=[0, 1]) actual = jax.jit(circuit_mutual_info)(params) # compare measurement results with transform results - expected = jax.jit(qml.qinfo.mutual_info(circuit_state, wires0=[0], wires1=[1]))(params) + dm = circuit_state(params) + expected = qml.math.mutual_info(dm, indices0=[0], indices1=[1]) assert np.allclose(actual, expected) @@ -513,8 +537,11 @@ def circuit_expected(params): qml.RY(params[0], wires="a") qml.RY(params[1], wires="b") qml.CNOT(wires=["a", "b"]) - return qml.state() + return qml.density_matrix(wires=["a", "b"]) actual = circuit(params) - expected = qml.qinfo.mutual_info(circuit_expected, wires0=["a"], wires1=["b"])(params) + + dm = circuit_expected(params) + expected = qml.math.mutual_info(dm, indices0=[0], indices1=[1]) + assert np.allclose(actual, expected) diff --git a/tests/qinfo/test_entropies.py b/tests/qinfo/test_entropies.py index 92b8e48971b..c275036a58e 100644 --- a/tests/qinfo/test_entropies.py +++ b/tests/qinfo/test_entropies.py @@ -19,6 +19,15 @@ import pennylane as qml from pennylane import numpy as np +pytestmark = [ + pytest.mark.filterwarnings( + r"ignore:The qml\.qinfo\.(vn_entropy|mutual_info|reduced_dm) transform:pennylane.PennyLaneDeprecationWarning" + ), + pytest.mark.filterwarnings( + "ignore:qml.qinfo.relative_entropy is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] + def expected_entropy_ising_xx(param): """ @@ -70,6 +79,21 @@ class TestVonNeumannEntropy: parameters = np.linspace(0, 2 * np.pi, 10) devices = ["default.qubit", "default.mixed", "lightning.qubit"] + def test_qinfo_vn_entropy_deprecated(self): + """Test that qinfo.vn_entropy is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.vn_entropy transform is deprecated", + ): + _ = qml.qinfo.vn_entropy(circuit, [0])() + def test_vn_entropy_cannot_specify_device(self): """Test that an error is raised if a device or device wires are given to the vn_entropy transform manually.""" @@ -102,7 +126,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -124,7 +147,6 @@ def circuit_state(x): return qml.state() grad_entropy = qml.grad(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base))(param) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy) @@ -148,7 +170,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(torch.tensor(param)) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -176,8 +197,8 @@ def circuit_state(x): grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) - entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) + entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) entropy.backward() grad_entropy = param.grad @@ -203,7 +224,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(tf.Variable(param)) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -229,7 +249,6 @@ def circuit_state(x): entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(param) grad_entropy = tape.gradient(entropy, param) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy) @@ -254,7 +273,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)(jnp.array(param)) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -278,7 +296,6 @@ def circuit_state(x): grad_entropy = jax.grad(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base))( jax.numpy.array(param) ) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy, rtol=1e-04, atol=1e-05) @@ -305,7 +322,6 @@ def circuit_state(x): entropy = jax.jit(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base))( jnp.array(param) ) - expected_entropy = expected_entropy_ising_xx(param) / np.log(base) assert qml.math.allclose(entropy, expected_entropy) @@ -329,7 +345,6 @@ def circuit_state(x): grad_entropy = jax.jit( jax.grad(qml.qinfo.vn_entropy(circuit_state, wires=wires, base=base)) )(jax.numpy.array(param)) - grad_expected_entropy = expected_entropy_grad_ising_xx(param) / np.log(base) assert qml.math.allclose(grad_entropy, grad_expected_entropy, rtol=1e-04, atol=1e-05) @@ -361,7 +376,6 @@ def circuit_state(x): return qml.state() entropy = qml.qinfo.vn_entropy(circuit_state, wires=[0, 1])(param) - expected_entropy = 0.0 assert qml.math.allclose(entropy, expected_entropy) @@ -377,7 +391,6 @@ def circuit_state(x): entropy = qml.qinfo.vn_entropy(circuit_state, wires=[0, 1])(param) expected_entropy = 0.0 - assert qml.math.allclose(entropy, expected_entropy) @pytest.mark.parametrize("device", devices) @@ -394,10 +407,12 @@ def circuit(x): return qml.state() entropy0 = qml.qinfo.vn_entropy(circuit, wires=[wires[0]])(param) + eigs0 = [np.sin(param / 2) ** 2, np.cos(param / 2) ** 2] exp0 = -np.sum(eigs0 * np.log(eigs0)) entropy1 = qml.qinfo.vn_entropy(circuit, wires=[wires[1]])(param) + eigs1 = [np.cos(param / 2) ** 2, np.sin(param / 2) ** 2] exp1 = -np.sum(eigs1 * np.log(eigs1)) @@ -406,7 +421,7 @@ def circuit(x): class TestRelativeEntropy: - """Tests for the mutual information functions""" + """Tests for the relative entropy information functions""" diff_methods = ["backprop", "finite-diff"] @@ -415,6 +430,25 @@ class TestRelativeEntropy: # to avoid nan values in the gradient for relative entropy grad_params = [[0.123, 0.456], [0.789, 1.618]] + def test_qinfo_relative_entropy_deprecated(self): + """Test that qinfo.relative_entropy is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(param): + qml.RY(param, wires=0) + qml.CNOT(wires=[0, 1]) + return qml.state() + + x, y = 0.4, 0.6 + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="qml.qinfo.relative_entropy is deprecated", + ): + _ = qml.qinfo.relative_entropy(circuit, circuit, wires0=[0], wires1=[0])((x,), (y,)) + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "default.mixed", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @@ -464,7 +498,7 @@ def circuit2(param): @pytest.mark.parametrize("param", params) @pytest.mark.parametrize("interface", interfaces) def test_qnode_relative_entropy_jax_jit(self, param, interface): - """Test that the mutual information transform works for QNodes by comparing + """Test that the relative entropy transform works for QNodes by comparing against analytic values, for the JAX-jit interface""" import jax import jax.numpy as jnp @@ -660,6 +694,7 @@ def circuit2(param): ] param0, param1 = tf.Variable(param[0]), tf.Variable(param[1]) + with tf.GradientTape() as tape: out = qml.qinfo.relative_entropy(circuit1, circuit2, [0], [1])((param0,), (param1,)) @@ -701,9 +736,9 @@ def circuit2(param): param0 = torch.tensor(param[0], requires_grad=True) param1 = torch.tensor(param[1], requires_grad=True) + out = qml.qinfo.relative_entropy(circuit1, circuit2, [0], [1])((param0,), (param1,)) out.backward() - actual = [param0.grad, param1.grad] assert np.allclose(actual, expected, atol=1e-8) @@ -748,7 +783,6 @@ def circuit2(param): return qml.state() rel_ent_circuit = qml.qinfo.relative_entropy(circuit1, circuit2, [0], [0]) - x, y = np.array(0.3), np.array(0.7) # test that the circuit executes @@ -878,6 +912,7 @@ def circuit_state(x): x = np.array([0.4, 0.6, 0.8]) y = np.array([0.6, 0.8, 1.0]) + entropy = qml.qinfo.relative_entropy(circuit_state, circuit_state, wires0=[0], wires1=[1])( x, y ) diff --git a/tests/qinfo/test_fidelity.py b/tests/qinfo/test_fidelity.py index 90c59386d00..5ace7162908 100644 --- a/tests/qinfo/test_fidelity.py +++ b/tests/qinfo/test_fidelity.py @@ -18,6 +18,10 @@ import pennylane as qml from pennylane import numpy as np +pytestmark = pytest.mark.filterwarnings( + "ignore:qml.qinfo.fidelity is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def expected_fidelity_rx_pauliz(param): """Return the analytical fidelity for the RX and PauliZ.""" @@ -34,6 +38,21 @@ class TestFidelityQnode: devices = ["default.qubit", "lightning.qubit", "default.mixed"] + def test_qinfo_transform_deprecated(self): + """Test that qinfo.fidelity is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="qml.qinfo.fidelity is deprecated", + ): + _ = qml.qinfo.fidelity(circuit, circuit, wires0=[0], wires1=[1])() + @pytest.mark.parametrize("device", devices) def test_not_same_number_wires(self, device): """Test that wires must have the same length.""" @@ -120,7 +139,7 @@ def circuit1(x): qml.RX(x, wires=0) return qml.state() - fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(all_args1=(np.pi)) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(all_args1=np.pi) assert qml.math.allclose(fid, 0.0) @pytest.mark.parametrize("device", devices) @@ -266,6 +285,7 @@ def circuit(x): (qml.numpy.array(param, requires_grad=True)), (qml.numpy.array(2 * param, requires_grad=True)), ) + expected = expected_grad_fidelity_rx_pauliz(param) expected_fid = [-expected, expected] assert qml.math.allclose(fid_grad, expected_fid) @@ -319,6 +339,7 @@ def circuit1(): expected_fid_grad = expected_grad_fidelity_rx_pauliz(param) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])((param)) fid.backward() fid_grad = param.grad @@ -347,6 +368,7 @@ def circuit1(x): expected_fid = expected_grad_fidelity_rx_pauliz(param) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(None, (param)) fid.backward() fid_grad = param.grad @@ -374,6 +396,7 @@ def circuit(x): torch.tensor(param, dtype=torch.float64, requires_grad=True), torch.tensor(2 * param, dtype=torch.float64, requires_grad=True), ) + fid = qml.qinfo.fidelity(circuit, circuit, wires0=[0], wires1=[0])(*params) fid.backward() fid_grad = [p.grad for p in params] @@ -428,9 +451,9 @@ def circuit1(): expected_grad_fid = expected_grad_fidelity_rx_pauliz(param) param = tf.Variable(param) + with tf.GradientTape() as tape: entropy = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])((param)) - fid_grad = tape.gradient(entropy, param) assert qml.math.allclose(fid_grad, expected_grad_fid) @@ -456,9 +479,9 @@ def circuit1(x): expected_fid = expected_grad_fidelity_rx_pauliz(param) param = tf.Variable(param) + with tf.GradientTape() as tape: entropy = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])(None, (param)) - fid_grad = tape.gradient(entropy, param) assert qml.math.allclose(fid_grad, expected_fid) @@ -480,9 +503,9 @@ def circuit(x): expected = expected_grad_fidelity_rx_pauliz(param) expected_fid = [-expected, expected] params = (tf.Variable(param), tf.Variable(2 * param)) + with tf.GradientTape() as tape: fid = qml.qinfo.fidelity(circuit, circuit, wires0=[0], wires1=[0])(*params) - fid_grad = tape.gradient(fid, params) assert qml.math.allclose(fid_grad, expected_fid) @@ -712,7 +735,8 @@ def circuit1(x): return qml.state() fid_grad = qml.grad(qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0]))( - (qml.numpy.array(param, requires_grad=True)), (qml.numpy.array(2.0, requires_grad=True)) + (qml.numpy.array(param, requires_grad=True)), + (qml.numpy.array(2.0, requires_grad=True)), ) expected_fid_grad = expected_grad_fidelity_rx_pauliz(param) assert qml.math.allclose(fid_grad, (expected_fid_grad, 0.0)) @@ -744,6 +768,7 @@ def circuit1(x): expected_fid_grad = expected_grad_fidelity_rx_pauliz(param) param = torch.tensor(param, dtype=torch.float64, requires_grad=True) param2 = torch.tensor(0, dtype=torch.float64, requires_grad=True) + fid = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])((param), (param2)) fid.backward() fid_grad = (param.grad, param2.grad) @@ -777,11 +802,11 @@ def circuit1(x): param1 = tf.Variable(param) params2 = tf.Variable(0.0) + with tf.GradientTape() as tape: entropy = qml.qinfo.fidelity(circuit0, circuit1, wires0=[0], wires1=[0])( (param1), (params2) ) - fid_grad = tape.gradient(entropy, [param1, params2]) assert qml.math.allclose(fid_grad, (expected_fid_grad, 0.0)) @@ -858,7 +883,7 @@ def circuit_state(x): x = np.array([0.4, 0.6, 0.8]) y = np.array([0.6, 0.8, 1.0]) - fid = qml.qinfo.fidelity(circuit_state, circuit_state, wires0=[0], wires1=[1])(x, y) + fid = qml.qinfo.fidelity(circuit_state, circuit_state, wires0=[0], wires1=[1])(x, y) expected = 0.5 * (np.sin(x) * np.sin(y) + np.cos(x) * np.cos(y) + 1) assert qml.math.allclose(fid, expected) diff --git a/tests/qinfo/test_fisher_deprecation.py b/tests/qinfo/test_fisher_deprecation.py deleted file mode 100644 index b5412f56a82..00000000000 --- a/tests/qinfo/test_fisher_deprecation.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Tests for the deprecation of the classical and quantum fisher information matrix in the pennylane.qinfo -""" -import pytest - -import pennylane as qml -import pennylane.numpy as pnp -from pennylane.qinfo import classical_fisher, quantum_fisher - - -@pytest.mark.parametrize("fn", (classical_fisher, quantum_fisher)) -def test_qinfo_fisher_fns_raises_warning(fn): - n_wires = 3 - n_params = 3 - - dev = qml.device("default.qubit", shots=10000) - - @qml.qnode(dev) - def circ(params): - for i in range(n_wires): - qml.Hadamard(wires=i) - - for x in params: - for j in range(n_wires): - qml.RX(x, wires=j) - qml.RY(x, wires=j) - qml.RZ(x, wires=j) - - return qml.probs(wires=range(n_wires)) - - params = pnp.zeros(n_params, requires_grad=True) - - with pytest.warns(qml.PennyLaneDeprecationWarning, match=f"{fn.__name__} is being migrated"): - fn(circ)(params) diff --git a/tests/qinfo/test_purity.py b/tests/qinfo/test_purity.py index d59f140a89d..8a96815fddf 100644 --- a/tests/qinfo/test_purity.py +++ b/tests/qinfo/test_purity.py @@ -12,12 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for purities.""" + # pylint: disable=too-many-arguments import pytest import pennylane as qml from pennylane import numpy as np +pytestmark = pytest.mark.filterwarnings( + "ignore:The qml.qinfo.purity transform is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def expected_purity_ising_xx(param): """Returns the analytical purity for subsystems of the IsingXX""" @@ -60,6 +65,21 @@ class TestPurity: wires_list = [([0], True), ([1], True), ([0, 1], False)] + def test_qinfo_purity_deprecated(self): + """Test that qinfo.purity is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.purity transform is deprecated", + ): + _ = qml.qinfo.purity(circuit, [0])() + def test_purity_cannot_specify_device(self): """Test that an error is raised if a device or device wires are given to the purity transform manually.""" @@ -333,6 +353,7 @@ def circuit_state(x): expected_grad = expected_purity_grad_ising_xx(param) if is_partial else 0 param = torch.tensor(param, dtype=torch.float64, requires_grad=True) + purity = qml.qinfo.purity(circuit_state, wires=wires)(param) purity.backward() grad_purity = param.grad @@ -385,6 +406,7 @@ def circuit_state(x): grad_expected_purity = expected_purity_grad_ising_xx(param) if is_partial else 0 param = tf.Variable(param) + with tf.GradientTape() as tape: purity = qml.qinfo.purity(circuit_state, wires=wires)(param) @@ -407,6 +429,7 @@ def circuit_state(x): purity0 = qml.qinfo.purity(circuit_state, wires=[wires[0]])(param) purity1 = qml.qinfo.purity(circuit_state, wires=[wires[1]])(param) + expected = expected_purity_ising_xx(param) assert qml.math.allclose(purity0, expected, atol=tol) @@ -425,6 +448,5 @@ def circuit_state(x): x = np.array([0.4, 0.6, 0.8]) purity = qml.qinfo.purity(circuit_state, wires=[0])(x) - expected = expected_purity_ising_xx(x) assert qml.math.allclose(purity, expected) diff --git a/tests/qinfo/test_reduced_dm.py b/tests/qinfo/test_reduced_dm.py index 90f2fa271b8..0efc59cf1a1 100644 --- a/tests/qinfo/test_reduced_dm.py +++ b/tests/qinfo/test_reduced_dm.py @@ -18,7 +18,12 @@ import pennylane as qml from pennylane import numpy as np -pytestmark = pytest.mark.all_interfaces +pytestmark = [ + pytest.mark.all_interfaces, + pytest.mark.filterwarnings( + "ignore:The qml.qinfo.reduced_dm transform is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] tf = pytest.importorskip("tensorflow", minversion="2.1") torch = pytest.importorskip("torch") @@ -42,6 +47,21 @@ class TestDensityMatrixQNode: """Tests for the (reduced) density matrix for QNodes returning states.""" + def test_qinfo_transform_deprecated(self): + """Test that qinfo.reduced_dm is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.reduced_dm transform is deprecated", + ): + _ = qml.qinfo.reduced_dm(circuit, [0])() + def test_reduced_dm_cannot_specify_device(self): """Test that an error is raised if a device or device wires are given to the reduced_dm transform manually.""" @@ -165,6 +185,7 @@ def circuit(x): jit_compile=True, input_signature=(tf.TensorSpec(shape=(), dtype=tf.float32),), ) + density_matrix = density_matrix(tf.Variable(0.0, dtype=tf.float32)) assert np.allclose(density_matrix, [[1, 0], [0, 0]]) diff --git a/tests/qinfo/test_trace_distance.py b/tests/qinfo/test_trace_distance.py index 892000a9737..f90411c51fa 100644 --- a/tests/qinfo/test_trace_distance.py +++ b/tests/qinfo/test_trace_distance.py @@ -18,7 +18,12 @@ import pennylane as qml from pennylane import numpy as np -pytestmark = pytest.mark.all_interfaces +pytestmark = [ + pytest.mark.all_interfaces, + pytest.mark.filterwarnings( + "ignore:qml.qinfo.trace_distance is deprecated:pennylane.PennyLaneDeprecationWarning" + ), +] tf = pytest.importorskip("tensorflow", minversion="2.1") torch = pytest.importorskip("torch") @@ -41,6 +46,21 @@ class TestTraceDistanceQnode: devices = ["default.qubit", "lightning.qubit", "default.mixed"] + def test_qinfo_transform_deprecated(self): + """Test that qinfo.trace_distance is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="qml.qinfo.trace_distance is deprecated", + ): + _ = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[1])() + @pytest.mark.parametrize("device", devices) def test_not_same_number_wires(self, device): """Test that wires must have the same length.""" diff --git a/tests/qinfo/test_vn_entanglement_entropy.py b/tests/qinfo/test_vn_entanglement_entropy_qinfo.py similarity index 92% rename from tests/qinfo/test_vn_entanglement_entropy.py rename to tests/qinfo/test_vn_entanglement_entropy_qinfo.py index b160c228a8b..ca4927ff4ea 100644 --- a/tests/qinfo/test_vn_entanglement_entropy.py +++ b/tests/qinfo/test_vn_entanglement_entropy_qinfo.py @@ -20,10 +20,29 @@ import pennylane as qml +pytestmark = pytest.mark.filterwarnings( + "ignore:The qml.qinfo.vn_entanglement_entropy transform is deprecated:pennylane.PennyLaneDeprecationWarning" +) + class TestVnEntanglementEntropy: """Tests for the vn entanglement entropy transform""" + def test_qinfo_transform_deprecated(self): + """Test that vn_entanglement_entropy is deprecated.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + return qml.state() + + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="The qml.qinfo.vn_entanglement_entropy transform is deprecated", + ): + _ = qml.qinfo.vn_entanglement_entropy(circuit, [0], [1])() + @pytest.mark.all_interfaces @pytest.mark.parametrize("device", ["default.qubit", "lightning.qubit"]) @pytest.mark.parametrize("interface", ["autograd", "jax", "tensorflow", "torch"]) @@ -175,6 +194,7 @@ def circuit(theta): ) params = torch.tensor(params, dtype=torch.float64, requires_grad=True) + entropy = qml.qinfo.vn_entanglement_entropy(circuit, wires0=[0], wires1=[1])(params) entropy.backward() actual = params.grad diff --git a/tests/shadow/test_shadow_entropies.py b/tests/shadow/test_shadow_entropies.py index 009607bdf0b..6549cd5e984 100644 --- a/tests/shadow/test_shadow_entropies.py +++ b/tests/shadow/test_shadow_entropies.py @@ -68,9 +68,7 @@ def test_constant_distribution(self, n_wires, base): expected = np.log(2) / np.log(base) assert np.allclose(entropies, expected, atol=2e-2) - def test_non_constant_distribution( - self, - ): + def test_non_constant_distribution(self): """Test entropies match roughly with exact solution for a non-constant distribution using other PennyLane functionalities""" n_wires = 4 # exact solution @@ -108,7 +106,11 @@ def qnode(x): # this is intentionally not done in a parametrize loop because this would re-execute the quantum function # exact solution - rdm = qml.qinfo.reduced_dm(qnode_exact, wires=rdm_wires)(x) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match=("The qml.qinfo.reduced_dm transform is deprecated"), + ): + rdm = qml.qinfo.reduced_dm(qnode_exact, wires=rdm_wires)(x) evs = qml.math.eigvalsh(rdm) evs = evs[np.where(evs > 0)] From a40e3e7a5774afb398d0083855446aea31c8733e Mon Sep 17 00:00:00 2001 From: Mudit Pandey Date: Tue, 17 Sep 2024 15:52:41 -0400 Subject: [PATCH 14/14] Miscellaneous deprecations (#6277) * Deprecate `qml.broadcast`. * Deprecate `qml.shadows.shadow_expval`. * Deprecate the `'ancilla'` argument of `qml.iterative_qpe`in favour of `aux_wire`. [sc-69798] [sc-72497] [sc-71185] --------- Co-authored-by: David Wierichs Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- doc/development/deprecations.rst | 19 +++++++- doc/releases/changelog-dev.md | 25 +++++++---- pennylane/devices/_qubit_device.py | 2 +- pennylane/devices/_qutrit_device.py | 2 +- pennylane/measurements/classical_shadow.py | 2 +- pennylane/ops/functions/iterative_qpe.py | 41 +++++++++++++---- pennylane/shadows/transforms.py | 12 +++++ pennylane/templates/broadcast.py | 12 +++++ .../subroutines/arbitrary_unitary.py | 3 +- .../templates/subroutines/qubitization.py | 2 +- tests/ops/functions/test_iterative_qpe.py | 36 +++++++++++++-- .../op_math/test_controlled_decompositions.py | 45 ++++++++++++------- tests/shadow/test_shadow_transforms.py | 15 ++++++- tests/templates/test_broadcast.py | 13 ++++++ .../test_subroutines/test_qubitization.py | 2 +- tests/transforms/test_tape_expand.py | 13 +++++- 16 files changed, 201 insertions(+), 43 deletions(-) diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index f44598fc765..d67c9abf636 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -9,6 +9,23 @@ deprecations are listed below. Pending deprecations -------------------- +* The ``'ancilla'`` argument for :func:`~pennylane.iterative_qpe` has been deprecated. Instead, use the ``'aux_wire'`` + argument. + + - Deprecated in v0.39 + - Will be removed in v0.40 + +* The ``qml.shadows.shadow_expval`` transform has been deprecated. Instead, please use the + ``qml.shadow_expval`` measurement process. + + - Deprecated in v0.39 + - Will be removed in v0.40 + +* ``qml.broadcast`` has been deprecated. Users should use ``for`` loops instead. + + - Deprecated in v0.39 + - Will be removed in v0.40 + * The ``qml.qinfo`` module has been deprecated. Please see the respective functions in the ``qml.math`` and ``qml.measurements`` modules instead. @@ -22,7 +39,7 @@ Pending deprecations - Deprecated top level access in v0.39 - Top level access will be removed in v0.40 -* `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` instead. `QNode.get_gradient_fn` can also be used to +* ``QNode.gradient_fn`` is deprecated. Please use ``QNode.diff_method`` instead. ``QNode.get_gradient_fn`` can also be used to process the diff method. - Deprecated in v0.39 diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index c75f86c9e55..bbcf978442f 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -97,14 +97,23 @@

Deprecations πŸ‘‹

+* The `'ancilla'` argument for `qml.iterative_qpe` has been deprecated. Instead, use the `'aux_wire'` argument. + [(#6277)](https://github.com/PennyLaneAI/pennylane/pull/6277) + +* `qml.shadows.shadow_expval` has been deprecated. Instead, use the `qml.shadow_expval` measurement + process. + [(#6277)](https://github.com/PennyLaneAI/pennylane/pull/6277) + +* `qml.broadcast` has been deprecated. Please use `for` loops instead. + [(#6277)](https://github.com/PennyLaneAI/pennylane/pull/6277) + +* The `qml.QubitStateVector` template is deprecated. Instead, use `qml.StatePrep`. + [(#6172)](https://github.com/PennyLaneAI/pennylane/pull/6172) + * The `qml.qinfo` module has been deprecated. Please see the respective functions in the `qml.math` and `qml.measurements` modules instead. [(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911) -* The ``QubitStateVector`` template is deprecated. - Instead, use ``StatePrep``. - [(#6172)](https://github.com/PennyLaneAI/pennylane/pull/6172) - * `Device`, `QubitDevice`, and `QutritDevice` will no longer be accessible via top-level import in v0.40. They will still be accessible as `qml.devices.LegacyDevice`, `qml.devices.QubitDevice`, and `qml.devices.QutritDevice` respectively. @@ -120,19 +129,19 @@ * Fix a bug where zero-valued JVPs were calculated wrongly in the presence of shot vectors. [(#6219)](https://github.com/PennyLaneAI/pennylane/pull/6219) -* Fix `qml.PrepSelPrep` template to work with `torch`: +* Fix `qml.PrepSelPrep` template to work with `torch`. [(#6191)](https://github.com/PennyLaneAI/pennylane/pull/6191) * Now `qml.equal` compares correctly `qml.PrepSelPrep` operators. [(#6182)](https://github.com/PennyLaneAI/pennylane/pull/6182) -* The ``qml.QSVT`` template now orders the ``projector`` wires first and the ``UA`` wires second, which is the expected order of the decomposition. +* The `qml.QSVT` template now orders the `projector` wires first and the `UA` wires second, which is the expected order of the decomposition. [(#6212)](https://github.com/PennyLaneAI/pennylane/pull/6212) -* The ``qml.Qubitization`` template now orders the ``control`` wires first and the ``hamiltonian`` wires second, which is the expected according to other templates. +* The `qml.Qubitization` template now orders the `control` wires first and the `hamiltonian` wires second, which is the expected according to other templates. [(#6229)](https://github.com/PennyLaneAI/pennylane/pull/6229) -* The ``qml.FABLE`` template now returns the correct value when JIT is enabled. +* The `qml.FABLE` template now returns the correct value when JIT is enabled. [(#6263)](https://github.com/PennyLaneAI/pennylane/pull/6263) * Fixes a bug where a circuit using the `autograd` interface sometimes returns nested values that are not of the `autograd` interface. diff --git a/pennylane/devices/_qubit_device.py b/pennylane/devices/_qubit_device.py index ef9f41f0378..60110ee956a 100644 --- a/pennylane/devices/_qubit_device.py +++ b/pennylane/devices/_qubit_device.py @@ -1153,7 +1153,7 @@ def classical_shadow(self, obs, circuit): def shadow_expval(self, obs, circuit): r"""Compute expectation values using classical shadows in a differentiable manner. - Please refer to :func:`~.pennylane.shadow_expval` for detailed documentation. + Please refer to :func:`~pennylane.shadow_expval` for detailed documentation. Args: obs (~.pennylane.measurements.ClassicalShadowMP): The classical shadow expectation diff --git a/pennylane/devices/_qutrit_device.py b/pennylane/devices/_qutrit_device.py index fad5dfc8ff3..53e3fdd31db 100644 --- a/pennylane/devices/_qutrit_device.py +++ b/pennylane/devices/_qutrit_device.py @@ -230,7 +230,7 @@ def classical_shadow(self, obs, circuit): def shadow_expval(self, obs, circuit): r"""Compute expectation values using classical shadows in a differentiable manner. - Please refer to :func:`~.pennylane.shadow_expval` for detailed documentation. + Please refer to :func:`~pennylane.shadow_expval` for detailed documentation. .. seealso:: :func:`~pennylane.shadow_expval` diff --git a/pennylane/measurements/classical_shadow.py b/pennylane/measurements/classical_shadow.py index 200a48a25a5..4179ab02e09 100644 --- a/pennylane/measurements/classical_shadow.py +++ b/pennylane/measurements/classical_shadow.py @@ -479,7 +479,7 @@ def __copy__(self): class ShadowExpvalMP(MeasurementTransform): """Measures the expectation value of an operator using the classical shadow measurement process. - Please refer to :func:`shadow_expval` for detailed documentation. + Please refer to :func:`~pennylane.shadow_expval` for detailed documentation. Args: H (Operator, Sequence[Operator]): Operator or list of Operators to compute the expectation value over. diff --git a/pennylane/ops/functions/iterative_qpe.py b/pennylane/ops/functions/iterative_qpe.py index 8ccc429cfb0..eea5aefa408 100644 --- a/pennylane/ops/functions/iterative_qpe.py +++ b/pennylane/ops/functions/iterative_qpe.py @@ -15,12 +15,14 @@ This module contains the qml.iterative_qpe function. """ +from warnings import warn + import numpy as np import pennylane as qml -def iterative_qpe(base, ancilla, iters): +def iterative_qpe(base, aux_wire="unset", iters="unset", ancilla="unset"): r"""Performs the `iterative quantum phase estimation `_ circuit. Given a unitary :math:`U`, this function applies the circuit for iterative quantum phase @@ -28,8 +30,11 @@ def iterative_qpe(base, ancilla, iters): Args: base (Operator): the phase estimation unitary, specified as an :class:`~.Operator` - ancilla (Union[Wires, int, str]): the wire to be used for the estimation + aux_wire (Union[Wires, int, str]): the wire to be used for the estimation iters (int): the number of measurements to be performed + ancilla (Union[Wires, int, str]): the wire to be used for the estimation. This argument + is deprecated, and the ``aux_wire`` argument should be used instead. If both arguments + are provided, ``aux_wire`` will be used and ``ancilla`` will be ignored. Returns: list[MidMeasureMP]: the list of measurements performed @@ -49,7 +54,7 @@ def circuit(): qml.X(0) # Iterative QPE - measurements = qml.iterative_qpe(qml.RZ(2.0, wires=[0]), ancilla=1, iters=3) + measurements = qml.iterative_qpe(qml.RZ(2.0, wires=[0]), aux_wire=1, iters=3) return qml.sample(measurements) @@ -74,17 +79,37 @@ def circuit(): β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•©β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•‘β•β•β•β•β•β•β•β•‘ β”œSample[MCM] β•šβ•β•β•β•β•β•β•β•‘ β•°Sample[MCM] """ + missing = [] + if aux_wire == "unset" and ancilla == "unset": + missing.append("'aux_wire'") + if iters == "unset": + missing.append("'iters'") + + if missing: + missing_args = " and ".join(missing) + raise TypeError( + f"iterative_qpe() missing {len(missing)} required positional argument(s): {missing_args}" + ) + + if ancilla != "unset": + warn( + "The 'ancilla' argument for qml.iterative_qpe has been deprecated. Please use the " + "'aux_wire' argument instead.", + qml.PennyLaneDeprecationWarning, + ) + if aux_wire == "unset": + aux_wire = ancilla measurements = [] for i in range(iters): - qml.Hadamard(wires=ancilla) - qml.ctrl(qml.pow(base, z=2 ** (iters - i - 1)), control=ancilla) + qml.Hadamard(wires=aux_wire) + qml.ctrl(qml.pow(base, z=2 ** (iters - i - 1)), control=aux_wire) for ind, meas in enumerate(measurements): - qml.cond(meas, qml.PhaseShift)(-2.0 * np.pi / 2 ** (ind + 2), wires=ancilla) + qml.cond(meas, qml.PhaseShift)(-2.0 * np.pi / 2 ** (ind + 2), wires=aux_wire) - qml.Hadamard(wires=ancilla) - measurements.insert(0, qml.measure(wires=ancilla, reset=True)) + qml.Hadamard(wires=aux_wire) + measurements.insert(0, qml.measure(wires=aux_wire, reset=True)) return measurements diff --git a/pennylane/shadows/transforms.py b/pennylane/shadows/transforms.py index feb37a19a06..95007d50d52 100644 --- a/pennylane/shadows/transforms.py +++ b/pennylane/shadows/transforms.py @@ -62,6 +62,11 @@ def shadow_expval(tape: QuantumScript, H, k=1) -> tuple[QuantumScriptBatch, Post See :func:`~.pennylane.shadow_expval` for more usage details. + .. warning:: + + ``qml.shadows.shadow_expval`` is deprecated. Please use the :func:`~pennylane.shadow_expval` + measurement process in your circuits instead. + Args: tape (QNode or QuantumTape or Callable): A quantum circuit. H (:class:`~.pennylane.Observable` or list[:class:`~.pennylane.Observable`]): Observables @@ -96,6 +101,13 @@ def circuit(x): >>> qml.grad(circuit)(x) -0.9323999999999998 """ + + warnings.warn( + "qml.shadows.shadow_expval is deprecated. Instead, use the qml.shadow_expval " + "measurement process in your circuit.", + qml.PennyLaneDeprecationWarning, + ) + tapes, _ = _replace_obs(tape, qml.shadow_expval, H, k=k) def post_processing_fn(res): diff --git a/pennylane/templates/broadcast.py b/pennylane/templates/broadcast.py index 8f770f6a55d..8c78b294927 100644 --- a/pennylane/templates/broadcast.py +++ b/pennylane/templates/broadcast.py @@ -19,6 +19,8 @@ ``details`` section, * add tests to parametrizations in :func:`test_templates_broadcast`. """ +from warnings import warn + # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml from pennylane.wires import Wires @@ -212,6 +214,10 @@ def broadcast(unitary, wires, pattern, parameters=None, kwargs=None): For more details, see *Usage Details* below. + .. warning:: + + ``qml.broadcast`` has been deprecated. Please use ``for`` loops instead. + Args: unitary (func): quantum gate or template pattern (str): specifies the wire pattern of the broadcast @@ -553,6 +559,12 @@ def circuit(pars): # We deliberately disable iterating using enumerate here, since # it causes a slowdown when iterating over TensorFlow variables. # pylint: disable=consider-using-enumerate + + warn( + "qml.broadcast is deprecated. Please use a for loop instead", + qml.PennyLaneDeprecationWarning, + ) + wires = Wires(wires) if kwargs is None: kwargs = {} diff --git a/pennylane/templates/subroutines/arbitrary_unitary.py b/pennylane/templates/subroutines/arbitrary_unitary.py index 5c15dfdfb60..3527bc0145b 100644 --- a/pennylane/templates/subroutines/arbitrary_unitary.py +++ b/pennylane/templates/subroutines/arbitrary_unitary.py @@ -83,7 +83,8 @@ class ArbitraryUnitary(Operation): .. code-block:: python def arbitrary_nearest_neighbour_interaction(weights, wires): - qml.broadcast(unitary=ArbitraryUnitary, pattern="double", wires=wires, parameters=weights) + for i, w in enumerate(range(0, len(wires) - 1, 2)): + ArbitraryUnitary(weights[i], wires=[w, w + 1]) Args: weights (tensor_like): The angles of the Pauli word rotations, needs to have length :math:`4^n - 1` diff --git a/pennylane/templates/subroutines/qubitization.py b/pennylane/templates/subroutines/qubitization.py index f79bfe3d152..e043463f9c0 100644 --- a/pennylane/templates/subroutines/qubitization.py +++ b/pennylane/templates/subroutines/qubitization.py @@ -55,7 +55,7 @@ def circuit(): # apply QPE measurements = qml.iterative_qpe( - qml.Qubitization(H, control = [3,4]), ancilla = 5, iters = 3 + qml.Qubitization(H, control = [3,4]), aux_wire = 5, iters = 3 ) return qml.probs(op = measurements) diff --git a/tests/ops/functions/test_iterative_qpe.py b/tests/ops/functions/test_iterative_qpe.py index e06bdb9a4b9..e3507c60f8b 100644 --- a/tests/ops/functions/test_iterative_qpe.py +++ b/tests/ops/functions/test_iterative_qpe.py @@ -23,6 +23,36 @@ class TestIQPE: """Test to check that the iterative quantum phase estimation function works as expected.""" + def test_ancilla_deprecation(self): + """Test that the ancilla argument is deprecated and superceded by the aux_wire argument + if provided.""" + aux_wire = 1 + ancilla = 2 + + with pytest.warns(qml.PennyLaneDeprecationWarning, match="The 'ancilla' argument"): + meas1 = qml.iterative_qpe(qml.RZ(2.0, wires=0), ancilla=ancilla, iters=3) + meas2 = qml.iterative_qpe( + qml.RZ(2.0, wires=0), aux_wire=aux_wire, iters=3, ancilla=ancilla + ) + + assert all(m.wires == qml.wires.Wires(ancilla) for m in meas1) + assert all(m.wires == qml.wires.Wires(aux_wire) for m in meas2) + + @pytest.mark.parametrize( + "args, n_missing, missing_args", + [ + ({"aux_wire": 1}, 1, "'iters'"), + ({"ancilla": 1}, 1, "'iters'"), + ({"iters": 1}, 1, "'aux_wire'"), + ({}, 2, "'aux_wire' and 'iters'"), + ], + ) + def test_args_not_provided(self, args, n_missing, missing_args): + """Test that the correct error is raised if there are missing arguments""" + err_msg = rf"iterative_qpe\(\) missing {n_missing} required positional argument\(s\): {missing_args}" + with pytest.raises(TypeError, match=err_msg): + _ = qml.iterative_qpe(qml.RZ(1.5, 0), **args) + @pytest.mark.parametrize("mcm_method", ["deferred", "tree-traversal"]) @pytest.mark.parametrize("phi", (1.0, 2.0, 3.0)) def test_compare_qpe(self, mcm_method, phi): @@ -37,7 +67,7 @@ def circuit_iterative(): qml.PauliX(wires=[0]) # Iterative QPE - measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), ancilla=[1], iters=3) + measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), aux_wire=[1], iters=3) return [qml.sample(op=meas) for meas in measurements] @@ -206,7 +236,7 @@ def circuit_iterative(): qml.PauliX(wires=[0]) # Iterative QPE - measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), ancilla=[1], iters=3) + measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), aux_wire=[1], iters=3) return [qml.probs(op=i) for i in measurements] @@ -235,7 +265,7 @@ def circuit_iterative(): qml.PauliX(wires=[0]) # Iterative QPE - measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), ancilla=[1], iters=3) + measurements = qml.iterative_qpe(qml.RZ(phi, wires=[0]), aux_wire=[1], iters=3) return [qml.expval(op=i) for i in measurements] diff --git a/tests/ops/op_math/test_controlled_decompositions.py b/tests/ops/op_math/test_controlled_decompositions.py index 6c1adda9f92..03290fca0d5 100644 --- a/tests/ops/op_math/test_controlled_decompositions.py +++ b/tests/ops/op_math/test_controlled_decompositions.py @@ -120,13 +120,15 @@ def test_decomposition_circuit_general_ops(self, op, control_wires, tol): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) ctrl_decomp_zyz(op, Wires(control_wires)) return qml.probs() @qml.qnode(dev) def expected_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) qml.ctrl(op, control_wires) return qml.probs() @@ -144,7 +146,8 @@ def test_decomposition_circuit_general_ops_error(self, op, control_wires): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) ctrl_decomp_zyz(op, Wires(control_wires)) return qml.probs() @@ -230,14 +233,16 @@ def test_decomp_queues_correctly(self, op, control_wires, tol): @qml.qnode(dev) def queue_from_list(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) for o in decomp: qml.apply(o) return qml.state() @qml.qnode(dev) def queue_from_qnode(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) ctrl_decomp_zyz(op, control_wires=Wires(control_wires)) return qml.state() @@ -452,7 +457,8 @@ def test_decomposition_circuit(self, op, control_wires, tol): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) record_from_list(_ctrl_decomp_bisect_od)( _convert_to_su2(op.matrix()), op.wires, Wires(control_wires) ) @@ -460,7 +466,8 @@ def decomp_circuit(): @qml.qnode(dev) def expected_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) qml.ctrl(op, control_wires) return qml.probs() @@ -604,7 +611,8 @@ def test_decomposition_circuit(self, op, control_wires, tol): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) record_from_list(_ctrl_decomp_bisect_md)( _convert_to_su2(op.matrix()), op.wires, Wires(control_wires) ) @@ -612,7 +620,8 @@ def decomp_circuit(): @qml.qnode(dev) def expected_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) qml.ctrl(op, control_wires) return qml.probs() @@ -733,7 +742,8 @@ def test_decomposition_circuit(self, op, control_wires, auto, tol): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) if auto: ctrl_decomp_bisect(op, Wires(control_wires)) else: @@ -744,7 +754,8 @@ def decomp_circuit(): @qml.qnode(dev) def expected_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) qml.ctrl(op, control_wires) return qml.probs() @@ -877,13 +888,15 @@ def test_decomposition_circuit(self, op, control_wires, tol): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) _decompose_multicontrolled_unitary(op, Wires(control_wires)) return qml.probs() @qml.qnode(dev) def expected_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) qml.ctrl(op, control_wires) return qml.probs() @@ -982,7 +995,8 @@ def test_decomposition_circuit(self, op, control_wires, tol): @qml.qnode(dev) def decomp_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) record_from_list(_decompose_recursive)( op, 1.0, Wires(control_wires), op.wires, Wires([]) ) @@ -990,7 +1004,8 @@ def decomp_circuit(): @qml.qnode(dev) def expected_circuit(): - qml.broadcast(unitary=qml.Hadamard, pattern="single", wires=control_wires) + for wire in control_wires: + qml.Hadamard(wire) qml.ctrl(op, control_wires) return qml.probs() diff --git a/tests/shadow/test_shadow_transforms.py b/tests/shadow/test_shadow_transforms.py index 0bce7c2876c..1910293264b 100644 --- a/tests/shadow/test_shadow_transforms.py +++ b/tests/shadow/test_shadow_transforms.py @@ -22,6 +22,10 @@ from pennylane import numpy as np from pennylane.shadows.transforms import _replace_obs +pytestmark = pytest.mark.filterwarnings( + "ignore:qml.shadows.shadow_expval is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def hadamard_circuit(wires, shots=10000, interface="autograd"): """Hadamard circuit to put all qubits in equal superposition (locally)""" @@ -108,7 +112,7 @@ def test_replace_tape(self): new_tapes, _ = _replace_obs(tape, qml.probs, wires=0) assert len(new_tapes) == 1 - assert new_tapes[0].operations == [] + assert len(new_tapes[0].operations) == 0 assert len(new_tapes[0].observables) == 1 assert isinstance(new_tapes[0].observables[0], qml.measurements.ProbabilityMP) @@ -326,6 +330,15 @@ def test_backward_torch(self): class TestExpvalTransform: """Test that the expval transform is applied correctly""" + def test_shadow_expval_deprecation(self): + """Test that the shadow_expval transform is deprecated""" + tape = qml.tape.QuantumScript([], [qml.classical_shadow(wires=[0, 1])]) + + with pytest.warns( + qml.PennyLaneDeprecationWarning, match="qml.shadows.shadow_expval is deprecated" + ): + _, _ = qml.shadows.shadow_expval(tape, [qml.Z(0)]) + def test_hadamard_forward(self): """Test that the expval estimation is correct for a uniform superposition of qubits""" diff --git a/tests/templates/test_broadcast.py b/tests/templates/test_broadcast.py index 9b185d82d4f..49d0b77fc60 100644 --- a/tests/templates/test_broadcast.py +++ b/tests/templates/test_broadcast.py @@ -27,6 +27,10 @@ from pennylane.templates.broadcast import wires_all_to_all, wires_pyramid, wires_ring from pennylane.wires import Wires +pytestmark = pytest.mark.filterwarnings( + "ignore:qml.broadcast is deprecated:pennylane.PennyLaneDeprecationWarning" +) + def ConstantTemplate(wires): T(wires=wires) @@ -131,6 +135,15 @@ def KwargTemplateDouble(par, wires, a=True): ] +def test_broadcast_deprecation(): + """Test that a warning is raised when using qml.broadcast""" + op = qml.Hadamard + wires = [0, 1, 2] + + with pytest.warns(qml.PennyLaneDeprecationWarning, match="qml.broadcast is deprecated"): + qml.broadcast(op, wires, "single") + + class TestBuiltinPatterns: """Tests the built-in patterns ("single", "ring", etc) of the broadcast template constructor.""" diff --git a/tests/templates/test_subroutines/test_qubitization.py b/tests/templates/test_subroutines/test_qubitization.py index 6545af9bbd5..50c23080400 100644 --- a/tests/templates/test_subroutines/test_qubitization.py +++ b/tests/templates/test_subroutines/test_qubitization.py @@ -49,7 +49,7 @@ def circuit(theta): # apply QPE (used iterative qpe here) measurements = qml.iterative_qpe( - qml.Qubitization(hamiltonian, control=[3, 4]), ancilla=5, iters=8 + qml.Qubitization(hamiltonian, control=[3, 4]), aux_wire=5, iters=8 ) return qml.probs(op=measurements) diff --git a/tests/transforms/test_tape_expand.py b/tests/transforms/test_tape_expand.py index 3575034d08c..db7472df086 100644 --- a/tests/transforms/test_tape_expand.py +++ b/tests/transforms/test_tape_expand.py @@ -418,7 +418,18 @@ def custom_rot(phi, theta, omega, wires): # Decompose a template into another template def custom_basic_entangler_layers(weights, wires, **kwargs): # pylint: disable=unused-argument - cnot_broadcast = qml.tape.make_qscript(qml.broadcast)(qml.CNOT, pattern="ring", wires=wires) + def cnot_circuit(wires): + n_wires = len(wires) + + if n_wires == 2: + qml.CNOT(wires) + return + + for wire in wires: + op_wires = [wire % n_wires, (wire + 1) % n_wires] + qml.CNOT(op_wires) + + cnot_broadcast = qml.tape.make_qscript(cnot_circuit)(wires) return [ qml.AngleEmbedding(weights[0], wires=wires), *cnot_broadcast,