Skip to content

Commit

Permalink
[WIRES] Make plugin compatible with wires refactor (#55)
Browse files Browse the repository at this point in the history
* backup

* Rename op.params to op.data

* Fix renaming

* bump requirements to install PL from git

* fix import

* fix tests

* Skip pyqvm tests

* fix default qubit import path

* fix

* refactor

* delete .idea file

* add .idea to gitignore

* fix tests

Co-authored-by: Theodor Isacsson <theodor.isacsson@gmail.com>
Co-authored-by: Josh Izaac <josh146@gmail.com>
  • Loading branch information
3 people committed Aug 6, 2020
1 parent f692b4b commit a749d77
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 133 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
build/*
dist/*
*egg-info*
.idea
57 changes: 31 additions & 26 deletions pennylane_forest/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,20 @@

import numpy as np

from collections import OrderedDict

from pyquil import Program
from pyquil.api._base_connection import ForestConnection
from pyquil.api._config import PyquilConfig

from pyquil.quil import DefGate
from pyquil.gates import X, Y, Z, H, PHASE, RX, RY, RZ, CZ, SWAP, CNOT
from pyquil.gates import X, Y, Z, H, PHASE, RX, RY, RZ, CZ, SWAP, CNOT, S, T, CSWAP

# following gates are not supported by PennyLane
from pyquil.gates import S, T, CPHASE00, CPHASE01, CPHASE10, CPHASE, CCNOT, CSWAP, ISWAP, PSWAP
from pyquil.gates import CPHASE00, CPHASE01, CPHASE10, CPHASE, CCNOT, ISWAP, PSWAP

from pennylane import QubitDevice, DeviceError
from pennylane.wires import Wires

from ._version import __version__

Expand Down Expand Up @@ -162,12 +165,14 @@ class ForestDevice(QubitDevice):
r"""Abstract Forest device for PennyLane.
Args:
wires (int): the number of modes 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.
For simulator devices, 0 means the exact EV is returned.
"""
pennylane_requires = ">=0.9"
pennylane_requires = ">=0.11"
version = __version__
author = "Rigetti Computing Inc."

Expand Down Expand Up @@ -198,16 +203,15 @@ def program(self):
"""View the last evaluated Quil program"""
return self.prog

def remap_wires(self, wires):
"""Use the wiring specified for the device if applicable.
def define_wire_map(self, wires):

Returns:
list: wires as integers corresponding to the wiring if applicable
"""
if hasattr(self, "wiring"):
return [int(self.wiring[i]) for i in wires]
device_wires = Wires(self.wiring)
else:
# if no wiring given, use consecutive wire labels
device_wires = Wires(range(self.num_wires))

return [int(w) for w in wires]
return OrderedDict(zip(wires, device_wires))

def apply(self, operations, **kwargs):
# pylint: disable=attribute-defined-outside-init
Expand All @@ -218,16 +222,16 @@ def apply(self, operations, **kwargs):

# Apply the circuit operations
for i, operation in enumerate(operations):
# map the operation wires to the physical device qubits
wires = self.remap_wires(operation.wires)
# map the ops' wires to the wire labels used by the device
device_wires = self.map_wires(operation.wires)
par = operation.parameters

if i > 0 and operation.name in ("QubitStateVector", "BasisState"):
raise DeviceError(
"Operation {} cannot be used after other Operations have already "
"been applied on a {} device.".format(operation.name, self.short_name)
)
self.prog += self._operation_map[operation.name](*par, *wires)
self.prog += self._operation_map[operation.name](*par, *device_wires.labels)

self.prog += self.apply_rotations(rotations)

Expand All @@ -245,33 +249,34 @@ def apply_rotations(self, rotations):
"""
rotation_operations = Program()
for operation in rotations:
wires = self.remap_wires(operation.wires)
# map the ops' wires to the wire labels used by the device
device_wires = self.map_wires(operation.wires)
par = operation.parameters
rotation_operations += self._operation_map[operation.name](*par, *wires)
rotation_operations += self._operation_map[operation.name](*par, *device_wires.labels)

return rotation_operations

def reset(self):
self.prog = Program()
self._active_wires = set()
self._active_wires = Wires([])
self._state = None

@property
def operations(self):
return set(self._operation_map.keys())

def mat_vec_product(self, mat, vec, wires):
def mat_vec_product(self, mat, vec, device_wire_labels):
r"""Apply multiplication of a matrix to subsystems of the quantum state.
Args:
mat (array): matrix to multiply
vec (array): state vector to multiply
wires (Sequence[int]): target subsystems
device_wire_labels (Sequence[int]): labels of device subsystems
Returns:
array: output vector after applying ``mat`` to input ``vec`` on specified subsystems
"""
num_wires = len(wires)
num_wires = len(device_wire_labels)

if mat.shape != (2 ** num_wires, 2 ** num_wires):
raise ValueError(
Expand All @@ -294,7 +299,7 @@ def mat_vec_product(self, mat, vec, wires):
# and wires=[0, 1], then
# the reshaped dimensions of mat are such that
# mat[i, j, k, l] == c_{ijkl}.
mat = np.reshape(mat, [2] * len(wires) * 2)
mat = np.reshape(mat, [2] * len(device_wire_labels) * 2)

# Reshape the state vector to ``size=[2, 2, ..., 2]``,
# where ``len(size) == num_wires``.
Expand All @@ -313,7 +318,7 @@ def mat_vec_product(self, mat, vec, wires):
# For example, if num_wires=3 and wires=[2, 0], then
# axes=((2, 3), (2, 0)). This is equivalent to doing
# np.einsum("ijkl,lnk", mat, vec).
axes = (np.arange(len(wires), 2 * len(wires)), wires)
axes = (np.arange(len(device_wire_labels), 2 * len(device_wire_labels)), device_wire_labels)

# After the tensor dot operation, the resulting array
# will have shape ``size=[2, 2, ..., 2]``,
Expand All @@ -325,8 +330,8 @@ def mat_vec_product(self, mat, vec, wires):
# of the resulting tensor. This corresponds to a (partial) transpose of
# the correct output state
# We'll need to invert this permutation to put the indices in the correct place
unused_idxs = [idx for idx in range(self.num_wires) if idx not in wires]
perm = wires + unused_idxs
unused_idxs = [idx for idx in range(self.num_wires) if idx not in device_wire_labels]
perm = device_wire_labels + unused_idxs

# argsort gives the inverse permutation
inv_perm = np.argsort(perm)
Expand All @@ -345,7 +350,7 @@ def analytic_probability(self, wires=None):
the ``device._state`` attribute. This attribute might not be available for such devices.
Args:
wires (Sequence[int]): Sequence of wires to return
wires (Iterable[Number, str], Number, str, Wires): wires to return
marginal probabilities for. Wires not provided
are traced out of the system.
Expand All @@ -356,6 +361,6 @@ def analytic_probability(self, wires=None):
return None

wires = wires or range(self.num_wires)
wires = self.remap_wires(wires)
wires = Wires(wires)
prob = self.marginal_prob(np.abs(self._state) ** 2, wires)
return prob
2 changes: 1 addition & 1 deletion pennylane_forest/numpy_wavefunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class NumpyWavefunctionDevice(ForestDevice):

def __init__(self, wires, *, shots=1000, analytic=True, **kwargs):
super().__init__(wires, shots, analytic, **kwargs)
self.qc = PyQVM(n_qubits=wires, quantum_simulator_type=NumpyWavefunctionSimulator)
self.qc = PyQVM(n_qubits=len(self.wires), quantum_simulator_type=NumpyWavefunctionSimulator)
self._state = None

def apply(self, operations, **kwargs):
Expand Down
22 changes: 10 additions & 12 deletions pennylane_forest/qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,32 +159,31 @@ def __init__(
self.wiring = {i: q for i, q in enumerate(self.qc.qubits())}

def expval(self, observable):
wires = observable.wires
# translate operator wires to wire labels on the device
device_wires = self.map_wires(observable.wires)

# `measure_observables` called only when parametric compilation is turned off
if not self.parametric_compilation:

# Single-qubit observable
if len(wires) == 1:
if len(device_wires) == 1:

# Ensure sensible observable
assert observable.name in ["PauliX", "PauliY", "PauliZ", "Identity", "Hadamard"], "Unknown observable"

# Create appropriate PauliZ operator
wire = wires[0]
qubit = self.wiring[wire]
pauli_obs = sZ(qubit)
wire = device_wires.labels[0]
pauli_obs = sZ(wire)

# Multi-qubit observable
elif len(wires) > 1 and isinstance(observable, Tensor) and not self.parametric_compilation:
elif len(device_wires) > 1 and isinstance(observable, Tensor) and not self.parametric_compilation:

# All observables are rotated to be measured in the Z-basis, so we just need to
# check which wires exist in the observable, map them to physical qubits, and measure
# the product of PauliZ operators on those qubits
pauli_obs = sI()
for wire in observable.wires:
qubit = wire
pauli_obs *= sZ(self.wiring[qubit])
for label in device_wires.labels:
pauli_obs *= sZ(label)


# Program preparing the state in which to measure observable
Expand All @@ -201,10 +200,9 @@ def expval(self, observable):
prep_prog += Program(str_instr)

if self.readout_error is not None:
for wire in observable.wires:
qubit = wire
for label in device_wires.labels:
prep_prog.define_noisy_readout(
self.wiring[qubit], p00=self.readout_error[0], p11=self.readout_error[1]
label, p00=self.readout_error[0], p11=self.readout_error[1]
)

# Measure out multi-qubit observable
Expand Down
6 changes: 3 additions & 3 deletions pennylane_forest/qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def apply_parametric_program(self, operations, **kwargs):
# Apply the circuit operations
for i, operation in enumerate(operations):
# map the operation wires to the physical device qubits
wires = self.remap_wires(operation.wires)
device_wires = self.map_wires(operation.wires)

if i > 0 and operation.name in ("QubitStateVector", "BasisState"):
raise DeviceError(
Expand All @@ -171,7 +171,7 @@ def apply_parametric_program(self, operations, **kwargs):

# Prepare for parametric compilation
par = []
for param in operation.params:
for param in operation.data:
if isinstance(param, Variable):
# Using the idx for each Variable instance to specify the
# corresponding symbolic parameter
Expand All @@ -192,7 +192,7 @@ def apply_parametric_program(self, operations, **kwargs):
else:
par.append(param)

self.prog += self._operation_map[operation.name](*par, *wires)
self.prog += self._operation_map[operation.name](*par, *device_wires.labels)

self.prog += self.apply_rotations(rotations)

Expand Down
11 changes: 7 additions & 4 deletions pennylane_forest/wavefunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import numpy as np
from numpy.linalg import eigh
from pennylane.wires import Wires

from pyquil.api import WavefunctionSimulator

Expand Down Expand Up @@ -95,17 +96,19 @@ def expand_state(self):
# all wires in the device have been initialised
return

# there are some wires in the device that have not yet been initialised
inactive_wires = set(range(self.num_wires)) - self._active_wires
num_inactive_wires = len(self.wires) - len(self._active_wires)

# translate active wires to the device's labels
device_active_wires = self.map_wires(self._active_wires)

# place the inactive subsystems in the vacuum state
other_subsystems = np.zeros([2 ** len(inactive_wires)])
other_subsystems = np.zeros([2 ** num_inactive_wires])
other_subsystems[0] = 1

# expand the state of the device into a length-num_wire state vector
expanded_state = np.kron(self._state, other_subsystems).reshape([2] * self.num_wires)
expanded_state = np.moveaxis(
expanded_state, range(len(self._active_wires)), self._active_wires
expanded_state, range(len(device_active_wires)), device_active_wires.labels
)
expanded_state = expanded_state.flatten()

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pyquil>=2.16
pennylane>=0.10
git+https://github.com/PennyLaneAI/pennylane.git
networkx
flaky

0 comments on commit a749d77

Please sign in to comment.