diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml
index 17088962858..41f2f3434e3 100644
--- a/.github/workflows/interface-unit-tests.yml
+++ b/.github/workflows/interface-unit-tests.yml
@@ -527,6 +527,7 @@ jobs:
# shots: None
- device: default.qubit.autograd
shots: None
+ skip_interface: jax,tf,torch
- device: default.mixed
shots: None
python-version: >-
@@ -542,9 +543,9 @@ jobs:
coverage_artifact_name: devices-coverage-${{ matrix.config.device }}-${{ matrix.config.shots }}
python_version: ${{ matrix.python-version }}
pipeline_mode: ${{ inputs.pipeline_mode }}
- install_jax: ${{ contains(matrix.config.device, 'jax') }}
- install_tensorflow: ${{ contains(matrix.config.device, 'tf') }}
- install_pytorch: ${{ contains(matrix.config.device, 'torch') }}
+ install_jax: ${{ !contains(matrix.config.skip_interface, 'jax') }}
+ install_tensorflow: ${{ !contains(matrix.config.skip_interface, 'tf') }}
+ install_pytorch: ${{ !contains(matrix.config.skip_interface, 'torch') }}
install_pennylane_lightning_master: true
pytest_test_directory: pennylane/devices/tests
pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index b117f4b3f82..b4456e59708 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -209,6 +209,13 @@
[stim](https://github.com/quantumlib/Stim) `v1.13.0`.
[(#5409)](https://github.com/PennyLaneAI/pennylane/pull/5409)
+* `qml.specs` and `qml.Tracker` now return information about algorithmic errors for the qnode as well.
+ [(#5464)](https://github.com/PennyLaneAI/pennylane/pull/5464)
+ [(#5465)](https://github.com/PennyLaneAI/pennylane/pull/5465)
+
+* `qml.specs` now returns information regarding algorithmic errors for the qnode as well.
+ [(#5464)](https://github.com/PennyLaneAI/pennylane/pull/5464)
+
* `qml.transforms.hamiltonian_expand` can now handle multi-term observables with a constant offset.
[(#5414)](https://github.com/PennyLaneAI/pennylane/pull/5414)
@@ -241,6 +248,9 @@
[(#5256)](https://github.com/PennyLaneAI/pennylane/pull/5256)
[(#5395)](https://github.com/PennyLaneAI/pennylane/pull/5395)
+* Extend the device test suite to cover gradient methods, templates and arithmetic observables.
+ [(#5273)](https://github.com/PennyLaneAI/pennylane/pull/5273)
+
* Add type hints for unimplemented methods of the abstract class `Operator`.
[(#5490)](https://github.com/PennyLaneAI/pennylane/pull/5490)
@@ -322,6 +332,9 @@
Documentation 📝
+* Adds a page explaining the shapes and nesting of result objects.
+ [(#5418)](https://github.com/PennyLaneAI/pennylane/pull/5418)
+
* Removed some redundant documentation for the `evolve` function.
[(#5347)](https://github.com/PennyLaneAI/pennylane/pull/5347)
@@ -339,6 +352,12 @@
Bug fixes 🐛
+* The `qml.QNSPSAOptimizer` now correctly handles optimization for legacy devices that do not follow the new API design.
+ [(#5497)](https://github.com/PennyLaneAI/pennylane/pull/5497)
+
+* Operators applied to all wires are now drawn correctly in a circuit with mid-circuit measurements.
+ [(#5501)](https://github.com/PennyLaneAI/pennylane/pull/5501)
+
* Fix a bug where certain unary mid-circuit measurement expressions would raise an uncaught error.
[(#5480)](https://github.com/PennyLaneAI/pennylane/pull/5480)
diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py
index e52a3a66704..c02e7443765 100644
--- a/pennylane/_qubit_device.py
+++ b/pennylane/_qubit_device.py
@@ -152,7 +152,7 @@ def _const_mul(constant, array):
"Identity",
"Projector",
"Sum",
- "Sprod",
+ "SProd",
"Prod",
}
diff --git a/pennylane/devices/device_api.py b/pennylane/devices/device_api.py
index 8c5639002ac..2f6abd19f1f 100644
--- a/pennylane/devices/device_api.py
+++ b/pennylane/devices/device_api.py
@@ -324,6 +324,8 @@ def execute(
.. details::
:title: Return Shape
+ See :ref:`Return Type Specification ` for more detailed information.
+
The result for each :class:`~.QuantumTape` must match the shape specified by :class:`~.QuantumTape.shape`.
The level of priority for dimensions from outer dimension to inner dimension is:
diff --git a/pennylane/devices/modifiers/simulator_tracking.py b/pennylane/devices/modifiers/simulator_tracking.py
index 72f86d93547..0ebde382ead 100644
--- a/pennylane/devices/modifiers/simulator_tracking.py
+++ b/pennylane/devices/modifiers/simulator_tracking.py
@@ -46,6 +46,7 @@ def execute(self, circuits, execution_config=DefaultExecutionConfig):
results=r,
shots=shots,
resources=c.specs["resources"],
+ errors=c.specs["errors"],
)
else:
self.tracker.update(
@@ -53,6 +54,7 @@ def execute(self, circuits, execution_config=DefaultExecutionConfig):
executions=qpu_executions,
results=r,
resources=c.specs["resources"],
+ errors=c.specs["errors"],
)
self.tracker.record()
return results
@@ -85,7 +87,7 @@ def execute_and_compute_derivatives(self, circuits, execution_config=DefaultExec
if self.tracker.active:
batch = (circuits,) if isinstance(circuits, QuantumScript) else circuits
for c in batch:
- self.tracker.update(resources=c.specs["resources"])
+ self.tracker.update(resources=c.specs["resources"], errors=c.specs["errors"])
self.tracker.update(
execute_and_derivative_batches=1,
executions=len(batch),
@@ -119,7 +121,7 @@ def execute_and_compute_jvp(self, circuits, tangents, execution_config=DefaultEx
if self.tracker.active:
batch = (circuits,) if isinstance(circuits, QuantumScript) else circuits
for c in batch:
- self.tracker.update(resources=c.specs["resources"])
+ self.tracker.update(resources=c.specs["resources"], errors=c.specs["errors"])
self.tracker.update(execute_and_jvp_batches=1, executions=len(batch), jvps=len(batch))
self.tracker.record()
@@ -153,7 +155,7 @@ def execute_and_compute_vjp(
if self.tracker.active:
batch = (circuits,) if isinstance(circuits, QuantumScript) else circuits
for c in batch:
- self.tracker.update(resources=c.specs["resources"])
+ self.tracker.update(resources=c.specs["resources"], errors=c.specs["errors"])
self.tracker.update(execute_and_vjp_batches=1, executions=len(batch), vjps=len(batch))
self.tracker.record()
return untracked_execute_and_compute_vjp(self, circuits, cotangents, execution_config)
@@ -176,6 +178,7 @@ def simulator_tracking(cls: type) -> type:
* ``executions``: the number of unique circuits that would be required on quantum hardware
* ``shots``: the number of shots
* ``resources``: the :class:`~.resource.Resources` for the executed circuit.
+ * ``"errors"``: combined algorithmic errors from the quantum operations executed by the qnode.
* ``simulations``: the number of simulations performed. One simulation can cover multiple QPU executions,
such as for non-commuting measurements and batched parameters.
* ``batches``: The number of times :meth:`~pennylane.devices.Device.execute` is called.
@@ -218,7 +221,8 @@ def execute(self, circuits, execution_config = qml.devices.DefaultExecutionConfi
'shots': [100],
'resources': [Resources(num_wires=1, num_gates=1, gate_types=defaultdict(, {'S': 1}),
gate_sizes=defaultdict(, {1: 1}), depth=1, shots=Shots(total_shots=50,
- shot_vector=(ShotCopies(50 shots x 1),)))]}
+ shot_vector=(ShotCopies(50 shots x 1),)))],
+ 'errors': {}}
"""
if not issubclass(cls, Device):
diff --git a/pennylane/devices/tests/conftest.py b/pennylane/devices/tests/conftest.py
index 779088c79db..0a4be0264c5 100755
--- a/pennylane/devices/tests/conftest.py
+++ b/pennylane/devices/tests/conftest.py
@@ -85,8 +85,20 @@ def _skip_if(dev, capabilities):
return _skip_if
-@pytest.fixture(scope="function")
-def device(device_kwargs):
+@pytest.fixture
+def validate_diff_method(device, diff_method, device_kwargs):
+ """Skip tests if a device does not support a diff_method"""
+ if diff_method == "backprop" and device_kwargs.get("shots") is not None:
+ pytest.skip(reason="test should only be run in analytic mode")
+ dev = device(1)
+ if isinstance(dev, qml.Device):
+ passthru_devices = dev.capabilities().get("passthru_devices")
+ if diff_method == "backprop" and passthru_devices is None:
+ pytest.skip(reason="device does not support backprop")
+
+
+@pytest.fixture(scope="function", name="device")
+def fixture_device(device_kwargs):
"""Fixture to create a device."""
# internally used by pytest
diff --git a/pennylane/devices/tests/test_gradients_autograd.py b/pennylane/devices/tests/test_gradients_autograd.py
new file mode 100644
index 00000000000..2b0414ee184
--- /dev/null
+++ b/pennylane/devices/tests/test_gradients_autograd.py
@@ -0,0 +1,201 @@
+# Copyright 2024 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests trainable circuits using the Autograd interface."""
+# pylint:disable=no-self-use
+import pytest
+
+import numpy as np
+
+import pennylane as qml
+from pennylane import numpy as pnp
+
+
+@pytest.mark.usefixtures("validate_diff_method")
+@pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "hadamard"])
+class TestGradients:
+ """Test various gradient computations."""
+
+ def test_basic_grad(self, diff_method, device, tol):
+ """Test a basic function with one RX and one expectation."""
+ wires = 2 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ if diff_method == "hadamard":
+ tol += 0.01
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x):
+ qml.RX(x, 0)
+ return qml.expval(qml.Z(0))
+
+ x = pnp.array(0.5)
+ res = qml.grad(circuit)(x)
+ assert np.isclose(res, -pnp.sin(x), atol=tol, rtol=0)
+
+ def test_backprop_state(self, diff_method, device, tol):
+ """Test the trainability of parameters in a circuit returning the state."""
+ if diff_method != "backprop":
+ pytest.skip(reason="test only works with backprop")
+ dev = device(2)
+ if dev.shots:
+ pytest.skip("test uses backprop, must be in analytic mode")
+ if "mixed" in dev.name:
+ pytest.skip("mixed-state simulator will wrongly use grad on non-scalar results")
+ tol = tol(dev.shots)
+
+ x = pnp.array(0.543)
+ y = pnp.array(-0.654)
+
+ @qml.qnode(dev, diff_method=diff_method, grad_on_execution=True)
+ 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)
+ probs = pnp.abs(res) ** 2
+ return probs[0] + probs[2]
+
+ res = qml.grad(cost_fn)(x, y)
+ expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
+ assert np.allclose(res, expected, atol=tol, rtol=0)
+
+ y = pnp.array(-0.654, requires_grad=False)
+ res = qml.grad(cost_fn)(x, y)
+ assert np.allclose(res, expected[0], atol=tol, rtol=0)
+
+ def test_parameter_shift(self, diff_method, device, tol):
+ """Test a multi-parameter circuit with parameter-shift."""
+ if diff_method != "parameter-shift":
+ pytest.skip(reason="test only works with parameter-shift")
+
+ a = pnp.array(0.1)
+ b = pnp.array(0.2)
+
+ dev = device(2)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method="parameter-shift", grad_on_execution=False)
+ def circuit(a, b):
+ qml.RY(a, wires=0)
+ qml.RX(b, wires=1)
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Hamiltonian([1, 1], [qml.Z(0), qml.Y(1)]))
+
+ res = qml.grad(circuit)(a, b)
+ 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
+ b = pnp.array(0.2, requires_grad=False)
+ res = qml.grad(circuit)(a, b)
+ assert np.allclose(res, expected[0], atol=tol, rtol=0)
+
+ def test_probs(self, diff_method, device, tol):
+ """Test differentiation of a circuit returning probs()."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = pnp.array(0.543)
+ y = pnp.array(-0.654)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ 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)
+
+ expected = np.array(
+ [
+ [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2],
+ [np.cos(y) * np.sin(x) / 2, np.cos(x) * np.sin(y) / 2],
+ ]
+ )
+
+ assert isinstance(res, tuple)
+ assert len(res) == 2
+
+ assert isinstance(res[0], pnp.ndarray)
+ assert res[0].shape == (2,)
+
+ assert isinstance(res[1], pnp.ndarray)
+ assert res[1].shape == (2,)
+
+ if diff_method == "hadamard" and "raket" in dev.name:
+ pytest.xfail(reason="braket gets wrong results for hadamard here")
+ assert np.allclose(res[0], expected.T[0], atol=tol, rtol=0)
+ assert np.allclose(res[1], expected.T[1], atol=tol, rtol=0)
+
+ def test_multi_meas(self, diff_method, device, tol):
+ """Test differentiation of a circuit with both scalar and array-like returns."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = pnp.array(0.543)
+ y = pnp.array(-0.654, requires_grad=False)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x, y):
+ qml.RX(x, wires=[0])
+ qml.RY(y, wires=[1])
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Z(0)), qml.probs(wires=[1])
+
+ def cost_fn(x, y):
+ return pnp.hstack(circuit(x, y))
+
+ jac = qml.jacobian(cost_fn)(x, y)
+
+ expected = [-np.sin(x), -np.sin(x) * np.cos(y) / 2, np.cos(y) * np.sin(x) / 2]
+ assert isinstance(jac, pnp.ndarray)
+ assert np.allclose(jac, expected, atol=tol, rtol=0)
+
+ def test_hessian(self, diff_method, device, tol):
+ """Test hessian computation."""
+ wires = 3 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method=diff_method, max_diff=2)
+ def circuit(x):
+ qml.RY(x[0], wires=0)
+ qml.RX(x[1], wires=0)
+ return qml.expval(qml.Z(0))
+
+ x = pnp.array([1.0, 2.0])
+ res = circuit(x)
+
+ a, b = x
+
+ expected_res = np.cos(a) * np.cos(b)
+ 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 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 np.allclose(hess, expected_hess, atol=tol, rtol=0)
diff --git a/pennylane/devices/tests/test_gradients_jax.py b/pennylane/devices/tests/test_gradients_jax.py
new file mode 100644
index 00000000000..8e9b40c254c
--- /dev/null
+++ b/pennylane/devices/tests/test_gradients_jax.py
@@ -0,0 +1,216 @@
+# Copyright 2024 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests trainable circuits using the JAX interface."""
+# pylint:disable=no-self-use
+import pytest
+
+import numpy as np
+
+import pennylane as qml
+
+jax = pytest.importorskip("jax")
+jnp = pytest.importorskip("jax.numpy")
+
+
+@pytest.mark.usefixtures("validate_diff_method")
+@pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "hadamard"])
+class TestGradients:
+ """Test various gradient computations."""
+
+ def test_basic_grad(self, diff_method, device, tol):
+ """Test a basic function with one RX and one expectation."""
+ wires = 2 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ if diff_method == "hadamard":
+ tol += 0.01
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x):
+ qml.RX(x, 0)
+ return qml.expval(qml.Z(0))
+
+ x = jnp.array(0.5)
+ res = jax.grad(circuit)(x)
+ assert np.isclose(res, -jnp.sin(x), atol=tol, rtol=0)
+
+ def test_backprop_state(self, diff_method, device, tol):
+ """Test the trainability of parameters in a circuit returning the state."""
+ if diff_method != "backprop":
+ pytest.skip(reason="test only works with backprop")
+ dev = device(2)
+ if dev.shots:
+ pytest.skip("test uses backprop, must be in analytic mode")
+ if "mixed" in dev.name:
+ pytest.skip("mixed-state simulator will wrongly use grad on non-scalar results")
+ tol = tol(dev.shots)
+
+ x = jnp.array(0.543)
+ y = jnp.array(-0.654)
+
+ @qml.qnode(dev, diff_method="backprop", grad_on_execution=True)
+ 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)
+ probs = jnp.abs(res) ** 2
+ return probs[0] + probs[2]
+
+ res = jax.grad(cost_fn, argnums=[0, 1])(x, y)
+ expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
+ assert np.allclose(res, expected, atol=tol, rtol=0)
+
+ res = jax.grad(cost_fn, argnums=[0])(x, y)
+ assert np.allclose(res, expected[0], atol=tol, rtol=0)
+
+ def test_parameter_shift(self, diff_method, device, tol):
+ """Test a multi-parameter circuit with parameter-shift."""
+ if diff_method != "parameter-shift":
+ pytest.skip(reason="test only works with parameter-shift")
+
+ a = jnp.array(0.1)
+ b = jnp.array(0.2)
+
+ dev = device(2)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method="parameter-shift", grad_on_execution=False)
+ def circuit(a, b):
+ qml.RY(a, wires=0)
+ qml.RX(b, wires=1)
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Hamiltonian([1, 1], [qml.Z(0), qml.Y(1)]))
+
+ res = jax.grad(circuit, argnums=[0, 1])(a, b)
+ 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
+ res = jax.grad(circuit, argnums=[0])(a, b)
+ assert np.allclose(res, expected[0], atol=tol, rtol=0)
+
+ def test_probs(self, diff_method, device, tol):
+ """Test differentiation of a circuit returning probs()."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = jnp.array(0.543)
+ y = jnp.array(-0.654)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ 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 = jax.jacobian(circuit, argnums=[0, 1])(x, y)
+
+ expected = np.array(
+ [
+ [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2],
+ [np.cos(y) * np.sin(x) / 2, np.cos(x) * np.sin(y) / 2],
+ ]
+ )
+
+ assert isinstance(res, tuple)
+ assert len(res) == 2
+
+ assert isinstance(res[0], jnp.ndarray)
+ assert res[0].shape == (2,)
+
+ assert isinstance(res[1], jnp.ndarray)
+ assert res[1].shape == (2,)
+
+ if diff_method == "hadamard" and "raket" in dev.name:
+ pytest.xfail(reason="braket gets wrong results for hadamard here")
+ assert np.allclose(res[0], expected.T[0], atol=tol, rtol=0)
+ assert np.allclose(res[1], expected.T[1], atol=tol, rtol=0)
+
+ def test_multi_meas(self, diff_method, device, tol):
+ """Test differentiation of a circuit with both scalar and array-like returns."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = jnp.array(0.543)
+ y = jnp.array(-0.654)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x, y):
+ qml.RX(x, wires=[0])
+ qml.RY(y, wires=[1])
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Z(0)), qml.probs(wires=[1])
+
+ jac = jax.jacobian(circuit, argnums=[0])(x, y)
+
+ expected = [
+ [-np.sin(x), 0],
+ [
+ [-np.sin(x) * np.cos(y) / 2, np.cos(y) * np.sin(x) / 2],
+ [-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], tuple)
+ assert len(jac[0]) == 1
+ assert isinstance(jac[0][0], jnp.ndarray)
+ assert jac[0][0].shape == ()
+ assert np.allclose(jac[0][0], expected[0][0], atol=tol, rtol=0)
+
+ assert isinstance(jac[1], tuple)
+ assert len(jac[1]) == 1
+ assert isinstance(jac[1][0], jnp.ndarray)
+ assert jac[1][0].shape == (2,)
+ assert np.allclose(jac[1][0], expected[1][0], atol=tol, rtol=0)
+
+ def test_hessian(self, diff_method, device, tol):
+ """Test hessian computation."""
+ wires = 3 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method=diff_method, max_diff=2)
+ def circuit(x):
+ qml.RY(x[0], wires=0)
+ qml.RX(x[1], wires=0)
+ return qml.expval(qml.Z(0))
+
+ x = jnp.array([1.0, 2.0])
+ res = circuit(x)
+
+ a, b = x
+
+ expected_res = np.cos(a) * np.cos(b)
+ assert np.allclose(res, expected_res, atol=tol, rtol=0)
+
+ grad_fn = jax.grad(circuit)
+ g = grad_fn(x)
+
+ expected_g = [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)]
+ assert np.allclose(g, expected_g, atol=tol, rtol=0)
+
+ hess = jax.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 np.allclose(hess, expected_hess, atol=tol, rtol=0)
diff --git a/pennylane/devices/tests/test_gradients_tf.py b/pennylane/devices/tests/test_gradients_tf.py
new file mode 100644
index 00000000000..2621060795e
--- /dev/null
+++ b/pennylane/devices/tests/test_gradients_tf.py
@@ -0,0 +1,226 @@
+# Copyright 2024 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests trainable circuits using the TensorFlow interface."""
+# pylint:disable=no-self-use
+import pytest
+
+import numpy as np
+
+import pennylane as qml
+
+tf = pytest.importorskip("tensorflow")
+
+
+@pytest.mark.usefixtures("validate_diff_method")
+@pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "hadamard"])
+class TestGradients:
+ """Test various gradient computations."""
+
+ @pytest.fixture(autouse=True)
+ def skip_if_braket(self, device):
+ """Skip braket tests with tensorflow."""
+ dev = device(1)
+ if "raket" in dev.name:
+ pytest.skip(reason="braket cannot convert TF variables to literal")
+
+ def test_basic_grad(self, diff_method, device, tol):
+ """Test a basic function with one RX and one expectation."""
+ wires = 2 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ if diff_method == "hadamard":
+ tol += 0.01
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x):
+ qml.RX(x, 0)
+ return qml.expval(qml.Z(0))
+
+ x = tf.Variable(0.5)
+ with tf.GradientTape() as tape:
+ res = circuit(x)
+ grad = tape.gradient(res, x)
+ assert np.isclose(grad, -np.sin(x), atol=tol, rtol=0)
+
+ def test_backprop_state(self, diff_method, device, tol):
+ """Test the trainability of parameters in a circuit returning the state."""
+ if diff_method != "backprop":
+ pytest.skip(reason="test only works with backprop")
+ dev = device(2)
+ if dev.shots:
+ pytest.skip("test uses backprop, must be in analytic mode")
+ if "mixed" in dev.name:
+ pytest.skip("mixed-state simulator will wrongly use grad on non-scalar results")
+ tol = tol(dev.shots)
+
+ x = tf.Variable(0.543)
+ y = tf.Variable(-0.654)
+
+ @qml.qnode(dev, diff_method="backprop", grad_on_execution=True)
+ 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)
+ probs = tf.abs(res) ** 2
+ return probs[0] + probs[2]
+
+ with tf.GradientTape() as tape:
+ res = cost_fn(x, y)
+ grad = tape.gradient(res, [x, y])
+ expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
+ assert np.allclose(grad, expected, atol=tol, rtol=0)
+
+ def test_parameter_shift(self, diff_method, device, tol):
+ """Test a multi-parameter circuit with parameter-shift."""
+ if diff_method != "parameter-shift":
+ pytest.skip(reason="test only works with parameter-shift")
+
+ a = tf.Variable(0.1)
+ b = tf.Variable(0.2)
+
+ dev = device(2)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method="parameter-shift", grad_on_execution=False)
+ def circuit(a, b):
+ qml.RY(a, wires=0)
+ qml.RX(b, wires=1)
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Hamiltonian([1, 1], [qml.Z(0), qml.Y(1)]))
+
+ with tf.GradientTape() as tape:
+ res = circuit(a, b)
+ grad = tape.gradient(res, [a, b])
+ expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
+ assert np.allclose(grad, expected, atol=tol, rtol=0)
+
+ def test_probs(self, diff_method, device, tol):
+ """Test differentiation of a circuit returning probs()."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = tf.Variable(0.543)
+ y = tf.Variable(-0.654)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x, y):
+ qml.RX(x, wires=[0])
+ qml.RY(y, wires=[1])
+ qml.CNOT(wires=[0, 1])
+ return qml.probs(wires=[1])
+
+ with tf.GradientTape() as tape:
+ res0 = circuit(x, y)
+ res = tape.jacobian(res0, [x, y])
+
+ expected = np.array(
+ [
+ [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2],
+ [np.cos(y) * np.sin(x) / 2, np.cos(x) * np.sin(y) / 2],
+ ]
+ )
+
+ assert isinstance(res, list)
+ assert len(res) == 2
+
+ assert isinstance(res[0], tf.Tensor)
+ assert res[0].shape == (2,)
+
+ assert isinstance(res[1], tf.Tensor)
+ assert res[1].shape == (2,)
+
+ assert np.allclose(res[0], expected.T[0], atol=tol, rtol=0)
+ assert np.allclose(res[1], expected.T[1], atol=tol, rtol=0)
+
+ def test_multi_meas(self, diff_method, device, tol):
+ """Test differentiation of a circuit with both scalar and array-like returns."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = tf.Variable(0.543)
+ y = tf.Variable(-0.654)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x, y):
+ qml.RX(x, wires=[0])
+ qml.RY(y, wires=[1])
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Z(0)), qml.probs(wires=[1])
+
+ with tf.GradientTape() as tape:
+ res = circuit(x, y)
+ res = tf.experimental.numpy.hstack(res)
+
+ jac = tape.jacobian(res, [x, y])
+
+ expected = np.array(
+ [
+ [-np.sin(x), -np.sin(x) * np.cos(y) / 2, np.cos(y) * np.sin(x) / 2],
+ [0.0, -np.cos(x) * np.sin(y) / 2, np.cos(x) * np.sin(y) / 2],
+ ]
+ )
+ assert isinstance(jac, list)
+ assert len(jac) == 2
+ assert all(isinstance(j, tf.Tensor) and j.shape == (3,) for j in jac)
+ assert np.allclose(jac, expected, atol=tol, rtol=0)
+
+ # assert isinstance(jac[0], tuple)
+ # assert len(jac[0]) == 1
+ # assert isinstance(jac[0][0], tf.Tensor)
+ # assert jac[0][0].shape == ()
+ # assert np.allclose(jac[0][0], expected[0][0], atol=tol, rtol=0)
+
+ # assert isinstance(jac[1], tuple)
+ # assert len(jac[1]) == 1
+ # assert isinstance(jac[1][0], tf.Tensor)
+ # assert jac[1][0].shape == (2,)
+ # assert np.allclose(jac[1][0], expected[1][0], atol=tol, rtol=0)
+
+ def test_hessian(self, diff_method, device, tol):
+ """Test hessian computation."""
+ wires = 3 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method=diff_method, max_diff=2)
+ def circuit(x):
+ qml.RY(x[0], wires=0)
+ qml.RX(x[1], wires=0)
+ return qml.expval(qml.Z(0))
+
+ x = tf.Variable([1.0, 2.0], dtype=tf.float64)
+
+ with tf.GradientTape() as tape1:
+ with tf.GradientTape() as tape2:
+ res = circuit(x)
+ g = tape2.gradient(res, x)
+
+ hess = tape1.jacobian(g, 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_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 np.allclose(hess, expected_hess, atol=tol, rtol=0)
diff --git a/pennylane/devices/tests/test_gradients_torch.py b/pennylane/devices/tests/test_gradients_torch.py
new file mode 100644
index 00000000000..f4644535cb8
--- /dev/null
+++ b/pennylane/devices/tests/test_gradients_torch.py
@@ -0,0 +1,218 @@
+# Copyright 2024 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests trainable circuits using the Torch interface."""
+# pylint:disable=no-self-use,no-member
+import pytest
+
+import numpy as np
+
+import pennylane as qml
+
+torch = pytest.importorskip("torch")
+
+
+@pytest.mark.usefixtures("validate_diff_method")
+@pytest.mark.parametrize("diff_method", ["backprop", "parameter-shift", "hadamard"])
+class TestGradients:
+ """Test various gradient computations."""
+
+ def test_basic_grad(self, diff_method, device, tol):
+ """Test a basic function with one RX and one expectation."""
+ wires = 2 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ if diff_method == "hadamard":
+ tol += 0.01
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x):
+ qml.RX(x, 0)
+ return qml.expval(qml.Z(0))
+
+ x = torch.tensor(0.5, requires_grad=True)
+ res = circuit(x)
+ res.backward()
+ assert np.isclose(x.grad, -np.sin(x.detach()), atol=tol, rtol=0)
+
+ def test_backprop_state(self, diff_method, device, tol):
+ """Test the trainability of parameters in a circuit returning the state."""
+ if diff_method != "backprop":
+ pytest.skip(reason="test only works with backprop")
+ dev = device(2)
+ if dev.shots:
+ pytest.skip("test uses backprop, must be in analytic mode")
+ if "mixed" in dev.name:
+ pytest.skip("mixed-state simulator will wrongly use grad on non-scalar results")
+ tol = tol(dev.shots)
+
+ x = torch.tensor(0.543, requires_grad=True)
+ y = torch.tensor(-0.654, requires_grad=True)
+
+ @qml.qnode(dev, diff_method="backprop", grad_on_execution=True)
+ 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)
+ probs = torch.abs(res) ** 2
+ return probs[0] + probs[2]
+
+ res = cost_fn(x, y)
+ res.backward()
+ grad = [x.grad, y.grad]
+ x, y = x.detach(), y.detach()
+ expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
+ assert np.allclose(grad, expected, atol=tol, rtol=0)
+
+ def test_parameter_shift(self, diff_method, device, tol):
+ """Test a multi-parameter circuit with parameter-shift."""
+ if diff_method != "parameter-shift":
+ pytest.skip(reason="test only works with parameter-shift")
+
+ a = torch.tensor(0.1, requires_grad=True)
+ b = torch.tensor(0.2, requires_grad=True)
+
+ dev = device(2)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method="parameter-shift", grad_on_execution=False)
+ def circuit(a, b):
+ qml.RY(a, wires=0)
+ qml.RX(b, wires=1)
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Hamiltonian([1, 1], [qml.Z(0), qml.Y(1)]))
+
+ res = circuit(a, b)
+ res.backward()
+ grad = [a.grad, b.grad]
+ a, b = a.detach(), b.detach()
+ expected = [-np.sin(a) + np.sin(a) * np.sin(b), -np.cos(a) * np.cos(b)]
+ assert np.allclose(grad, expected, atol=tol, rtol=0)
+
+ def test_probs(self, diff_method, device, tol):
+ """Test differentiation of a circuit returning probs()."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = torch.tensor(0.543, requires_grad=True)
+ y = torch.tensor(-0.654, requires_grad=True)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ 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 = torch.autograd.functional.jacobian(circuit, (x, y))
+
+ x, y = x.detach(), y.detach()
+ expected = np.array(
+ [
+ [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2],
+ [np.cos(y) * np.sin(x) / 2, np.cos(x) * np.sin(y) / 2],
+ ]
+ )
+
+ assert isinstance(res, tuple)
+ assert len(res) == 2
+
+ assert isinstance(res[0], torch.Tensor)
+ assert res[0].shape == (2,)
+
+ assert isinstance(res[1], torch.Tensor)
+ assert res[1].shape == (2,)
+
+ if diff_method == "hadamard" and "raket" in dev.name:
+ pytest.xfail(reason="braket gets wrong results for hadamard here")
+ assert np.allclose(res[0], expected.T[0], atol=tol, rtol=0)
+ assert np.allclose(res[1], expected.T[1], atol=tol, rtol=0)
+
+ def test_multi_meas(self, diff_method, device, tol):
+ """Test differentiation of a circuit with both scalar and array-like returns."""
+ wires = 3 if diff_method == "hadamard" else 2
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+ x = torch.tensor(0.543, requires_grad=True)
+ y = torch.tensor(-0.654, requires_grad=True)
+
+ @qml.qnode(dev, diff_method=diff_method)
+ def circuit(x, y):
+ qml.RX(x, wires=[0])
+ qml.RY(y, wires=[1])
+ qml.CNOT(wires=[0, 1])
+ return qml.expval(qml.Z(0)), qml.probs(wires=[1])
+
+ jac = torch.autograd.functional.jacobian(circuit, (x, y))
+
+ x, y = x.detach(), y.detach()
+ expected = [
+ [-np.sin(x), 0],
+ [
+ [-np.sin(x) * np.cos(y) / 2, np.cos(y) * np.sin(x) / 2],
+ [-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], tuple)
+ assert len(jac[0]) == 2
+ assert all(isinstance(j, torch.Tensor) and j.shape == () for j in jac[0])
+ assert np.allclose(jac[0], expected[0], atol=tol, rtol=0)
+
+ assert isinstance(jac[1], tuple)
+ assert len(jac[1]) == 2
+ assert all(isinstance(j, torch.Tensor) and j.shape == (2,) for j in jac[1])
+ assert np.allclose(jac[1], expected[1], atol=tol, rtol=0)
+
+ def test_hessian(self, diff_method, device, tol):
+ """Test hessian computation."""
+ wires = 3 if diff_method == "hadamard" else 1
+ dev = device(wires=wires)
+ tol = tol(dev.shots)
+
+ @qml.qnode(dev, diff_method=diff_method, max_diff=2)
+ def circuit(x):
+ qml.RY(x[0], wires=0)
+ qml.RX(x[1], wires=0)
+ return qml.expval(qml.Z(0))
+
+ x = torch.tensor([1.0, 2.0], requires_grad=True)
+ res = circuit(x)
+
+ res.backward()
+ g = x.grad
+
+ hess = torch.autograd.functional.hessian(circuit, x)
+ a, b = x.detach().numpy()
+
+ assert isinstance(hess, torch.Tensor)
+ assert tuple(hess.shape) == (2, 2)
+
+ expected_res = np.cos(a) * np.cos(b)
+ assert np.allclose(res.detach(), expected_res, atol=tol, rtol=0)
+
+ expected_g = [-np.sin(a) * np.cos(b), -np.cos(a) * np.sin(b)]
+ assert np.allclose(g.detach(), expected_g, atol=tol, rtol=0)
+
+ 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 np.allclose(hess.detach(), expected_hess, atol=tol, rtol=0)
diff --git a/pennylane/devices/tests/test_measurements.py b/pennylane/devices/tests/test_measurements.py
index d0362451a50..393d498c5ea 100644
--- a/pennylane/devices/tests/test_measurements.py
+++ b/pennylane/devices/tests/test_measurements.py
@@ -51,13 +51,18 @@
],
"SparseHamiltonian": qml.SparseHamiltonian(csr_matrix(np.eye(8)), wires=[0, 1, 2]),
"Hamiltonian": qml.Hamiltonian([1, 1], [qml.Z(0), qml.X(0)]),
+ "Prod": qml.prod(qml.X(0), qml.Z(1)),
+ "SProd": qml.s_prod(0.1, qml.Z(0)),
+ "Sum": qml.sum(qml.s_prod(0.1, qml.Z(0)), qml.prod(qml.X(0), qml.Z(1))),
"LinearCombination": qml.ops.LinearCombination([1, 1], [qml.Z(0), qml.X(0)]),
}
all_obs = obs.keys()
# All qubit observables should be available to test in the device test suite
-all_available_obs = qml.ops._qubit__obs__.copy() # pylint: disable=protected-access
+all_available_obs = qml.ops._qubit__obs__.copy().union( # pylint: disable=protected-access
+ {"Prod", "SProd", "Sum"}
+)
# Note that the identity is not technically a qubit observable
all_available_obs |= {"Identity"}
@@ -408,6 +413,30 @@ def circuit():
assert np.allclose(res, expected, atol=tol(dev.shots))
+ @pytest.mark.parametrize(
+ "o",
+ [
+ qml.prod(qml.X(0), qml.Z(1)),
+ qml.s_prod(0.1, qml.Z(0)),
+ qml.sum(qml.s_prod(0.1, qml.Z(0)), qml.prod(qml.X(0), qml.Z(1))),
+ ],
+ )
+ def test_op_arithmetic_matches_default_qubit(self, o, device, tol):
+ """Test that devices (which support the observable) match default.qubit results."""
+ dev = device(2)
+ if isinstance(dev, qml.Device) and o.name not in dev.observables:
+ pytest.skip(f"Skipped because device does not support the {o.name} observable.")
+
+ def circuit():
+ qml.Hadamard(0)
+ qml.CNOT([0, 1])
+ return qml.expval(o)
+
+ res_dq = qml.QNode(circuit, qml.device("default.qubit"))()
+ res = qml.QNode(circuit, dev)()
+ assert res.shape == ()
+ assert np.isclose(res, res_dq, atol=tol(dev.shots))
+
@flaky(max_runs=10)
class TestTensorExpval:
diff --git a/pennylane/devices/tests/test_templates.py b/pennylane/devices/tests/test_templates.py
new file mode 100644
index 00000000000..434e6210a8a
--- /dev/null
+++ b/pennylane/devices/tests/test_templates.py
@@ -0,0 +1,1006 @@
+# Copyright 2024 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Tests that various templates work correctly on a device."""
+# pylint: disable=no-self-use
+
+# Can generate a list of all templates using the following code:
+#
+# from inspect import getmembers, isclass
+# all_templates = [i for (_, i) in getmembers(qml.templates) if isclass(i) and issubclass(i, qml.operation.Operator)]
+
+from functools import partial
+import pytest
+import numpy as np
+from scipy.stats import norm
+
+import pennylane as qml
+from pennylane import math
+
+
+pytestmark = pytest.mark.skip_unsupported
+
+
+def check_op_supported(op, dev):
+ """Skip test if device does not support an operation. Works with both device APIs"""
+ if isinstance(dev, qml.Device):
+ if op.name not in dev.operations:
+ pytest.skip("operation not supported.")
+ else:
+ prog, _ = dev.preprocess()
+ tape = qml.tape.QuantumScript([op])
+ try:
+ prog((tape,))
+ except qml.DeviceError:
+ pytest.skip("operation not supported on the device")
+
+
+class TestTemplates: # pylint:disable=too-many-public-methods
+ """Test various templates."""
+
+ def test_AQFT(self, device, tol):
+ """Test the AQFT template."""
+ wires = 3
+ dev = device(wires=wires)
+
+ @qml.qnode(dev)
+ def circuit_aqft():
+ qml.X(0)
+ qml.Hadamard(1)
+ qml.AQFT(order=1, wires=range(wires))
+ return qml.probs()
+
+ expected = [0.25, 0.125, 0.0, 0.125, 0.25, 0.125, 0.0, 0.125]
+ assert np.allclose(circuit_aqft(), expected, atol=tol(dev.shots))
+
+ def test_AllSinglesDoubles(self, device, tol):
+ """Test the AllSinglesDoubles template."""
+ qubits = 4
+ dev = device(qubits)
+
+ electrons = 2
+
+ # Define the HF state
+ hf_state = qml.qchem.hf_state(electrons, qubits)
+
+ # Generate all single and double excitations
+ singles, doubles = qml.qchem.excitations(electrons, qubits)
+
+ wires = range(qubits)
+
+ @qml.qnode(dev)
+ def circuit(weights, hf_state, singles, doubles):
+ qml.AllSinglesDoubles(weights, wires, hf_state, singles, doubles)
+ return qml.expval(qml.Z(0))
+
+ # Evaluate the QNode for a given set of parameters
+ params = np.array([0.12, 1.23, 2.34])
+ res = circuit(params, hf_state, singles=singles, doubles=doubles)
+ assert np.isclose(res, 0.6905612772956113, atol=tol(dev.shots))
+
+ def test_AmplitudeEmbedding(self, device, tol):
+ """Test the AmplitudeEmbedding template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit(f):
+ qml.AmplitudeEmbedding(features=f, wires=range(2))
+ return qml.probs()
+
+ res = circuit([1 / 2] * 4)
+ expected = [1 / 4] * 4
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_AngleEmbedding(self, device, tol):
+ """Test the AngleEmbedding template."""
+ n_wires = 3
+ dev = device(n_wires)
+
+ @qml.qnode(dev)
+ def circuit(feature_vector):
+ qml.AngleEmbedding(features=feature_vector, wires=range(n_wires), rotation="X")
+ qml.Hadamard(0)
+ return qml.probs(wires=range(3))
+
+ x = [np.pi / 2] * 3
+ res = circuit(x)
+ expected = [0.125] * 8
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_ApproxTimeEvolution(self, device, tol):
+ """Test the ApproxTimeEvolution template."""
+ n_wires = 2
+ dev = device(n_wires)
+ wires = range(n_wires)
+
+ coeffs = [1, 1]
+ obs = [qml.X(0), qml.X(1)]
+ hamiltonian = qml.Hamiltonian(coeffs, obs)
+
+ @qml.qnode(dev)
+ def circuit(time):
+ qml.ApproxTimeEvolution(hamiltonian, time, 1)
+ return [qml.expval(qml.Z(i)) for i in wires]
+
+ res = circuit(1)
+ expected = [-0.41614684, -0.41614684]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_ArbitraryStatePreparation(self, device, tol):
+ """Test the ArbitraryStatePreparation template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.ArbitraryStatePreparation(weights, wires=[0, 1])
+ return qml.probs()
+
+ weights = np.arange(1, 7) / 10
+ res = circuit(weights)
+ expected = [0.784760658335564, 0.0693785880617069, 0.00158392607496555, 0.1442768275277600]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_ArbitraryUnitary(self, device, tol):
+ """Test the ArbitraryUnitary template."""
+ dev = device(1)
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.ArbitraryUnitary(weights, wires=[0])
+ return qml.probs()
+
+ weights = np.arange(3)
+ res = circuit(weights)
+ expected = [0.77015115293406, 0.22984884706593]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_BasicEntanglerLayers(self, device, tol):
+ """Test the BasicEntanglerLayers template."""
+ n_wires = 3
+ dev = device(n_wires)
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.BasicEntanglerLayers(weights=weights, wires=range(n_wires))
+ return [qml.expval(qml.Z(i)) for i in range(n_wires)]
+
+ params = [[np.pi, np.pi, np.pi]]
+ res = circuit(params)
+ expected = [1.0, 1.0, -1.0]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_BasisEmbedding(self, device, tol):
+ """Test the BasisEmbedding template."""
+ dev = device(3)
+
+ @qml.qnode(dev)
+ def circuit(basis):
+ qml.BasisEmbedding(basis, wires=range(3))
+ return qml.probs()
+
+ basis = (1, 0, 1)
+ res = circuit(basis)
+
+ basis_idx = np.dot(basis, 2 ** np.arange(3))
+ expected = np.zeros(8)
+ expected[basis_idx] = 1.0
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_BasisRotation(self, device, tol):
+ """Test the BasisRotation template."""
+ dev = device(2)
+ if dev.shots or "mixed" in dev.name or "Mixed" in dev.name:
+ pytest.skip("test only works with analytic-mode pure statevector simulators")
+
+ unitary_matrix = np.array(
+ [
+ [-0.77228482 + 0.0j, -0.02959195 + 0.63458685j],
+ [0.63527644 + 0.0j, -0.03597397 + 0.77144651j],
+ ]
+ )
+ eigen_values = np.array([-1.45183325, 3.47550075])
+ exp_state = np.array([0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, -0.43754907 - 0.89919453j])
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.PauliX(0)
+ qml.PauliX(1)
+ qml.adjoint(qml.BasisRotation(wires=[0, 1], unitary_matrix=unitary_matrix))
+ for idx, eigenval in enumerate(eigen_values):
+ qml.RZ(-eigenval, wires=[idx])
+ qml.BasisRotation(wires=[0, 1], unitary_matrix=unitary_matrix)
+ return qml.state()
+
+ assert np.allclose(
+ [math.fidelity_statevector(circuit(), exp_state)], [1.0], atol=tol(dev.shots)
+ )
+
+ def test_BasisStatePreparation(self, device, tol):
+ """Test the BasisStatePreparation template."""
+ dev = device(4)
+
+ @qml.qnode(dev)
+ def circuit(basis_state):
+ qml.BasisStatePreparation(basis_state, wires=range(4))
+ return [qml.expval(qml.Z(i)) for i in range(4)]
+
+ basis_state = [0, 1, 1, 0]
+ res = circuit(basis_state)
+ expected = [1.0, -1.0, -1.0, 1.0]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ @pytest.mark.xfail(reason="most devices do not support CV")
+ def test_CVNeuralNetLayers(self, device):
+ """Test the CVNeuralNetLayers template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.CVNeuralNetLayers(*weights, wires=[0, 1])
+ return qml.expval(qml.QuadX(0))
+
+ shapes = qml.CVNeuralNetLayers.shape(n_layers=2, n_wires=2)
+ weights = [np.random.random(shape) for shape in shapes]
+
+ circuit(weights)
+
+ def test_CommutingEvolution(self, device, tol):
+ """Test the CommutingEvolution template."""
+ n_wires = 2
+ dev = device(n_wires)
+ coeffs = [1, -1]
+ obs = [qml.X(0) @ qml.Y(1), qml.Y(0) @ qml.X(1)]
+ hamiltonian = qml.Hamiltonian(coeffs, obs)
+ frequencies = (2, 4)
+
+ @qml.qnode(dev)
+ def circuit(time):
+ qml.X(0)
+ qml.CommutingEvolution(hamiltonian, time, frequencies)
+ return qml.expval(qml.Z(0))
+
+ res = circuit(1)
+ expected = 0.6536436208636115
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_ControlledSequence(self, device, tol):
+ """Test the ControlledSequence template."""
+ dev = device(4)
+
+ @qml.qnode(dev)
+ def circuit():
+ for i in range(3):
+ qml.Hadamard(wires=i)
+ qml.ControlledSequence(qml.RX(0.25, wires=3), control=[0, 1, 2])
+ qml.adjoint(qml.QFT)(wires=range(3))
+ return qml.probs(wires=range(3))
+
+ res = circuit()
+ expected = [
+ 0.92059345,
+ 0.02637178,
+ 0.00729619,
+ 0.00423258,
+ 0.00360545,
+ 0.00423258,
+ 0.00729619,
+ 0.02637178,
+ ]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_CosineWindow(self, device, tol):
+ """Test the CosineWindow template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.CosineWindow(wires=range(2))
+ return qml.probs()
+
+ res = circuit()
+ expected = [0.0, 0.25, 0.5, 0.25]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ @pytest.mark.xfail(reason="most devices do not support CV")
+ def test_DisplacementEmbedding(self, device, tol):
+ """Test the DisplacementEmbedding template."""
+ dev = device(3)
+
+ @qml.qnode(dev)
+ def circuit(feature_vector):
+ qml.DisplacementEmbedding(features=feature_vector, wires=range(3))
+ qml.QuadraticPhase(0.1, wires=1)
+ return qml.expval(qml.NumberOperator(wires=1))
+
+ X = [1, 2, 3]
+
+ res = circuit(X)
+ expected = 4.1215690638748494
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_FermionicDoubleExcitation(self, device, tol):
+ """Test the FermionicDoubleExcitation template."""
+ dev = device(5)
+ if getattr(dev, "short_name", None) == "cirq.mixedsimulator" and dev.shots:
+ pytest.xfail(reason="device is generating negative probabilities")
+
+ @qml.qnode(dev)
+ def circuit(weight, wires1=None, wires2=None):
+ qml.FermionicDoubleExcitation(weight, wires1=wires1, wires2=wires2)
+ return qml.expval(qml.Z(0))
+
+ res = circuit(1.34817, wires1=[0, 1], wires2=[2, 3, 4])
+ expected = 1.0
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_FermionicSingleExcitation(self, device, tol):
+ """Test the FermionicSingleExcitation template."""
+ dev = device(3)
+ if getattr(dev, "short_name", None) == "cirq.mixedsimulator" and dev.shots:
+ pytest.xfail(reason="device is generating negative probabilities")
+
+ @qml.qnode(dev)
+ def circuit(weight, wires=None):
+ qml.FermionicSingleExcitation(weight, wires=wires)
+ return qml.expval(qml.Z(0))
+
+ res = circuit(0.56, wires=[0, 1, 2])
+ expected = 1.0
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_FlipSign(self, device, tol):
+ """Test the FlipSign template."""
+ dev = device(2)
+ if dev.shots:
+ pytest.skip("test only works with analytic-mode simulations")
+ basis_state = [1, 0]
+
+ @qml.qnode(dev)
+ def circuit():
+ for wire in list(range(2)):
+ qml.Hadamard(wires=wire)
+ qml.FlipSign(basis_state, wires=list(range(2)))
+ return qml.state()
+
+ res = circuit()
+ expected = [0.5, 0.5, -0.5, 0.5]
+ if "mixed" in dev.name or "Mixed" in dev.name:
+ expected = math.dm_from_state_vector(expected)
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_GroverOperator(self, device, tol):
+ """Test the GroverOperator template."""
+ n_wires = 3
+ dev = device(n_wires)
+ wires = list(range(n_wires))
+
+ def oracle():
+ qml.Hadamard(wires[-1])
+ qml.Toffoli(wires=wires)
+ qml.Hadamard(wires[-1])
+
+ @qml.qnode(dev)
+ def circuit(num_iterations=1):
+ for wire in wires:
+ qml.Hadamard(wire)
+
+ for _ in range(num_iterations):
+ oracle()
+ qml.GroverOperator(wires=wires)
+ return qml.probs(wires)
+
+ res = circuit(num_iterations=2)
+ expected = [
+ 0.0078125,
+ 0.0078125,
+ 0.0078125,
+ 0.0078125,
+ 0.0078125,
+ 0.0078125,
+ 0.0078125,
+ 0.9453125,
+ ]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_HilbertSchmidt(self, device, tol):
+ """Test the HilbertSchmidt template."""
+ dev = device(2)
+ u_tape = qml.tape.QuantumScript([qml.Hadamard(0)])
+
+ def v_function(params):
+ qml.RZ(params[0], wires=1)
+
+ @qml.qnode(dev)
+ def hilbert_test(v_params, v_function, v_wires, u_tape):
+ qml.HilbertSchmidt(v_params, v_function=v_function, v_wires=v_wires, u_tape=u_tape)
+ return qml.probs(u_tape.wires + v_wires)
+
+ def cost_hst(parameters, v_function, v_wires, u_tape):
+ # pylint:disable=unsubscriptable-object
+ return (
+ 1
+ - hilbert_test(
+ v_params=parameters, v_function=v_function, v_wires=v_wires, u_tape=u_tape
+ )[0]
+ )
+
+ res = cost_hst([0], v_function=v_function, v_wires=[1], u_tape=u_tape)
+ expected = 1.0
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_IQPEmbedding(self, device, tol):
+ """Test the IQPEmbedding template."""
+ dev = device(3)
+
+ @qml.qnode(dev)
+ def circuit(features):
+ qml.IQPEmbedding(features, wires=range(3), n_repeats=4)
+ return [qml.expval(qml.Z(w)) for w in range(3)]
+
+ res = circuit([1.0, 2.0, 3.0])
+ expected = [0.40712208, 0.32709118, 0.89125407]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ @pytest.mark.xfail(reason="most devices do not support CV")
+ def test_Interferometer(self, device):
+ """Test the Interferometer template."""
+ dev = device(4)
+
+ @qml.qnode(dev)
+ def circuit(params):
+ qml.Interferometer(*params, wires=range(4))
+ return qml.expval(qml.Identity(0))
+
+ shapes = [[6], [6], [4]]
+ params = []
+ for shape in shapes:
+ params.append(np.random.random(shape))
+
+ _ = circuit(params)
+
+ def test_LocalHilbertSchmidt(self, device, tol):
+ """Test the LocalHilbertSchmidt template."""
+ dev = device(4)
+ u_tape = qml.tape.QuantumScript([qml.CZ(wires=(0, 1))])
+
+ def v_function(params):
+ qml.RZ(params[0], wires=2)
+ qml.RZ(params[1], wires=3)
+ qml.CNOT(wires=[2, 3])
+ qml.RZ(params[2], wires=3)
+ qml.CNOT(wires=[2, 3])
+
+ @qml.qnode(dev)
+ def local_hilbert_test(v_params, v_function, v_wires, u_tape):
+ qml.LocalHilbertSchmidt(v_params, v_function=v_function, v_wires=v_wires, u_tape=u_tape)
+ return qml.probs(u_tape.wires + v_wires)
+
+ def cost_lhst(parameters, v_function, v_wires, u_tape):
+ # pylint:disable=unsubscriptable-object
+ return (
+ 1
+ - local_hilbert_test(
+ v_params=parameters, v_function=v_function, v_wires=v_wires, u_tape=u_tape
+ )[0]
+ )
+
+ res = cost_lhst(
+ [3 * np.pi / 2, 3 * np.pi / 2, np.pi / 2],
+ v_function=v_function,
+ v_wires=[2, 3],
+ u_tape=u_tape,
+ )
+ expected = 0.5
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_MERA(self, device, tol):
+ """Test the MERA template."""
+
+ def block(weights, wires):
+ qml.CNOT(wires=[wires[0], wires[1]])
+ qml.RY(weights[0], wires=wires[0])
+ qml.RY(weights[1], wires=wires[1])
+
+ n_wires = 4
+ n_block_wires = 2
+ n_params_block = 2
+ n_blocks = qml.MERA.get_n_blocks(range(n_wires), n_block_wires)
+ template_weights = [[0.1, -0.3]] * n_blocks
+ dev = device(n_wires)
+
+ @qml.qnode(dev)
+ def circuit(template_weights):
+ qml.MERA(range(n_wires), n_block_wires, block, n_params_block, template_weights)
+ return qml.expval(qml.Z(1))
+
+ res = circuit(template_weights)
+ expected = 0.799260896638786
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_MPS(self, device, tol):
+ """Test the MPS template."""
+
+ def block(weights, wires):
+ qml.CNOT(wires=[wires[0], wires[1]])
+ qml.RY(weights[0], wires=wires[0])
+ qml.RY(weights[1], wires=wires[1])
+
+ n_wires = 4
+ n_block_wires = 2
+ n_params_block = 2
+ n_blocks = qml.MPS.get_n_blocks(range(n_wires), n_block_wires)
+ template_weights = [[0.1, -0.3]] * n_blocks
+ dev = device(n_wires)
+
+ @qml.qnode(dev)
+ def circuit(template_weights):
+ qml.MPS(range(n_wires), n_block_wires, block, n_params_block, template_weights)
+ return qml.expval(qml.Z(n_wires - 1))
+
+ res = circuit(template_weights)
+ expected = 0.8719048589118708
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_MottonenStatePreparation_probs(self, device, tol):
+ """Test the MottonenStatePreparation template (up to a phase)."""
+ dev = device(3)
+
+ @qml.qnode(dev)
+ def circuit(state):
+ qml.MottonenStatePreparation(state_vector=state, wires=range(3))
+ return qml.probs()
+
+ state = np.array([1, 2j, 3, 4j, 5, 6j, 7, 8j])
+ state = state / np.linalg.norm(state)
+ res = circuit(state)
+ expected = np.abs(state**2)
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_MottonenStatePreparation_state(self, device, tol):
+ """Test the MottonenStatePreparation template on analytic-mode devices."""
+ dev = device(3)
+ if dev.shots:
+ pytest.skip("test only works with analytic-mode simulations")
+
+ @qml.qnode(dev)
+ def circuit(state):
+ qml.MottonenStatePreparation(state_vector=state, wires=range(3))
+ return qml.state()
+
+ state = np.array([1, 2j, 3, 4j, 5, 6j, 7, 8j])
+ state = state / np.linalg.norm(state)
+ res = circuit(state)
+ expected = state
+ if "mixed" in dev.name or "Mixed" in dev.name:
+ expected = math.dm_from_state_vector(expected)
+ if np.allclose(res, expected, atol=tol(dev.shots)):
+ # GlobalPhase supported
+ return
+ # GlobalPhase not supported
+ global_phase = qml.math.sum(-1 * qml.math.angle(expected) / len(expected))
+ global_phase = np.exp(-1j * global_phase)
+ assert np.allclose(expected / res, global_phase)
+
+ def test_Permute(self, device, tol):
+ """Test the Permute template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.StatePrep([1 / np.sqrt(2), 0.4, 0.5, 0.3], wires=[0, 1])
+ qml.Permute([1, 0], [0, 1])
+ return qml.probs()
+
+ res = circuit()
+ expected = [0.5, 0.25, 0.16, 0.09]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_QAOAEmbedding(self, device, tol):
+ """Test the QAOAEmbedding template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit(weights, f=None):
+ qml.QAOAEmbedding(features=f, weights=weights, wires=range(2))
+ return qml.expval(qml.Z(0))
+
+ features = [1.0, 2.0]
+ layer1 = [0.1, -0.3, 1.5]
+ layer2 = [3.1, 0.2, -2.8]
+ weights = [layer1, layer2]
+
+ res = circuit(weights, f=features)
+ expected = 0.49628561029741747
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_QDrift(self, device, tol):
+ """Test the QDrift template."""
+ dev = device(2)
+ coeffs = [0.25, 0.75]
+ ops = [qml.X(0), qml.Z(0)]
+ H = qml.dot(coeffs, ops)
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.Hadamard(0)
+ qml.QDrift(H, time=1.2, n=10, seed=10)
+ return qml.probs()
+
+ res = circuit()
+ expected = [0.65379493, 0.0, 0.34620507, 0.0]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_QFT(self, device, tol):
+ """Test the QFT template."""
+ wires = 3
+ dev = device(wires)
+
+ @qml.qnode(dev)
+ def circuit_qft(state):
+ qml.StatePrep(state, wires=range(wires))
+ qml.QFT(wires=range(wires))
+ return qml.probs()
+
+ res = circuit_qft([0.8, 0.6] + [0.0] * 6)
+ expected = [0.245, 0.20985281, 0.125, 0.04014719, 0.005, 0.04014719, 0.125, 0.20985281]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_QSVT(self, device, tol):
+ """Test the QSVT template."""
+ dev = device(2)
+ A = np.array([[0.1]])
+ block_encode = qml.BlockEncode(A, wires=[0, 1])
+ check_op_supported(block_encode, dev)
+ shifts = [qml.PCPhase(i + 0.1, dim=1, wires=[0, 1]) for i in range(3)]
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.QSVT(block_encode, shifts)
+ return qml.expval(qml.Z(1))
+
+ res = circuit()
+ expected = 0.9370953557566887
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_QuantumMonteCarlo(self, device, tol):
+ """Test the QuantumMonteCarlo template."""
+ m = 2
+ M = 2**m
+
+ xmax = np.pi # bound to region [-pi, pi]
+ xs = np.linspace(-xmax, xmax, M)
+
+ probs = np.array([norm().pdf(x) for x in xs])
+ probs /= np.sum(probs)
+
+ def func(i):
+ return np.sin(xs[i]) ** 2
+
+ n = 3
+ N = 2**n
+
+ target_wires = range(m + 1)
+ estimation_wires = range(m + 1, n + m + 1)
+ dev = device(wires=n + m + 1)
+ check_op_supported(qml.ControlledQubitUnitary(np.eye(2), [1], [0]), dev)
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.QuantumMonteCarlo(
+ probs,
+ func,
+ target_wires=target_wires,
+ estimation_wires=estimation_wires,
+ )
+ return qml.probs(estimation_wires)
+
+ # pylint:disable=unsubscriptable-object
+ phase_estimated = np.argmax(circuit()[: int(N / 2)]) / N
+ res = (1 - np.cos(np.pi * phase_estimated)) / 2
+ expected = 0.3086582838174551
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_QuantumPhaseEstimation(self, device, tol):
+ """Test the QuantumPhaseEstimation template."""
+ unitary = qml.RX(np.pi / 2, wires=[0]) @ qml.CNOT(wires=[0, 1])
+ eigenvector = np.array([-1 / 2, -1 / 2, 1 / 2, 1 / 2])
+
+ n_estimation_wires = 3
+ estimation_wires = range(2, n_estimation_wires + 2)
+ target_wires = [0, 1]
+
+ dev = device(wires=n_estimation_wires + 2)
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.StatePrep(eigenvector, wires=target_wires)
+ qml.QuantumPhaseEstimation(
+ unitary,
+ estimation_wires=estimation_wires,
+ )
+ return qml.probs(estimation_wires)
+
+ res = np.argmax(circuit()) / 2**n_estimation_wires
+ expected = 0.125
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_QutritBasisStatePreparation(self, device, tol):
+ """Test the QutritBasisStatePreparation template."""
+ dev = device(4)
+ if "qutrit" not in dev.name:
+ pytest.skip("QutritBasisState template only works on qutrit devices")
+
+ @qml.qnode(dev)
+ def circuit(basis_state, obs):
+ qml.QutritBasisStatePreparation(basis_state, wires=range(4))
+ return [qml.expval(qml.THermitian(obs, wires=i)) for i in range(4)]
+
+ basis_state = [0, 1, 1, 0]
+ obs = np.array([[1, 1, 0], [1, -1, 0], [0, 0, np.sqrt(2)]]) / np.sqrt(2)
+
+ res = circuit(basis_state, obs)
+ expected = np.array([1, -1, -1, 1]) / np.sqrt(2)
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_RandomLayers(self, device, tol):
+ """Test the RandomLayers template."""
+ dev = device(2)
+ weights = np.array([[0.1, -2.1, 1.4]])
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.RandomLayers(weights=weights, wires=range(2), seed=42)
+ return qml.expval(qml.Z(0))
+
+ res = circuit(weights)
+ expected = 0.9950041652780259
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_Select(self, device, tol):
+ """Test the Select template."""
+ dev = device(4)
+ check_op_supported(qml.MultiControlledX(wires=[0, 1, 2]), dev)
+
+ ops = [qml.X(2), qml.X(3), qml.Y(2), qml.SWAP([2, 3])]
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.Select(ops, control=[0, 1])
+ return qml.probs()
+
+ res = circuit()
+ expected = np.zeros(16)
+ expected[2] = 1.0
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_SimplifiedTwoDesign(self, device, tol):
+ """Test the SimplifiedTwoDesign template."""
+ n_wires = 3
+ dev = device(n_wires)
+
+ @qml.qnode(dev)
+ def circuit(init_weights, weights):
+ qml.SimplifiedTwoDesign(
+ initial_layer_weights=init_weights, weights=weights, wires=range(n_wires)
+ )
+ return [qml.expval(qml.Z(i)) for i in range(n_wires)]
+
+ init_weights = [np.pi, np.pi, np.pi]
+ weights_layer1 = [[0.0, np.pi], [0.0, np.pi]]
+ weights_layer2 = [[np.pi, 0.0], [np.pi, 0.0]]
+ weights = [weights_layer1, weights_layer2]
+
+ res = circuit(init_weights, weights)
+ expected = [1.0, -1.0, 1.0]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ @pytest.mark.xfail(reason="most devices do not support CV")
+ def test_SqueezingEmbedding(self, device, tol):
+ """Test the SqueezingEmbedding template."""
+ dev = device(2)
+
+ @qml.qnode(dev)
+ def circuit(feature_vector):
+ qml.SqueezingEmbedding(features=feature_vector, wires=range(3))
+ qml.QuadraticPhase(0.1, wires=1)
+ return qml.expval(qml.NumberOperator(wires=1))
+
+ X = [1, 2, 3]
+
+ res = circuit(X)
+ expected = 13.018280763205285
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_StronglyEntanglingLayers(self, device, tol):
+ """Test the StronglyEntanglingLayers template."""
+ dev = device(4)
+
+ @qml.qnode(dev)
+ def circuit(parameters):
+ qml.StronglyEntanglingLayers(weights=parameters, wires=range(4))
+ return qml.expval(qml.Z(0))
+
+ shape = qml.StronglyEntanglingLayers.shape(n_layers=2, n_wires=4)
+ params = np.arange(1, np.prod(shape) + 1).reshape(shape) / 10
+ res = circuit(params)
+ expected = -0.07273693957824906
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_TTN(self, device, tol):
+ """Test the TTN template."""
+
+ def block(weights, wires):
+ qml.CNOT(wires=[wires[0], wires[1]])
+ qml.RY(weights[0], wires=wires[0])
+ qml.RY(weights[1], wires=wires[1])
+
+ n_wires = 4
+ n_block_wires = 2
+ n_params_block = 2
+ n_blocks = qml.TTN.get_n_blocks(range(n_wires), n_block_wires)
+ template_weights = [[0.1, -0.3]] * n_blocks
+ dev = device(n_wires)
+
+ @qml.qnode(dev)
+ def circuit(template_weights):
+ qml.TTN(range(n_wires), n_block_wires, block, n_params_block, template_weights)
+ return qml.expval(qml.Z(n_wires - 1))
+
+ res = circuit(template_weights)
+ expected = 0.7845726663667097
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_TrotterProduct(self, device, tol):
+ """Test the TrotterProduct template."""
+ dev = device(2)
+ coeffs = [0.25, 0.75]
+ ops = [qml.X(0), qml.Z(0)]
+ H = qml.dot(coeffs, ops)
+
+ @qml.qnode(dev)
+ def circuit():
+ qml.Hadamard(0)
+ qml.TrotterProduct(H, time=2.4, order=2)
+ return qml.probs()
+
+ res = circuit()
+ expected = [0.37506708, 0.0, 0.62493292, 0.0]
+ assert np.allclose(res, expected, atol=tol(dev.shots))
+
+ def test_TwoLocalSwapNetwork(self, device, tol):
+ """Test the TwoLocalSwapNetwork template."""
+ dev = device(3)
+
+ def acquaintances(index, wires, param=None): # pylint:disable=unused-argument
+ return qml.CNOT(index)
+
+ @qml.qnode(dev)
+ def circuit(state):
+ qml.StatePrep(state, range(3))
+ qml.TwoLocalSwapNetwork(dev.wires, acquaintances, fermionic=True, shift=False)
+ return qml.probs()
+
+ state = np.arange(8, dtype=float)
+ state /= np.linalg.norm(state)
+ probs = state**2
+ res = circuit(state)
+ order = np.argsort(np.argsort(res))
+ tol = tol(dev.shots)
+ assert all(np.isclose(val, probs[i], atol=tol) for i, val in zip(order, res))
+
+
+class TestMoleculeTemplates:
+ """Test templates using the H2 molecule."""
+
+ @pytest.fixture(scope="class")
+ def h2(self):
+ """Return attributes needed for H2."""
+ symbols, coordinates = (["H", "H"], np.array([0.0, 0.0, -0.66140414, 0.0, 0.0, 0.66140414]))
+ h, qubits = qml.qchem.molecular_hamiltonian(symbols, coordinates)
+ electrons = 2
+ ref_state = qml.qchem.hf_state(electrons, qubits)
+ return qubits, ref_state, h
+
+ def test_GateFabric(self, device, tol, h2):
+ """Test the GateFabric template."""
+ qubits, ref_state, H = h2
+ dev = device(qubits)
+ if getattr(dev, "short_name", None) == "cirq.mixedsimulator" and dev.shots:
+ pytest.xfail(reason="device is generating negative probabilities")
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.GateFabric(weights, wires=[0, 1, 2, 3], init_state=ref_state, include_pi=True)
+ return qml.expval(H)
+
+ layers = 2
+ shape = qml.GateFabric.shape(n_layers=layers, n_wires=qubits)
+ weights = np.array([0.1, 0.2, 0.3, 0.4]).reshape(shape)
+ res = circuit(weights)
+ expected = -0.9453094224618628
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_ParticleConservingU1(self, device, tol, h2):
+ """Test the ParticleConservingU1 template."""
+ qubits, ref_state, h = h2
+ dev = device(qubits)
+ ansatz = partial(qml.ParticleConservingU1, init_state=ref_state, wires=dev.wires)
+
+ @qml.qnode(dev)
+ def circuit(params):
+ ansatz(params)
+ return qml.expval(h)
+
+ layers = 2
+ shape = qml.ParticleConservingU1.shape(layers, qubits)
+ params = np.arange(1, 13).reshape(shape) / 10
+ res = circuit(params)
+ expected = -0.5669084184194393
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_ParticleConservingU2(self, device, tol, h2):
+ """Test the ParticleConservingU2 template."""
+ qubits, ref_state, h = h2
+ dev = device(qubits)
+ ansatz = partial(qml.ParticleConservingU2, init_state=ref_state, wires=dev.wires)
+
+ @qml.qnode(dev)
+ def circuit(params):
+ ansatz(params)
+ return qml.expval(h)
+
+ layers = 1
+ shape = qml.ParticleConservingU2.shape(layers, qubits)
+ params = np.arange(1, 8).reshape(shape) / 10
+ res = circuit(params)
+ expected = -0.8521967086461301
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_UCCSD(self, device, tol, h2):
+ """Test the UCCSD template."""
+ qubits, hf_state, H = h2
+ electrons = 2
+ singles, doubles = qml.qchem.excitations(electrons, qubits)
+ s_wires, d_wires = qml.qchem.excitations_to_wires(singles, doubles)
+ dev = device(qubits)
+
+ @qml.qnode(dev)
+ def circuit(params, wires, s_wires, d_wires, hf_state):
+ qml.UCCSD(params, wires, s_wires, d_wires, hf_state)
+ return qml.expval(H)
+
+ params = np.arange(len(singles) + len(doubles)) / 4
+ res = circuit(
+ params, wires=range(qubits), s_wires=s_wires, d_wires=d_wires, hf_state=hf_state
+ )
+ expected = -1.0864433121798176
+ assert np.isclose(res, expected, atol=tol(dev.shots))
+
+ def test_kUpCCGSD(self, device, tol, h2):
+ """Test the kUpCCGSD template."""
+ qubits, ref_state, H = h2
+ dev = device(qubits)
+ if getattr(dev, "short_name", None) == "cirq.mixedsimulator" and dev.shots:
+ pytest.xfail(reason="device is generating negative probabilities")
+
+ @qml.qnode(dev)
+ def circuit(weights):
+ qml.kUpCCGSD(weights, wires=[0, 1, 2, 3], k=1, delta_sz=0, init_state=ref_state)
+ return qml.expval(H)
+
+ layers = 1
+ shape = qml.kUpCCGSD.shape(k=layers, n_wires=qubits, delta_sz=0)
+ weights = np.arange(np.prod(shape)).reshape(shape) / 10
+ res = circuit(weights)
+ expected = -1.072648130451027
+ assert np.isclose(res, expected, atol=tol(dev.shots))
diff --git a/pennylane/drawer/tape_text.py b/pennylane/drawer/tape_text.py
index 8e635ef50ea..7069e4749fe 100644
--- a/pennylane/drawer/tape_text.py
+++ b/pennylane/drawer/tape_text.py
@@ -142,7 +142,8 @@ def _add_op(op, layer_str, config):
label = op.label(decimals=config.decimals, cache=config.cache).replace("\n", "")
if len(op.wires) == 0: # operation (e.g. barrier, snapshot) across all wires
- for i, s in enumerate(layer_str):
+ n_wires = len(config.wire_map)
+ for i, s in enumerate(layer_str[:n_wires]):
layer_str[i] = s + label
else:
for w in op.wires:
@@ -225,7 +226,8 @@ def _add_measurement(m, layer_str, config):
meas_label = m.return_type.value
if len(m.wires) == 0: # state or probability across all wires
- for i, s in enumerate(layer_str):
+ n_wires = len(config.wire_map)
+ for i, s in enumerate(layer_str[:n_wires]):
layer_str[i] = s + meas_label
for w in m.wires:
diff --git a/pennylane/optimize/qnspsa.py b/pennylane/optimize/qnspsa.py
index be183d285cd..da1df886e86 100644
--- a/pennylane/optimize/qnspsa.py
+++ b/pennylane/optimize/qnspsa.py
@@ -439,10 +439,16 @@ def _apply_blocking(self, cost, args, kwargs, params_next):
cost.construct(params_next, kwargs)
tape_loss_next = cost.tape.copy(copy_operations=True)
- program, _ = cost.device.preprocess()
- loss_curr, loss_next = qml.execute(
- [tape_loss_curr, tape_loss_next], cost.device, None, transform_program=program
- )
+
+ if isinstance(cost.device, qml.devices.Device):
+ program, _ = cost.device.preprocess()
+
+ loss_curr, loss_next = qml.execute(
+ [tape_loss_curr, tape_loss_next], cost.device, None, transform_program=program
+ )
+
+ else:
+ loss_curr, loss_next = qml.execute([tape_loss_curr, tape_loss_next], cost.device, None)
# self.k has been updated earlier
ind = (self.k - 2) % self.last_n_steps.size
diff --git a/pennylane/resource/error/__init__.py b/pennylane/resource/error/__init__.py
index fd758061035..8abe9f434bc 100644
--- a/pennylane/resource/error/__init__.py
+++ b/pennylane/resource/error/__init__.py
@@ -17,4 +17,4 @@
"""
from .trotter_error import _one_norm_error, _commutator_error
-from .error import AlgorithmicError, ErrorOperation, SpectralNormError
+from .error import AlgorithmicError, ErrorOperation, SpectralNormError, _compute_algo_error
diff --git a/pennylane/resource/error/error.py b/pennylane/resource/error/error.py
index acb7a5bf424..53a5ca39c9d 100644
--- a/pennylane/resource/error/error.py
+++ b/pennylane/resource/error/error.py
@@ -15,6 +15,7 @@
Stores classes and logic to define and track algorithmic error in a quantum workflow.
"""
from abc import ABC, abstractmethod
+from typing import Dict
import pennylane as qml
from pennylane.operation import Operation, Operator
@@ -142,3 +143,26 @@ def get_error(approximate_op: Operator, exact_op: Operator):
m1 = qml.matrix(exact_op, wire_order=wire_order)
m2 = qml.matrix(approximate_op, wire_order=wire_order)
return qml.math.max(qml.math.svd(m1 - m2, compute_uv=False))
+
+
+def _compute_algo_error(tape) -> Dict[str, AlgorithmicError]:
+ """Given a quantum circuit (tape), this function computes the algorithmic error
+ generated by standard PennyLane operations.
+
+ Args:
+ tape (.QuantumTape): The quantum circuit for which we compute errors
+
+ Returns:
+ dict[str->.AlgorithmicError]: dict with error name and combined error as key-value pair
+ """
+
+ algo_errors = {}
+ for op in tape.operations:
+ if isinstance(op, ErrorOperation):
+ op_error = op.error()
+ error_name = op_error.__class__.__name__
+ algo_error = algo_errors.get(error_name, None)
+ error_value = op_error if algo_error is None else algo_error.combine(op_error)
+ algo_errors[error_name] = error_value
+
+ return algo_errors
diff --git a/pennylane/resource/specs.py b/pennylane/resource/specs.py
index 1112e1b9ac1..d515ef84d6c 100644
--- a/pennylane/resource/specs.py
+++ b/pennylane/resource/specs.py
@@ -52,19 +52,23 @@ def specs(qnode, max_expansion=None, expansion_strategy=None):
.. code-block:: python3
x = np.array([0.1, 0.2])
+ hamiltonian = qml.dot([1.0, 0.5], [qml.X(0), qml.Y(0)])
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev, diff_method="parameter-shift", shifts=np.pi / 4)
def circuit(x, add_ry=True):
qml.RX(x[0], wires=0)
qml.CNOT(wires=(0,1))
+ qml.TrotterProduct(hamiltonian, time=1.0, n=4, order=2)
if add_ry:
qml.RY(x[1], wires=1)
+ qml.TrotterProduct(hamiltonian, time=1.0, n=4, order=4)
return qml.probs(wires=(0,1))
>>> qml.specs(circuit)(x, add_ry=False)
- {'resources': Resources(num_wires=2, num_gates=2, gate_types=defaultdict(, {'RX': 1, 'CNOT': 1}),
- gate_sizes=defaultdict(, {1: 1, 2: 1}), depth=2, shots=Shots(total_shots=None, shot_vector=())),
+ {'resources': Resources(num_wires=2, num_gates=4, gate_types=defaultdict(, {'RX': 1, 'CNOT': 1, 'TrotterPro
+ duct': 2}}), gate_sizes=defaultdict(, {1: 3, 2: 1}), depth=4, shots=Shots(total_shots=None, shot_vector=())),
+ 'errors': {'SpectralNormError': SpectralNormError(0.42998560822421455)},
'num_observables': 1,
'num_diagonalizing_gates': 0,
'num_trainable_params': 1,
@@ -74,7 +78,7 @@ def circuit(x, add_ry=True):
'gradient_options': {'shifts': 0.7853981633974483},
'interface': 'auto',
'diff_method': 'parameter-shift',
- 'gradient_fn': 'pennylane.transforms.core.transform_dispatcher.param_shift',
+ 'gradient_fn': 'pennylane.gradients.parameter_shift.param_shift',
'num_gradient_executions': 2}
"""
@@ -87,6 +91,7 @@ def specs_qnode(*args, **kwargs):
* ``"num_observables"`` number of observables in the qnode
* ``"num_diagonalizing_gates"`` number of diagonalizing gates required for execution of the qnode
* ``"resources"``: a :class:`~.resource.Resources` object containing resource quantities used by the qnode
+ * ``"errors"``: combined algorithmic errors from the quantum operations executed by the qnode
* ``"num_used_wires"``: number of wires used by the circuit
* ``"num_device_wires"``: number of wires in device
* ``"depth"``: longest path in directed acyclic graph representation
diff --git a/pennylane/tape/qscript.py b/pennylane/tape/qscript.py
index f450bf0161f..3a681d2f61f 100644
--- a/pennylane/tape/qscript.py
+++ b/pennylane/tape/qscript.py
@@ -979,13 +979,14 @@ def specs(self):
gate_sizes:
{1: 4, 2: 2}
"""
+ # pylint: disable=protected-access
if self._specs is None:
- resources = qml.resource.resource._count_resources(
- self
- ) # pylint: disable=protected-access
+ resources = qml.resource.resource._count_resources(self)
+ algo_errors = qml.resource.error._compute_algo_error(self)
self._specs = {
"resources": resources,
+ "errors": algo_errors,
"num_observables": len(self.observables),
"num_diagonalizing_gates": len(self.diagonalizing_gates),
"num_trainable_params": self.num_params,
diff --git a/pennylane/tracker.py b/pennylane/tracker.py
index 5b07867fe2b..24aeda55df1 100644
--- a/pennylane/tracker.py
+++ b/pennylane/tracker.py
@@ -82,7 +82,9 @@ def circuit(x):
gate_types=defaultdict(, {'RX': 1}),
gate_sizes=defaultdict(, {1: 1}),
depth=1,
- shots=Shots(total_shots=100, shot_vector=(ShotCopies(100 shots x 1),)))}
+ shots=Shots(total_shots=100, shot_vector=(ShotCopies(100 shots x 1),))),
+ 'errors': {}
+ }
>>> tracker.history.keys()
dict_keys(['batches', 'simulations', 'executions', 'results', 'shots', 'resources'])
>>> tracker.history['results']
diff --git a/pennylane/workflow/__init__.py b/pennylane/workflow/__init__.py
index e8c2d739785..5103c404a3f 100644
--- a/pennylane/workflow/__init__.py
+++ b/pennylane/workflow/__init__.py
@@ -52,6 +52,8 @@
~workflow.jacobian_products.DeviceJacobianProducts
~workflow.jacobian_products.LightningVJPs
+.. include:: ../../pennylane/workflow/return_types_spec.rst
+
"""
from .set_shots import set_shots
from .execution import execute, SUPPORTED_INTERFACES, INTERFACE_MAP
diff --git a/pennylane/workflow/return_types_spec.rst b/pennylane/workflow/return_types_spec.rst
new file mode 100644
index 00000000000..57ff7c6e196
--- /dev/null
+++ b/pennylane/workflow/return_types_spec.rst
@@ -0,0 +1,179 @@
+
+.. _ReturnTypeSpec:
+
+Return Type Specification
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This section describes the shape and type of the numerical output from executing a quantum circuit
+in PennyLane.
+
+The specification applies for the entire workflow, from the device instance all the
+way up to the ``QNode``. The result object corresponding to a given circuit
+will match whether the circuit is being passed to a device, processed
+by a transform, having it's derivative bound to an ML interface, or returned from a ``QNode``.
+
+While this section says ``tuple`` and includes examples using ``tuple`` throughout this document, the
+return type specification allows ``tuple`` and ``list`` to be used interchangably.
+When examining and postprocessing
+results, you should always allow for a ``list`` to be substituted for a ``tuple``. Given their
+improved performance and protection against unintended side-effects, ``tuple``'s are recommended
+over ``list`` where feasible.
+
+The nesting for dimensions from outer dimension to inner dimension is:
+
+1. Quantum Tape in batch. This dimension will always exist for a batch of tapes.
+2. Shot choice in a shot vector. This dimension will not exist of a shot vector is not present.
+3. Measurement in the quantum tape. This dimension will not exist if the quantum tape only has one measurement.
+4. Parameter broadcasting. Does not exist if no parameter broadcasting. Adds to array shape instead of adding tuple nesting.
+5. Fundamental measurement shape.
+
+Individual measurements
+-----------------------
+
+Each individual measurement corresponds to its own type of result. This result can be
+a Tensor-like (Python number, numpy array, ML array), but may also be any other type of object.
+For example, :class:`~.CountsMP` corresponds to a dictionary. We can also imagine a scenario where
+a measurement corresponds to some other type of custom data structure.
+
+>>> def example_value(m):
+... tape = qml.tape.QuantumScript((), (m,), shots=50)
+... return qml.device('default.qubit').execute(tape)
+>>> example_value(qml.probs(wires=0))
+array([1., 0.])
+>>> example_value(qml.expval(qml.Z(0)))
+1.0
+>>> example_value(qml.counts(wires=0))
+{'0': 50}
+>>> example_value(qml.sample(wires=0))
+array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0])
+
+
+Empty Wires
+^^^^^^^^^^^
+
+Some measurments allow broadcasting over all available wires, like ``qml.probs()``, ``qml.sample()``,
+or ``qml.state()``. In such a case, the measurement process instance should have empty wires.
+The shape of the result object may be dictated either by the device or the other operations present in the circuit.
+
+>>> qml.probs().wires
+
+>>> tape = qml.tape.QuantumScript([qml.S(0)], (qml.probs(),))
+>>> qml.device('default.qubit').execute(tape)
+array([1., 0.])
+>>> qml.device('lightning.qubit', wires=(0,1,2)).execute(tape)
+array([1., 0., 0., 0., 0., 0., 0., 0.])
+
+Broadcasting
+^^^^^^^^^^^^
+
+Parameter broadcasting adds a leading dimension to the numeric array itself.
+
+If the corresponding tape has a ``batch_size`` and the result object is numeric, then the numeric object should
+gain a leading dimension.
+
+>>> op = qml.RX((0, np.pi/4, np.pi/2), wires=0))
+>>> tape = qml.tape.QuantumScript((op,), [qml.probs(wires=0)])
+>>> result = qml.device('default.qubit').execute(tape)
+>>> result
+array([[1. , 0. ],
+ [0.85355339, 0.14644661],
+ [0.5 , 0.5 ]])
+>>> result.shape
+(3, 2)
+>>> tape = qml.tape.QuantumScript((op,), [qml.expval(qml.Z(0))])
+>>> result = qml.device('default.qubit').execute(tape)
+>>> result
+array([1.00000000e+00, 7.07106781e-01, 2.22044605e-16])
+>>> result.shape
+(3,)
+
+Non-tensorlike arrays may handle broadcasting in different ways. The ``'default.qubit'`` output
+for :class:`~.CountsMP` is a list of dictionaries, but when used in conjunction with
+:func:`~.transforms.broadcast_expand`, the result object becomes a ``numpy.ndarray`` of dtype ``object``.
+
+>>> tape = qml.tape.QuantumScript((op,), (qml.counts(),), shots=50)
+>>> result = qml.device('default.qubit').execute(tape)
+>>> result
+[{'0': 50}, {'0': 46, '1': 4}, {'0': 32, '1': 18}]
+>>> batch, fn = qml.transforms.broadcast_expand(tape)
+>>> fn(qml.device('default.qubit').execute(batch))
+array([{'0': 50}, {'0': 39, '1': 11}, {'0': 28, '1': 22}], dtype=object)
+
+
+Single Tape
+-----------
+
+If the tape has a single measurement, then the result corresponding to that tape simply obeys the specification
+above. Otherwise, the result for a single tape is a ``tuple`` where each entry corresponds to each
+of the corresponding measurements. In the below example, the first entry corresponds to the first
+measurement process ``qml.expval(qml.Z(0))``, the second entry corresponds to the second measurement process
+``qml.probs(wires=0)``, and the third result corresponds to the third measurement process ``qml.state()``.
+
+>>> tape = qml.tape.QuantumScript((), (qml.expval(qml.Z(0)), qml.probs(wires=0), qml.state()))
+>>> qml.device('default.qubit').execute(tape)
+(1.0, array([1., 0.]), array([1.+0.j, 0.+0.j]))
+
+Shot vectors
+^^^^^^^^^^^^
+
+When a shot vector is present ``shots.has_partitioned_shot``, the measurement instead becomes a
+tuple where each entry corresponds to a different shot value.
+
+>>> measurements = (qml.expval(qml.Z(0)), qml.probs(wires=0))
+>>> tape = qml.tape.QuantumScript((), measurements, shots=(50,50,50))
+>>> result = qml.device('default.qubit').execute(tape)
+>>> result
+((1.0, array([1., 0.])), (1.0, array([1., 0.])), (1.0, array([1., 0.])))
+>>> result[0]
+(1.0, array([1., 0.]))
+>>> tape = qml.tape.QuantumScript((), [qml.counts(wires=0)], shots=(1, 10, 100))
+>>> qml.device('default.qubit').execute(tape)
+({'0': 1}, {'0': 10}, {'0': 100})
+
+Let's look at an example with all forms of nesting. Here, we have a tape with a batch size of ``3``, three
+diferent measurements with different fundamental shapes, and a shot vector with three different values.
+
+>>> op = qml.RX((1.2, 2.3, 3.4), 0)
+>>> ms = (qml.expval(qml.Z(0)), qml.probs(wires=0), qml.counts())
+>>> tape = qml.tape.QuantumScript((op,), ms, shots=(1, 100, 1000))
+>>> result = qml.device('default.qubit').execute(tape)
+>>> result
+((array([ 1., -1., -1.]),
+array([[1., 0.],
+ [0., 1.],
+ [0., 1.]]),
+[{'0': 1}, {'1': 1}, {'1': 1}]),
+(array([ 0.3 , -0.66, -0.98]),
+array([[0.61, 0.39],
+ [0.13, 0.87],
+ [0.03, 0.97]]),
+[{'0': 61, '1': 39}, {'0': 13, '1': 87}, {'0': 3, '1': 97}]),
+(array([ 0.364, -0.648, -0.962]),
+array([[0.669, 0.331],
+ [0.165, 0.835],
+ [0.012, 0.988]]),
+[{'0': 669, '1': 331}, {'0': 165, '1': 835}, {'0': 12, '1': 988}]))
+
+
+>>> result[0][0] # first shot value, first measurement
+array([ 1., -1., -1.])
+>>> result[0][0][0] # first shot value, first measurement, and parameter of 1.2
+1.0
+>>> result[1][2] # second shot value, third measurement, all three parameter values
+[{'0': 74, '1': 26}, {'0': 23, '1': 77}, {'1': 100}]
+
+
+Batches
+-------
+
+A batch is a tuple or list of multiple tapes. In this case, the result should always be a tuple
+where each entry corresponds to the result for the corresponding tape.
+
+>>> tape1 = qml.tape.QuantumScript([qml.X(0)], [qml.state()])
+>>> tape2 = qml.tape.QuantumScript([qml.Hadamard(0)], [qml.counts()], shots=100)
+>>> tape3 = qml.tape.QuantumScript([], [qml.expval(qml.Z(0)), qml.expval(qml.X(0))])
+>>> batch = (tape1, tape2, tape3)
+>>> qml.device('default.qubit').execute(batch)
+(array([0.+0.j, 1.+0.j]), {'0': 50, '1': 50}, (1.0, 0.0))
\ No newline at end of file
diff --git a/tests/devices/default_qubit/test_default_qubit_tracking.py b/tests/devices/default_qubit/test_default_qubit_tracking.py
index 5390ee04fde..f12af5eab8d 100644
--- a/tests/devices/default_qubit/test_default_qubit_tracking.py
+++ b/tests/devices/default_qubit/test_default_qubit_tracking.py
@@ -59,6 +59,7 @@ def test_tracking_batch(self):
"resources": [Resources(num_wires=1), Resources(num_wires=1), Resources(num_wires=1)],
"derivative_batches": [1],
"derivatives": [1],
+ "errors": [{}, {}, {}],
}
assert tracker.totals == {
"batches": 2,
@@ -73,6 +74,7 @@ def test_tracking_batch(self):
"simulations": 1,
"results": 1,
"resources": Resources(num_wires=1),
+ "errors": {},
}
def test_tracking_execute_and_derivatives(self):
@@ -103,6 +105,7 @@ def test_tracking_execute_and_derivatives(self):
"vjp_batches": [1],
"execute_and_vjp_batches": [1],
"resources": [Resources(num_wires=1)] * 12,
+ "errors": [{}] * 12,
}
def test_tracking_resources(self):
diff --git a/tests/devices/modifiers/test_all_modifiers.py b/tests/devices/modifiers/test_all_modifiers.py
index 69a40f3cf0b..fa315dbbc81 100644
--- a/tests/devices/modifiers/test_all_modifiers.py
+++ b/tests/devices/modifiers/test_all_modifiers.py
@@ -44,7 +44,7 @@ def execute(self, circuits, execution_config=qml.devices.DefaultExecutionConfig)
# result unwrapped
assert out == 0.0
- assert len(dev.tracker.history) == 6
+ assert len(dev.tracker.history) == 7
assert dev.tracker.history["batches"] == [1]
assert dev.tracker.history["simulations"] == [1]
assert dev.tracker.history["executions"] == [1]
diff --git a/tests/devices/modifiers/test_simulator_tracking.py b/tests/devices/modifiers/test_simulator_tracking.py
index 2d889c2d46b..88109eed8f6 100644
--- a/tests/devices/modifiers/test_simulator_tracking.py
+++ b/tests/devices/modifiers/test_simulator_tracking.py
@@ -44,7 +44,7 @@ def execute(self, circuits, execution_config=qml.devices.DefaultExecutionConfig)
out = dev.execute((tape1, tape2))
assert out == ((0.0, 0.0), 0.0)
- assert len(dev.tracker.history) == 6
+ assert len(dev.tracker.history) == 7
assert dev.tracker.history["batches"] == [1]
assert dev.tracker.history["simulations"] == [1, 1]
assert dev.tracker.history["executions"] == [2, 2]
diff --git a/tests/devices/test_default_clifford.py b/tests/devices/test_default_clifford.py
index 9ceb1fdb492..1ff88630ed7 100644
--- a/tests/devices/test_default_clifford.py
+++ b/tests/devices/test_default_clifford.py
@@ -484,6 +484,7 @@ def test_tracker():
"batches": [1, 1],
"simulations": [1, 1],
"executions": [1, 1],
+ "errors": [{}, {}],
}
diff --git a/tests/devices/test_null_qubit.py b/tests/devices/test_null_qubit.py
index 775ae7adde9..507fc9f2ea4 100644
--- a/tests/devices/test_null_qubit.py
+++ b/tests/devices/test_null_qubit.py
@@ -106,6 +106,7 @@ def test_tracking():
)
]
* 13,
+ "errors": [{}] * 13,
}
diff --git a/tests/drawer/test_draw.py b/tests/drawer/test_draw.py
index 1bd7790f523..b571ed4fd0d 100644
--- a/tests/drawer/test_draw.py
+++ b/tests/drawer/test_draw.py
@@ -284,6 +284,8 @@ def circ():
class TestMidCircuitMeasurements:
"""Tests for drawing mid-circuit measurements and classical conditions."""
+ # pylint: disable=too-many-public-methods
+
@pytest.mark.parametrize("device_name", ["default.qubit"])
def test_qnode_mid_circuit_measurement_not_deferred(self, device_name, mocker):
"""Test that a circuit containing mid-circuit measurements is transformed by the drawer
@@ -329,6 +331,51 @@ def func():
assert drawing == expected_drawing
+ @pytest.mark.parametrize(
+ "op", [qml.GlobalPhase(0.1), qml.Identity(), qml.Snapshot(), qml.Barrier()]
+ )
+ @pytest.mark.parametrize("decimals", [None, 2])
+ def test_draw_all_wire_ops(self, op, decimals):
+ """Test that operators acting on all wires are drawn correctly"""
+
+ def func():
+ qml.X(0)
+ qml.X(1)
+ m = qml.measure(0)
+ qml.cond(m, qml.X)(0)
+ qml.apply(op)
+ return qml.expval(qml.Z(0))
+
+ # Stripping to remove trailing white-space because length of white-space at the
+ # end of the drawing depends on the length of each individual line
+ drawing = qml.draw(func, decimals=decimals)().strip()
+ label = op.label(decimals=decimals).replace("\n", "")
+ expected_drawing = (
+ f"0: ──X──┤↗├──X──{label}─┤ \n1: ──X───║───║──{label}─┤ \n ╚═══╝"
+ )
+
+ assert drawing == expected_drawing
+
+ @pytest.mark.parametrize(
+ "mp, label", [(qml.sample(), "Sample"), (qml.probs(), "Probs"), (qml.counts(), "Counts")]
+ )
+ def test_draw_all_wire_measurements(self, mp, label):
+ """Test that operators acting on all wires are drawn correctly"""
+
+ def func():
+ qml.X(0)
+ qml.X(1)
+ m = qml.measure(0)
+ qml.cond(m, qml.X)(0)
+ return qml.apply(mp)
+
+ # Stripping to remove trailing white-space because length of white-space at the
+ # end of the drawing depends on the length of each individual line
+ drawing = qml.draw(func)().strip()
+ expected_drawing = f"0: ──X──┤↗├──X─┤ {label}\n1: ──X───║───║─┤ {label}\n ╚═══╝"
+
+ assert drawing == expected_drawing
+
def test_draw_mid_circuit_measurement_multiple_wires(self):
"""Test that mid-circuit measurements are correctly drawn in circuits
with multiple wires."""
diff --git a/tests/optimize/test_qnspsa.py b/tests/optimize/test_qnspsa.py
index 40c8bb37aee..0ffd652c64f 100644
--- a/tests/optimize/test_qnspsa.py
+++ b/tests/optimize/test_qnspsa.py
@@ -347,8 +347,12 @@ def test_step_and_cost_from_multi_input(self, finite_diff_step, seed):
new_params_tensor_expected,
)
- def test_step_and_cost_with_non_trainable_input(self, finite_diff_step, seed):
- """Test step_and_cost() function with the qnode with non-trainable input."""
+ @pytest.mark.parametrize("device_name", ["default.qubit", "default.qubit.legacy"])
+ def test_step_and_cost_with_non_trainable_input(self, device_name, finite_diff_step, seed):
+ """
+ Test step_and_cost() function with the qnode with non-trainable input,
+ both using the `default.qubit` and `default.qubit.legacy` device.
+ """
regularization = 1e-3
stepsize = 1e-2
opt = qml.QNSPSAOptimizer(
@@ -362,7 +366,7 @@ def test_step_and_cost_with_non_trainable_input(self, finite_diff_step, seed):
)
# a deep copy of the same opt, to be applied to qnode_reduced
target_opt = deepcopy(opt)
- dev = qml.device("default.qubit", wires=2)
+ dev = qml.device(device_name, wires=2)
non_trainable_param = np.random.rand(1)
non_trainable_param.requires_grad = False
diff --git a/tests/resource/test_error/test_error.py b/tests/resource/test_error/test_error.py
index 8e07e0c1c03..4b690e49a18 100644
--- a/tests/resource/test_error/test_error.py
+++ b/tests/resource/test_error/test_error.py
@@ -20,7 +20,12 @@
import numpy as np
import pennylane as qml
-from pennylane.resource.error import AlgorithmicError, SpectralNormError, ErrorOperation
+from pennylane.resource.error import (
+ AlgorithmicError,
+ SpectralNormError,
+ ErrorOperation,
+ _compute_algo_error,
+)
from pennylane.operation import Operation
@@ -181,3 +186,112 @@ class NoErrorOp(ErrorOperation):
num_wires = 3
_ = NoErrorOp(wires=[1, 2, 3])
+
+
+class MultiplicativeError(AlgorithmicError):
+ """Multiplicative error object"""
+
+ def combine(self, other):
+ return self.__class__(self.error * other.error)
+
+ def __repr__(self):
+ """Return formal string representation."""
+ return f"MultiplicativeError({self.error})"
+
+
+class AdditiveError(AlgorithmicError):
+ """Additive error object"""
+
+ def combine(self, other):
+ return self.__class__(self.error + other.error)
+
+ def __repr__(self):
+ """Return formal string representation."""
+ return f"AdditiveError({self.error})"
+
+
+class CustomErrorOp1(ErrorOperation):
+ """Custome error operation with multiplicative error"""
+
+ def __init__(self, phase, wires):
+ self.phase = phase
+ super().__init__(phase, wires=wires)
+
+ def error(self, *args, **kwargs):
+ return MultiplicativeError(self.phase)
+
+
+class CustomErrorOp2(ErrorOperation):
+ """Custome error with additive error"""
+
+ def __init__(self, flips, wires):
+ self.flips = flips
+ super().__init__(flips, wires=wires)
+
+ def error(self, *args, **kwargs):
+ return AdditiveError(self.flips)
+
+
+_HAMILTONIAN = qml.dot([1.0, -0.5], [qml.X(0) @ qml.Y(1), qml.Y(0) @ qml.Y(1)])
+
+
+class TestSpecAndTracker:
+ """Test capture of ErrorOperation in specs and tracker."""
+
+ # TODO: remove this when support for below is present
+ # little hack for stopping device-level decomposition for custom ops
+ @staticmethod
+ def preprocess(execution_config=qml.devices.DefaultExecutionConfig):
+ """A vanilla preprocesser"""
+ return qml.transforms.core.TransformProgram(), execution_config
+
+ dev = qml.device("null.qubit", wires=2)
+ dev.preprocess = preprocess.__func__
+
+ @staticmethod
+ @qml.qnode(dev)
+ def circuit():
+ """circuit with custom ops"""
+ qml.TrotterProduct(_HAMILTONIAN, time=1.0, n=4, order=2)
+ CustomErrorOp1(0.31, [0])
+ CustomErrorOp2(0.12, [1])
+ qml.TrotterProduct(_HAMILTONIAN, time=1.0, n=4, order=4)
+ CustomErrorOp1(0.24, [1])
+ CustomErrorOp2(0.73, [0])
+ return qml.state()
+
+ errors_types = ["MultiplicativeError", "AdditiveError", "SpectralNormError"]
+
+ def test_computation(self):
+ """Test that _compute_algo_error are adding up errors as expected."""
+
+ _ = self.circuit()
+ algo_errors = _compute_algo_error(self.circuit.qtape)
+ assert len(algo_errors) == 3
+ assert all(error in algo_errors for error in self.errors_types)
+ assert algo_errors["MultiplicativeError"].error == 0.31 * 0.24
+ assert algo_errors["AdditiveError"].error == 0.73 + 0.12
+ assert algo_errors["SpectralNormError"].error == 0.25 + 0.17998560822421455
+
+ def test_specs(self):
+ """Test that specs are tracking errors as expected."""
+
+ algo_errors = qml.specs(self.circuit)()["errors"]
+ assert len(algo_errors) == 3
+ assert all(error in algo_errors for error in self.errors_types)
+ assert algo_errors["MultiplicativeError"].error == 0.31 * 0.24
+ assert algo_errors["AdditiveError"].error == 0.73 + 0.12
+ assert algo_errors["SpectralNormError"].error == 0.25 + 0.17998560822421455
+
+ def test_tracker(self):
+ """Test that tracker are tracking errors as expected."""
+
+ with qml.Tracker(self.dev) as tracker:
+ self.circuit()
+
+ algo_errors = tracker.latest["errors"]
+ assert len(algo_errors) == 3
+ assert all(error in algo_errors for error in self.errors_types)
+ assert algo_errors["MultiplicativeError"].error == 0.31 * 0.24
+ assert algo_errors["AdditiveError"].error == 0.73 + 0.12
+ assert algo_errors["SpectralNormError"].error == 0.25 + 0.17998560822421455
diff --git a/tests/resource/test_specs.py b/tests/resource/test_specs.py
index be31aaf1365..788348d2496 100644
--- a/tests/resource/test_specs.py
+++ b/tests/resource/test_specs.py
@@ -25,7 +25,7 @@ class TestSpecsTransform:
"""Tests for the transform specs using the QNode"""
@pytest.mark.parametrize(
- "diff_method, len_info", [("backprop", 11), ("parameter-shift", 12), ("adjoint", 11)]
+ "diff_method, len_info", [("backprop", 12), ("parameter-shift", 13), ("adjoint", 12)]
)
def test_empty(self, diff_method, len_info):
dev = qml.device("default.qubit", wires=1)
@@ -57,7 +57,7 @@ def circ():
assert info["gradient_fn"] == "pennylane.gradients.parameter_shift.param_shift"
@pytest.mark.parametrize(
- "diff_method, len_info", [("backprop", 11), ("parameter-shift", 12), ("adjoint", 11)]
+ "diff_method, len_info", [("backprop", 12), ("parameter-shift", 13), ("adjoint", 12)]
)
def test_specs(self, diff_method, len_info):
"""Test the specs transforms works in standard situations"""
@@ -102,7 +102,7 @@ def circuit(x, y, add_RY=True):
assert info["num_gradient_executions"] == 6
@pytest.mark.parametrize(
- "diff_method, len_info", [("backprop", 11), ("parameter-shift", 12), ("adjoint", 11)]
+ "diff_method, len_info", [("backprop", 12), ("parameter-shift", 13), ("adjoint", 12)]
)
def test_specs_state(self, diff_method, len_info):
"""Test specs works when state returned"""
@@ -173,7 +173,7 @@ def test_expansion_strategy(self):
info = qml.specs(circuit, expansion_strategy="device")(params)
assert circuit.expansion_strategy == "gradient"
- assert len(info) == 11
+ assert len(info) == 12
def test_gradient_transform(self):
"""Test that a gradient transform is properly labelled"""
diff --git a/tests/tape/test_qscript.py b/tests/tape/test_qscript.py
index 9c002c4baf4..8493a8a80b3 100644
--- a/tests/tape/test_qscript.py
+++ b/tests/tape/test_qscript.py
@@ -448,7 +448,7 @@ def test_empty_qs_specs(self):
assert qs.specs["num_diagonalizing_gates"] == 0
assert qs.specs["num_trainable_params"] == 0
- assert len(qs.specs) == 4
+ assert len(qs.specs) == 5
assert qs._specs is qs.specs
@@ -460,7 +460,7 @@ def test_specs_tape(self, make_script):
specs = qs.specs
assert qs._specs is specs
- assert len(specs) == 4
+ assert len(specs) == 5
gate_types = defaultdict(int, {"RX": 2, "Rot": 1, "CNOT": 1})
gate_sizes = defaultdict(int, {1: 3, 2: 1})
diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py
index 5a1e42d72f7..3f8e8138035 100644
--- a/tests/tape/test_tape.py
+++ b/tests/tape/test_tape.py
@@ -551,7 +551,7 @@ def test_specs_empty_tape(self, make_empty_tape):
assert tape.specs["num_diagonalizing_gates"] == 0
assert tape.specs["num_trainable_params"] == 0
- assert len(tape.specs) == 4
+ assert len(tape.specs) == 5
def test_specs_tape(self, make_tape):
"""Tests that regular tapes return correct specifications"""
@@ -559,7 +559,7 @@ def test_specs_tape(self, make_tape):
specs = tape.specs
- assert len(specs) == 4
+ assert len(specs) == 5
gate_sizes = defaultdict(int, {1: 3, 2: 1})
gate_types = defaultdict(int, {"RX": 2, "Rot": 1, "CNOT": 1})
@@ -577,7 +577,7 @@ def test_specs_add_to_tape(self, make_extendible_tape):
tape = make_extendible_tape
specs1 = tape.specs
- assert len(specs1) == 4
+ assert len(specs1) == 5
gate_sizes = defaultdict(int, {1: 3, 2: 1})
gate_types = defaultdict(int, {"RX": 2, "Rot": 1, "CNOT": 1})
@@ -599,7 +599,7 @@ def test_specs_add_to_tape(self, make_extendible_tape):
specs2 = tape.specs
- assert len(specs2) == 4
+ assert len(specs2) == 5
gate_sizes = defaultdict(int, {1: 4, 2: 2})
gate_types = defaultdict(int, {"RX": 2, "Rot": 1, "CNOT": 2, "RZ": 1})