Skip to content

Commit

Permalink
Deprecate top level access to legacy device base classes (#6238)
Browse files Browse the repository at this point in the history
**Context:**

`qml.Device`, `qml.QubitDevice`, and `qml.QutritDevice` all reflect the
legacy device interface, which is no longer the recommended way of
creating devices.

**Description of the Change:**

Deprecate top level access to the `Device`, `QubitDevice`, and
`QutritDevice`.

**Benefits:**

Further isolation of the legacy device interface.

**Possible Drawbacks:**

All deprecations propagate through the ecosystem and can cause issues.

**Related GitHub Issues:**

[sc-71519]

---------

Co-authored-by: Mudit Pandey <mudit.pandey@xanadu.ai>
  • Loading branch information
albi3ro and mudit2812 committed Sep 16, 2024
1 parent 84ee4b9 commit c2d4725
Show file tree
Hide file tree
Showing 27 changed files with 216 additions and 178 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/interface-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,7 @@ jobs:
# catalyst requires the latest version of pennylane that is about to be released.
# Installing catalyst after pennylane to make sure that the latest catalyst is used.
install_catalyst_nightly: true
# using lightning master does not work for the tests with external libraries
install_pennylane_lightning_master: false
install_pennylane_lightning_master: true
pytest_coverage_flags: ${{ inputs.pytest_coverage_flags }}
pytest_markers: external
additional_pip_packages: pyzx matplotlib stim quimb mitiq pennylane-qiskit ply
Expand Down
7 changes: 7 additions & 0 deletions doc/development/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ Pending deprecations
- Deprecated in v0.39
- Will be removed in v0.40

* ``Device``, ``QubitDevice``, and ``QutritDevice`` will no longer be imported top level in v0.40. They instead
we be available as ``qml.devices.LegacyDevice``, ``qml.devices.QubitDevice``, and ``qml.devices.QutritDevice``
respectively.

- Deprecated top level access in v0.39
- Top level access removed in v0.40

* `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` instead. `QNode.get_gradient_fn` can also be used to
process the diff method.

Expand Down
5 changes: 5 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@
`qml.measurements` modules instead.
[(#5911)](https://github.com/PennyLaneAI/pennylane/pull/5911)

* `Device`, `QubitDevice`, and `QutritDevice` will no longer be accessible via top-level import in v0.40.
They will still be accessible as `qml.devices.LegacyDevice`, `qml.devices.QubitDevice`, and `qml.devices.QutritDevice`
respectively.
[(#6238)](https://github.com/PennyLaneAI/pennylane/pull/6238/)

* `QNode.gradient_fn` is deprecated. Please use `QNode.diff_method` and `QNode.get_gradient_fn` instead.
[(#6244)](https://github.com/PennyLaneAI/pennylane/pull/6244)

Expand Down
19 changes: 17 additions & 2 deletions pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
PennyLane can be directly imported.
"""

import numpy as _np


from pennylane.boolean_fn import BooleanFn
import pennylane.numpy
Expand Down Expand Up @@ -180,13 +178,30 @@ def __getattr__(name):
if name == "plugin_devices":
return pennylane.devices.device_constructor.plugin_devices

from warnings import warn # pylint: disable=import-outside-toplevel

if name == "QubitDevice":
warn(
"QubitDevice will no longer be accessible top level. Please access "
" the class as pennylane.devices.QubitDevice",
PennyLaneDeprecationWarning,
)
return pennylane.devices._qubit_device.QubitDevice # pylint:disable=protected-access

if name == "QutritDevice":
warn(
"QutritDevice will no longer be accessible top level. Please access "
" the class as pennylane.devices.QutritDevice",
PennyLaneDeprecationWarning,
)
return pennylane.devices._qutrit_device.QutritDevice # pylint:disable=protected-access

if name == "Device":
warn(
"Device will no longer be accessible top level. Please access "
" the class as pennylane.devices.LegacyDevice",
PennyLaneDeprecationWarning,
)
return pennylane.devices._legacy_device.Device # pylint:disable=protected-access

raise AttributeError(f"module 'pennylane' has no attribute '{name}'")
Expand Down
12 changes: 0 additions & 12 deletions pennylane/devices/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,6 @@ def validate_diff_method(device, diff_method, device_kwargs):
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")
return

config = qml.devices.ExecutionConfig(gradient_method=diff_method)
if not dev.supports_derivatives(execution_config=config):
pytest.skip(reason="device does not support diff_method")
Expand All @@ -141,12 +135,6 @@ def _device(wires):
f"plugin and all of its dependencies must be installed."
)

if isinstance(dev, qml.Device):
capabilities = dev.capabilities()
if capabilities.get("model", None) != "qubit":
# 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.")

return dev

return _device
Expand Down
5 changes: 1 addition & 4 deletions pennylane/devices/tests/test_compare_default_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ def test_hermitian_expectation(self, device, tol, benchmark):
if dev.shots:
pytest.skip("Device is in non-analytical mode.")

if isinstance(dev, qml.Device) and "Hermitian" not in dev.observables:
pytest.skip("Device does not support the Hermitian observable.")

if dev.name == "default.qubit":
pytest.skip("Device is default.qubit.")

Expand Down Expand Up @@ -107,7 +104,7 @@ def test_projector_expectation(self, device, state, tol, benchmark):
if dev.shots:
pytest.skip("Device is in non-analytical mode.")

if isinstance(dev, qml.Device) and "Projector" not in dev.observables:
if isinstance(dev, qml.devices.LegacyDevice) and "Projector" not in dev.observables:
pytest.skip("Device does not support the Projector observable.")

if dev.name == "default.qubit":
Expand Down
22 changes: 11 additions & 11 deletions pennylane/devices/tests/test_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ def test_supported_gates_can_be_implemented(self, device_kwargs, operation):
device_kwargs["wires"] = 4 # maximum size of current gates
dev = qml.device(**device_kwargs)

if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
if operation not in dev.operations:
pytest.skip("operation not supported.")
else:
Expand Down Expand Up @@ -395,7 +395,7 @@ def test_basis_state(self, device, basis_state, tol, skip_if):
"""Test basis state initialization."""
n_wires = 4
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

@qml.qnode(dev)
Expand All @@ -413,7 +413,7 @@ def test_state_prep(self, device, init_state, tol, skip_if):
"""Test StatePrep initialisation."""
n_wires = 1
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

rnd_state = init_state(n_wires)
Expand All @@ -433,7 +433,7 @@ def test_single_qubit_no_parameters(self, device, init_state, op, mat, tol, skip
"""Test PauliX application."""
n_wires = 1
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

rnd_state = init_state(n_wires)
Expand All @@ -457,7 +457,7 @@ def test_single_qubit_parameters(
"""Test single qubit gates taking a single scalar argument."""
n_wires = 1
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

rnd_state = init_state(n_wires)
Expand All @@ -477,7 +477,7 @@ def test_rotation(self, device, init_state, tol, skip_if, benchmark):
"""Test three axis rotation gate."""
n_wires = 1
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

rnd_state = init_state(n_wires)
Expand All @@ -501,7 +501,7 @@ def test_two_qubit_no_parameters(self, device, init_state, op, mat, tol, skip_if
"""Test two qubit gates."""
n_wires = 2
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})
if not dev.supports_operation(op(wires=range(n_wires)).name):
pytest.skip("op not supported")
Expand All @@ -527,7 +527,7 @@ def test_two_qubit_parameters(
"""Test parametrized two qubit gates taking a single scalar argument."""
n_wires = 2
dev = device(n_wires)
if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

rnd_state = init_state(n_wires)
Expand All @@ -549,7 +549,7 @@ def test_qubit_unitary(self, device, init_state, mat, tol, skip_if, benchmark):
n_wires = int(np.log2(len(mat)))
dev = device(n_wires)

if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
if "QubitUnitary" not in dev.operations:
pytest.skip("Skipped because device does not support QubitUnitary.")

Expand All @@ -574,7 +574,7 @@ def test_special_unitary(self, device, init_state, theta_, tol, skip_if, benchma
n_wires = int(np.log(len(theta_) + 1) / np.log(4))
dev = device(n_wires)

if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
if "SpecialUnitary" not in dev.operations:
pytest.skip("Skipped because device does not support SpecialUnitary.")

Expand Down Expand Up @@ -603,7 +603,7 @@ def test_three_qubit_no_parameters(self, device, init_state, op, mat, tol, skip_
n_wires = 3
dev = device(n_wires)

if isinstance(dev, qml.Device):
if isinstance(dev, qml.devices.LegacyDevice):
skip_if(dev, {"returns_probs": False})

rnd_state = init_state(n_wires)
Expand Down
2 changes: 1 addition & 1 deletion pennylane/devices/tests/test_gates_with_expval.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def test_supported_gate_two_wires_no_parameters(self, device, tol, name, expecte
dev = device(n_wires)

op = getattr(qml.ops, name)
if isinstance(dev, qml.Device) and not dev.supports_operation(op):
if isinstance(dev, qml.devices.LegacyDevice) and not dev.supports_operation(op):
pytest.skip("operation not supported")

@qml.qnode(dev)
Expand Down
Loading

0 comments on commit c2d4725

Please sign in to comment.