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 97697a8de93..9d4a79c2e7b 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)
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)),