diff --git a/doc/conf.py b/doc/conf.py index 06a738dc..b94f97db 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -20,7 +20,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("..")) sys.path.insert(0, os.path.abspath("_ext")) -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath('.')), 'doc')) +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(".")), "doc")) # -- General configuration ------------------------------------------------ @@ -39,8 +39,8 @@ "sphinx.ext.napoleon", "sphinx.ext.inheritance_diagram", "sphinx.ext.intersphinx", - 'sphinx.ext.viewcode', - "sphinx_automodapi.automodapi" + "sphinx.ext.viewcode", + "sphinx_automodapi.automodapi", ] autosummary_generate = True @@ -50,6 +50,7 @@ # Add any paths that contain templates here, relative to this directory. from pennylane_sphinx_theme import templates_dir + templates_path = [templates_dir()] # The suffix(es) of source filenames. @@ -111,7 +112,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -#html_theme = "sphinx_rtd_theme" +# html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -135,12 +136,12 @@ # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -html_favicon = '_static/favicon.ico' +html_favicon = "_static/favicon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -220,7 +221,7 @@ htmlhelp_basename = "PennyLane-Rigettidoc" # # -- Xanadu theme --------------------------------------------------------- -html_theme = 'pennylane' +html_theme = "pennylane" # Register the theme as an extension to generate a sitemap.xml # extensions.append("guzzle_sphinx_theme") @@ -229,16 +230,15 @@ html_theme_options = { "navbar_name": "PennyLane-Rigetti", "extra_copyrights": [ - "TensorFlow, the TensorFlow logo, and any related marks are trademarks " - "of Google Inc." + "TensorFlow, the TensorFlow logo, and any related marks are trademarks " "of Google Inc." ], "toc_overview": True, "navbar_active_link": 3, - "google_analytics_tracking_id": "G-C480Z9JL0D" + "google_analytics_tracking_id": "G-C480Z9JL0D", } -edit_on_github_project = 'PennyLaneAI/pennylane-rigetti' -edit_on_github_branch = 'master/doc' +edit_on_github_project = "PennyLaneAI/pennylane-rigetti" +edit_on_github_branch = "master/doc" # -- Options for LaTeX output --------------------------------------------- @@ -263,7 +263,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, "PennyLane-Rigetti.tex", "PennyLane-Rigetti Documentation", "Xanadu Inc.", "manual"), + ( + master_doc, + "PennyLane-Rigetti.tex", + "PennyLane-Rigetti Documentation", + "Xanadu Inc.", + "manual", + ), ] @@ -295,10 +301,10 @@ # ============================================================ # the order in which autodoc lists the documented members -autodoc_member_order = 'bysource' +autodoc_member_order = "bysource" # inheritance_diagram graphviz attributes -inheritance_node_attrs = dict(color='lightskyblue1', style='filled') +inheritance_node_attrs = dict(color="lightskyblue1", style="filled") -#autodoc_default_flags = ['members'] +# autodoc_default_flags = ['members'] autosummary_generate = True diff --git a/pennylane_rigetti/__init__.py b/pennylane_rigetti/__init__.py index a203cfdf..608ab7eb 100644 --- a/pennylane_rigetti/__init__.py +++ b/pennylane_rigetti/__init__.py @@ -2,6 +2,7 @@ Plugin overview =============== """ + from .ops import CPHASE from .qpu import QPUDevice from .qvm import QVMDevice diff --git a/pennylane_rigetti/device.py b/pennylane_rigetti/device.py index e1f8d17c..3b3b7899 100644 --- a/pennylane_rigetti/device.py +++ b/pennylane_rigetti/device.py @@ -29,12 +29,11 @@ Code details ~~~~~~~~~~~~ """ + import uuid import numpy as np -from collections import OrderedDict - from pyquil import Program from pyquil.quil import DefGate from pyquil.gates import ( @@ -63,7 +62,7 @@ ) from pennylane import QubitDevice, DeviceError -from pennylane.wires import Wires +from pennylane.wires import WireError from ._version import __version__ @@ -178,6 +177,7 @@ class RigettiDevice(QubitDevice): to estimate expectation values of observables. For simulator devices, 0 means the exact EV is returned. """ + pennylane_requires = ">=0.18" version = __version__ author = "Rigetti Computing Inc." @@ -199,14 +199,37 @@ def program(self): """View the last evaluated Quil program""" return self.prog - def define_wire_map(self, wires): + def map_wires_to_backend(self, wires): + """This is a plugin-specific method for Rigetti that maps the wire + labels as provided by the user on device initialization (and used + in defining PennyLane circuits) to the backend qubit labels. This + mapping, should it exist, is stored in the self.wiring object. + + If no hardware wire labels exist (e.g. simulator backends), this + method will map to consecutive wire ordering. Mapping from the + user-provided labels to consecutive integers is already included in + the map_wires function of the PennyLane device base class. + + **Example** + + A device created with wires ["a", "b", "c"] on a hardware backend with + qubits [100, 101, 102...] will have a self.wiring of {"a":100, "b":101, "c":102}, + which will be used to map the wires from the PennyLane circuit to the backend. + + If the backend doesn't have self.wiring, this will be mapped according to the + wire_map, {"a":0, "b":1, "c":2}, instead""" if hasattr(self, "wiring"): - device_wires = Wires(self.wiring) + backend_wire_map = self.wiring else: - # if no wiring given, use consecutive wire labels - device_wires = Wires(range(self.num_wires)) + backend_wire_map = self.wire_map + try: + mapped_wires = wires.map(backend_wire_map) + except WireError as e: + raise WireError( + f"Did not find some of the wires {wires} on device with wires {self.wires}." + ) from e - return OrderedDict(zip(wires, device_wires)) + return mapped_wires def apply(self, operations, **kwargs): self.prog += self.apply_circuit_operations(operations) @@ -227,9 +250,9 @@ def apply_rotations(self, rotations): rotation_operations = Program() for operation in rotations: # map the ops' wires to the wire labels used by the device - device_wires = self.map_wires(operation.wires) + backend_wires = self.map_wires_to_backend(operation.wires) par = operation.parameters - rotation_operations += self._operation_map[operation.name](*par, *device_wires.labels) + rotation_operations += self._operation_map[operation.name](*par, *backend_wires.labels) return rotation_operations @@ -245,7 +268,7 @@ def apply_circuit_operations(self, operations): prog = Program() for i, operation in enumerate(operations): # map the ops' wires to the wire labels used by the device - device_wires = self.map_wires(operation.wires) + backend_wires = self.map_wires_to_backend(operation.wires) par = operation.parameters if isinstance(par, list) and par: @@ -261,7 +284,7 @@ def apply_circuit_operations(self, operations): f"been applied on a {short_name} device." ) - prog += self._operation_map[operation.name](*par, *device_wires.labels) + prog += self._operation_map[operation.name](*par, *backend_wires.labels) return prog diff --git a/pennylane_rigetti/numpy_wavefunction.py b/pennylane_rigetti/numpy_wavefunction.py index 166f4d22..38eb5976 100644 --- a/pennylane_rigetti/numpy_wavefunction.py +++ b/pennylane_rigetti/numpy_wavefunction.py @@ -19,6 +19,7 @@ Code details ~~~~~~~~~~~~ """ + from pyquil.pyqvm import PyQVM from pyquil.simulation import NumpyWavefunctionSimulator @@ -36,6 +37,7 @@ class NumpyWavefunctionDevice(RigettiDevice): shots (int): Number of circuit evaluations/random samples used to estimate expectation values of observables. """ + name = "pyQVM NumpyWavefunction Simulator Device" short_name = "rigetti.numpy_wavefunction" diff --git a/pennylane_rigetti/ops.py b/pennylane_rigetti/ops.py index f1a97cf8..029db67a 100644 --- a/pennylane_rigetti/ops.py +++ b/pennylane_rigetti/ops.py @@ -21,6 +21,7 @@ Code details ~~~~~~~~~~~~ """ + import pennylane as qml from pennylane.operation import Operation @@ -53,6 +54,7 @@ class CPHASE(Operation): gets applied wires (int): the subsystem the gate acts on """ + num_params = 2 num_wires = 2 par_domain = "R" diff --git a/pennylane_rigetti/qc.py b/pennylane_rigetti/qc.py index 12318d65..519c9006 100644 --- a/pennylane_rigetti/qc.py +++ b/pennylane_rigetti/qc.py @@ -21,16 +21,14 @@ from abc import ABC, abstractmethod from typing import Dict -from collections import OrderedDict +from pennylane.utils import Iterable +from pennylane import DeviceError, numpy as np from pyquil import Program -from pyquil.api import QAMExecutionResult, QuantumComputer, QuantumExecutable +from pyquil.api import QPU, QAMExecutionResult, QuantumComputer, QuantumExecutable from pyquil.gates import RESET, MEASURE from pyquil.quil import Pragma -from pennylane import DeviceError, numpy as np -from pennylane.wires import Wires - from .device import RigettiDevice from ._version import __version__ @@ -45,11 +43,6 @@ class QuantumComputerDevice(RigettiDevice, ABC): device (str): the name of the device to initialise. shots (int): number of circuit evaluations/random samples used to estimate expectation values of observables. - wires (Iterable[Number, str]): Iterable that contains unique labels for the - qubits as numbers or strings (i.e., ``['q1', ..., 'qN']``). - The number of labels must match the number of qubits accessible on the backend. - If not provided, qubits are addressed as consecutive integers ``[0, 1, ...]``, and their number - is inferred from the backend. active_reset (bool): whether to actively reset qubits instead of waiting for for qubits to decay to the ground state naturally. Setting this to ``True`` results in a significantly faster expectation value @@ -57,14 +50,19 @@ class QuantumComputerDevice(RigettiDevice, ABC): Keyword args: compiler_timeout (int): number of seconds to wait for a response from quilc (default 10). + wires (Optional[Union[int, Iterable[Number, str]]]): Number of qubits represented by the device, or an iterable that contains + unique labels for the qubits as numbers or strings (i.e., ``['q1', ..., 'qN']``). When an integer is provided, qubits + are addressed as consecutive integers ``[0, 1, n]`` and mapped to the first available qubits on the device. `wires` must + be provided for QPU devices. If not provided for a QVM device, then the number of wires is inferred from the QVM. execution_timeout (int): number of seconds to wait for a response from the QVM (default 10). parametric_compilation (bool): a boolean value of whether or not to use parametric compilation. """ + version = __version__ author = "Rigetti Computing Inc." - def __init__(self, device, *, shots=1000, wires=None, active_reset=False, **kwargs): + def __init__(self, device, *, wires=None, shots=1000, active_reset=False, **kwargs): if shots is not None and shots <= 0: raise ValueError("Number of shots must be a positive integer or None.") @@ -94,27 +92,43 @@ def __init__(self, device, *, shots=1000, wires=None, active_reset=False, **kwar self.qc = self.get_qc(device, **timeout_args) - self.num_wires = len(self.qc.qubits()) + qubits = sorted( + [ + qubit.id + for qubit in self.qc.quantum_processor.to_compiler_isa().qubits.values() + if qubit.dead is False + ] + ) if wires is None: - # infer the number of modes from the device specs - # and use consecutive integer wire labels - wires = range(self.num_wires) + if not isinstance(self.qc.qam, QPU): + wires = len(qubits) + else: + raise ValueError("Wires must be specified for a QPU device. Got None for wires.") if isinstance(wires, int): + if wires > len(qubits): + raise ValueError( + "Wires must not exceed available qubits on the device. ", + f"The requested device only has {len(qubits)} qubits, received {wires}.", + ) + wires = dict(zip(range(wires), qubits[:wires])) + elif isinstance(wires, Iterable): + if len(wires) > len(qubits): + raise ValueError( + "Wires must not exceed available qubits on the device. ", + f"The requested device only has {len(qubits)} qubits, received {len(wires)}.", + ) + wires = dict(zip(wires, qubits[: len(wires)])) + else: raise ValueError( - f"Device has a fixed number of {self.num_wires} qubits. The wires argument can only be used " - "to specify an iterable of wire labels." - ) - - if self.num_wires != len(wires): - raise ValueError( - f"Device has a fixed number of {self.num_wires} qubits and " - f"cannot be created with {len(wires)} wires." + "Wires for a device must be an integer or an iterable of numbers or strings." ) - self.wiring = dict(enumerate(self.qc.qubits())) + self.num_wires = len(qubits) + self._qubits = qubits[: len(wires)] self.active_reset = active_reset + self.wiring = wires super().__init__(wires, shots) @@ -169,21 +183,11 @@ def _get_timeout_args(self, **kwargs) -> Dict[str, float]: return timeout_args - def define_wire_map(self, wires): - if hasattr(self, "wiring"): - device_wires = Wires(self.wiring) - else: - # if no wiring given, use consecutive wire labels - device_wires = Wires(range(self.num_wires)) - - return OrderedDict(zip(wires, device_wires)) - def apply(self, operations, **kwargs): """Applies the given quantum operations.""" - prag = Program(Pragma("INITIAL_REWIRING", ['"PARTIAL"'])) + self.prog = Program(Pragma("INITIAL_REWIRING", ['"PARTIAL"'])) if self.active_reset: - prag += RESET() - self.prog = prag + self.prog + self.prog += RESET() if self.parametric_compilation: self.prog += self.apply_parametric_operations(operations) @@ -193,7 +197,7 @@ def apply(self, operations, **kwargs): rotations = kwargs.get("rotations", []) self.prog += self.apply_rotations(rotations) - qubits = sorted(self.wiring.values()) + qubits = self._qubits ro = self.prog.declare("ro", "BIT", len(qubits)) for i, q in enumerate(qubits): self.prog.inst(MEASURE(q, ro[i])) @@ -207,15 +211,19 @@ def apply_parametric_operations(self, operations): operations (List[pennylane.Operation]): quantum operations that need to be applied Returns: - pyquil.Prgram(): a pyQuil Program with the given operations + pyquil.Program(): a pyQuil Program with the given operations """ prog = Program() # Apply the circuit operations for i, operation in enumerate(operations): # map the operation wires to the physical device qubits - device_wires = self.map_wires(operation.wires) + backend_wires = self.map_wires_to_backend(operation.wires) - if i > 0 and operation.name in ("QubitStateVector", "StatePrep", "BasisState"): + if i > 0 and operation.name in ( + "QubitStateVector", + "StatePrep", + "BasisState", + ): raise DeviceError( f"Operation {operation.name} cannot be used after other Operations have already been applied " f"on a {self.short_name} device." @@ -244,7 +252,7 @@ def apply_parametric_operations(self, operations): else: par.append(param) - prog += self._operation_map[operation.name](*par, *device_wires.labels) + prog += self._operation_map[operation.name](*par, *backend_wires.labels) return prog diff --git a/pennylane_rigetti/qpu.py b/pennylane_rigetti/qpu.py index decb0816..30771c1c 100644 --- a/pennylane_rigetti/qpu.py +++ b/pennylane_rigetti/qpu.py @@ -19,6 +19,7 @@ Code details ~~~~~~~~~~~~ """ + import warnings import numpy as np @@ -47,13 +48,11 @@ class QPUDevice(QuantumComputerDevice): Args: device (str): the name of the device to initialise. + wires (Iterable[Number, str]): Iterable that contains unique labels for the + qubits as numbers or strings (i.e., ``['q1', ..., 'qN']``). The number of labels must + not exceed the number of qubits accessible on the backend. shots (int): number of circuit evaluations/random samples used to estimate expectation values of observables. - wires (Iterable[Number, str]): Iterable that contains unique labels for the - qubits as numbers or strings (i.e., ``['q1', ..., 'qN']``). - The number of labels must match the number of qubits accessible on the backend. - If not provided, qubits are addressed as consecutive integers ``[0, 1, ...]``, and their number - is inferred from the backend. active_reset (bool): whether to actively reset qubits instead of waiting for for qubits to decay to the ground state naturally. Setting this to ``True`` results in a significantly faster expectation value @@ -74,15 +73,16 @@ class QPUDevice(QuantumComputerDevice): parametric_compilation (bool): a boolean value of whether or not to use parametric compilation. """ + name = "Rigetti QPU Device" short_name = "rigetti.qpu" def __init__( self, device, + wires, *, shots=1000, - wires=None, active_reset=True, load_qc=True, readout_error=None, diff --git a/pennylane_rigetti/qvm.py b/pennylane_rigetti/qvm.py index 297cd101..ebf45d12 100644 --- a/pennylane_rigetti/qvm.py +++ b/pennylane_rigetti/qvm.py @@ -19,6 +19,7 @@ Code details ~~~~~~~~~~~~ """ + import networkx as nx from pyquil import get_qc from pyquil.api import QuantumComputer, QuantumExecutable @@ -51,9 +52,8 @@ class QVMDevice(QuantumComputerDevice): If a list of integers is passed, the circuit evaluations are batched over the list of shots. wires (Iterable[Number, str]): Iterable that contains unique labels for the qubits as numbers or strings (i.e., ``['q1', ..., 'qN']``). - The number of labels must match the number of qubits accessible on the backend. - If not provided, qubits are addressed as consecutive integers [0, 1, ...], and their number - is inferred from the backend. + The number of labels must match the number of qubits accessible for the selected QMV. + If not provided, qubits are addressed as consecutive integers [0, 1, ...], and their number is inferred from the QVM. noisy (bool): set to ``True`` to add noise models to your QVM. Keyword args: @@ -62,10 +62,11 @@ class QVMDevice(QuantumComputerDevice): parametric_compilation (bool): a boolean value of whether or not to use parametric compilation. """ + name = "Rigetti QVM Device" short_name = "rigetti.qvm" - def __init__(self, device, *, shots=1000, wires=None, noisy=False, **kwargs): + def __init__(self, device, *, wires=None, shots=1000, noisy=False, **kwargs): if shots is None: raise ValueError("QVM device cannot be used for analytic computations.") diff --git a/pennylane_rigetti/wavefunction.py b/pennylane_rigetti/wavefunction.py index 6851d8b4..4d402642 100644 --- a/pennylane_rigetti/wavefunction.py +++ b/pennylane_rigetti/wavefunction.py @@ -26,6 +26,7 @@ Code details ~~~~~~~~~~~~ """ + import numpy as np from pyquil.api import WavefunctionSimulator @@ -55,6 +56,7 @@ class WavefunctionDevice(RigettiDevice): shots (int): Number of circuit evaluations/random samples used to estimate expectation values of observables. """ + name = "Rigetti Wavefunction Simulator Device" short_name = "rigetti.wavefunction" diff --git a/tests/test_gradients.py b/tests/test_gradients.py index 63f98e8d..8db2930a 100644 --- a/tests/test_gradients.py +++ b/tests/test_gradients.py @@ -12,7 +12,7 @@ def test_simulator_qvm_default_agree(tol, qvm, compiler): dev1 = qml.device("default.qubit", wires=w) dev2 = qml.device("rigetti.wavefunction", wires=w) - dev3 = qml.device("rigetti.qvm", device="9q-square-qvm", shots=5000) + dev3 = qml.device("rigetti.qvm", wires=9, device="9q-square-qvm", shots=5000) in_state = np.zeros([w], dtype=np.int64, requires_grad=False) in_state[0] = 1 @@ -56,7 +56,7 @@ def test_gradient_with_custom_operator(qvm, compiler): w = 9 dev2 = qml.device("rigetti.wavefunction", wires=w) - dev3 = qml.device("rigetti.qvm", device="9q-square-qvm", shots=5000) + dev3 = qml.device("rigetti.qvm", wires=9, device="9q-square-qvm", shots=5000) def func(x, y): """Reference QNode""" diff --git a/tests/test_pyqvm.py b/tests/test_pyqvm.py index fb9a1146..160a2e1e 100644 --- a/tests/test_pyqvm.py +++ b/tests/test_pyqvm.py @@ -7,8 +7,6 @@ import pennylane as qml import pytest from conftest import U2, BaseTest, H, I, U, Z, test_operation_map -from pennylane.circuit_graph import CircuitGraph -from pennylane.tape import QuantumTape import pennylane_rigetti as plf @@ -58,7 +56,9 @@ def circuit(): res = circuit() # below are the analytic expectation values for this circuit assert np.allclose( - res, np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), atol=3 / np.sqrt(shots) + res, + np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), + atol=3 / np.sqrt(shots), ) def test_paulix_expectation(self, shots): @@ -78,7 +78,9 @@ def circuit(): res = circuit() # below are the analytic expectation values for this circuit assert np.allclose( - res, np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), atol=3 / np.sqrt(shots) + res, + np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), + atol=3 / np.sqrt(shots), ) def test_pauliy_expectation(self, shots): @@ -120,7 +122,10 @@ def circuit(): # below are the analytic expectation values for this circuit expected = np.array( - [np.sin(theta) * np.sin(phi) + np.cos(theta), np.cos(theta) * np.cos(phi) + np.sin(phi)] + [ + np.sin(theta) * np.sin(phi) + np.cos(theta), + np.cos(theta) * np.cos(phi) + np.sin(phi), + ] ) / np.sqrt(2) assert np.allclose(res, expected, atol=3 / np.sqrt(shots)) @@ -287,8 +292,8 @@ class TestQVMIntegration(BaseTest): def test_qubit_unitary(self, shots): """Test that an arbitrary unitary operation works""" - dev1 = qml.device("rigetti.qvm", device="3q-pyqvm", shots=shots) - dev2 = qml.device("rigetti.qvm", device="9q-square-pyqvm", shots=shots) + dev1 = qml.device("rigetti.qvm", wires=3, device="3q-pyqvm", shots=shots) + dev2 = qml.device("rigetti.qvm", wires=9, device="9q-square-pyqvm", shots=shots) def circuit(): """Reference QNode""" @@ -306,11 +311,11 @@ def circuit(): assert np.allclose(circuit1(), np.vdot(out_state, obs @ out_state), atol=3 / np.sqrt(shots)) assert np.allclose(circuit2(), np.vdot(out_state, obs @ out_state), atol=3 / np.sqrt(shots)) - @pytest.mark.parametrize("device", ["2q-pyqvm"]) + @pytest.mark.parametrize("device", ["3q-pyqvm"]) def test_one_qubit_wavefunction_circuit(self, device, shots): """Test that the wavefunction plugin provides correct result for simple circuit.""" shots = 10000 - dev = qml.device("rigetti.qvm", device=device, shots=shots) + dev = qml.device("rigetti.qvm", wires=3, device=device, shots=shots) a = 0.543 b = 0.123 diff --git a/tests/test_qpu.py b/tests/test_qpu.py index 9da8fae8..76fbd7ea 100644 --- a/tests/test_qpu.py +++ b/tests/test_qpu.py @@ -28,48 +28,51 @@ class TestQPUIntegration(BaseTest): def test_load_qpu_device(self): """Test that the QPU device loads correctly""" device = TEST_QPU_LATTICES[0] - dev = qml.device("rigetti.qpu", device=device, load_qc=False) - qc = pyquil.get_qc(device) - num_wires = len(qc.qubits()) - self.assertEqual(dev.num_wires, num_wires) + dev = qml.device("rigetti.qpu", wires=4, device=device, load_qc=False) + pyquil.get_qc(device) + self.assertEqual(dev.num_wires, 4) self.assertEqual(dev.shots, 1000) self.assertEqual(dev.short_name, "rigetti.qpu") def test_load_virtual_qpu_device(self): """Test that the QPU simulators load correctly""" device = np.random.choice(TEST_QPU_LATTICES) - qml.device("rigetti.qpu", device=device, load_qc=False) + qml.device("rigetti.qpu", wires=4, device=device, load_qc=False) def test_qpu_args(self): """Test that the QPU plugin requires correct arguments""" device = np.random.choice(TEST_QPU_LATTICES) with pytest.raises(TypeError, match="missing 1 required positional argument"): - qml.device("rigetti.qpu") + qml.device("rigetti.qpu", wires=2) with pytest.raises(ValueError, match="Number of shots must be a positive integer"): - qml.device("rigetti.qpu", device=device, shots=0) + qml.device("rigetti.qpu", wires=2, device=device, shots=0) with pytest.raises(ValueError, match="Readout error cannot be set on the physical QPU"): - qml.device("rigetti.qpu", device=device, load_qc=True, readout_error=[0.9, 0.75]) - - dev_no_wires = qml.device("rigetti.qpu", device=device, shots=5, load_qc=False) - assert dev_no_wires.wires == Wires(range(4)) + qml.device( + "rigetti.qpu", + wires=2, + device=device, + load_qc=True, + readout_error=[0.9, 0.75], + ) - with pytest.raises(ValueError, match="Device has a fixed number of"): - qml.device("rigetti.qpu", device=device, shots=5, wires=100, load_qc=False) + with pytest.raises(ValueError, match="Wires must not exceed"): + qml.device("rigetti.qpu", device=device, shots=5, wires=2000, load_qc=False) dev_iterable_wires = qml.device( "rigetti.qpu", device=device, shots=5, wires=range(4), load_qc=False ) assert dev_iterable_wires.wires == Wires(range(4)) - with pytest.raises(ValueError, match="Device has a fixed number of"): - qml.device("rigetti.qpu", device=device, shots=5, wires=range(100), load_qc=False) + with pytest.raises(ValueError, match="Wires must not exceed"): + qml.device("rigetti.qpu", device=device, shots=5, wires=range(2000), load_qc=False) @flaky(max_runs=10, min_passes=3) @pytest.mark.parametrize( - "obs", [qml.PauliX(0), qml.PauliZ(0), qml.PauliY(0), qml.Hadamard(0), qml.Identity(0)] + "obs", + [qml.PauliX(0), qml.PauliZ(0), qml.PauliY(0), qml.Hadamard(0), qml.Identity(0)], ) def test_tensor_expval_parametric_compilation(self, obs): """Test the QPU expval method for Tensor observables made up of a single observable when parametric compilation is @@ -81,6 +84,7 @@ def test_tensor_expval_parametric_compilation(self, obs): p = np.pi / 8 dev = qml.device( "rigetti.qpu", + wires=4, device=device, shots=10000, load_qc=False, @@ -88,6 +92,7 @@ def test_tensor_expval_parametric_compilation(self, obs): ) dev_1 = qml.device( "rigetti.qpu", + wires=4, device=device, shots=10000, load_qc=False, @@ -116,7 +121,8 @@ def circuit_obs(param): @flaky(max_runs=10, min_passes=3) @pytest.mark.parametrize( - "obs", [qml.PauliX(0), qml.PauliZ(0), qml.PauliY(0), qml.Hadamard(0), qml.Identity(0)] + "obs", + [qml.PauliX(0), qml.PauliZ(0), qml.PauliY(0), qml.Hadamard(0), qml.Identity(0)], ) def test_tensor_expval_operator_estimation(self, obs, shots): """Test the QPU expval method for Tensor observables made up of a single observable when parametric compilation is @@ -128,6 +134,7 @@ def test_tensor_expval_operator_estimation(self, obs, shots): p = np.pi / 7 dev = qml.device( "rigetti.qpu", + wires=4, device=device, shots=shots, load_qc=False, @@ -136,6 +143,7 @@ def test_tensor_expval_operator_estimation(self, obs, shots): ) dev_1 = qml.device( "rigetti.qpu", + wires=4, device=device, shots=shots, load_qc=False, @@ -169,6 +177,7 @@ def test_skip_generate_samples(self, shots): device = np.random.choice(TEST_QPU_LATTICES) dev = qml.device( "rigetti.qpu", + wires=4, device=device, shots=shots, load_qc=False, @@ -205,8 +214,9 @@ def test_warnings_raised_parametric_compilation_and_operator_estimation(self): """Test that a warning is raised if parameter compilation and operator estimation are both turned on.""" device = np.random.choice(TEST_QPU_LATTICES) with pytest.warns(Warning, match="Operator estimation is being turned off."): - dev = qml.device( + qml.device( "rigetti.qpu", + wires=4, device=device, shots=1000, load_qc=False, @@ -218,6 +228,7 @@ def test_no_readout_correction(self, shots): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], @@ -281,6 +292,7 @@ def test_readout_correction(self): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], @@ -344,6 +356,7 @@ def test_multi_qub_no_readout_errors(self): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, symmetrize_readout=SymmetrizationLevel.NONE, @@ -370,6 +383,7 @@ def test_multi_qub_readout_errors(self): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, shots=10_000, @@ -395,6 +409,7 @@ def test_multi_qub_readout_correction(self): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, shots=10_000, @@ -424,6 +439,7 @@ def test_2q_gate(self, shots): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], @@ -449,6 +465,7 @@ def test_2q_gate_pauliz_identity_tensor(self, shots): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], @@ -475,6 +492,7 @@ def test_2q_gate_pauliz_pauliz_tensor(self, a, shots): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], @@ -507,6 +525,7 @@ def test_2q_circuit_pauliz_pauliz_tensor(self, a, b, shots): device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], @@ -546,6 +565,7 @@ def test_2q_gate_pauliz_pauliz_tensor_parametric_compilation_off(self, a, b, sho device = np.random.choice(TEST_QPU_LATTICES) dev_qpu = qml.device( "rigetti.qpu", + wires=4, device=device, load_qc=False, readout_error=[0.9, 0.75], diff --git a/tests/test_qvm.py b/tests/test_qvm.py index bb979ace..4954f239 100644 --- a/tests/test_qvm.py +++ b/tests/test_qvm.py @@ -86,7 +86,9 @@ def test_pauliz_expectation(self, shots, qvm, compiler): # below are the analytic expectation values for this circuit self.assertAllAlmostEqual( - res, np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), delta=3 / np.sqrt(shots) + res, + np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), + delta=3 / np.sqrt(shots), ) def test_paulix_expectation(self, shots, qvm, compiler): @@ -109,7 +111,9 @@ def test_paulix_expectation(self, shots, qvm, compiler): res = np.array([dev.expval(O1.obs), dev.expval(O2.obs)]) # below are the analytic expectation values for this circuit self.assertAllAlmostEqual( - res, np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), delta=3 / np.sqrt(shots) + res, + np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), + delta=3 / np.sqrt(shots), ) def test_pauliy_expectation(self, shots, qvm, compiler): @@ -157,7 +161,10 @@ def test_hadamard_expectation(self, shots, qvm, compiler): # below are the analytic expectation values for this circuit expected = np.array( - [np.sin(theta) * np.sin(phi) + np.cos(theta), np.cos(theta) * np.cos(phi) + np.sin(phi)] + [ + np.sin(theta) * np.sin(phi) + np.cos(theta), + np.cos(theta) * np.cos(phi) + np.sin(phi), + ] ) / np.sqrt(2) self.assertAllAlmostEqual(res, expected, delta=3 / np.sqrt(shots)) @@ -210,7 +217,11 @@ def test_multi_qubit_hermitian_expectation(self, shots, execution_timeout, qvm, ] ) - dev = plf.QVMDevice(device="2q-qvm", shots=10 * shots, execution_timeout=execution_timeout) + dev = plf.QVMDevice( + device="2q-qvm", + shots=10 * shots, + execution_timeout=execution_timeout, + ) with qml.tape.QuantumTape() as tape: qml.RY(theta, wires=[0]) @@ -256,7 +267,11 @@ def test_var(self, shots, qvm, compiler): def test_var_hermitian(self, shots, execution_timeout, qvm, compiler): """Tests for variance calculation using an arbitrary Hermitian observable""" - dev = plf.QVMDevice(device="2q-qvm", shots=100 * shots, execution_timeout=execution_timeout) + dev = plf.QVMDevice( + device="2q-qvm", + shots=100 * shots, + execution_timeout=execution_timeout, + ) phi = 0.543 theta = 0.6543 @@ -396,7 +411,10 @@ def test_sample_values_hermitian(self, qvm, execution_timeout, tol): # the analytic variance is 0.25*(sin(theta)-4*cos(theta))^2 assert np.allclose( - np.var(s1), 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2, atol=0.1, rtol=0 + np.var(s1), + 0.25 * (np.sin(theta) - 4 * np.cos(theta)) ** 2, + atol=0.1, + rtol=0, ) def test_sample_values_hermitian_multi_qubit(self, qvm, execution_timeout, tol): @@ -452,34 +470,39 @@ def test_wires_argument(self): dev_no_wires = plf.QVMDevice(device="2q-qvm", shots=5) assert dev_no_wires.wires == Wires(range(2)) - with pytest.raises(ValueError, match="Device has a fixed number of"): - plf.QVMDevice(device="2q-qvm", shots=5, wires=1000) - dev_iterable_wires = plf.QVMDevice(device="2q-qvm", shots=5, wires=range(2)) assert dev_iterable_wires.wires == Wires(range(2)) - with pytest.raises(ValueError, match="Device has a fixed number of"): - plf.QVMDevice(device="2q-qvm", shots=5, wires=range(1000)) + with pytest.raises(ValueError, match="Wires must not exceed"): + plf.QVMDevice(device="2q-qvm", wires=3, shots=5) + + with pytest.raises(ValueError, match="Wires must not exceed"): + plf.QVMDevice(device="2q-qvm", wires=Wires(range(3)), shots=5) @pytest.mark.parametrize("shots", list(range(0, -10, -1))) def test_raise_error_if_shots_is_not_positive(self, shots): """Test that instantiating a QVMDevice if the number of shots is not a postivie integer raises an error""" with pytest.raises(ValueError, match="Number of shots must be a positive integer."): - dev = plf.QVMDevice(device="2q-qvm", shots=shots) + plf.QVMDevice(device="2q-qvm", shots=shots) def test_raise_error_if_shots_is_none(self, shots): """Test that instantiating a QVMDevice to be used for analytic computations raises an error""" with pytest.raises( ValueError, match="QVM device cannot be used for analytic computations." ): - dev = plf.QVMDevice(device="2q-qvm", shots=None) + plf.QVMDevice(device="2q-qvm", shots=None) @pytest.mark.parametrize("device", ["2q-qvm", np.random.choice(TEST_QPU_LATTICES)]) def test_timeout_set_correctly(self, shots, device): """Test that the timeout attrbiute for the QuantumComputer stored by the QVMDevice is set correctly when passing a value as keyword argument""" - dev = plf.QVMDevice(device=device, shots=shots, compiler_timeout=100, execution_timeout=101) + dev = plf.QVMDevice( + device=device, + shots=shots, + compiler_timeout=100, + execution_timeout=101, + ) assert dev.qc.compiler._timeout == 100 assert dev.qc.qam._qvm_client.timeout == 101 @@ -497,7 +520,7 @@ def test_timeout_default(self, shots, device): def test_compiled_program_stored(self, qvm, monkeypatch): """Test that QVM device stores the latest compiled program.""" - dev = qml.device("rigetti.qvm", device="2q-qvm") + dev = qml.device("rigetti.qvm", wires=2, device="2q-qvm") assert dev.compiled_program is None @@ -508,8 +531,8 @@ def test_compiled_program_stored(self, qvm, monkeypatch): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.CNOT(wires=[0, 1]) - O1 = qml.expval(qml.Identity(wires=[0])) - O2 = qml.expval(qml.Identity(wires=[1])) + qml.expval(qml.Identity(wires=[0])) + qml.expval(qml.Identity(wires=[1])) dev.apply(tape.operations, rotations=tape.diagonalizing_gates) @@ -519,7 +542,7 @@ def test_compiled_program_stored(self, qvm, monkeypatch): def test_stored_compiled_program_correct(self, qvm, monkeypatch): """Test that QVM device stores the latest compiled program.""" - dev = qml.device("rigetti.qvm", device="2q-qvm") + dev = qml.device("rigetti.qvm", wires=2, device="2q-qvm") assert dev.compiled_program is None @@ -528,7 +551,7 @@ def test_stored_compiled_program_correct(self, qvm, monkeypatch): with qml.tape.QuantumTape() as tape: qml.RZ(theta, wires=[0]) qml.CZ(wires=[0, 1]) - O1 = qml.expval(qml.PauliZ(wires=[0])) + qml.expval(qml.PauliZ(wires=[0])) dev.apply(tape.operations, rotations=tape.diagonalizing_gates) @@ -542,7 +565,7 @@ class TestParametricCompilation(BaseTest): def test_compiled_program_was_stored_in_dict(self, qvm, mock_qvm, monkeypatch): """Test that QVM device stores the compiled program correctly in a dictionary""" - dev = qml.device("rigetti.qvm", device="2q-qvm") + dev = qml.device("rigetti.qvm", wires=2, device="2q-qvm") theta = 0.432 phi = 0.123 @@ -550,8 +573,8 @@ def test_compiled_program_was_stored_in_dict(self, qvm, mock_qvm, monkeypatch): qml.RX(theta, wires=[0]) qml.RX(phi, wires=[1]) qml.CNOT(wires=[0, 1]) - O1 = qml.expval(qml.Identity(wires=[0])) - O2 = qml.expval(qml.Identity(wires=[1])) + qml.expval(qml.Identity(wires=[0])) + qml.expval(qml.Identity(wires=[1])) dev.apply(tape.operations, rotations=tape.diagonalizing_gates) @@ -576,7 +599,9 @@ def test_parametric_compilation_with_numeric_and_symbolic_queue( ): """Tests that a program containing numeric and symbolic variables as well is only compiled once.""" - dev = qml.device("rigetti.qvm", device="2q-qvm", execution_timeout=execution_timeout) + dev = qml.device( + "rigetti.qvm", device="2q-qvm", wires=2, execution_timeout=execution_timeout + ) param1 = np.array(1, requires_grad=False) param2 = np.array(2, requires_grad=True) @@ -610,7 +635,7 @@ def test_parametric_compilation_with_numeric_and_symbolic_queue( def test_apply_qubitstatesvector_raises_an_error_if_not_first(self, op): """Test that there is an error raised when the QubitStateVector or StatPrep is not applied as the first operation.""" - dev = qml.device("rigetti.qvm", device="2q-qvm", parametric_compilation=True) + dev = qml.device("rigetti.qvm", device="2q-qvm", wires=2, parametric_compilation=True) operation = op(np.array([1, 0]), wires=[0]) queue = [qml.PauliX(0), operation] @@ -630,7 +655,7 @@ class TestQVMIntegration(BaseTest): def test_load_qvm_device(self, qvm): """Test that the QVM device loads correctly""" - dev = qml.device("rigetti.qvm", device="2q-qvm") + dev = qml.device("rigetti.qvm", wires=2, device="2q-qvm") self.assertEqual(dev.num_wires, 2) self.assertEqual(dev.shots, 1000) self.assertEqual(dev.short_name, "rigetti.qvm") @@ -638,28 +663,32 @@ def test_load_qvm_device(self, qvm): def test_load_qvm_device_from_topology(self, qvm): """Test that the QVM device, from an input topology, loads correctly""" topology = nx.complete_graph(2) - dev = qml.device("rigetti.qvm", device=topology) + dev = qml.device("rigetti.qvm", wires=2, device=topology) self.assertEqual(dev.num_wires, 2) self.assertEqual(dev.shots, 1000) self.assertEqual(dev.short_name, "rigetti.qvm") def test_load_virtual_qpu_device(self, qvm): """Test that the QPU simulators load correctly""" - qml.device("rigetti.qvm", device=np.random.choice(TEST_QPU_LATTICES)) + qml.device("rigetti.qvm", wires=2, device=np.random.choice(TEST_QPU_LATTICES)) def test_qvm_args(self): """Test that the QVM plugin requires correct arguments""" with pytest.raises(TypeError, match="missing 1 required positional argument"): - qml.device("rigetti.qvm") + qml.device( + "rigetti.qvm", + wires=2, + ) with pytest.raises(ValueError, match="Number of shots must be a positive integer"): - qml.device("rigetti.qvm", "2q-qvm", shots=0) + qml.device("rigetti.qvm", "2q-qvm", wires=2, shots=0) def test_qubit_unitary(self, shots, compiler_timeout, execution_timeout, qvm, compiler): """Test that an arbitrary unitary operation works""" dev1 = qml.device( "rigetti.qvm", device="3q-qvm", + wires=3, shots=shots, compiler_timeout=compiler_timeout, execution_timeout=execution_timeout, @@ -668,6 +697,7 @@ def test_qubit_unitary(self, shots, compiler_timeout, execution_timeout, qvm, co dev2 = qml.device( "rigetti.qvm", device="9q-square-qvm", + wires=9, shots=shots, compiler_timeout=compiler_timeout, execution_timeout=execution_timeout, @@ -703,7 +733,7 @@ def test_one_qubit_wavefunction_circuit(self, device, qvm, compiler, requires_gr As the results coming from the qvm are stochastic, a constraint of 2 out of 5 runs was added. """ shots = 100_000 - dev = qml.device("rigetti.qvm", device=device, shots=QVM_SHOTS) + dev = qml.device("rigetti.qvm", wires=2, device=device, shots=QVM_SHOTS) a = 0.543 b = 0.123 @@ -726,7 +756,7 @@ def test_2q_gate(self, device, qvm, compiler): As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added. """ - dev = qml.device("rigetti.qvm", device=device, shots=QVM_SHOTS) + dev = qml.device("rigetti.qvm", wires=2, device=device, shots=QVM_SHOTS) @qml.qnode(dev) def circuit(): @@ -743,7 +773,7 @@ def test_2q_gate_pauliz_identity_tensor(self, device, qvm, compiler): As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added. """ - dev = qml.device("rigetti.qvm", device=device, shots=QVM_SHOTS) + dev = qml.device("rigetti.qvm", wires=2, device=device, shots=QVM_SHOTS) @qml.qnode(dev) def circuit(): @@ -760,7 +790,7 @@ def test_2q_gate_pauliz_pauliz_tensor(self, device, qvm, compiler): As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added. """ - dev = qml.device("rigetti.qvm", device=device, shots=QVM_SHOTS) + dev = qml.device("rigetti.qvm", wires=2, device=device, shots=QVM_SHOTS) @qml.qnode(dev) def circuit(): @@ -773,7 +803,7 @@ def circuit(): @pytest.mark.parametrize("device", ["2q-qvm", np.random.choice(TEST_QPU_LATTICES)]) def test_compiled_program_was_stored(self, qvm, device): """Test that QVM device stores the compiled program correctly""" - dev = qml.device("rigetti.qvm", device=device, timeout=100) + dev = qml.device("rigetti.qvm", wires=2, device=device, timeout=100) assert len(dev._compiled_program_dict.items()) == 0 @@ -803,7 +833,7 @@ def test_compiled_program_was_stored_mutable_qnode_with_if_statement( self, qvm, device, statements ): """Test that QVM device stores the compiled program when the QNode is mutated correctly""" - dev = qml.device("rigetti.qvm", device=device, timeout=100) + dev = qml.device("rigetti.qvm", wires=2, device=device, timeout=100) assert len(dev._compiled_program_dict.items()) == 0 @@ -830,7 +860,7 @@ def circuit(statement=None): def test_compiled_program_was_stored_mutable_qnode_with_loop(self, qvm, device): """Test that QVM device stores the compiled program when the QNode is mutated correctly""" - dev = qml.device("rigetti.qvm", device=device, timeout=80) + dev = qml.device("rigetti.qvm", wires=2, device=device, timeout=80) assert len(dev._compiled_program_dict.items()) == 0 @@ -851,7 +881,7 @@ def circuit(rounds=1): @pytest.mark.parametrize("device", ["2q-qvm", np.random.choice(TEST_QPU_LATTICES)]) def test_compiled_program_was_used(self, qvm, device, monkeypatch): """Test that QVM device used the compiled program correctly, after it was stored""" - dev = qml.device("rigetti.qvm", device=device, timeout=100) + dev = qml.device("rigetti.qvm", wires=2, device=device, timeout=100) shape = qml.StronglyEntanglingLayers.shape(n_layers=4, n_wires=dev.num_wires) params = np.random.random(size=shape) @@ -882,7 +912,7 @@ def test_compiled_program_was_correct_compared_with_default_qubit(self, qvm, dev As the results coming from the qvm are stochastic, a constraint of 1 out of 5 runs was added. """ - dev = qml.device("rigetti.qvm", device=device, timeout=100) + dev = qml.device("rigetti.qvm", wires=2, device=device, timeout=100) shape = qml.StronglyEntanglingLayers.shape(n_layers=4, n_wires=dev.num_wires) params = np.random.random(size=shape) @@ -912,7 +942,11 @@ def test_2q_gate_pauliz_pauliz_tensor_parametric_compilation_off(self, device, q """ dev = qml.device( - "rigetti.qvm", device=device, shots=QVM_SHOTS, parametric_compilation=False + "rigetti.qvm", + wires=2, + device=device, + shots=QVM_SHOTS, + parametric_compilation=False, ) @qml.qnode(dev)