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

Reorder result bits from real devices #430

Merged
merged 10 commits into from
May 3, 2018
4 changes: 3 additions & 1 deletion doc/dev_introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``).
57 changes: 57 additions & 0 deletions qiskit/backends/ibmq/ibmqbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ def run(self, q_job):
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
Expand Down Expand Up @@ -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
23 changes: 23 additions & 0 deletions test/python/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -202,6 +203,26 @@ def __exit__(self, exc_type, exc_value, tb):
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:
Expand All @@ -217,6 +238,7 @@ def requires_qe_access(func):
Returns:
callable: the decorated function.
"""

@functools.wraps(func)
def _(*args, **kwargs):
# pylint: disable=invalid-name
Expand Down Expand Up @@ -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']
108 changes: 108 additions & 0 deletions test/python/test_reordering.py
Original file line number Diff line number Diff line change
@@ -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)