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

[WIP] Redesign capabilities dictionary #781

Merged
merged 53 commits into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c365ff5
prototype
mariaschuld Aug 13, 2020
d0b2435
backup
mariaschuld Aug 17, 2020
fae2c75
backup
mariaschuld Aug 17, 2020
b118948
finish prototype
mariaschuld Aug 17, 2020
8f8c05a
backup
mariaschuld Aug 17, 2020
48915e5
Merge branch 'master' into capabilities_redesign
mariaschuld Aug 24, 2020
511710e
make values boolean
mariaschuld Aug 24, 2020
ffdf851
backup
mariaschuld Aug 25, 2020
36e2300
update dictionary keys
mariaschuld Aug 25, 2020
d48d568
backup
mariaschuld Aug 26, 2020
db44760
fix bugs in tensor tests, set tol higher for sampled tests
mariaschuld Aug 27, 2020
a433006
add noisy capability
mariaschuld Aug 27, 2020
0251a3e
improve developer information
mariaschuld Aug 27, 2020
1bf5d3b
remove sampled from tensor device, and add analytic attribute
mariaschuld Aug 27, 2020
1a2b9cd
fix all tests for base devices
mariaschuld Aug 27, 2020
37050ee
polish docstring some more
mariaschuld Aug 27, 2020
a3c8166
reverse changing of tolerance
mariaschuld Aug 27, 2020
12e7efd
fix general tests
mariaschuld Aug 27, 2020
909a775
fix some more tests
mariaschuld Aug 27, 2020
ec2ec7a
Merge branch 'master' into capabilities_redesign
mariaschuld Aug 27, 2020
45498d9
black
mariaschuld Aug 27, 2020
455bb89
fix code factor
mariaschuld Aug 27, 2020
e5b0b18
black again
mariaschuld Aug 27, 2020
0a6c28c
add capabilities tests
mariaschuld Aug 27, 2020
9a3376a
update black version and rerun black
mariaschuld Aug 27, 2020
106c719
Update pennylane/_device.py
mariaschuld Aug 27, 2020
2f0ba49
Update pennylane/_device.py
mariaschuld Aug 27, 2020
a7159b6
Update pennylane/devices/tests/test_gates.py
mariaschuld Aug 27, 2020
b168253
rewrite core to check for old and new keys
mariaschuld Aug 27, 2020
a3acad7
Merge branch 'capabilities_redesign' of github.com:XanaduAI/pennylane…
mariaschuld Aug 27, 2020
aba06fe
add test for reversible diff
mariaschuld Aug 27, 2020
f22737a
Merge branch 'master' into capabilities_redesign
mariaschuld Aug 27, 2020
09a983a
fix isinstance
mariaschuld Aug 27, 2020
227d4d4
Merge branch 'capabilities_redesign' of github.com:XanaduAI/pennylane…
mariaschuld Aug 27, 2020
9b277a9
black needs to be applied again
mariaschuld Aug 27, 2020
0b78eeb
Merge branch 'master' of github.com:XanaduAI/pennylane into capabilit…
mariaschuld Sep 5, 2020
5d01545
polish
mariaschuld Sep 5, 2020
48c4bae
fix test bug
mariaschuld Sep 5, 2020
34c4bfe
Merge branch 'master' into capabilities_redesign
antalszava Sep 8, 2020
b9b5a60
Update doc/development/plugins.rst
mariaschuld Sep 9, 2020
0cc5b10
remove unused capabilities
mariaschuld Sep 9, 2020
21b9726
remove accidentally commited files
mariaschuld Sep 9, 2020
3c7b9eb
fix test
mariaschuld Sep 9, 2020
f9ee3d0
polish docstring
mariaschuld Sep 9, 2020
1d8cef9
remove default keys from Device, make better capability device tests,…
mariaschuld Sep 10, 2020
fc86431
black
mariaschuld Sep 10, 2020
6cecd28
fix codefactor issues
mariaschuld Sep 10, 2020
5b0925a
black
mariaschuld Sep 10, 2020
35413ec
Merge branch 'master' into capabilities_redesign
mariaschuld Sep 14, 2020
1b4e97c
Merge branch 'master' into capabilities_redesign
mariaschuld Sep 14, 2020
b5aa8b2
implement Tom's code review
mariaschuld Sep 14, 2020
73be770
fix test
mariaschuld Sep 14, 2020
d658563
fix test again
mariaschuld Sep 14, 2020
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
26 changes: 20 additions & 6 deletions doc/development/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ and use the device. These include:
Defining all these attributes is mandatory.


Supporting operations
---------------------
Device capabilities
-------------------

You must further tell PennyLane about the operations that your device supports
as well as potential further capabilities, by providing the following class attributes/properties:
Expand All @@ -101,15 +101,29 @@ as well as potential further capabilities, by providing the following class attr
conversion between the two conventions takes place automatically
by the plugin device.

* :attr:`.Device._capabilities`: a dictionary containing information about the capabilities of
the device. Keys currently supported include:
* :func:`.Device.capabilities`: A class method which returns the dictionary of capabilities of a device. A
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Just wondering if it would make sense to update the subsection title to make it more capability focused, e.g., "Defining capabilities"

new device should override this method to retrieve the parent classes' capabilities dictionary, make a copy
and update and/or add capabilities before returning the copy.

* ``'model'`` (*str*): either ``'qubit'`` or ``'CV'``.
Examples of capabilities are:

* ``'inverse_operations'`` (*bool*): ``True`` if the device supports
* ``'model'`` (*str*): either ``'qubit'`` or ``'cv'``.

* ``'returns_state'`` (*bool*): ``True`` if the device returns the quantum state via ``dev.state``.

* ``'supports_inverse_operations'`` (*bool*): ``True`` if the device supports
applying the inverse of operations. Operations which should be inverted
have the property ``operation.inverse == True``.

* ``'supports_tensor_observables'`` (*bool*): ``True`` if the device supports observables composed from tensor
products such as ``PauliZ(wires=0) @ PauliZ(wires=1)``.

Some capabilities are queried by PennyLane core to make decisions on how to best run computations, others are used
by external apps built on top of the device ecosystem.

To find out which capabilities are (possibly automatically) defined for your device ``dev = device('my.device')``,
check the output of ``dev.capabilities()``.

Adding arguments to your device
--------------------------------

Expand Down
49 changes: 40 additions & 9 deletions pennylane/_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ class Device(abc.ABC):
"""

# pylint: disable=too-many-public-methods
_capabilities = {} #: dict[str->*]: plugin capabilities
_capabilities = {
"model": None,
}
"""The capabilities dictionary stores the properties of a device. Devices can add their
own custom properties and overwrite existing ones by overriding the ``capabilities()`` method."""
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

_circuits = {} #: dict[str->Circuit]: circuit templates associated with this API class
_asarray = staticmethod(np.asarray)

Expand Down Expand Up @@ -213,9 +218,20 @@ def map_wires(self, wires):

@classmethod
def capabilities(cls):
"""Get the other capabilities of the plugin.
"""Get the capabilities of this device class.

Inheriting classes that change or add capabilities must override this method, for example via

Measurements, batching etc.
.. code-block:: python

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
supports_inverse_operations=False,
supports_a_new_capability=True,
)
return capabilities

Returns:
dict[str->*]: results
Expand Down Expand Up @@ -411,9 +427,12 @@ def supports_operation(self, operation):
if isinstance(operation, str):

if operation.endswith(Operation.string_for_inverse):
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
return operation[
: -len(Operation.string_for_inverse)
] in self.operations and self.capabilities().get("inverse_operations", False)
in_ops = operation[: -len(Operation.string_for_inverse)] in self.operations
# TODO: update when all capabilities keys changed to "supports_inverse_operations"
supports_inv = self.capabilities().get(
"supports_inverse_operations", False
) or self.capabilities().get("inverse_operations", False)
return in_ops and supports_inv
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

return operation in self.operations

Expand Down Expand Up @@ -468,7 +487,11 @@ def check_validity(self, queue, observables):
operation_name = o.name

if o.inverse:
if not self.capabilities().get("inverse_operations", False):
# TODO: update when all capabilities keys changed to "supports_inverse_operations"
supports_inv = self.capabilities().get(
"supports_inverse_operations", False
) or self.capabilities().get("inverse_operations", False)
if not supports_inv:
raise DeviceError(
"The inverse of gates are not supported on device {}".format(
self.short_name
Expand All @@ -484,7 +507,11 @@ def check_validity(self, queue, observables):
for o in observables:

if isinstance(o, Tensor):
if not self.capabilities().get("tensor_observables", False):
# TODO: update when all capabilities keys changed to "supports_tensor_observables"
supports_tensor = self.capabilities().get(
"supports_tensor_observables", False
) or self.capabilities().get("tensor_observables", False)
if not supports_tensor:
raise DeviceError(
"Tensor observables not supported on device {}".format(self.short_name)
)
Expand All @@ -501,7 +528,11 @@ def check_validity(self, queue, observables):
observable_name = o.name

if issubclass(o.__class__, Operation) and o.inverse:
if not self.capabilities().get("inverse_operations", False):
# TODO: update when all capabilities keys changed to "supports_inverse_operations"
supports_inv = self.capabilities().get(
"supports_inverse_operations", False
) or self.capabilities().get("inverse_operations", False)
if not supports_inv:
raise DeviceError(
"The inverse of gates are not supported on device {}".format(
self.short_name
Expand Down
32 changes: 7 additions & 25 deletions pennylane/_qubit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,32 +113,14 @@ def __init__(self, wires=1, shots=1000, analytic=True):

@classmethod
def capabilities(cls):
"""Get the capabilities of the plugin.

Capabilities include:

* ``"model"`` (*str*): either ``"qubit"`` or ``"CV"``.

* ``"inverse_operations"`` (*bool*): ``True`` if the device supports
applying the inverse of operations. Operations which should be inverted
have ``operation.inverse == True``.

* ``"tensor_observables" (*bool*): ``True`` if the device supports
expectation values/variance/samples of :class:`~.Tensor` observables.

The qubit device class has built-in support for tensor observables. As a
result, devices that inherit from this class automatically
have the following items in their capabilities
dictionary:

* ``"model": "qubit"``
* ``"tensor_observables": True``

Returns:
dict[str->*]: results
"""
capabilities = cls._capabilities
capabilities.update(model="qubit", tensor_observables=True)
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
capabilities = super().capabilities().copy()
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
capabilities.update(
model="qubit",
supports_finite_shots=True,
supports_tensor_observables=True,
returns_probs=True,
)
return capabilities

def reset(self):
Expand Down
16 changes: 15 additions & 1 deletion pennylane/beta/devices/default_tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
r"""
Experimental simulator plugin based on tensor network contractions
"""
# pylint: disable=too-many-instance-attributes
import warnings
from itertools import product

Expand Down Expand Up @@ -101,7 +102,6 @@ class DefaultTensor(Device):
pennylane_requires = "0.12"
version = "0.12.0"
author = "Xanadu Inc."
_capabilities = {"model": "qubit", "tensor_observables": True}

_operation_map = {
"BasisState": None,
Expand Down Expand Up @@ -164,6 +164,20 @@ def __init__(self, wires, shots=1000, representation="exact", contraction_method
self._rep = representation
self._contraction_method = contraction_method
self.reset()
self.analytic = False
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
model="qubit",
supports_analytic_computation=True,
supports_finite_shots=False,
supports_tensor_observables=True,
returns_state=False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! 👍

returns_probs=False,
)
return capabilities
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

def reset(self):
"""Reset the device."""
Expand Down
15 changes: 9 additions & 6 deletions pennylane/beta/devices/default_tensor_tf.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,6 @@ class DefaultTensorTF(DefaultTensor):
# pylint: disable=too-many-instance-attributes
name = "PennyLane TensorNetwork (TensorFlow) simulator plugin"
short_name = "default.tensor.tf"
_capabilities = {
"model": "qubit",
"tensor_observables": True,
"provides_jacobian": True,
"passthru_interface": "tf",
}

_operation_map = copy.copy(DefaultTensor._operation_map)
_operation_map.update(
Expand Down Expand Up @@ -174,6 +168,15 @@ def __init__(self, wires, shots=1000, representation="exact", contraction_method
contraction_method=contraction_method,
)

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
provides_jacobian=True,
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
passthru_interface="tf",
)
return capabilities

def reset(self):
self.res = None
self.variables = []
Expand Down
3 changes: 3 additions & 0 deletions pennylane/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@
autograd_ops
tests
"""
# DefaultQubitTF and DefaultQubitAutograd not imported here since this
# would lead to an automatic import of tensorflow and autograd, which are
# not PennyLane core dependencies
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
from .default_qubit import DefaultQubit
from .default_gaussian import DefaultGaussian
15 changes: 13 additions & 2 deletions pennylane/devices/default_gaussian.py
Original file line number Diff line number Diff line change
Expand Up @@ -656,8 +656,6 @@ class DefaultGaussian(Device):
version = "0.12.0"
author = "Xanadu Inc."

_capabilities = {"model": "cv"}
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

_operation_map = {
"Beamsplitter": beamsplitter,
"ControlledAddition": controlled_addition,
Expand Down Expand Up @@ -695,6 +693,19 @@ def __init__(self, wires, *, shots=1000, hbar=2, analytic=True):

self.reset()

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
model="cv",
supports_analytic_computation=True,
supports_finite_shots=True,
returns_probs=False,
returns_state=False,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could set returns_state=False in the base Device, and then only turn it on when needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why I reduced the default values in the base device is that not having the value is better than misreporting. Also, it would set this value for all devices, and it would be better to go through them one by one...

supports_reversible_diff=False,
)
return capabilities

def pre_apply(self):
self.reset()

Expand Down
13 changes: 12 additions & 1 deletion pennylane/devices/default_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ class DefaultQubit(QubitDevice):
pennylane_requires = "0.12"
version = "0.12.0"
author = "Xanadu Inc."
_capabilities = {"inverse_operations": True, "reversible_diff": True}

operations = {
"BasisState",
Expand Down Expand Up @@ -360,6 +359,18 @@ def _get_unitary_matrix(self, unitary): # pylint: disable=no-self-use

return unitary.matrix

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
model="qubit",
supports_reversible_diff=True,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this would not be defined among the capabilities in Device?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is set to False in Device...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh not sure how I missed that, my bad!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for my understanding, how do we know if something supports reversible diff?

supports_inverse_operations=True,
supports_analytic_computation=True,
returns_state=True,
)
return capabilities

def _create_basis_state(self, index):
"""Return a computational basis state over all wires.

Expand Down
15 changes: 9 additions & 6 deletions pennylane/devices/default_qubit_autograd.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,6 @@ class DefaultQubitAutograd(DefaultQubit):
name = "Default qubit (Autograd) PennyLane plugin"
short_name = "default.qubit.autograd"

_capabilities = {
"model": "qubit",
"provides_jacobian": False,
"passthru_interface": "autograd",
}

parametric_ops = {
"PhaseShift": autograd_ops.PhaseShift,
"RX": autograd_ops.RX,
Expand Down Expand Up @@ -118,6 +112,15 @@ def __init__(self, wires, *, shots=1000, analytic=True):
del self._apply_ops["Hadamard"]
del self._apply_ops["CZ"]

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
passthru_interface="autograd",
supports_reversible_diff=False,
)
return capabilities

@staticmethod
def _scatter(indices, array, new_dimensions):
new_array = np.zeros(new_dimensions, dtype=array.dtype.type)
Expand Down
15 changes: 9 additions & 6 deletions pennylane/devices/default_qubit_tf.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,6 @@ class DefaultQubitTF(DefaultQubit):
name = "Default qubit (TensorFlow) PennyLane plugin"
short_name = "default.qubit.tf"

_capabilities = {
"model": "qubit",
"provides_jacobian": False,
"passthru_interface": "tf",
}

parametric_ops = {
"PhaseShift": tf_ops.PhaseShift,
"RX": tf_ops.RX,
Expand Down Expand Up @@ -165,6 +159,15 @@ def __init__(self, wires, *, shots=1000, analytic=True):
# prevent using special apply method for this gate due to slowdown in TF implementation
del self._apply_ops["CZ"]

@classmethod
def capabilities(cls):
capabilities = super().capabilities().copy()
capabilities.update(
passthru_interface="tf",
supports_reversible_diff=False,
)
return capabilities

@staticmethod
def _scatter(indices, array, new_dimensions):
indices = np.expand_dims(indices, 1)
Expand Down
4 changes: 2 additions & 2 deletions pennylane/devices/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
# List of all devices that are included in PennyLane
LIST_CORE_DEVICES = {"default.qubit", "default.qubit.tf", "default.qubit.autograd"}
# TODO: add beta devices "default.tensor", "default.tensor.tf", which currently
# do not have an "analytic" attribute.
# do not return probabilities.


@pytest.fixture(scope="function")
Expand Down Expand Up @@ -104,7 +104,7 @@ def _device(wires):
)

capabilities = dev.capabilities()
if "model" not in capabilities or not capabilities["model"] == "qubit":
if capabilities.get("model", None) != "qubit":
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
# exit the tests if device based on cv model (currently not supported)
pytest.exit("The device test suite currently only runs on qubit-based devices.")

Expand Down
Loading