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

Update runtime sampler #201

Merged
merged 8 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
67 changes: 44 additions & 23 deletions pennylane_qiskit/runtime_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import numpy as np

import qiskit.result.postprocess
from qiskit.providers.ibmq import RunnerResult
from pennylane_qiskit.ibmq import IBMQDevice

Expand Down Expand Up @@ -104,7 +103,6 @@ def generate_samples(self, circuit=None):
array[complex]: array of samples in the shape ``(dev.shots, dev.num_wires)``
"""
counts = self._current_job.get_counts()

# Batch of circuits
if not isinstance(counts, dict):
counts = self._current_job.get_counts()[circuit]
Expand All @@ -130,13 +128,10 @@ class IBMQSamplerDevice(IBMQDevice):
to estimate expectation values and variances of observables. Default=1024.

Keyword Args:
return_mitigation_overhead (bool): Return mitigation overhead factor. Default is False.
run_config (dict): A collection of kwargs passed to backend.run, if shots are given here it will take
circuit_indices (bool): Indices of the circuits to evaluate. Default is ``range(0, len(circuits))``.
run_options (dict): A collection of kwargs passed to backend.run, if shots are given here it will take
precedence over the shots arg.
skip_transpilation (bool): Skip circuit transpilation. Default is False.
transpile_config (dict): A collection of kwargs passed to transpile.
use_measurement_mitigation (bool): Use measurement mitigation to improve results. Default is False.
use_dynamical_decoupling (bool): Use dynamical decoupling to improve fidelities.
"""

short_name = "qiskit.ibmq.sampler"
Expand All @@ -151,11 +146,17 @@ def batch_execute(self, circuits):

program_inputs = {"circuits": compiled_circuits}

if "run_config" in self.kwargs:
if not "shots" in self.kwargs["run_config"]:
self.kwargs["run_config"]["shots"] = self.shots
if "circuits_indices" not in self.kwargs:
circuit_indices = list(range(0, len(compiled_circuits)))
program_inputs["circuit_indices"] = circuit_indices
else:
circuit_indices = self.kwargs.get("circuit_indices")

if "run_options" in self.kwargs:
if not "shots" in self.kwargs["run_options"]:
self.kwargs["run_options"]["shots"] = self.shots
else:
self.kwargs["run_config"] = {"shots": self.shots}
self.kwargs["run_options"] = {"shots": self.shots}

for kwarg in self.kwargs:
program_inputs[kwarg] = self.kwargs.get(kwarg)
Expand All @@ -167,12 +168,17 @@ def batch_execute(self, circuits):
program_id="sampler", options=options, inputs=program_inputs
)
self._current_job = job.result()

results = []

counter = 0
for index, circuit in enumerate(circuits):
self._samples = self.generate_samples(index)
res = self.statistics(circuit.observables)
results.append(res)

if index in circuit_indices:
self._samples = self.generate_samples(counter)
counter += 1
res = self.statistics(circuit.observables)
results.append(res)

if self.tracker.active:
self.tracker.update(batches=1, batch_len=len(circuits))
Expand All @@ -192,13 +198,28 @@ def generate_samples(self, circuit_id=None):
Returns:
array[complex]: array of samples in the shape ``(dev.shots, dev.num_wires)``
"""
counts = self._current_job.get("counts")[circuit_id]
counts_formatted = qiskit.result.postprocess.format_counts(
counts, {"memory_slots": self._circuit.num_qubits}
)
counts = self._current_job.get("quasi_dists")[circuit_id]
keys = list(counts.keys())

samples = []
for key, value in counts_formatted.items():
for _ in range(0, value):
samples.append(key)
return np.vstack([np.array([int(i) for i in s[::-1]]) for s in samples])
number_of_states = 2 ** len(keys[0])

# Convert state to int
for i, elem in enumerate(keys):
keys[i] = int(elem, 2)

values = list(counts.values())
states, probs = zip(*sorted(zip(keys, values)))

states = list(states)
probs = list(probs)

# If prob for a state is 0, it does not appear in counts.
if len(states) != number_of_states:
for i in range(0, number_of_states):
if states[i] != i:
states.insert(i, i)
probs.insert(i, 0.0)

return self.states_to_binary(
self.sample_basis_states(number_of_states, probs), self.num_wires
)
4 changes: 4 additions & 0 deletions tests/test_ibmq.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,13 @@ def test_custom_provider(monkeypatch):
device."""
mock_provider = "MockProvider"
mock_qiskit_device = MockQiskitDeviceInit()
monkeypatch.setenv("IBMQX_TOKEN", '1')

with monkeypatch.context() as m:
m.setattr(ibmq.QiskitDevice, "__init__", mock_qiskit_device.mocked_init)
m.setattr(ibmq.IBMQ, "enable_account", lambda *args, **kwargs: None)


# Here mocking to a value such that it is not None
m.setattr(ibmq.IBMQ, "active_account", lambda *args, **kwargs: {"token": '1'})
dev = IBMQDevice(wires=2, backend="ibmq_qasm_simulator", provider=mock_provider)
Expand All @@ -133,6 +135,7 @@ def test_default_provider(monkeypatch):
"""Tests that the default provider is used when no custom provider was
specified."""
mock_qiskit_device = MockQiskitDeviceInit()
monkeypatch.setenv("IBMQX_TOKEN", '1')

with monkeypatch.context() as m:
m.setattr(ibmq.QiskitDevice, "__init__", mock_qiskit_device.mocked_init)
Expand All @@ -151,6 +154,7 @@ def test_custom_provider_hub_group_project(monkeypatch):
"""Tests that the custom arguments passed during device instantiation are
used when calling get_provider."""
mock_qiskit_device = MockQiskitDeviceInit()
monkeypatch.setenv("IBMQX_TOKEN", '1')

custom_hub = "SomeHub"
custom_group = "SomeGroup"
Expand Down
69 changes: 49 additions & 20 deletions tests/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@

from pennylane_qiskit import opstr_to_meas_circ
from pennylane_qiskit import IBMQCircuitRunnerDevice, IBMQSamplerDevice
from pennylane_qiskit.vqe_runtime_runner import vqe_runner, upload_vqe_runner, delete_vqe_runner, hamiltonian_to_list_string
from pennylane_qiskit.vqe_runtime_runner import (
vqe_runner,
upload_vqe_runner,
delete_vqe_runner,
hamiltonian_to_list_string,
)


class TestCircuitRunner:
"""Test class for the circuit runner IBMQ runtime device."""

def test_load_from_env(self, token, monkeypatch):
"""Test loading an IBMQ Circuit Runner Qiskit runtime device from an env variable."""
monkeypatch.setenv("IBMQX_TOKEN", token)
Expand Down Expand Up @@ -141,6 +148,7 @@ def circuit():

class TestSampler:
"""Test class for the sampler IBMQ runtime device."""

def test_load_from_env(self, token, monkeypatch):
"""Test loading an IBMQ Sampler Qiskit runtime device from an env variable."""
monkeypatch.setenv("IBMQX_TOKEN", token)
Expand Down Expand Up @@ -177,11 +185,9 @@ def circuit(theta, phi):
"kwargs",
[
{
"return_mitigation_overhead": True,
"run_config": {"seed_simulator": 42},
"circuit_indices": [0],
"run_options": {"seed_simulator": 42},
"skip_transpilation": False,
"transpile_config": {"approximation_degree": 1.0},
"use_measurement_mitigation": True,
}
],
)
Expand All @@ -208,7 +214,7 @@ def circuit(theta, phi):
def test_batch_circuits(self, token, tol, shots):
"""Test executing batched circuits submitted to IBMQ using the Sampler device."""
IBMQ.enable_account(token)
dev = IBMQSamplerDevice(wires=2, backend="ibmq_qasm_simulator", shots=shots)
dev = IBMQSamplerDevice(wires=1, backend="ibmq_qasm_simulator", shots=shots)

# Batch the input parameters
batch_dim = 3
Expand Down Expand Up @@ -255,19 +261,25 @@ def test_hamiltonian_to_list_string(self):
hamiltonian = qml.Hamiltonian(coeffs, obs)
result = hamiltonian_to_list_string(hamiltonian, hamiltonian.wires)

assert [(1, 'XIX'), (1, 'HZI')] == result
assert [(1, "XIX"), (1, "HZI")] == result

def test_op_str_measurement_circ(self):
"""Test that the opstr_to_meas_circ function finds the necessary rotations before measurements in the
circuit. """
circ = opstr_to_meas_circ('HIHXZ')
circuit."""
circ = opstr_to_meas_circ("HIHXZ")
results = []
for c in circ:
if c:
results.append((c.data[0][0].name, c.data[0][0].params))
else:
results.append(())
assert [('ry', [-0.7853981633974483]), (), ('ry', [-0.7853981633974483]), ('h', []), ()] == results
assert [
("ry", [-0.7853981633974483]),
(),
("ry", [-0.7853981633974483]),
("h", []),
(),
] == results

@pytest.mark.parametrize("shots", [8000])
def test_simple_hamiltonian(self, token, tol, shots):
Expand Down Expand Up @@ -350,15 +362,21 @@ def test_ansatz_qiskit_invalid(self, token, tol, shots):
hamiltonian = qml.Hamiltonian(coeffs, obs)
program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations")

with pytest.raises(ValueError, match="Ansatz InEfficientSU2 not in n_local circuit library."):
with pytest.raises(
ValueError, match="Ansatz InEfficientSU2 not in n_local circuit library."
):
vqe_runner(
program_id=program_id,
backend="ibmq_qasm_simulator",
hamiltonian=hamiltonian,
ansatz="InEfficientSU2",
x0=[3.97507603, 3.00854038],
shots=shots,
kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"},
kwargs={
"hub": "ibm-q-startup",
"group": "ibm-q-startup",
"project": "reservations",
},
)

provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations")
Expand All @@ -379,7 +397,9 @@ def test_qnode(self, token, tol, shots):
hamiltonian = qml.Hamiltonian(coeffs, obs)
program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations")

with pytest.raises(qml.QuantumFunctionError, match="The ansatz must be a callable quantum function."):
with pytest.raises(
qml.QuantumFunctionError, match="The ansatz must be a callable quantum function."
):
vqe_runner(
program_id=program_id,
backend="ibmq_qasm_simulator",
Expand Down Expand Up @@ -417,7 +437,9 @@ def vqe_circuit(params):
hamiltonian = qml.Hamiltonian(coeffs, obs)
program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations")

with pytest.raises(qml.QuantumFunctionError, match="The ansatz must be a callable quantum function."):
with pytest.raises(
qml.QuantumFunctionError, match="The ansatz must be a callable quantum function."
):
vqe_runner(
program_id=program_id,
backend="ibmq_qasm_simulator",
Expand Down Expand Up @@ -567,12 +589,16 @@ def vqe_circuit(params):
x0=[3.97507603, 3.00854038, 3.55637849],
shots=shots,
optimizer_config={"maxiter": 10},
kwargs={"hub": "ibm-q-startup", "group": "ibm-q-startup", "project": "reservations"},
kwargs={
"hub": "ibm-q-startup",
"group": "ibm-q-startup",
"project": "reservations",
},
)

assert (
record[-1].message.args[0]
== "In order to match the tape expansion, the number of parameters has been changed."
record[-1].message.args[0]
== "In order to match the tape expansion, the number of parameters has been changed."
)

provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations")
Expand Down Expand Up @@ -706,7 +732,9 @@ def vqe_circuit(params):
hamiltonian = qml.PauliZ(wires=0)
program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations")

with pytest.raises(qml.QuantumFunctionError, match="A PennyLane Hamiltonian object is required."):
with pytest.raises(
qml.QuantumFunctionError, match="A PennyLane Hamiltonian object is required."
):
vqe_runner(
program_id=program_id,
backend="ibmq_qasm_simulator",
Expand Down Expand Up @@ -921,7 +949,9 @@ def vqe_circuit(params):
hamiltonian = qml.Hamiltonian(coeffs, obs)
program_id = upload_vqe_runner(hub="ibm-q-startup", group="xanadu", project="reservations")

with pytest.raises(qml.QuantumFunctionError, match="Function contains no quantum operations."):
with pytest.raises(
qml.QuantumFunctionError, match="Function contains no quantum operations."
):
vqe_runner(
program_id=program_id,
backend="ibmq_qasm_simulator",
Expand All @@ -940,7 +970,6 @@ def vqe_circuit(params):
provider = IBMQ.get_provider(hub="ibm-q-startup", group="xanadu", project="reservations")
delete_vqe_runner(provider=provider, program_id=program_id)


@pytest.mark.parametrize("shots", [8000])
def test_invalid_ansatz(self, token, tol, shots):
"""Test that an invalid ansatz cannot be passed."""
Expand Down