From 1cbaeeccf554b0b8e5a3f5e9746f2a48b6ab6758 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 13 Sep 2024 16:20:14 -0400 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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 ):