mirror of https://github.com/Qiskit/qiskit.git
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:
parent
6b6c370725
commit
07b2485cb0
|
@ -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``).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue