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

[New Device Capabilities] Add capabilities to the device API #6433

Open
wants to merge 102 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 96 commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
63ba6fd
[New Device Capabilities] Implement the `DeviceCapabilities` data cla…
astralcai Oct 16, 2024
42cba1f
Unit tests for TOML loading
astralcai Oct 16, 2024
7a073b4
use tomli instead of tomlkit
astralcai Oct 16, 2024
a99bb75
make pylint happy
astralcai Oct 17, 2024
eeb4d90
all unit tests for toml loading
astralcai Oct 17, 2024
8c48c5d
tests for device capabilities
astralcai Oct 17, 2024
567269c
minor doc string updates
astralcai Oct 17, 2024
139ff4a
changelo
astralcai Oct 17, 2024
2dbb4a0
add no cover
astralcai Oct 17, 2024
93b7883
change mid_circuit_measurements to supported_mcm_methods
astralcai Oct 17, 2024
0f9dffc
get rid of redundant ProgramFeatures class
astralcai Oct 17, 2024
d21fb4d
Revert "change mid_circuit_measurements to supported_mcm_methods"
astralcai Oct 17, 2024
5c2e6a8
add from_toml_file
astralcai Oct 18, 2024
fbbc1ea
make pylint happy
astralcai Oct 18, 2024
36fba16
add missing coverage
astralcai Oct 18, 2024
b75247c
apply suggestions from code review
astralcai Oct 18, 2024
2ad8fc0
Merge branch 'master' into device-cap
astralcai Oct 18, 2024
31c083f
change mid_circuit_measurements to supported_mcm_methods
astralcai Oct 21, 2024
91def80
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Oct 21, 2024
735cfb6
trigger CI
astralcai Oct 21, 2024
fffafe2
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Oct 22, 2024
418f53c
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Oct 28, 2024
3e8e7a2
revert changelog entry
astralcai Oct 28, 2024
21d8e21
fix changelog
astralcai Oct 28, 2024
d3539ba
Merge branch 'master' into device-cap
astralcai Nov 1, 2024
0c27011
add intersection method
astralcai Nov 4, 2024
2b6f7ba
Merge branch 'master' into device-cap
astralcai Nov 4, 2024
79598d9
Merge branch 'master' into device-cap
astralcai Nov 5, 2024
f6687ea
retrigger CI
astralcai Nov 6, 2024
97d2c07
Merge branch 'master' into device-cap
astralcai Nov 6, 2024
d79dab6
retrigger CI
astralcai Nov 6, 2024
f53fc83
let's seed one more
astralcai Nov 6, 2024
447ba48
Add capabilities to the device API
astralcai Oct 22, 2024
0df1b12
fix toml assert errors
astralcai Oct 24, 2024
06f4ca8
fix name conflict
astralcai Oct 25, 2024
eb0f95a
fix docstring
astralcai Oct 25, 2024
e34990c
skip coverage for line
astralcai Oct 25, 2024
d31bc41
change attribute name
astralcai Oct 28, 2024
a1a6b17
oooooops
astralcai Oct 28, 2024
7ed1e27
fix isort
astralcai Oct 28, 2024
10044e4
minor update
astralcai Nov 4, 2024
2422507
add tests
astralcai Nov 4, 2024
a63c883
make pylint happy
astralcai Nov 4, 2024
45a1195
fix capabilities in tests
astralcai Nov 5, 2024
a5115da
revert test file changes
astralcai Nov 6, 2024
872ade6
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 6, 2024
d9700fe
ooooops
astralcai Nov 6, 2024
d4f3fd5
ooooops
astralcai Nov 6, 2024
98fb25e
fix tests
astralcai Nov 7, 2024
db30d65
fix type hint error
astralcai Nov 7, 2024
9c8cb46
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 7, 2024
56ecc13
make black happy
astralcai Nov 7, 2024
e5ea265
make isort happy
astralcai Nov 7, 2024
18a6c4e
add method to verify operator support
astralcai Nov 7, 2024
8a826e2
update tests
astralcai Nov 7, 2024
f173996
ooops
astralcai Nov 7, 2024
b11d5bb
make pylint happy
astralcai Nov 7, 2024
1650fee
small update
astralcai Nov 7, 2024
36237ca
add missing coverage
astralcai Nov 7, 2024
202cb36
fix format
astralcai Nov 7, 2024
933b369
make isort happy
astralcai Nov 7, 2024
9fc5b9f
Apply suggestions from code review
astralcai Nov 7, 2024
a98b80a
no cover lines that are going to be deleted anyways
astralcai Nov 8, 2024
fe9104d
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 8, 2024
2ec8ef6
Merge branch 'master' into device-api-cap
astralcai Nov 8, 2024
16f071b
update docs
astralcai Nov 8, 2024
1a3b16f
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 8, 2024
3ab53ba
[skip-ci]
astralcai Nov 8, 2024
b8285eb
update docs [skip ci]
astralcai Nov 8, 2024
f98ebdc
changelog
astralcai Nov 8, 2024
42b25af
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 8, 2024
f20f9eb
[skip ci]
astralcai Nov 8, 2024
62e91f3
examples in the changelog
astralcai Nov 8, 2024
048ec44
[skip ci]
astralcai Nov 8, 2024
8dc3f6a
let me sneak this in :)
astralcai Nov 11, 2024
3ad7310
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 11, 2024
2fa39f4
fix link in docs
astralcai Nov 11, 2024
6851caf
Merge branch 'master' into device-api-cap
astralcai Nov 11, 2024
666b842
try this
astralcai Nov 11, 2024
33b10c4
Merge branch 'master' into device-api-cap
astralcai Nov 11, 2024
69badd9
Update doc/releases/changelog-dev.md
astralcai Nov 11, 2024
378e167
Merge branch 'master' into device-api-cap
astralcai Nov 11, 2024
005c1ec
see if this works
astralcai Nov 11, 2024
0f5b1d2
Revert "see if this works"
astralcai Nov 11, 2024
14576f7
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 11, 2024
09bb6a9
minor update
astralcai Nov 11, 2024
f838d2b
minor update
astralcai Nov 12, 2024
b2298e1
one more
astralcai Nov 12, 2024
39e0a31
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
astralcai Nov 12, 2024
6f1366d
ooops
astralcai Nov 12, 2024
d4120a7
update
astralcai Nov 12, 2024
26c5208
Merge branch 'master' into device-api-cap
astralcai Nov 12, 2024
e711660
update
astralcai Nov 12, 2024
8b9d233
one more
astralcai Nov 12, 2024
4bb50af
pylint
astralcai Nov 12, 2024
69967f6
seeding
astralcai Nov 12, 2024
e3809c4
Merge branch 'master' into device-api-cap
astralcai Nov 13, 2024
e71fea9
adopt suggestions from code review
astralcai Nov 13, 2024
42ca101
Merge branch 'device-api-cap' of https://github.com/PennyLaneAI/penny…
astralcai Nov 13, 2024
87a4e8c
remove the options section
astralcai Nov 13, 2024
961d413
happy now?
astralcai Nov 13, 2024
4c117fd
try again
astralcai Nov 13, 2024
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
251 changes: 227 additions & 24 deletions doc/development/plugins.rst

Large diffs are not rendered by default.

46 changes: 34 additions & 12 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,39 @@
# Release 0.40.0-dev (development release)

<h3>New features since last release</h3>

* A `DeviceCapabilities` data class is defined to contain all capabilities of the device's execution interface (i.e. its implementation of `Device.execute`). A TOML file can be used to define the capabilities of a device, and it can be loaded into a `DeviceCapabilities` object.
[(#6407)](https://github.com/PennyLaneAI/pennylane/pull/6407)

```pycon
>>> from pennylane.devices.capabilities import load_toml_file, parse_toml_document, DeviceCapabilities
>>> document = load_toml_file("my_device.toml")
>>> capabilities = parse_toml_document(document)
>>> isinstance(capabilities, DeviceCapabilities)
True
```
* Developers of plugin devices now have the option of providing a TOML-formatted configuration file
to declare the capabilities of the device. See [Device Capabilities](https://docs.pennylane.ai/en/latest/development/plugins.html#device-capabilities) for details.
[(#6407)](https://github.com/PennyLaneAI/pennylane/pull/6407)
[(#6433)](https://github.com/PennyLaneAI/pennylane/pull/6433)

* An internal module `pennylane.devices.capabilities` is added that defines a new `DeviceCapabilites`
data class, as well as functions that load and parse the TOML-formatted configuration files.

```pycon
>>> from pennylane.devices.capabilities import DeviceCapabilities
>>> capabilities = DeviceCapabilities.from_toml_file("my_device.toml")
>>> isinstance(capabilities, DeviceCapabilities)
True
```

* Devices that extends `qml.devices.Device` now has an optional class attribute `capabilities`
that is an instance of the `DeviceCapabilities` data class, constructed from the configuration
file if it exists. Otherwise, it is set to `None`.

```python
from pennylane.devices import Device

class MyDevice(Device):

config_filepath = "path/to/config.toml"

...
```
```pycon
>>> isinstance(MyDevice.capabilities, DeviceCapabilities)
True
```

<h4>New API for Qubit Mixed</h4>

Expand Down Expand Up @@ -109,11 +131,11 @@

This release contains contributions from (in alphabetical order):

Shiwen An
Shiwen An,
Astral Cai,
Yushao Chen,
Pietropaolo Frisoni,
Austin Huang,
Christina Lee,
Andrija Paurevic,
Justin Pickering
Justin Pickering,
2 changes: 1 addition & 1 deletion pennylane/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
.. autosummary::
:toctree: api


default_qubit
default_gaussian
default_mixed
Expand Down Expand Up @@ -147,6 +146,7 @@ def execute(self, circuits, execution_config = qml.devices.DefaultExecutionConfi
"""


from .capabilities import DeviceCapabilities
from .execution_config import ExecutionConfig, DefaultExecutionConfig, MCMConfig
from .device_constructor import device, refresh_devices
from .device_api import Device
Expand Down
31 changes: 28 additions & 3 deletions pennylane/devices/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""
Defines the DeviceCapabilities class, and tools to load it from a TOML file.
"""

import re
import sys
from dataclasses import dataclass, field, replace
from enum import Enum
Expand Down Expand Up @@ -79,6 +79,23 @@ def __and__(self, other: "OperatorProperties") -> "OperatorProperties":
)


def _supports_operator(op_name: str, op_dict: dict[str, OperatorProperties]) -> bool:
"""Checks if the given operator is supported by name."""

if op_name in op_dict:
return True

if match := re.match(r"Adjoint\((.*)\)", op_name):
base_op_name = match.group(1)
return base_op_name in op_dict and op_dict[base_op_name].invertible
albi3ro marked this conversation as resolved.
Show resolved Hide resolved

if match := re.match(r"C\((.*)\)", op_name):
base_op_name = match.group(1)
return base_op_name in op_dict and op_dict[base_op_name].controllable

return False


@dataclass
class DeviceCapabilities: # pylint: disable=too-many-instance-attributes
"""Capabilities of a quantum device.
Expand Down Expand Up @@ -152,6 +169,14 @@ def from_toml_file(cls, file_path: str, runtime_interface="pennylane") -> "Devic
update_device_capabilities(capabilities, document, runtime_interface)
return capabilities

def supports_operation(self, operation_name: str) -> bool:
"""Checks if the given operation is supported by name."""
return _supports_operator(operation_name, self.operations)

def supports_observable(self, observable_name: str) -> bool:
"""Checks if the given observable is supported by name."""
return _supports_operator(observable_name, self.observables)


VALID_COMPILATION_OPTIONS = {
"qjit_compatible",
Expand Down Expand Up @@ -322,7 +347,7 @@ def _get_compilation_options(document: dict, prefix: str = "") -> dict[str, bool
return section


def _get_options(document: dict) -> dict[str, str]:
def _get_options(document: dict) -> dict[str, any]:
"""Get custom options"""
return document.get("options", {})

Expand All @@ -337,7 +362,7 @@ def parse_toml_document(document: dict) -> DeviceCapabilities:
"""

schema = int(document["schema"])
assert schema in ALL_SUPPORTED_SCHEMAS, f"Unsupported capabilities TOML schema {schema}"
assert schema in ALL_SUPPORTED_SCHEMAS, f"Unsupported config TOML schema {schema}"
operations = _get_operations(document)
observables = _get_observables(document)
measurement_processes = _get_measurement_processes(document)
Expand Down
23 changes: 22 additions & 1 deletion pennylane/devices/device_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"""
# pylint: disable=comparison-with-callable
import abc
import warnings
from collections.abc import Iterable
from dataclasses import replace
from numbers import Number
Expand All @@ -29,6 +30,7 @@
from pennylane.typing import Result, ResultBatch
from pennylane.wires import Wires

from .capabilities import DeviceCapabilities
from .execution_config import DefaultExecutionConfig, ExecutionConfig


Expand All @@ -45,7 +47,7 @@ class Device(abc.ABC):
**Streamlined interface:** Only methods that are required to interact with the rest of PennyLane will be placed in the
interface. Developers will be able to clearly see what they can change while still having a fully functional device.

**Reduction of duplicate methods:** Methods that solve similar problems are combined together. Only one place will have
**Reduction of duplicate methods:** Methods that solve similar problems are combined. Only one place will have
to solve each individual problem.

**Support for dynamic execution configurations:** Properties such as shots belong to specific executions.
Expand Down Expand Up @@ -121,6 +123,25 @@ class Device(abc.ABC):

"""

config_filepath: Optional[str] = None
"""A device can use a `toml` file to specify the capabilities of the backend device. If this
is provided, the file will be loaded into a :class:`~.DeviceCapabilities` object assigned to
the :attr:`capabilities` attribute."""

capabilities: Optional[DeviceCapabilities] = None
"""A :class:`~.DeviceCapabilities` object describing the capabilities of the backend device."""

def __init_subclass__(cls, **kwargs):
if cls.config_filepath is not None:
# TODO: remove this try-except block once all TOML files are updated to the new schema.
try:
cls.capabilities = DeviceCapabilities.from_toml_file(cls.config_filepath)
except AssertionError as e:
if "Unsupported config TOML schema" not in str(e):
raise e # pragma: no cover
warnings.warn(str(e))
super().__init_subclass__(**kwargs)

@property
def name(self) -> str:
"""The name of the device or set of devices.
Expand Down
1 change: 1 addition & 0 deletions pennylane/devices/legacy_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ def __init__(self, device: "qml.devices.LegacyDevice"):
)

self._device = device
self.config_filepath = getattr(self._device, "config_filepath", None)

@property
def tracker(self):
Expand Down
10 changes: 6 additions & 4 deletions pennylane/devices/preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from collections.abc import Callable, Generator, Sequence
from copy import copy
from itertools import chain
from typing import Optional, Union
from typing import Optional, Type, Union

import pennylane as qml
from pennylane import Snapshot, transform
Expand All @@ -48,7 +48,7 @@ def _operator_decomposition_gen(
max_expansion: Optional[int] = None,
current_depth=0,
name: str = "device",
error: Optional[Exception] = None,
error: Optional[Type[Exception]] = None,
) -> Generator[qml.operation.Operator, None, None]:
"""A generator that yields the next operation that is accepted."""
if error is None:
Expand Down Expand Up @@ -302,7 +302,7 @@ def decompose(
] = None,
max_expansion: Union[int, None] = None,
name: str = "device",
error: Optional[Exception] = None,
error: Optional[Type[Exception]] = None,
) -> tuple[QuantumScriptBatch, PostprocessingFn]:
"""Decompose operations until the stopping condition is met.

Expand Down Expand Up @@ -456,7 +456,9 @@ def validate_observables(
if m.obs is not None:
if isinstance(m.obs, Tensor):
if any(not stopping_condition(o) for o in m.obs.obs):
raise qml.DeviceError(f"Observable {repr(m.obs)} not supported on {name}")
raise qml.DeviceError( # pragma: no cover
f"Observable {repr(m.obs)} not supported on {name}" # pragma: no cover
) # pragma: no cover
elif not stopping_condition(m.obs):
raise qml.DeviceError(f"Observable {repr(m.obs)} not supported on {name}")

Expand Down
15 changes: 14 additions & 1 deletion pennylane/devices/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,27 @@ def _init_state(n):
return _init_state


def get_legacy_capabilities(dev):
"""Gets the capabilities dictionary of a device."""

if isinstance(dev, qml.devices.LegacyDeviceFacade):
return dev.target_device.capabilities()

if isinstance(dev, qml.devices.LegacyDevice):
return dev.capabilities()

return {}


@pytest.fixture(scope="session")
def skip_if():
"""Fixture to skip tests."""

def _skip_if(dev, capabilities):
"""Skip test if device has any of the given capabilities."""

dev_capabilities = dev.capabilities()
dev_capabilities = get_legacy_capabilities(dev)

for capability, value in capabilities.items():
# skip if capability not found, or if capability has specific value
if capability not in dev_capabilities or dev_capabilities[capability] == value:
Expand Down
12 changes: 6 additions & 6 deletions pennylane/devices/tests/test_compare_default_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from pennylane import numpy as pnp # Import from PennyLane to mirror the standard approach in demos
from pennylane.templates.layers import RandomLayers

from .conftest import get_legacy_capabilities

pytestmark = pytest.mark.skip_unsupported


Expand Down Expand Up @@ -147,9 +149,8 @@ def test_pauliz_expectation_analytic(self, device, tol):
if dev.name == dev_def.name:
pytest.skip("Device is default.qubit.")

supports_tensor = isinstance(dev, qml.devices.Device) or (
"supports_tensor_observables" in dev.capabilities()
and dev.capabilities()["supports_tensor_observables"]
supports_tensor = isinstance(dev, qml.devices.Device) or get_legacy_capabilities(dev).get(
"supports_tensor_observables", False
)

if not supports_tensor:
Expand Down Expand Up @@ -186,9 +187,8 @@ def test_random_circuit(self, device, tol, ret):
if dev.name == dev_def.name:
pytest.skip("Device is default.qubit.")

supports_tensor = isinstance(dev, qml.devices.Device) or (
"supports_tensor_observables" in dev.capabilities()
and dev.capabilities()["supports_tensor_observables"]
supports_tensor = isinstance(dev, qml.devices.Device) or get_legacy_capabilities(dev).get(
"supports_tensor_observables", False
)

if not supports_tensor:
Expand Down
7 changes: 4 additions & 3 deletions pennylane/devices/tests/test_measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
)
from pennylane.wires import Wires

from .conftest import get_legacy_capabilities

pytestmark = pytest.mark.skip_unsupported

# ==========================================================
Expand Down Expand Up @@ -140,9 +142,8 @@ def test_tensor_observables_can_be_implemented(self, device_kwargs):
This test is skipped for devices that do not support tensor observables."""
device_kwargs["wires"] = 2
dev = qml.device(**device_kwargs)
supports_tensor = isinstance(dev, qml.devices.Device) or (
"supports_tensor_observables" in dev.capabilities()
and dev.capabilities()["supports_tensor_observables"]
supports_tensor = isinstance(dev, qml.devices.Device) or get_legacy_capabilities(dev).get(
"supports_tensor_observables", False
)
if not supports_tensor:
pytest.skip("Device does not support tensor observables.")
Expand Down
16 changes: 9 additions & 7 deletions pennylane/devices/tests/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import pennylane as qml
import pennylane.numpy as pnp

from .conftest import get_legacy_capabilities

try:
import tensorflow as tf

Expand Down Expand Up @@ -113,7 +115,7 @@ def test_has_capabilities_dictionary(self, device_kwargs):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)
assert isinstance(cap, dict)

def test_model_is_defined_valid_and_correct(self, device_kwargs):
Expand All @@ -122,7 +124,7 @@ def test_model_is_defined_valid_and_correct(self, device_kwargs):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)
assert "model" in cap
assert cap["model"] in ["qubit", "cv"]

Expand All @@ -149,7 +151,7 @@ def test_passthru_interface_is_correct(self, device_kwargs):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)

if "passthru_interface" not in cap:
pytest.skip("No passthru_interface capability specified by device.")
Expand Down Expand Up @@ -200,7 +202,7 @@ def test_supports_tensor_observables(self, device_kwargs):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)

if "supports_tensor_observables" not in cap:
pytest.skip("No supports_tensor_observables capability specified by device.")
Expand All @@ -226,7 +228,7 @@ def test_returns_state(self, device_kwargs):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)

@qml.qnode(dev)
def circuit():
Expand Down Expand Up @@ -263,7 +265,7 @@ def test_returns_probs(self, device_kwargs):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)

if "returns_probs" not in cap:
pytest.skip("No returns_probs capability specified by device.")
Expand All @@ -290,7 +292,7 @@ def test_supports_broadcasting(self, device_kwargs, mocker):
dev = qml.device(**device_kwargs)
if isinstance(dev, qml.devices.Device):
pytest.skip("test is old interface specific.")
cap = dev.capabilities()
cap = get_legacy_capabilities(dev)

assert "supports_broadcasting" in cap

Expand Down
Loading