From 6ba223070f370ba3cf9db467b0856869aa6e71fa Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Wed, 25 Apr 2018 20:57:54 -0400 Subject: [PATCH 1/9] _reorder_bits method in ibmqbackend for real backends --- qiskit/backends/ibmq/ibmqbackend.py | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index c403951fc608..d82317698543 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -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) # FIXME: remove this after Qobj return this_result @property @@ -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 From 8160725982cd3d0008c9ebbb5075ec9d6a42e818 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Thu, 26 Apr 2018 12:47:17 -0400 Subject: [PATCH 2/9] add reordering tests lint --- qiskit/backends/ibmq/ibmqbackend.py | 2 +- test/python/test_reordering.py | 104 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test/python/test_reordering.py diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index d82317698543..9f44aaea9a2f 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -122,7 +122,7 @@ def run(self, q_job): job_result['backend'] = qobj['config']['backend_name'] this_result = Result(job_result, qobj) if not self.configuration['simulator']: - _reorder_bits(this_result) # FIXME: remove this after Qobj + _reorder_bits(this_result) # TODO: remove this after Qobj return this_result @property diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py new file mode 100644 index 000000000000..eb8ed1f3b5d9 --- /dev/null +++ b/test/python/test_reordering.py @@ -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 +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): + """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) From b6580845eb4229328fafadb56ea90c3a71db58dc Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 2 May 2018 11:00:25 -0400 Subject: [PATCH 3/9] qk -> qiskit --- test/python/test_reordering.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index eb8ed1f3b5d9..58d6d7b24a89 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -18,7 +18,7 @@ """Tests for bit reordering fix.""" import unittest -import qiskit as qk +import qiskit from qiskit.wrapper import register, available_backends, get_backend, execute from .common import requires_qe_access, QiskitTestCase @@ -55,9 +55,9 @@ class TestBitReordering(QiskitTestCase): """ 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) + 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]) @@ -72,13 +72,13 @@ def test_basic_reordering(self): 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) + 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]) From 5bc7fba60ed7896513f35e452f1565c75bb4b270 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 2 May 2018 13:54:32 -0400 Subject: [PATCH 4/9] SKIP_SLOW_TESTS --- doc/dev_introduction.rst | 4 +++- test/python/common.py | 17 +++++++++++++++++ test/python/test_reordering.py | 4 +++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/doc/dev_introduction.rst b/doc/dev_introduction.rst index 955c31bb26cf..782166762e1d 100644 --- a/doc/dev_introduction.rst +++ b/doc/dev_introduction.rst @@ -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``). diff --git a/test/python/common.py b/test/python/common.py index 7dcb712e0c8a..055e8a6f0070 100644 --- a/test/python/common.py +++ b/test/python/common.py @@ -202,6 +202,22 @@ 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): """ @@ -268,3 +284,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'] diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 58d6d7b24a89..744ff1d53423 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -20,7 +20,7 @@ import unittest import qiskit from qiskit.wrapper import register, available_backends, get_backend, execute -from .common import requires_qe_access, QiskitTestCase +from .common import requires_qe_access, QiskitTestCase, slow_test def lowest_pending_jobs(list_of_backends): @@ -53,6 +53,7 @@ class TestBitReordering(QiskitTestCase): The bug will be fixed with the introduction of qobj, in which case these tests can be used to verify correctness. """ + @slow_test def test_basic_reordering(self): """a simple reordering within a 2-qubit register""" q = qiskit.QuantumRegister(2) @@ -70,6 +71,7 @@ def test_basic_reordering(self): threshold = 0.1 * shots self.assertDictAlmostEqual(counts_real, counts_sim, threshold) + @slow_test def test_multi_register_reordering(self): """a more complicated reordering across 3 registers of different sizes""" q0 = qiskit.QuantumRegister(2) From 5e99aed7f724078af4991478abe33acbb5f7d0b4 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 2 May 2018 14:36:23 -0400 Subject: [PATCH 5/9] check the status is COMPLETED before reordering --- qiskit/backends/ibmq/ibmqbackend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/backends/ibmq/ibmqbackend.py b/qiskit/backends/ibmq/ibmqbackend.py index 9f44aaea9a2f..7d869019fc7a 100644 --- a/qiskit/backends/ibmq/ibmqbackend.py +++ b/qiskit/backends/ibmq/ibmqbackend.py @@ -121,7 +121,7 @@ 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']: + if not self.configuration['simulator'] and this_result.get_status() == "COMPLETED": _reorder_bits(this_result) # TODO: remove this after Qobj return this_result From 14aa80ca2892d38d7934d9b3c38c779895038a49 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 2 May 2018 14:47:54 -0400 Subject: [PATCH 6/9] local_qiskit_simulator -> local_qasm_simulator --- test/python/test_reordering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 744ff1d53423..1ff79540b094 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -32,7 +32,7 @@ def lowest_pending_jobs(list_of_backends): @requires_qe_access def _authenticate(QE_TOKEN, QE_URL): - sim_backend = 'local_qiskit_simulator' + sim_backend = 'local_qasm_simulator' try: register(QE_TOKEN, QE_URL) real_backends = available_backends({'simulator': False}) From 08edcd12654513077d1207b0d2587ea364149122 Mon Sep 17 00:00:00 2001 From: Luciano Bello Date: Wed, 2 May 2018 14:58:52 -0400 Subject: [PATCH 7/9] style --- test/python/common.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/python/common.py b/test/python/common.py index d4eaf85e24e8..861a11e64a33 100644 --- a/test/python/common.py +++ b/test/python/common.py @@ -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] @@ -201,6 +202,7 @@ 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. @@ -211,13 +213,16 @@ def slow_test(func): 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: @@ -233,6 +238,7 @@ def requires_qe_access(func): Returns: callable: the decorated function. """ + @functools.wraps(func) def _(*args, **kwargs): # pylint: disable=invalid-name From 832734cfb17f73c925ab56c6277291ca2aca0e12 Mon Sep 17 00:00:00 2001 From: Ali Javadi Date: Wed, 2 May 2018 17:28:43 -0400 Subject: [PATCH 8/9] revise timeout for test_reordering --- test/python/test_reordering.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index 1ff79540b094..e37a0300618a 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -64,7 +64,7 @@ def test_basic_reordering(self): circ.measure(q[1], c[0]) shots = 2000 - result_real = execute(circ, real, {"shots": shots, "timeout": 300}) + 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() @@ -94,7 +94,7 @@ def test_multi_register_reordering(self): circ.measure(q2[0], c1[1]) shots = 4000 - result_real = execute(circ, real, {"shots": shots, "timeout": 300}) + 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() From 661286e1cf96465e298ab2f297ccc95d186f6ebc Mon Sep 17 00:00:00 2001 From: "Diego M. Rodriguez" Date: Thu, 3 May 2018 10:55:16 +0200 Subject: [PATCH 9/9] 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). --- test/python/test_reordering.py | 40 ++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/test/python/test_reordering.py b/test/python/test_reordering.py index e37a0300618a..c15a7650717a 100644 --- a/test/python/test_reordering.py +++ b/test/python/test_reordering.py @@ -30,23 +30,6 @@ def lowest_pending_jobs(list_of_backends): return by_pending_jobs[0] -@requires_qe_access -def _authenticate(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 - - -sim, real = _authenticate() - - -@unittest.skipIf(not real, 'no remote device available.') class TestBitReordering(QiskitTestCase): """Test QISKit's fix for the ibmq hardware reordering bug. @@ -54,8 +37,12 @@ class TestBitReordering(QiskitTestCase): in which case these tests can be used to verify correctness. """ @slow_test - def test_basic_reordering(self): + @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) @@ -72,8 +59,12 @@ def test_basic_reordering(self): self.assertDictAlmostEqual(counts_real, counts_sim, threshold) @slow_test - def test_multi_register_reordering(self): + @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) @@ -101,6 +92,17 @@ def test_multi_register_reordering(self): 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)