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

Analytic true hw simulator warning #59

Merged
merged 11 commits into from
Nov 20, 2019
30 changes: 23 additions & 7 deletions pennylane_qiskit/qiskit_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,26 @@
~~~~~~~~~~~~
"""
# pylint: disable=too-many-instance-attributes

import abc
from collections import OrderedDict
import functools
import inspect
import itertools
import warnings
from collections import OrderedDict

import numpy as np

from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit import extensions as ex
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit

from qiskit.compiler import transpile, assemble
from qiskit.circuit.measure import measure
from qiskit.converters import dag_to_circuit, circuit_to_dag
from qiskit.compiler import assemble, transpile
from qiskit.converters import circuit_to_dag, dag_to_circuit

from pennylane import Device, DeviceError
from pennylane.operation import Sample

from .gates import BasisState, Rot
from ._version import __version__
from .gates import BasisState, Rot
Copy link
Member

Choose a reason for hiding this comment

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

👍 glad the ordering here has been fixed



@functools.lru_cache()
Expand Down Expand Up @@ -134,6 +134,10 @@ class QiskitDevice(Device, abc.ABC):
operations = set(_operation_map.keys())
observables = {"PauliX", "PauliY", "PauliZ", "Identity", "Hadamard", "Hermitian"}

hw_analytic_warning_message = "The analytic calculation of expectations and variances "\
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created separate warning message for expval and var

"is only supported on statevector backends, not on the {}. "\
"The obtained result is based on sampling."

_eigs = {}

def __init__(self, wires, provider, backend, shots=1024, **kwargs):
Expand Down Expand Up @@ -349,6 +353,12 @@ def expval(self, observable, wires, par):
prob = np.fromiter(self.probabilities(wires=wires).values(), dtype=np.float64)
return (eigvals @ prob).real

if self.analytic:
# Raise a warning if backend is a hardware simulator
warnings.warn(self.hw_analytic_warning_message.
format(self.backend),
UserWarning)
Copy link
Member

Choose a reason for hiding this comment

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

Should we move the warning to __init__? That way,

  • It doesn't have to be repeated for expval and var,
  • The user will know straight away and can change it, rather than waiting for the qnode to be evaluated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I first went for it to be in the __init__.py. In that case, however, as by default analytic=True is used and there will be warnings raised for sampling on HW simulators


# estimate the ev
return np.mean(self.sample(observable, wires, par))

Expand All @@ -359,6 +369,12 @@ def var(self, observable, wires, par):
prob = np.fromiter(self.probabilities(wires=wires).values(), dtype=np.float64)
return (eigvals ** 2) @ prob - (eigvals @ prob).real ** 2

if self.analytic:
# Raise a warning if backend is a hardware simulator
warnings.warn(self.hw_analytic_warning_message.
format(self.backend),
UserWarning)

return np.var(self.sample(observable, wires, par))

def sample(self, observable, wires, par):
Expand Down
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ def backend(request):
return request.param


@pytest.fixture(params=state_backends)
def statevector_backend(request):
return request.param


@pytest.fixture(params=hw_backends)
def hardware_backend(request):
return request.param


@pytest.fixture(params=[AerDevice, BasicAerDevice])
def device(request, backend, shots, analytic):
if backend not in state_backends and analytic == True:
Expand Down
3 changes: 1 addition & 2 deletions tests/test_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,8 +691,7 @@ class TestConverterWarnings:
"""Tests that the converter.load function emits warnings."""

def test_barrier_not_supported(self, recorder):
"""Tests the load method raises a ValueError, if something that is
not a QuanctumCircuit was passed."""
"""Tests that a warning is raised if an unsupported instruction was reached."""
qc = QuantumCircuit(3, 1)
qc.barrier()

Expand Down
155 changes: 116 additions & 39 deletions tests/test_qiskit_device.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,116 @@
import pytest

import numpy as np

from pennylane_qiskit.qiskit_device import pauli_eigs
from pennylane_qiskit import AerDevice


Z = np.diag([1, -1])


class TestZEigs:
r"""Test that eigenvalues of Z^{\otimes n} are correctly generated"""

def test_one(self):
"""Test that eigs(Z) = [1, -1]"""
assert np.all(pauli_eigs(1) == np.array([1, -1]))

@pytest.mark.parametrize("n", [2, 3, 6])
def test_multiple(self, n):
r"""Test that eigs(Z^{\otimes n}) is correct"""
res = pauli_eigs(n)
Zn = np.kron(Z, Z)

for _ in range(n - 2):
Zn = np.kron(Zn, Z)

expected = np.diag(Zn)
assert np.all(res == expected)


class TestProbabilities:
"""Tests for the probability function"""

def test_probability_no_results(self):
"""Test that the probabilities function returns
None if no job has yet been run."""
dev = AerDevice(backend="statevector_simulator", wires=1, analytic=True)
assert dev.probabilities() is None
import numpy as np
import pytest

import pennylane as qml
Copy link
Member

Choose a reason for hiding this comment

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

Should this go below the import numpy line, since it is a third party import

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ran isort on it now and got the following:

import numpy as np
import pytest

import pennylane as qml
from pennylane_qiskit import AerDevice
from pennylane_qiskit.qiskit_device import pauli_eigs

Copy link
Member

Choose a reason for hiding this comment

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

👍

from pennylane_qiskit import AerDevice
from pennylane_qiskit.qiskit_device import pauli_eigs

Z = np.diag([1, -1])


class TestZEigs:
r"""Test that eigenvalues of Z^{\otimes n} are correctly generated"""

def test_one(self):
"""Test that eigs(Z) = [1, -1]"""
assert np.all(pauli_eigs(1) == np.array([1, -1]))

@pytest.mark.parametrize("n", [2, 3, 6])
def test_multiple(self, n):
r"""Test that eigs(Z^{\otimes n}) is correct"""
res = pauli_eigs(n)
Zn = np.kron(Z, Z)

for _ in range(n - 2):
Zn = np.kron(Zn, Z)

expected = np.diag(Zn)
assert np.all(res == expected)


class TestProbabilities:
"""Tests for the probability function"""

def test_probability_no_results(self):
"""Test that the probabilities function returns
None if no job has yet been run."""
dev = AerDevice(backend="statevector_simulator", wires=1, analytic=True)
assert dev.probabilities() is None


class TestAnalyticWarningHWSimulator:
"""Tests the warnings for when the analytic attribute of a device is set to true"""

def test_warning_raised_for_hardware_backend_analytic_expval(self, hardware_backend, recorder):
"""Tests that a warning is raised if the analytic attribute is true on
hardware simulators when calculating the expectation"""

dev = qml.device("qiskit.basicaer", backend=hardware_backend, wires=2)

@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
return qml.expval(qml.PauliZ(0))

with pytest.warns(UserWarning) as record:
circuit()

# check that only one warning was raised
assert len(record) == 1
# check that the message matches
assert record[0].message.args[0] == "The analytic calculation of expectations and variances "\
"is only supported on statevector backends, not on the {}. "\
"The obtained result is based on sampling.".format(dev.backend)

def test_no_warning_raised_for_software_backend_analytic_expval(self, statevector_backend, recorder, recwarn):
"""Tests that no warning is raised if the analytic attribute is true on
statevector simulators when calculating the expectation"""

dev = qml.device("qiskit.basicaer", backend=statevector_backend, wires=2)

@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
return qml.expval(qml.PauliZ(0))

circuit()

# check that no warnings were raised
assert len(recwarn) == 0

def test_warning_raised_for_hardware_backend_analytic_var(self, hardware_backend, recorder):
"""Tests that a warning is raised if the analytic attribute is true on
hardware simulators when calculating the variance"""

dev = qml.device("qiskit.basicaer", backend=hardware_backend, wires=2)

@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
return qml.var(qml.PauliZ(0))

with pytest.warns(UserWarning) as record:
circuit()

# check that only one warning was raised
assert len(record) == 1
# check that the message matches
assert record[0].message.args[0] == "The analytic calculation of expectations and variances "\
"is only supported on statevector backends, not on the {}. "\
"The obtained result is based on sampling.".format(dev.backend)

def test_no_warning_raised_for_software_backend_analytic_var(self, statevector_backend, recorder, recwarn):
"""Tests that no warning is raised if the analytic attribute is true on
statevector simulators when calculating the variance"""

dev = qml.device("qiskit.basicaer", backend=statevector_backend, wires=2)

@qml.qnode(dev)
def circuit():
qml.Hadamard(wires=0)
return qml.var(qml.PauliZ(0))

circuit()

# check that no warnings were raised
assert len(recwarn) == 0