Reorder result bits from real devices (#430)

* _reorder_bits method in ibmqbackend for real backends

* add reordering tests

lint

* qk -> qiskit

* SKIP_SLOW_TESTS

* check the status is COMPLETED before reordering

* local_qiskit_simulator -> local_qasm_simulator

* style

* revise timeout for test_reordering

* Reorganize authentication in test_reordering

Move the `_authenticate()` call to inside `TestBitReordering`, to avoid
`_requires_qe_access` raising an `SkipTest` during test discovery
(at module import time).
This commit is contained in:
Ali Javadi-Abhari 2018-05-03 05:25:57 -04:00 committed by Diego M. Rodríguez
parent 6b6c370725
commit 07b2485cb0
4 changed files with 191 additions and 1 deletions

View File

@ -196,4 +196,6 @@ Windows:
C:\..\> python -m unittest test/python/test_apps.py
Additionally, an environment variable ``SKIP_ONLINE_TESTS`` can be used for
toggling the execution of the tests that require network access to the API.
toggling the execution of the tests that require network access to the API and
``SKIP_SLOW_TESTS`` can be used to toggling execution of tests that are
particularly slow (default is ``True``).

View File

@ -120,6 +120,8 @@ class IBMQBackend(BaseBackend):
job_result['name'] = qobj['id']
job_result['backend'] = qobj['config']['backend_name']
this_result = Result(job_result, qobj)
if not self.configuration['simulator'] and this_result.get_status() == "COMPLETED":
_reorder_bits(this_result) # TODO: remove this after Qobj
return this_result
@property
@ -264,3 +266,58 @@ def _wait_for_job(job_id, api, wait=5, timeout=60):
'status': job_result['qasms'][index]['status']})
return {'job_id': job_id, 'status': job_result['status'],
'result': job_result_return}
def _reorder_bits(result):
"""temporary fix for ibmq backends.
for every ran circuit, get reordering information from qobj
and apply reordering on result"""
for idx, circ in enumerate(result._qobj['circuits']):
# device_qubit -> device_clbit (how it should have been)
measure_dict = {op['qubits'][0]: op['clbits'][0]
for op in circ['compiled_circuit']['operations']
if op['name'] == 'measure'}
res = result._result['result'][idx]
counts_dict_new = {}
for item in res['data']['counts'].items():
# fix clbit ordering to what it should have been
bits = list(item[0])
bits.reverse() # lsb in 0th position
count = item[1]
reordered_bits = list('x' * len(bits))
for device_clbit, bit in enumerate(bits):
if device_clbit in measure_dict:
correct_device_clbit = measure_dict[device_clbit]
reordered_bits[correct_device_clbit] = bit
reordered_bits.reverse()
# only keep the clbits specified by circuit, not everything on device
num_clbits = circ['compiled_circuit']['header']['number_of_clbits']
compact_key = reordered_bits[-num_clbits:]
compact_key = "".join([b if b != 'x' else '0'
for b in compact_key])
# insert spaces to signify different classical registers
cregs = circ['compiled_circuit']['header']['clbit_labels']
if sum([creg[1] for creg in cregs]) != num_clbits:
raise ResultError("creg sizes don't add up in result header.")
creg_begin_pos = []
creg_end_pos = []
acc = 0
for creg in reversed(cregs):
creg_size = creg[1]
creg_begin_pos.append(acc)
creg_end_pos.append(acc + creg_size)
acc += creg_size
compact_key = " ".join([compact_key[creg_begin_pos[i]:creg_end_pos[i]]
for i in range(len(cregs))])
# marginalize over unwanted measured qubits
if compact_key not in counts_dict_new:
counts_dict_new[compact_key] = count
else:
counts_dict_new[compact_key] += count
res['data']['counts'] = counts_dict_new

View File

@ -40,6 +40,7 @@ class Path(Enum):
class QiskitTestCase(unittest.TestCase):
"""Helper class that contains common functionality."""
@classmethod
def setUpClass(cls):
cls.moduleName = os.path.splitext(inspect.getfile(cls))[0]
@ -202,6 +203,26 @@ class _AssertNoLogsContext(unittest.case._AssertLogsContext):
self._raiseFailure(msg)
def slow_test(func):
"""
Decorator that signals that the test takes minutes to run.
Args:
func (callable): test function to be decorated.
Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _(*args, **kwargs):
if SKIP_SLOW_TESTS:
raise unittest.SkipTest('Skipping slow tests')
return func(*args, **kwargs)
return _
def requires_qe_access(func):
"""
Decorator that signals that the test uses the online API:
@ -217,6 +238,7 @@ def requires_qe_access(func):
Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _(*args, **kwargs):
# pylint: disable=invalid-name
@ -266,3 +288,4 @@ def _is_ci_fork_pull_request():
SKIP_ONLINE_TESTS = os.getenv('SKIP_ONLINE_TESTS', _is_ci_fork_pull_request())
SKIP_SLOW_TESTS = os.getenv('SKIP_SLOW_TESTS', True) not in ['false', 'False', '-1']

View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# pylint: disable=invalid-name,no-value-for-parameter,broad-except
# Copyright 2018 IBM RESEARCH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# =============================================================================
"""Tests for bit reordering fix."""
import unittest
import qiskit
from qiskit.wrapper import register, available_backends, get_backend, execute
from .common import requires_qe_access, QiskitTestCase, slow_test
def lowest_pending_jobs(list_of_backends):
"""Returns the backend with lowest pending jobs."""
by_pending_jobs = sorted(list_of_backends,
key=lambda x: get_backend(x).status['pending_jobs'])
return by_pending_jobs[0]
class TestBitReordering(QiskitTestCase):
"""Test QISKit's fix for the ibmq hardware reordering bug.
The bug will be fixed with the introduction of qobj,
in which case these tests can be used to verify correctness.
"""
@slow_test
@requires_qe_access
def test_basic_reordering(self, QE_TOKEN, QE_URL):
"""a simple reordering within a 2-qubit register"""
sim, real = self._get_backends(QE_TOKEN, QE_URL)
unittest.skipIf(not real, 'no remote device available.')
q = qiskit.QuantumRegister(2)
c = qiskit.ClassicalRegister(2)
circ = qiskit.QuantumCircuit(q, c)
circ.h(q[0])
circ.measure(q[0], c[1])
circ.measure(q[1], c[0])
shots = 2000
result_real = execute(circ, real, {"shots": shots}, timeout=600)
result_sim = execute(circ, sim, {"shots": shots})
counts_real = result_real.get_counts()
counts_sim = result_sim.get_counts()
threshold = 0.1 * shots
self.assertDictAlmostEqual(counts_real, counts_sim, threshold)
@slow_test
@requires_qe_access
def test_multi_register_reordering(self, QE_TOKEN, QE_URL):
"""a more complicated reordering across 3 registers of different sizes"""
sim, real = self._get_backends(QE_TOKEN, QE_URL)
unittest.skipIf(not real, 'no remote device available.')
q0 = qiskit.QuantumRegister(2)
q1 = qiskit.QuantumRegister(2)
q2 = qiskit.QuantumRegister(1)
c0 = qiskit.ClassicalRegister(2)
c1 = qiskit.ClassicalRegister(2)
c2 = qiskit.ClassicalRegister(1)
circ = qiskit.QuantumCircuit(q0, q1, q2, c0, c1, c2)
circ.h(q0[0])
circ.cx(q0[0], q2[0])
circ.x(q1[1])
circ.h(q2[0])
circ.ccx(q2[0], q1[1], q1[0])
circ.barrier()
circ.measure(q0[0], c2[0])
circ.measure(q0[1], c0[1])
circ.measure(q1[0], c0[0])
circ.measure(q1[1], c1[0])
circ.measure(q2[0], c1[1])
shots = 4000
result_real = execute(circ, real, {"shots": shots}, timeout=600)
result_sim = execute(circ, sim, {"shots": shots})
counts_real = result_real.get_counts()
counts_sim = result_sim.get_counts()
threshold = 0.2 * shots
self.assertDictAlmostEqual(counts_real, counts_sim, threshold)
def _get_backends(self, QE_TOKEN, QE_URL):
sim_backend = 'local_qasm_simulator'
try:
register(QE_TOKEN, QE_URL)
real_backends = available_backends({'simulator': False})
real_backend = lowest_pending_jobs(real_backends)
except Exception:
real_backend = None
return sim_backend, real_backend
if __name__ == '__main__':
unittest.main(verbosity=2)