Skip to content

Commit

Permalink
Merge branch 'master' into test_legacy_opmath
Browse files Browse the repository at this point in the history
  • Loading branch information
lillian542 committed Apr 12, 2024
2 parents b50bf15 + f7834de commit 1999627
Show file tree
Hide file tree
Showing 32 changed files with 2,366 additions and 41 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/interface-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: >-
Expand All @@ -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 }}
Expand Down
19 changes: 19 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -322,6 +332,9 @@

<h3>Documentation 📝</h3>

* 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)

Expand All @@ -339,6 +352,12 @@

<h3>Bug fixes 🐛</h3>

* 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)

Expand Down
2 changes: 1 addition & 1 deletion pennylane/_qubit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def _const_mul(constant, array):
"Identity",
"Projector",
"Sum",
"Sprod",
"SProd",
"Prod",
}

Expand Down
2 changes: 2 additions & 0 deletions pennylane/devices/device_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ def execute(
.. details::
:title: Return Shape
See :ref:`Return Type Specification <ReturnTypeSpec>` 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:
Expand Down
12 changes: 8 additions & 4 deletions pennylane/devices/modifiers/simulator_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ def execute(self, circuits, execution_config=DefaultExecutionConfig):
results=r,
shots=shots,
resources=c.specs["resources"],
errors=c.specs["errors"],
)
else:
self.tracker.update(
simulations=1,
executions=qpu_executions,
results=r,
resources=c.specs["resources"],
errors=c.specs["errors"],
)
self.tracker.record()
return results
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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(<class 'int'>, {'S': 1}),
gate_sizes=defaultdict(<class 'int'>, {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):
Expand Down
16 changes: 14 additions & 2 deletions pennylane/devices/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
201 changes: 201 additions & 0 deletions pennylane/devices/tests/test_gradients_autograd.py
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 1999627

Please sign in to comment.