Skip to content

Commit

Permalink
Testing single observable Tensor operator estimation, mutual exclusio…
Browse files Browse the repository at this point in the history
…n with parametric compilation (#41)

* Adding logic for single observable Tensor, adding integration test; modifying docstring for tests

* Comment

* Remove print statements

* Correct test to have PauliY in there

* Modify test parameter

* Modifying flaky parameters, modifying tolerance value, docstrings

* Adding flaky runs to hermitian test case

* Toggle flaky numbers

* Modify test such that no SWAP erros may be outputted

* Remove print stmt

* Revert tensor obs case QPUDevice.expval

* Add warning to the user, add test for it

* Test on wires expval with operator estimation

* Comments

* Test remvoe space

* Linting

* Test docstring

* Changing tests

* Changing PyQvm test settings

* Update pennylane_forest/qpu.py

Co-Authored-By: Josh Izaac <josh146@gmail.com>

* Update pennylane_forest/qpu.py

Co-Authored-By: Josh Izaac <josh146@gmail.com>

* Adjust delta for test

* Blacking and isort qvm.py

* Adjusting flaky tests

Co-authored-by: Josh Izaac <josh146@gmail.com>
  • Loading branch information
antalszava and josh146 committed Feb 7, 2020
1 parent 73369e7 commit 985ab2d
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 38 deletions.
47 changes: 29 additions & 18 deletions pennylane_forest/qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,24 @@
~~~~~~~~~~~~
"""
import re

from pyquil import get_qc

from .qvm import QVMDevice
from ._version import __version__
import warnings

import numpy as np

from pyquil import get_qc
from pyquil.api._quantum_computer import _get_qvm_with_topology
from pyquil.gates import MEASURE, RESET
from pyquil.quil import Pragma, Program
from pyquil.paulis import sI, sX, sY, sZ
from pyquil.operator_estimation import (
Experiment,
ExperimentSetting,
TensorProductState,
Experiment,
measure_observables,
group_experiments,
measure_observables,
)
from pyquil.paulis import sI, sX, sY, sZ
from pyquil.quil import Program
from pyquil.quilbase import Gate

from ._version import __version__
from .qvm import QVMDevice


class QPUDevice(QVMDevice):
r"""Forest QPU device for PennyLane.
Expand All @@ -61,8 +57,8 @@ class QPUDevice(QVMDevice):
readout errors need to be simulated; can only be set for the QPU-as-a-QVM
symmetrize_readout (str): method to perform readout symmetrization, using exhaustive
symmetrization by default
calibrate_readout (str): method to perform calibration for readout error mitigation, normalizing
by the expectation value in the +1-eigenstate of the observable by default
calibrate_readout (str): method to perform calibration for readout error mitigation,
normalizing by the expectation value in the +1-eigenstate of the observable by default
Keyword args:
forest_url (str): the Forest URL server. Can also be set by
Expand Down Expand Up @@ -106,6 +102,18 @@ def __init__(
"""Union[None, pyquil.ExecutableDesignator]: the latest compiled program. If parametric
compilation is turned on, this will be a parametric program."""

if kwargs.get("parametric_compilation", False):
# Raise a warning if parametric compilation was explicitly turned on by the user
# about turning the operator estimation off

# TODO: Remove the warning and toggling once a migration to the new operator estimation
# API has been executed. This new API provides compatibility between parametric
# compilation and operator estimation.
warnings.warn(
"Parametric compilation is currently not supported with operator"
"estimation. Operator estimation is being turned off."
)

self.parametric_compilation = kwargs.get("parametric_compilation", True)

if self.parametric_compilation:
Expand Down Expand Up @@ -151,8 +159,10 @@ def __init__(

def expval(self, observable):
wires = observable.wires
# Single-qubit observable
if len(wires) == 1:

if len(wires) == 1 and not self.parametric_compilation:
# Single-qubit observable when parametric compilation is turned off

# identify Experiment Settings for each of the possible single-qubit observables
wire = wires[0]
qubit = self.wiring[wire]
Expand All @@ -166,8 +176,10 @@ def expval(self, observable):
ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sZ(qubit)),
],
}
# expectation values for single-qubit observables

if observable.name in ["PauliX", "PauliY", "PauliZ", "Identity", "Hadamard"]:
# expectation values for single-qubit observables

prep_prog = Program()
for instr in self.program.instructions:
if isinstance(instr, Gate):
Expand Down Expand Up @@ -204,5 +216,4 @@ def expval(self, observable):
Hkey = tuple(par[0].flatten().tolist())
w = self._eigs[Hkey]["eigval"]
return w[0] * p0 + w[1] * p1

return super().expval(observable)
6 changes: 3 additions & 3 deletions pennylane_forest/qvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
import re

import networkx as nx

from pennylane import DeviceError
from pennylane.variable import Variable
from pyquil import get_qc
from pyquil.api._quantum_computer import _get_qvm_with_topology
from pyquil.gates import MEASURE, RESET
from pyquil.quil import Pragma, Program

from pennylane import DeviceError
from pennylane.variable import Variable

from ._version import __version__
from .device import ForestDevice

Expand Down
4 changes: 2 additions & 2 deletions tests/test_pyqvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ def circuit():
@pytest.mark.parametrize("device", ["2q-pyqvm"])
def test_one_qubit_wavefunction_circuit(self, device, shots):
"""Test that the wavefunction plugin provides correct result for simple circuit."""
shots = 100000
shots = 10000
dev = qml.device("forest.qvm", device=device, shots=shots)

a = 0.543
Expand All @@ -380,4 +380,4 @@ def circuit(x, y, z):
qml.Rot(x, y, z, wires=0)
return qml.expval(qml.PauliZ(0))

self.assertAlmostEqual(circuit(a, b, c), np.cos(a) * np.sin(b), delta=3 / np.sqrt(shots))
self.assertAlmostEqual(circuit(a, b, c), np.cos(a) * np.sin(b), delta=5 / np.sqrt(shots))
117 changes: 112 additions & 5 deletions tests/test_qpu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
Unit tests for the QPU device.
"""
import logging
import warnings
import re

import pytest
import pyquil
import pennylane as qml
from pennylane import numpy as np
from pennylane.operation import Tensor
import pennylane_forest as plf
from conftest import BaseTest, QVM_SHOTS

Expand Down Expand Up @@ -55,12 +57,116 @@ 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])

@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)]
)
def test_tensor_wires_expval_parametric_compilation(self, obs):
"""Test the QPU expval method for Tensor observables made up of a single observable when parametric compilation is
turned on.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
"""
p = np.pi / 8
dev = qml.device(
"forest.qpu",
device="Aspen-4-4Q-E",
shots=10000,
load_qc=False,
parametric_compilation=True,
)
dev_1 = qml.device(
"forest.qpu",
device="Aspen-4-4Q-E",
shots=10000,
load_qc=False,
parametric_compilation=True,
)

def template(param):
qml.BasisState(np.array([0, 0, 1, 1]), wires=list(range(4)))
qml.RY(param, wires=[2])
qml.CNOT(wires=[2, 3])

@qml.qnode(dev)
def circuit_tensor(param):
template(param)
return qml.expval(Tensor(obs))

@qml.qnode(dev_1)
def circuit_obs(param):
template(param)
return qml.expval(obs)

res = circuit_tensor(p)
exp = circuit_obs(p)

assert np.allclose(res, exp, atol=2e-2)

@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)]
)
def test_tensor_wires_expval_operator_estimation(self, obs):
"""Test the QPU expval method for Tensor observables made up of a single observable when parametric compilation is
turned off allowing operator estimation.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
"""
p = np.pi / 7
dev = qml.device(
"forest.qpu",
device="Aspen-4-4Q-E",
shots=1000,
load_qc=False,
parametric_compilation=False,
)
dev_1 = qml.device(
"forest.qpu",
device="Aspen-4-4Q-E",
shots=1000,
load_qc=False,
parametric_compilation=False,
)

def template(param):
qml.BasisState(np.array([0, 0, 1, 1]), wires=list(range(4)))
qml.RY(param, wires=[2])
qml.CNOT(wires=[2, 3])

@qml.qnode(dev)
def circuit_tensor(param):
template(param)
return qml.expval(Tensor(obs))

@qml.qnode(dev_1)
def circuit_obs(param):
template(param)
return qml.expval(obs)

res = circuit_tensor(p)
exp = circuit_obs(p)

assert np.allclose(res, exp, atol=2e-2)


class TestQPUBasic(BaseTest):
"""Unit tests for the QPU (as a QVM)."""

# pylint: disable=protected-access

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(VALID_QPU_LATTICES)
with pytest.warns(Warning, match="Operator estimation is being turned off."):
dev = qml.device(
"forest.qpu",
device="Aspen-4-4Q-E",
shots=1000,
load_qc=False,
parametric_compilation=True,
)

def test_no_readout_correction(self):
"""Test the QPU plugin with no readout correction"""
device = np.random.choice(VALID_QPU_LATTICES)
Expand All @@ -71,6 +177,7 @@ def test_no_readout_correction(self):
readout_error=[0.9, 0.75],
symmetrize_readout=None,
calibrate_readout=None,
parametric_compilation=False,
)
qubit = 0 # just run program on the first qubit

Expand Down Expand Up @@ -188,7 +295,7 @@ def circuit_Zmi():
def test_2q_gate(self):
"""Test that the two qubit gate with the PauliZ observable works correctly.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
"""

device = np.random.choice(VALID_QPU_LATTICES)
Expand All @@ -214,7 +321,7 @@ def circuit():
def test_2q_gate_pauliz_identity_tensor(self):
"""Test that the PauliZ tensor Identity observable works correctly.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
"""
device = np.random.choice(VALID_QPU_LATTICES)
dev_qpu = qml.device(
Expand All @@ -240,7 +347,7 @@ def circuit():
def test_2q_gate_pauliz_pauliz_tensor(self, a):
"""Test that the PauliZ tensor PauliZ observable works correctly.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
"""
device = np.random.choice(VALID_QPU_LATTICES)
dev_qpu = qml.device(
Expand Down Expand Up @@ -271,7 +378,7 @@ def test_2q_gate_pauliz_pauliz_tensor_parametric_compilation_off(self, a, b):
"""Test that the PauliZ tensor PauliZ observable works correctly, when parametric compilation
is turned off.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
As the results coming from the qvm are stochastic, a constraint of 3 out of 5 runs was added.
"""

device = np.random.choice(VALID_QPU_LATTICES)
Expand Down Expand Up @@ -313,7 +420,7 @@ def test_timeout_set_correctly(self, shots):

def test_timeout_default(self, shots):
"""Test that the timeout attrbiute for the QuantumComputer stored by the QVMDevice
is set correctly when passing a value as keyword argument"""
is set to default when no specific value is being passed."""
device = np.random.choice(VALID_QPU_LATTICES)
dev = plf.QVMDevice(device=device, shots=shots)
qc = pyquil.get_qc(device, as_qvm=True)
Expand Down

0 comments on commit 985ab2d

Please sign in to comment.