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
57 changes: 57 additions & 0 deletions qiskit/backends/ibmq/ibmqbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,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']:
_reorder_bits(this_result) # TODO: remove this after Qobj
return this_result

@property
Expand Down Expand Up @@ -265,3 +267,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
104 changes: 104 additions & 0 deletions test/python/test_reordering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# -*- 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 as qk
Copy link
Member

Choose a reason for hiding this comment

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

Nit: can you use the fully qualified name (ie. import qiskit), for consistency?

Copy link
Member

Choose a reason for hiding this comment

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

fixed in b658084

from qiskit.wrapper import register, available_backends, get_backend, execute
from .common import requires_qe_access, QiskitTestCase


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]


@requires_qe_access
def _authenticate(QE_TOKEN, QE_URL):
sim_backend = 'local_qiskit_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


sim, real = _authenticate()


@unittest.skipIf(not real, 'no remote device available.')
class TestBitReordering(QiskitTestCase):
Copy link
Member

Choose a reason for hiding this comment

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

Can you revise these tests? They seem to be raising an Exception intermitently:

qiskit/wrapper/_wrapper.py:150: in execute
    wait, timeout, skip_translation)
qiskit/_compiler.py:77: in execute
    result = backend.run(q_job)
qiskit/backends/ibmq/ibmqbackend.py:125: in run
    _reorder_bits(this_result)  # TODO: remove this after Qobj

    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():
E           TypeError: string indices must be integers

when running against ibmqx4 at least. It seems it might be related to a timeout which is not handled properly during run() - the contents of result._result on one of those failures where:

{'backend': 'ibmqx4',
 'job_id': '(redacted)',
 'name': '3ZwDQis6FwX0j5gRpYvmcAEZ273upm',
 'result': 'QISkit Time Out',
 'status': 'ERROR'}

From a practical point of view, they are also a bit problematic: since they depend on a real device, the running time of the tests can be rather large (in my tests, they ranged from 150 to 170 seconds, which is rather large considering that the current running time for all the test suite is ~120 seconds. I'd really like to avoid that one way or the other, by making them opt-in or similar - any ideas?

Copy link
Member

Choose a reason for hiding this comment

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

fixed in 5e99aed

"""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.
"""
def test_basic_reordering(self):
"""a simple reordering within a 2-qubit register"""
q = qk.QuantumRegister(2)
c = qk.ClassicalRegister(2)
circ = qk.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": 300})
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)

def test_multi_register_reordering(self):
"""a more complicated reordering across 3 registers of different sizes"""
q0 = qk.QuantumRegister(2)
q1 = qk.QuantumRegister(2)
q2 = qk.QuantumRegister(1)
c0 = qk.ClassicalRegister(2)
c1 = qk.ClassicalRegister(2)
c2 = qk.ClassicalRegister(1)
circ = qk.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": 300})
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)


if __name__ == '__main__':
unittest.main(verbosity=2)