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

Fix default wire mapping and QPU program compilation and add support for parallel execution #124

Closed
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,19 @@

### Bug fixes

* Wire mapping is now based on the device specification instead of being assumed to be a zero-indexed
sequence.

* QPU devices now configure the compiler to generate programs that will be compatible to
run on a real QPU.

### Contributors

This release contains contributions from (in alphabetical order):

Albert Mitjans Coma
Graham Enos
Marquess Valdez

---

Expand Down
28 changes: 12 additions & 16 deletions pennylane_rigetti/qc.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from pyquil import Program
from pyquil.api import 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
Expand All @@ -48,8 +47,7 @@ class QuantumComputerDevice(RigettiDevice, ABC):
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.
If not provided, qubits are addressed by the backend.
Copy link
Contributor

Choose a reason for hiding this comment

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

To make sure I understand, does this mean that the user can pass any wires and they'll be mapped to the device wires, or that the user must use the appropriate wires for the device?

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 Down Expand Up @@ -97,9 +95,8 @@ def __init__(self, device, *, shots=1000, wires=None, active_reset=False, **kwar
self.num_wires = len(self.qc.qubits())

if wires is None:
# infer the number of modes from the device specs
# and use consecutive integer wire labels
wires = range(self.num_wires)
# infer the wires from the device specs
wires = self.qc.qubits()

if isinstance(wires, int):
raise ValueError(
Expand All @@ -113,7 +110,7 @@ def __init__(self, device, *, shots=1000, wires=None, active_reset=False, **kwar
f"cannot be created with {len(wires)} wires."
)

self.wiring = dict(enumerate(self.qc.qubits()))
self.wiring = {q: q for q in sorted(wires)}
self.active_reset = active_reset

super().__init__(wires, shots)
Expand Down Expand Up @@ -180,10 +177,8 @@ def define_wire_map(self, wires):

def apply(self, operations, **kwargs):
"""Applies the given quantum operations."""
prag = Program(Pragma("INITIAL_REWIRING", ['"PARTIAL"']))
AlbertMitjans marked this conversation as resolved.
Show resolved Hide resolved
if self.active_reset:
prag += RESET()
self.prog = prag + self.prog
self.prog = Program(RESET()) + self.prog

if self.parametric_compilation:
self.prog += self.apply_parametric_operations(operations)
Expand All @@ -192,13 +187,14 @@ def apply(self, operations, **kwargs):

rotations = kwargs.get("rotations", [])
self.prog += self.apply_rotations(rotations)

qubits = sorted(self.wiring.values())
ro = self.prog.declare("ro", "BIT", len(qubits))
for i, q in enumerate(qubits):
self.prog.inst(MEASURE(q, ro[i]))

self.prog.wrap_in_numshots_loop(self.shots)
# Measure every qubit used by the program into a readout register.
# Devices don't always have sequentially adressed qubits, so
# we use a normalized value to index them into the readout register.
used_qubits = self.prog.get_qubits(indices=True)
ro = self.prog.declare("ro", "BIT", len(used_qubits))
for i, qubit in enumerate(used_qubits):
self.prog += MEASURE(qubit, ro[i])
MarquessV marked this conversation as resolved.
Show resolved Hide resolved

def apply_parametric_operations(self, operations):
"""Applies a parametric program by applying parametric operation with symbolic parameters.
Expand Down
5 changes: 4 additions & 1 deletion pennylane_rigetti/qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from pennylane.operation import Tensor
from pennylane.tape import QuantumTape
from pyquil import get_qc
from pyquil.api import QuantumComputer
from pyquil.api import QuantumComputer, QuantumExecutable
from pyquil.experiment import SymmetrizationLevel
from pyquil.operator_estimation import (
Experiment,
Expand Down Expand Up @@ -114,6 +114,9 @@ def __init__(

super().__init__(device, wires=wires, shots=shots, active_reset=active_reset, **kwargs)

def compile(self) -> QuantumExecutable:
return self.qc.compile(self.prog, protoquil=True)

def get_qc(self, device, **kwargs) -> QuantumComputer:
return get_qc(device, as_qvm=self.as_qvm, **kwargs)

Expand Down