Skip to content

Commit

Permalink
Adapt hardware devices to process custom wires (#59)
Browse files Browse the repository at this point in the history
* delete superfluous conversion

* rewrite wire handling of fixed-size devices

* sneak in software device docstring update
  • Loading branch information
mariaschuld committed Aug 10, 2020
1 parent 316dcc5 commit aed1e59
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 15 deletions.
4 changes: 3 additions & 1 deletion pennylane_forest/numpy_wavefunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class NumpyWavefunctionDevice(ForestDevice):
r"""NumpyWavefunction simulator device for PennyLane.
Args:
wires (int): the number of qubits to initialize the device in
wires (int or Iterable[Number, str]]): Number of subsystems represented by the device,
or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``)
or strings (``['ancilla', 'q1', 'q2']``).
shots (int): Number of circuit evaluations/random samples used
to estimate expectation values of observables.
"""
Expand Down
30 changes: 25 additions & 5 deletions pennylane_forest/qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ class QPUDevice(QVMDevice):
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
Expand Down Expand Up @@ -83,6 +88,7 @@ def __init__(
device,
*,
shots=1000,
wires=None,
active_reset=True,
load_qc=True,
readout_error=None,
Expand Down Expand Up @@ -132,9 +138,6 @@ def __init__(

timeout = kwargs.pop("timeout", None)

if "wires" in kwargs:
raise ValueError("QPU device does not support a wires parameter.")

if shots <= 0:
raise ValueError("Number of shots must be a positive integer.")

Expand All @@ -149,9 +152,26 @@ def __init__(
if timeout is not None:
self.qc.compiler.client.timeout = timeout

num_wires = len(self.qc.qubits())
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)

if isinstance(wires, int):
raise ValueError(
"Device has a fixed number of {} qubits. The wires argument can only be used "
"to specify an iterable of wire labels.".format(self.num_wires)
)

if self.num_wires != len(wires):
raise ValueError(
"Device has a fixed number of {} qubits and "
"cannot be created with {} wires.".format(self.num_wires, len(wires))
)

super(QVMDevice, self).__init__(num_wires, shots, **kwargs)
super(QVMDevice, self).__init__(wires, shots, **kwargs)

self.active_reset = active_reset
self.symmetrize_readout = symmetrize_readout
Expand Down
30 changes: 25 additions & 5 deletions pennylane_forest/qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ class QVMDevice(ForestDevice):
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.
noisy (bool): set to ``True`` to add noise models to your QVM.
Keyword args:
Expand All @@ -73,13 +78,11 @@ class QVMDevice(ForestDevice):
short_name = "forest.qvm"
observables = {"PauliX", "PauliY", "PauliZ", "Identity", "Hadamard", "Hermitian"}

def __init__(self, device, *, shots=1000, noisy=False, **kwargs):
def __init__(self, device, *, wires=None, shots=1000, noisy=False, **kwargs):

if shots <= 0:
raise ValueError("Number of shots must be a positive integer.")

# ignore any 'wires' keyword argument passed to the device
kwargs.pop("wires", None)
analytic = kwargs.get("analytic", False)
timeout = kwargs.pop("timeout", None)

Expand Down Expand Up @@ -116,9 +119,26 @@ def __init__(self, device, *, shots=1000, noisy=False, **kwargs):
elif isinstance(device, str):
self.qc = get_qc(device, as_qvm=True, noisy=noisy, connection=self.connection)

num_wires = len(self.qc.qubits())
self.num_wires = len(self.qc.qubits())

super().__init__(num_wires, shots, analytic=analytic, **kwargs)
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 isinstance(wires, int):
raise ValueError(
"Device has a fixed number of {} qubits. The wires argument can only be used "
"to specify an iterable of wire labels.".format(self.num_wires)
)

if self.num_wires != len(wires):
raise ValueError(
"Device has a fixed number of {} qubits and "
"cannot be created with {} wires.".format(self.num_wires, len(wires))
)

super().__init__(wires, shots, analytic=analytic, **kwargs)

if timeout is not None:
self.qc.compiler.client.timeout = timeout
Expand Down
4 changes: 3 additions & 1 deletion pennylane_forest/wavefunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ class WavefunctionDevice(ForestDevice):
r"""Wavefunction simulator device for PennyLane.
Args:
wires (int): the number of qubits to initialize the device in
wires (int or Iterable[Number, str]]): Number of subsystems represented by the device,
or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``)
or strings (``['ancilla', 'q1', 'q2']``).
shots (int): Number of circuit evaluations/random samples used
to estimate expectation values of observables.
Expand Down
16 changes: 13 additions & 3 deletions tests/test_qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import pennylane as qml
from pennylane import numpy as np
from pennylane.operation import Tensor
from pennylane.wires import Wires
import pennylane_forest as plf
from conftest import BaseTest, QVM_SHOTS

Expand Down Expand Up @@ -44,9 +45,6 @@ def test_qpu_args(self):
"""Test that the QPU plugin requires correct arguments"""
device = np.random.choice(TEST_QPU_LATTICES)

with pytest.raises(ValueError, match="QPU device does not support a wires parameter"):
qml.device("forest.qpu", device=device, wires=2)

with pytest.raises(TypeError, match="missing 1 required positional argument"):
qml.device("forest.qpu")

Expand All @@ -56,6 +54,18 @@ def test_qpu_args(self):
with pytest.raises(ValueError, match="Readout error cannot be set on the physical QPU"):
qml.device("forest.qpu", device=device, load_qc=True, readout_error=[0.9, 0.75])

dev_no_wires = qml.device("forest.qpu", device=device, shots=5, load_qc=False)
assert dev_no_wires.wires == Wires(range(4))

with pytest.raises(ValueError, match="Device has a fixed number of"):
qml.device("forest.qpu", device=device, shots=5, wires=100, load_qc=False)

dev_iterable_wires = qml.device("forest.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("forest.qpu", device=device, shots=5, wires=range(100), load_qc=False)

@flaky(max_runs=5, min_passes=3)
@pytest.mark.parametrize(
"obs", [qml.PauliX(0), qml.PauliZ(0), qml.PauliY(0), qml.Hadamard(0), qml.Identity(0)]
Expand Down
16 changes: 16 additions & 0 deletions tests/test_qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pennylane.operation import Tensor
from pennylane.circuit_graph import CircuitGraph
from pennylane.variable import Variable
from pennylane.wires import Wires

from pyquil.quil import Pragma, Program
from pyquil.api._quantum_computer import QuantumComputer
Expand Down Expand Up @@ -465,6 +466,21 @@ def test_sample_values_hermitian_multi_qubit(self, qvm, tol):
) / 32
assert np.allclose(np.mean(s1), expected, atol=0.1, rtol=0)

def test_wires_argument(self):
"""Test that the wires argument gets processed correctly."""

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

@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
Expand Down

0 comments on commit aed1e59

Please sign in to comment.