Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate experimental device with the QNode #4196

Merged
merged 27 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9cccfb6
integrate qnode with new device
albi3ro May 16, 2023
0c6c65c
some diff method improvements
albi3ro May 31, 2023
4c14628
Merge branch 'master' into experimental-device-qnode
albi3ro May 31, 2023
a792967
repr methods
albi3ro Jun 15, 2023
a4ce04c
add tests, always pass config to device
albi3ro Jun 15, 2023
1344744
Merge branch 'master' into experimental-device-qnode
albi3ro Jun 15, 2023
eb1fc3b
add tests, always pass config to device
albi3ro Jun 15, 2023
21e0e1c
resolve merge conflicts
albi3ro Jun 15, 2023
13130b6
Merge branch 'master' into experimental-device-qnode
albi3ro Jun 15, 2023
80d9ee1
final test
albi3ro Jun 15, 2023
cce807d
Merge branch 'master' into experimental-device-qnode
albi3ro Jun 15, 2023
e974c74
pylint
albi3ro Jun 16, 2023
6728330
Merge branch 'master' into experimental-device-qnode
albi3ro Jun 16, 2023
8427607
autograd integration tests
albi3ro Jun 16, 2023
7ed5cc3
Merge branch 'experimental-device-qnode' of https://github.com/PennyL…
albi3ro Jun 16, 2023
2acac79
pylint
albi3ro Jun 16, 2023
2245d2f
Update pennylane/interfaces/execution.py
albi3ro Jun 16, 2023
79e2f6e
pass shots through methods
albi3ro Jun 16, 2023
24fa55b
Merge branch 'experimental-device-qnode' of https://github.com/PennyL…
albi3ro Jun 16, 2023
9f75240
changelog
albi3ro Jun 16, 2023
619afe2
revert set shots change
albi3ro Jun 16, 2023
06ce58f
Apply suggestions from code review
albi3ro Jun 16, 2023
10111f8
Merge branch 'master' into experimental-device-qnode
rmoyard Jun 16, 2023
3b959f2
revert executionc hange, pylint:
albi3ro Jun 16, 2023
0b99e62
Merge branch 'master' into experimental-device-qnode
albi3ro Jun 16, 2023
905125b
pylint again
albi3ro Jun 16, 2023
a6b6f33
Merge branch 'master' into experimental-device-qnode
albi3ro Jun 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@

<h3>Improvements 🛠</h3>

* The experimental device interface is integrated with the `QNode`.
[(#4196)](https://github.com/PennyLaneAI/pennylane/pull/4196)

* `Projector` now accepts a state vector representation, which enables the creation of projectors
in any basis.
[(#4192)](https://github.com/PennyLaneAI/pennylane/pull/4192)
Expand Down
11 changes: 10 additions & 1 deletion pennylane/interfaces/execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,16 @@ def cost_fn(params, x):

# the default execution function is batch_execute
# use qml.interfaces so that mocker can spy on it during testing
execute_fn = qml.interfaces.cache_execute(batch_execute, cache, expand_fn=expand_fn)
if new_device_interface:

def device_execution_with_config(tapes):
return device.execute(tapes, execution_config=config)

execute_fn = qml.interfaces.cache_execute(
device_execution_with_config, cache, expand_fn=expand_fn
)
else:
execute_fn = qml.interfaces.cache_execute(batch_execute, cache, expand_fn=expand_fn)
albi3ro marked this conversation as resolved.
Show resolved Hide resolved

_grad_on_execution = False

Expand Down
89 changes: 61 additions & 28 deletions pennylane/qnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import inspect
import warnings
from collections.abc import Sequence
from typing import Union


import autograd

Expand Down Expand Up @@ -378,7 +380,7 @@ def circuit_unpacking(x):
def __init__(
self,
func,
device,
device: Union[Device, "qml.devices.experimental.Device"],
interface="auto",
diff_method="best",
expansion_strategy="gradient",
Expand All @@ -396,7 +398,7 @@ def __init__(
f"one of {SUPPORTED_INTERFACES}."
)

if not isinstance(device, Device):
if not isinstance(device, (Device, qml.devices.experimental.Device)):
raise qml.QuantumFunctionError(
"Invalid device. Device must be a valid PennyLane device."
)
Expand Down Expand Up @@ -461,6 +463,9 @@ def __init__(

def __repr__(self):
"""String representation."""
if isinstance(self.device, qml.devices.experimental.Device):
return f"<QNode: device='{self.device}', interface='{self.interface}', diff_method='{self.diff_method}'>"

detail = "<QNode: wires={}, device='{}', interface='{}', diff_method='{}'>"
return detail.format(
self.device.num_wires,
Expand Down Expand Up @@ -499,7 +504,7 @@ def add_transform(self, transform_container):
"""
self._transform_program.push_back(transform_container=transform_container)

def _update_gradient_fn(self):
def _update_gradient_fn(self, shots=None):
if self.diff_method is None:
self._interface = None
self.gradient_fn = None
Expand All @@ -508,16 +513,17 @@ def _update_gradient_fn(self):
if self.interface == "auto" and self.diff_method in ["backprop", "best"]:
if self.diff_method == "backprop":
# Check that the device has the capabilities to support backprop
rmoyard marked this conversation as resolved.
Show resolved Hide resolved
backprop_devices = self.device.capabilities().get("passthru_devices", None)
if backprop_devices is None:
raise qml.QuantumFunctionError(
f"The {self.device.short_name} device does not support native computations with "
"autodifferentiation frameworks."
)
if isinstance(self.device, Device):
backprop_devices = self.device.capabilities().get("passthru_devices", None)
if backprop_devices is None:
raise qml.QuantumFunctionError(
f"The {self.device.short_name} device does not support native computations with "
"autodifferentiation frameworks."
)
return

self.gradient_fn, self.gradient_kwargs, self.device = self.get_gradient_fn(
self._original_device, self.interface, self.diff_method
self._original_device, self.interface, self.diff_method, shots=shots
)
self.gradient_kwargs.update(self._user_gradient_kwargs or {})

Expand All @@ -541,7 +547,7 @@ def _update_original_device(self):

# pylint: disable=too-many-return-statements
@staticmethod
def get_gradient_fn(device, interface, diff_method="best"):
def get_gradient_fn(device, interface, diff_method="best", shots=None):
"""Determine the best differentiation method, interface, and device
for a requested device, interface, and diff method.

Expand All @@ -558,10 +564,10 @@ def get_gradient_fn(device, interface, diff_method="best"):
``gradient_kwargs``, and the device to use when calling the execute function.
"""
if diff_method == "best":
return QNode.get_best_method(device, interface)
return QNode.get_best_method(device, interface, shots=shots)

if diff_method == "backprop":
return QNode._validate_backprop_method(device, interface)
return QNode._validate_backprop_method(device, interface, shots=shots)

if diff_method == "adjoint":
return QNode._validate_adjoint_method(device)
Expand Down Expand Up @@ -596,7 +602,7 @@ def get_gradient_fn(device, interface, diff_method="best"):
)

@staticmethod
def get_best_method(device, interface):
def get_best_method(device, interface, shots=None):
"""Returns the 'best' differentiation method
for a particular device and interface combination.

Expand Down Expand Up @@ -624,7 +630,7 @@ def get_best_method(device, interface):
return QNode._validate_device_method(device)
except qml.QuantumFunctionError:
try:
return QNode._validate_backprop_method(device, interface)
return QNode._validate_backprop_method(device, interface, shots=shots)
except qml.QuantumFunctionError:
try:
return QNode._validate_parameter_shift(device)
Expand Down Expand Up @@ -670,10 +676,18 @@ def best_method_str(device, interface):
return transform

@staticmethod
def _validate_backprop_method(device, interface):
if device.shots is not None:
def _validate_backprop_method(device, interface, shots=None):
if shots is not None or getattr(device, "shots", None) is not None:
raise qml.QuantumFunctionError("Backpropagation is only supported when shots=None.")

if isinstance(device, qml.devices.experimental.Device):
config = qml.devices.experimental.ExecutionConfig(
gradient_method="backprop", interface=interface
)
if device.supports_derivatives(config):
return "backprop", {}, device
raise qml.QuantumFunctionError(f"Device {device.name} does not support backprop")

mapped_interface = INTERFACE_MAP.get(interface, interface)

# determine if the device supports backpropagation
Expand Down Expand Up @@ -731,6 +745,11 @@ def _validate_adjoint_method(device):
# need to inspect the circuit measurements to ensure only expectation values are taken. This
# cannot be done here since we don't yet know the composition of the circuit.

if isinstance(device, qml.devices.experimental.Device):
config = qml.devices.experimental.ExecutionConfig(gradient_method="adjoint")
if device.supports_derivatives(config):
return "adjoint", {}, device
raise ValueError(f"The {device} device does not support adjoint differentiation.")
required_attrs = ["_apply_operation", "_apply_unitary", "adjoint_jacobian"]
supported_device = all(hasattr(device, attr) for attr in required_attrs)
supported_device = supported_device and device.capabilities().get("returns_state")
Expand All @@ -750,17 +769,25 @@ def _validate_adjoint_method(device):

@staticmethod
def _validate_device_method(device):
# determine if the device provides its own jacobian method
if device.capabilities().get("provides_jacobian", False):
return "device", {}, device
if isinstance(device, Device):
# determine if the device provides its own jacobian method
if device.capabilities().get("provides_jacobian", False):
return "device", {}, device
name = device.short_name
else:
config = qml.devices.experimental.ExecutionConfig(gradient_method="device")
if device.supports_derivatives(config):
return "device", {}, device
name = device.name

raise qml.QuantumFunctionError(
f"The {device.short_name} device does not provide a native "
"method for computing the jacobian."
f"The {name} device does not provide a native " "method for computing the jacobian."
)

@staticmethod
def _validate_parameter_shift(device):
if isinstance(device, qml.devices.experimental.Device):
rmoyard marked this conversation as resolved.
Show resolved Hide resolved
return qml.gradients.param_shift, {}, device
model = device.capabilities().get("model", None)

if model in {"qubit", "qutrit"}:
Expand Down Expand Up @@ -890,14 +917,20 @@ def __call__(self, *args, **kwargs) -> qml.typing.Result:

# pylint: disable=not-callable
# update the gradient function
set_shots(self._original_device, override_shots)(self._update_gradient_fn)()
if isinstance(self._original_device, Device):
timmysilv marked this conversation as resolved.
Show resolved Hide resolved
set_shots(self._original_device, override_shots)(self._update_gradient_fn)()
else:
self._update_gradient_fn(shots=override_shots)

albi3ro marked this conversation as resolved.
Show resolved Hide resolved
else:
kwargs["shots"] = (
self._original_device._raw_shot_sequence
if self._original_device._shot_vector
else self._original_device.shots
)
if isinstance(self._original_device, Device):
kwargs["shots"] = (
self._original_device._raw_shot_sequence
if self._original_device._shot_vector
else self._original_device.shots
)
else:
kwargs["shots"] = None

# construct the tape
self.construct(args, kwargs)
Expand Down
Loading