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

draft: Fix how wire mapping is handled by the QuantumComputer device #147

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
38 changes: 22 additions & 16 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ------------------------------------------------

Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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 ---------------------------------------------

Expand All @@ -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",
),
]


Expand Down Expand Up @@ -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']
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Accidentally formatted this with black. Any qualms with keeping it this way? I doubt I'll be the last to do this, and this keeps it from popping up in diffs for future devs.

Copy link
Contributor

Choose a reason for hiding this comment

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

doesn't bother me :)

autosummary_generate = True
85 changes: 48 additions & 37 deletions pennylane_rigetti/qc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,16 @@
"""

from abc import ABC, abstractmethod
from typing import Dict
from collections import OrderedDict
from typing import Dict
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__

Expand All @@ -45,26 +44,25 @@ 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
MarquessV marked this conversation as resolved.
Show resolved Hide resolved
evaluation when the number of shots is larger than ~1000.

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.")

Expand Down Expand Up @@ -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."
)

MarquessV marked this conversation as resolved.
Show resolved Hide resolved
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)

Expand Down Expand Up @@ -170,20 +184,13 @@ 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))
return OrderedDict(zip(range(len(self._qubits)), self._qubits))

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)
Expand All @@ -193,7 +200,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]))
Expand All @@ -207,15 +214,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)

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."
Expand Down
10 changes: 4 additions & 6 deletions pennylane_rigetti/qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,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
Expand All @@ -80,9 +78,9 @@ class QPUDevice(QuantumComputerDevice):
def __init__(
self,
device,
wires,
*,
shots=1000,
wires=None,
active_reset=True,
load_qc=True,
readout_error=None,
Expand Down
7 changes: 3 additions & 4 deletions pennylane_rigetti/qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,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:
Expand All @@ -65,7 +64,7 @@ class QVMDevice(QuantumComputerDevice):
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.")

Expand Down
4 changes: 2 additions & 2 deletions tests/test_gradients.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"""
Expand Down
Loading
Loading